Merge pull request #225 from pubs/feat/212_n_authors
Add a max_authors option in the configuration, and fix test failures. Fixes #223, #212. Includes PR #224.
This commit is contained in:
commit
8ab89ab3bb
36
.travis.yml
36
.travis.yml
@ -3,11 +3,6 @@ matrix:
|
|||||||
include:
|
include:
|
||||||
|
|
||||||
# Full tests (with online API)
|
# Full tests (with online API)
|
||||||
- os: linux
|
|
||||||
language: python
|
|
||||||
python: 2.7
|
|
||||||
env:
|
|
||||||
- TO_TEST=TEST_FULL
|
|
||||||
- os: linux
|
- os: linux
|
||||||
language: python
|
language: python
|
||||||
python: 3.7
|
python: 3.7
|
||||||
@ -15,16 +10,6 @@ matrix:
|
|||||||
sudo: true
|
sudo: true
|
||||||
env:
|
env:
|
||||||
- TO_TEST=TEST_FULL
|
- 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
|
- os: osx
|
||||||
language: generic
|
language: generic
|
||||||
python: ">=3.6"
|
python: ">=3.6"
|
||||||
@ -36,16 +21,6 @@ matrix:
|
|||||||
- source env/bin/activate
|
- source env/bin/activate
|
||||||
|
|
||||||
# Mock tests (with mock API)
|
# Mock tests (with mock API)
|
||||||
- os: linux
|
|
||||||
language: python
|
|
||||||
python: 3.3
|
|
||||||
env:
|
|
||||||
- TO_TEST=TEST_MOCK
|
|
||||||
- os: linux
|
|
||||||
language: python
|
|
||||||
python: 3.4
|
|
||||||
env:
|
|
||||||
- TO_TEST=TEST_MOCK
|
|
||||||
- os: linux
|
- os: linux
|
||||||
language: python
|
language: python
|
||||||
python: 3.5
|
python: 3.5
|
||||||
@ -63,6 +38,13 @@ matrix:
|
|||||||
sudo: true
|
sudo: true
|
||||||
env:
|
env:
|
||||||
- TO_TEST=TEST_MOCK
|
- TO_TEST=TEST_MOCK
|
||||||
|
- os: linux
|
||||||
|
language: python
|
||||||
|
dist: xenial
|
||||||
|
python: 3.8
|
||||||
|
sudo: true
|
||||||
|
env:
|
||||||
|
- TO_TEST=TEST_MOCK
|
||||||
|
|
||||||
|
|
||||||
# Install tests
|
# Install tests
|
||||||
@ -76,7 +58,7 @@ matrix:
|
|||||||
language: python
|
language: python
|
||||||
dist: xenial
|
dist: xenial
|
||||||
sudo: true
|
sudo: true
|
||||||
python: 3.7
|
python: 3.8
|
||||||
env:
|
env:
|
||||||
- TO_TEST=INSTALL
|
- TO_TEST=INSTALL
|
||||||
if: type = cron
|
if: type = cron
|
||||||
@ -94,7 +76,7 @@ matrix:
|
|||||||
if: type = cron
|
if: type = cron
|
||||||
|
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- python: 3.3
|
- python: 2.7
|
||||||
|
|
||||||
# command to run tests
|
# command to run tests
|
||||||
script:
|
script:
|
||||||
|
@ -20,6 +20,8 @@ six
|
|||||||
# those are the additional packages required to run the tests
|
# those are the additional packages required to run the tests
|
||||||
pyfakefs
|
pyfakefs
|
||||||
certifi
|
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
|
mock
|
||||||
pytest # optional (python setup.py test works without it), but possible nonetheless
|
pytest # optional (python setup.py test works without it), but possible nonetheless
|
||||||
|
@ -147,7 +147,7 @@ def command(conf, args):
|
|||||||
doc_add = conf['main']['doc_add']
|
doc_add = conf['main']['doc_add']
|
||||||
|
|
||||||
rp.push_paper(p)
|
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:
|
if docfile is not None:
|
||||||
rp.push_doc(p.citekey, docfile, copy=(doc_add in ('copy', 'move')))
|
rp.push_doc(p.citekey, docfile, copy=(doc_add in ('copy', 'move')))
|
||||||
if doc_add == 'move' and content.content_type(docfile) != 'url':
|
if doc_add == 'move' and content.content_type(docfile) != 'url':
|
||||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from ..paper import Paper
|
from ..paper import Paper
|
||||||
from .. import repo
|
from .. import repo
|
||||||
|
from .. import color
|
||||||
|
|
||||||
from ..uis import get_ui
|
from ..uis import get_ui
|
||||||
from ..endecoder import EnDecoder
|
from ..endecoder import EnDecoder
|
||||||
@ -55,17 +56,18 @@ def command(conf, args):
|
|||||||
new_paper = Paper(paper.citekey, paper.bibdata,
|
new_paper = Paper(paper.citekey, paper.bibdata,
|
||||||
metadata=content)
|
metadata=content)
|
||||||
rp.push_paper(new_paper, overwrite=True, event=False)
|
rp.push_paper(new_paper, overwrite=True, event=False)
|
||||||
ui.info(('The metadata of paper `{}` was successfully '
|
ui.info(("The metadata of paper '{}' was successfully "
|
||||||
'edited.'.format(citekey)))
|
"edited.".format(color.dye_out(citekey, 'citekey'))))
|
||||||
else:
|
else:
|
||||||
new_paper = Paper.from_bibentry(content,
|
new_paper = Paper.from_bibentry(content,
|
||||||
metadata=paper.metadata)
|
metadata=paper.metadata)
|
||||||
if rp.rename_paper(new_paper, old_citekey=paper.citekey):
|
if rp.rename_paper(new_paper, old_citekey=paper.citekey):
|
||||||
ui.info(('Paper `{}` was successfully edited and renamed '
|
ui.info(("Paper '{}' was successfully edited and renamed "
|
||||||
'as `{}`.'.format(citekey, new_paper.citekey)))
|
"as '{}'.".format(color.dye_out(citekey, 'citekey'),
|
||||||
|
color.dye_out(new_paper.citekey, 'citekey'))))
|
||||||
else:
|
else:
|
||||||
ui.info(('Paper `{}` was successfully edited.'.format(
|
ui.info(("Paper '{}' was successfully edited.".format(
|
||||||
citekey)))
|
color.dye_out(citekey, 'citekey'))))
|
||||||
break
|
break
|
||||||
|
|
||||||
except coder.BibDecodingError:
|
except coder.BibDecodingError:
|
||||||
@ -84,7 +86,7 @@ def command(conf, args):
|
|||||||
break
|
break
|
||||||
elif choice == 'overwrite':
|
elif choice == 'overwrite':
|
||||||
paper = rp.push_paper(paper, overwrite=True)
|
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
|
break
|
||||||
# else edit again
|
# else edit again
|
||||||
# Also handle malformed bibtex and metadata
|
# Also handle malformed bibtex and metadata
|
||||||
|
@ -54,7 +54,7 @@ def command(conf, args):
|
|||||||
papers = sorted(papers, key=date_added)
|
papers = sorted(papers, key=date_added)
|
||||||
if len(papers) > 0:
|
if len(papers) > 0:
|
||||||
ui.message('\n'.join(
|
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))
|
for p in papers))
|
||||||
|
|
||||||
rp.close()
|
rp.close()
|
||||||
|
@ -117,7 +117,7 @@ def command(conf, args):
|
|||||||
len(p.tags.intersection(excluded)) == 0):
|
len(p.tags.intersection(excluded)) == 0):
|
||||||
papers_list.append(p)
|
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))
|
for p in papers_list))
|
||||||
|
|
||||||
rp.close()
|
rp.close()
|
||||||
|
@ -27,6 +27,10 @@ edit_cmd = string(default='')
|
|||||||
# Which default extension to use when creating a note file.
|
# Which default extension to use when creating a note file.
|
||||||
note_extension = string(default='txt')
|
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
|
# If true debug mode is on which means exceptions are not catched and
|
||||||
# the full python stack is printed.
|
# the full python stack is printed.
|
||||||
debug = boolean(default=False)
|
debug = boolean(default=False)
|
||||||
|
@ -23,19 +23,24 @@ def person_repr(p):
|
|||||||
' '.join(p.lineage(abbr=True))] if s)
|
' '.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:
|
try:
|
||||||
authors = [p for p in bibdata['author']]
|
authors = [p for p in bibdata['author']]
|
||||||
if len(authors) < 3:
|
if 0 < max_authors < len(authors):
|
||||||
return ' and '.join(authors)
|
authors_str = '{} et al.'.format(authors[0])
|
||||||
else:
|
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
|
except KeyError: # When no author is defined
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def bib_oneliner(bibdata):
|
def bib_oneliner(bibdata, max_authors=3):
|
||||||
authors = short_authors(bibdata)
|
authors = short_authors(bibdata, max_authors=max_authors)
|
||||||
journal = ''
|
journal = ''
|
||||||
if 'journal' in bibdata:
|
if 'journal' in bibdata:
|
||||||
journal = ' ' + bibdata['journal']
|
journal = ' ' + bibdata['journal']
|
||||||
@ -60,11 +65,11 @@ def bib_desc(bib_data):
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
def paper_oneliner(p, citekey_only=False):
|
def paper_oneliner(p, citekey_only=False, max_authors=3):
|
||||||
if citekey_only:
|
if citekey_only:
|
||||||
return p.citekey
|
return p.citekey
|
||||||
else:
|
else:
|
||||||
bibdesc = bib_oneliner(p.get_unicode_bibdata())
|
bibdesc = bib_oneliner(p.get_unicode_bibdata(), max_authors=max_authors)
|
||||||
doc_str = ''
|
doc_str = ''
|
||||||
if p.docpath is not None:
|
if p.docpath is not None:
|
||||||
doc_extension = os.path.splitext(p.docpath)[1]
|
doc_extension = os.path.splitext(p.docpath)[1]
|
||||||
|
@ -33,7 +33,7 @@ def resolve_citekey(repo, citekey, ui=None, exit_on_fail=True):
|
|||||||
"citekeys:".format(citekey))
|
"citekeys:".format(citekey))
|
||||||
for c in citekeys:
|
for c in citekeys:
|
||||||
p = repo.pull_paper(c)
|
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:
|
if exit_on_fail:
|
||||||
ui.exit()
|
ui.exit()
|
||||||
return citekey
|
return citekey
|
||||||
@ -73,11 +73,11 @@ def standardize_doi(doi):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
doi_regexes = (
|
doi_regexes = (
|
||||||
'(10\.\d{4,9}/[-._;()/:A-z0-9\>\<]+)',
|
r'(10\.\d{4,9}/[-._;()/:A-z0-9\>\<]+)',
|
||||||
'(10.1002/[^\s]+)',
|
r'(10.1002/[^\s]+)',
|
||||||
'(10\.\d{4}/\d+-\d+X?(\d+)\d+<[\d\w]+:[\d\w]*>\d+.\d+.\w+;\d)',
|
r'(10\.\d{4}/\d+-\d+X?(\d+)\d+<[\d\w]+:[\d\w]*>\d+.\d+.\w+;\d)',
|
||||||
'(10\.1021/\w\w\d+\+)',
|
r'(10\.1021/\w\w\d+\+)',
|
||||||
'(10\.1207/[\w\d]+\&\d+_\d+)')
|
r'(10\.1207/[\w\d]+\&\d+_\d+)')
|
||||||
doi_pattern = re.compile('|'.join(doi_regexes))
|
doi_pattern = re.compile('|'.join(doi_regexes))
|
||||||
|
|
||||||
match = doi_pattern.search(doi)
|
match = doi_pattern.search(doi)
|
||||||
|
@ -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.
|
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 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
|
## Installation
|
||||||
|
|
||||||
|
2
setup.py
2
setup.py
@ -61,7 +61,7 @@ setup(
|
|||||||
],
|
],
|
||||||
|
|
||||||
test_suite='tests',
|
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'
|
# in order to avoid 'zipimport.ZipImportError: bad local file header'
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
@ -28,5 +28,15 @@ class TestPretty(unittest.TestCase):
|
|||||||
line = 'Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web."'
|
line = 'Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web."'
|
||||||
self.assertEqual(color.undye(pretty.bib_oneliner(bibdata['Page99'])), line)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -161,11 +161,19 @@ class CommandTestCase(fake_env.TestFakeFs):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
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):
|
class DataCommandTestCase(CommandTestCase):
|
||||||
"""Abstract TestCase intializing the fake filesystem and
|
"""Abstract TestCase intializing the fake filesystem and copying fake data."""
|
||||||
copying fake data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self, nsec_stat=True):
|
def setUp(self, nsec_stat=True):
|
||||||
super(DataCommandTestCase, self).setUp(nsec_stat=nsec_stat)
|
super(DataCommandTestCase, self).setUp(nsec_stat=nsec_stat)
|
||||||
@ -182,8 +190,7 @@ class DataCommandTestCase(CommandTestCase):
|
|||||||
|
|
||||||
|
|
||||||
class URLContentTestCase(DataCommandTestCase):
|
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):
|
def setUp(self):
|
||||||
super(URLContentTestCase, self).setUp()
|
super(URLContentTestCase, self).setUp()
|
||||||
@ -209,6 +216,8 @@ class URLContentTestCase(DataCommandTestCase):
|
|||||||
content.url_exists = self._original_url_exist
|
content.url_exists = self._original_url_exist
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Actual tests
|
# Actual tests
|
||||||
|
|
||||||
class TestAlone(CommandTestCase):
|
class TestAlone(CommandTestCase):
|
||||||
@ -1079,7 +1088,8 @@ class TestUsecase(DataCommandTestCase):
|
|||||||
'pubs add -d data/no-ext data/pagerank.bib',
|
'pubs add -d data/no-ext data/pagerank.bib',
|
||||||
'pubs list',
|
'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)
|
@mock.patch('pubs.apis.requests.get', side_effect=mock_requests.mock_requests_get)
|
||||||
def test_readme(self, reqget):
|
def test_readme(self, reqget):
|
||||||
@ -1145,5 +1155,23 @@ class TestCache(DataCommandTestCase):
|
|||||||
self.assertEqual(line1, out[4])
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main(verbosity=2)
|
unittest.main(verbosity=2)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user