diff --git a/.travis.yml b/.travis.yml index cbd0ad2..9f84fc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,6 @@ matrix: include: # Full tests (with online API) - - os: linux - language: python - python: 2.7 - env: - - TO_TEST=TEST_FULL - os: linux language: python python: 3.7 @@ -15,16 +10,6 @@ matrix: sudo: true env: - TO_TEST=TEST_FULL - - os: osx - language: generic - python: 2.7 - env: - - TO_TEST=TEST_FULL - # before_install: - # - python2 --version - # - pip2 install -U virtualenv - # - virtualenv env -p python2 - # - source env/bin/activate - os: osx language: generic python: ">=3.6" @@ -38,28 +23,25 @@ matrix: # Mock tests (with mock API) - os: linux language: python - python: 3.3 - env: - - TO_TEST=TEST_MOCK - - os: linux - language: python - python: 3.4 + python: 3.5 env: - TO_TEST=TEST_MOCK - os: linux language: python - python: 3.5 + python: 3.6 env: - TO_TEST=TEST_MOCK - os: linux language: python - python: 3.6 + dist: xenial + python: 3.7 + sudo: true env: - TO_TEST=TEST_MOCK - os: linux language: python dist: xenial - python: 3.7 + python: 3.8 sudo: true env: - TO_TEST=TEST_MOCK @@ -76,7 +58,7 @@ matrix: language: python dist: xenial sudo: true - python: 3.7 + python: 3.8 env: - TO_TEST=INSTALL if: type = cron @@ -94,7 +76,7 @@ matrix: if: type = cron allow_failures: - - python: 3.3 + - python: 2.7 # command to run tests script: diff --git a/dev_requirements.txt b/dev_requirements.txt index 1891f81..f6555a6 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -20,6 +20,8 @@ six # those are the additional packages required to run the tests pyfakefs certifi -ddt +# FIXME: remove strict version when https://github.com/datadriventests/ddt/issues/83 is fixed. +# (also remove in setup.py) +ddt==1.3.1 mock pytest # optional (python setup.py test works without it), but possible nonetheless diff --git a/pubs/commands/add_cmd.py b/pubs/commands/add_cmd.py index a2af9cd..9e9dd0d 100644 --- a/pubs/commands/add_cmd.py +++ b/pubs/commands/add_cmd.py @@ -147,7 +147,7 @@ def command(conf, args): doc_add = conf['main']['doc_add'] rp.push_paper(p) - ui.message('added to pubs:\n{}'.format(pretty.paper_oneliner(p))) + ui.message('added to pubs:\n{}'.format(pretty.paper_oneliner(p, max_authors=conf['main']['max_authors']))) if docfile is not None: rp.push_doc(p.citekey, docfile, copy=(doc_add in ('copy', 'move'))) if doc_add == 'move' and content.content_type(docfile) != 'url': diff --git a/pubs/commands/edit_cmd.py b/pubs/commands/edit_cmd.py index 431d19c..d4ad366 100644 --- a/pubs/commands/edit_cmd.py +++ b/pubs/commands/edit_cmd.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from ..paper import Paper from .. import repo +from .. import color from ..uis import get_ui from ..endecoder import EnDecoder @@ -55,17 +56,18 @@ def command(conf, args): new_paper = Paper(paper.citekey, paper.bibdata, metadata=content) rp.push_paper(new_paper, overwrite=True, event=False) - ui.info(('The metadata of paper `{}` was successfully ' - 'edited.'.format(citekey))) + ui.info(("The metadata of paper '{}' was successfully " + "edited.".format(color.dye_out(citekey, 'citekey')))) else: new_paper = Paper.from_bibentry(content, metadata=paper.metadata) if rp.rename_paper(new_paper, old_citekey=paper.citekey): - ui.info(('Paper `{}` was successfully edited and renamed ' - 'as `{}`.'.format(citekey, new_paper.citekey))) + ui.info(("Paper '{}' was successfully edited and renamed " + "as '{}'.".format(color.dye_out(citekey, 'citekey'), + color.dye_out(new_paper.citekey, 'citekey')))) else: - ui.info(('Paper `{}` was successfully edited.'.format( - citekey))) + ui.info(("Paper '{}' was successfully edited.".format( + color.dye_out(citekey, 'citekey')))) break except coder.BibDecodingError: @@ -84,7 +86,7 @@ def command(conf, args): break elif choice == 'overwrite': paper = rp.push_paper(paper, overwrite=True) - ui.info(('Paper `{}` was overwritten.'.format(citekey))) + ui.info(('Paper `{}` was overwritten.'.format(color.dye_out(citekey, 'citekey')))) break # else edit again # Also handle malformed bibtex and metadata diff --git a/pubs/commands/list_cmd.py b/pubs/commands/list_cmd.py index b615e54..24e533b 100644 --- a/pubs/commands/list_cmd.py +++ b/pubs/commands/list_cmd.py @@ -54,7 +54,7 @@ def command(conf, args): papers = sorted(papers, key=date_added) if len(papers) > 0: ui.message('\n'.join( - pretty.paper_oneliner(p, citekey_only=args.citekeys) + pretty.paper_oneliner(p, citekey_only=args.citekeys, max_authors=conf['main']['max_authors']) for p in papers)) rp.close() diff --git a/pubs/commands/tag_cmd.py b/pubs/commands/tag_cmd.py index da656b4..78357c3 100644 --- a/pubs/commands/tag_cmd.py +++ b/pubs/commands/tag_cmd.py @@ -117,7 +117,7 @@ def command(conf, args): len(p.tags.intersection(excluded)) == 0): papers_list.append(p) - ui.message('\n'.join(pretty.paper_oneliner(p) + ui.message('\n'.join(pretty.paper_oneliner(p, max_authors=conf['main']['max_authors']) for p in papers_list)) rp.close() diff --git a/pubs/config/spec.py b/pubs/config/spec.py index 4c50f4c..6921bae 100644 --- a/pubs/config/spec.py +++ b/pubs/config/spec.py @@ -27,6 +27,10 @@ edit_cmd = string(default='') # Which default extension to use when creating a note file. note_extension = string(default='txt') +# How many authors to display when displaying a citation. If there are more +# authors, only the first author is diplayed followed by 'et al.'. +max_authors = integer(default=3) + # If true debug mode is on which means exceptions are not catched and # the full python stack is printed. debug = boolean(default=False) diff --git a/pubs/pretty.py b/pubs/pretty.py index 0c951e6..6b12fb6 100644 --- a/pubs/pretty.py +++ b/pubs/pretty.py @@ -23,19 +23,24 @@ def person_repr(p): ' '.join(p.lineage(abbr=True))] if s) -def short_authors(bibdata): +def short_authors(bibdata, max_authors=3): + """ + :param max_authors: number of authors to display completely. Additional authors will be + represented by 'et al.'. + """ try: authors = [p for p in bibdata['author']] - if len(authors) < 3: - return ' and '.join(authors) + if 0 < max_authors < len(authors): + authors_str = '{} et al.'.format(authors[0]) else: - return authors[0] + (' et al.' if len(authors) > 1 else '') + authors_str = ' and '.join(authors) + return authors_str except KeyError: # When no author is defined return '' -def bib_oneliner(bibdata): - authors = short_authors(bibdata) +def bib_oneliner(bibdata, max_authors=3): + authors = short_authors(bibdata, max_authors=max_authors) journal = '' if 'journal' in bibdata: journal = ' ' + bibdata['journal'] @@ -60,11 +65,11 @@ def bib_desc(bib_data): return s -def paper_oneliner(p, citekey_only=False): +def paper_oneliner(p, citekey_only=False, max_authors=3): if citekey_only: return p.citekey else: - bibdesc = bib_oneliner(p.get_unicode_bibdata()) + bibdesc = bib_oneliner(p.get_unicode_bibdata(), max_authors=max_authors) doc_str = '' if p.docpath is not None: doc_extension = os.path.splitext(p.docpath)[1] diff --git a/pubs/utils.py b/pubs/utils.py index eda7480..9b93aa3 100644 --- a/pubs/utils.py +++ b/pubs/utils.py @@ -33,7 +33,7 @@ def resolve_citekey(repo, citekey, ui=None, exit_on_fail=True): "citekeys:".format(citekey)) for c in citekeys: p = repo.pull_paper(c) - ui.message(' {}'.format(pretty.paper_oneliner(p))) + ui.message(' {}'.format(pretty.paper_oneliner(p, max_authors=conf['main']['max_authors']))) if exit_on_fail: ui.exit() return citekey @@ -73,11 +73,11 @@ def standardize_doi(doi): """ doi_regexes = ( - '(10\.\d{4,9}/[-._;()/:A-z0-9\>\<]+)', - '(10.1002/[^\s]+)', - '(10\.\d{4}/\d+-\d+X?(\d+)\d+<[\d\w]+:[\d\w]*>\d+.\d+.\w+;\d)', - '(10\.1021/\w\w\d+\+)', - '(10\.1207/[\w\d]+\&\d+_\d+)') + r'(10\.\d{4,9}/[-._;()/:A-z0-9\>\<]+)', + r'(10.1002/[^\s]+)', + r'(10\.\d{4}/\d+-\d+X?(\d+)\d+<[\d\w]+:[\d\w]*>\d+.\d+.\w+;\d)', + r'(10\.1021/\w\w\d+\+)', + r'(10\.1207/[\w\d]+\&\d+_\d+)') doi_pattern = re.compile('|'.join(doi_regexes)) match = doi_pattern.search(doi) diff --git a/readme.md b/readme.md index 33fdadd..aa4c691 100644 --- a/readme.md +++ b/readme.md @@ -15,7 +15,7 @@ correct bugs, but have no short-term plans to add major features to it. Pubs doe is supposed to do: help us do science, so now we are mostly doing that. **Notice:** pubs is relatively stable but comes with no warranty; do keep backups of your data. -**Notice:** pubs currently works with Python 2.7, but support will be dropped as soon as maintaining it becomes tedious. +**Notice:** pubs currently works with Python 2.7, but support is being dropped (tests are not run anymore). ## Installation diff --git a/setup.py b/setup.py index d7fe2dd..a8eb7de 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ setup( ], test_suite='tests', - tests_require=['pyfakefs>=3.4', 'mock', 'ddt', 'certifi'], + tests_require=['pyfakefs>=3.4', 'mock', 'ddt==1.3.1', 'certifi'], # in order to avoid 'zipimport.ZipImportError: bad local file header' zip_safe=False, diff --git a/tests/test_pretty.py b/tests/test_pretty.py index 0c09211..b73edfd 100644 --- a/tests/test_pretty.py +++ b/tests/test_pretty.py @@ -28,5 +28,15 @@ class TestPretty(unittest.TestCase): line = 'Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web."' self.assertEqual(color.undye(pretty.bib_oneliner(bibdata['Page99'])), line) + def test_oneliner_max_authors(self): + decoder = endecoder.EnDecoder() + bibdata = decoder.decode_bibdata(bibtex_raw0) + for max_authors in [1, 2, 3]: + line = 'Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999)' + self.assertEqual(color.undye(pretty.bib_oneliner(bibdata['Page99'], max_authors=max_authors)), line) + for max_authors in [-1, 0, 4, 5, 10]: + line = 'Page, Lawrence and Brin, Sergey and Motwani, Rajeev and Winograd, Terry "The PageRank Citation Ranking: Bringing Order to the Web." (1999)' + self.assertEqual(color.undye(pretty.bib_oneliner(bibdata['Page99'], max_authors=max_authors)), line) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_usecase.py b/tests/test_usecase.py index 348366e..a591485 100644 --- a/tests/test_usecase.py +++ b/tests/test_usecase.py @@ -161,11 +161,19 @@ class CommandTestCase(fake_env.TestFakeFs): def tearDown(self): pass + def update_config(self, config_update, path=None): + """Allow to set the config parameters. Must have done a `pubs init` beforehand.""" + if path is None: + path = self.default_conf_path + cfg = conf.load_conf(path=path) + for section, section_update in config_update.items(): + cfg[section].update(section_update) + conf.save_conf(cfg, path=path) + + class DataCommandTestCase(CommandTestCase): - """Abstract TestCase intializing the fake filesystem and - copying fake data. - """ + """Abstract TestCase intializing the fake filesystem and copying fake data.""" def setUp(self, nsec_stat=True): super(DataCommandTestCase, self).setUp(nsec_stat=nsec_stat) @@ -182,8 +190,7 @@ class DataCommandTestCase(CommandTestCase): class URLContentTestCase(DataCommandTestCase): - """Mocks access to online files by using files in data directory. - """ + """Mocks access to online files by using files in data directory.""" def setUp(self): super(URLContentTestCase, self).setUp() @@ -209,6 +216,8 @@ class URLContentTestCase(DataCommandTestCase): content.url_exists = self._original_url_exist + + # Actual tests class TestAlone(CommandTestCase): @@ -1079,7 +1088,8 @@ class TestUsecase(DataCommandTestCase): 'pubs add -d data/no-ext data/pagerank.bib', 'pubs list', ] - self.assertEqual(correct, self.execute_cmds(cmds, capture_output=True)) + actual = self.execute_cmds(cmds, capture_output=True) + self.assertEqual(correct, actual) @mock.patch('pubs.apis.requests.get', side_effect=mock_requests.mock_requests_get) def test_readme(self, reqget): @@ -1145,5 +1155,23 @@ class TestCache(DataCommandTestCase): self.assertEqual(line1, out[4]) +class TestConfigChange(DataCommandTestCase): + + def test_max_authors_default(self): + line_al = '[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \n' + line_full = '[Page99] Page, Lawrence and Brin, Sergey and Motwani, Rajeev and Winograd, Terry "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \n' + + self.execute_cmds(['pubs init', 'pubs add data/pagerank.bib']) + + for max_authors in [1, 2, 3]: + self.update_config({'main': {'max_authors': max_authors}}) + self.execute_cmds([('pubs list', None, line_al, None)]) + + for max_authors in [-1, 0, 4, 5, 10]: + self.update_config({'main': {'max_authors': max_authors}}) + self.execute_cmds([('pubs list', None, line_full, None)]) + + + if __name__ == '__main__': unittest.main(verbosity=2)