Merge branch 'master' into git-plugin
This commit is contained in:
commit
1f1bbea917
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,7 +1,7 @@
|
|||||||
.python-version
|
.python-version
|
||||||
*~
|
*~
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.eggs
|
||||||
|
|
||||||
|
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
104
.travis.yml
104
.travis.yml
@ -1,49 +1,105 @@
|
|||||||
# list of environments to test
|
# list of environments to test
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
|
||||||
|
# Full tests (with online API)
|
||||||
- os: linux
|
- os: linux
|
||||||
language: python
|
language: python
|
||||||
python: 2.7
|
python: 2.7
|
||||||
- os: linux
|
env:
|
||||||
language: python
|
- TO_TEST=TEST_FULL
|
||||||
python: 3.3
|
|
||||||
- os: linux
|
|
||||||
language: python
|
|
||||||
python: 3.4
|
|
||||||
- os: linux
|
|
||||||
language: python
|
|
||||||
python: 3.5
|
|
||||||
- os: linux
|
|
||||||
language: python
|
|
||||||
python: 3.6
|
|
||||||
- os: linux
|
- os: linux
|
||||||
language: python
|
language: python
|
||||||
python: 3.7
|
python: 3.7
|
||||||
dist: xenial
|
dist: xenial
|
||||||
sudo: true
|
sudo: true
|
||||||
|
env:
|
||||||
|
- TO_TEST=TEST_FULL
|
||||||
- os: osx
|
- os: osx
|
||||||
language: generic
|
language: generic
|
||||||
python: 2.7
|
python: 2.7
|
||||||
before_install:
|
env:
|
||||||
- python2 --version
|
- TO_TEST=TEST_FULL
|
||||||
- pip2 install -U virtualenv
|
# before_install:
|
||||||
- virtualenv env -p python2
|
# - python2 --version
|
||||||
- source env/bin/activate
|
# - 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"
|
||||||
|
env:
|
||||||
|
- TO_TEST=TEST_FULL
|
||||||
before_install:
|
before_install:
|
||||||
- brew update
|
|
||||||
- brew outdated python3 || brew install python3 || brew upgrade python3
|
- brew outdated python3 || brew install python3 || brew upgrade python3
|
||||||
- python3 -m venv env
|
- python3 -m venv env
|
||||||
- source env/bin/activate
|
- source env/bin/activate
|
||||||
|
|
||||||
# command to install dependencies
|
# Mock tests (with mock API)
|
||||||
install:
|
- os: linux
|
||||||
- python --version
|
language: python
|
||||||
- export PUBS_TESTS_MODE=ONLINE
|
python: 3.3
|
||||||
|
env:
|
||||||
|
- TO_TEST=TEST_MOCK
|
||||||
|
- os: linux
|
||||||
|
language: python
|
||||||
|
python: 3.4
|
||||||
|
env:
|
||||||
|
- TO_TEST=TEST_MOCK
|
||||||
|
- os: linux
|
||||||
|
language: python
|
||||||
|
python: 3.5
|
||||||
|
env:
|
||||||
|
- TO_TEST=TEST_MOCK
|
||||||
|
- os: linux
|
||||||
|
language: python
|
||||||
|
python: 3.6
|
||||||
|
env:
|
||||||
|
- TO_TEST=TEST_MOCK
|
||||||
|
- os: linux
|
||||||
|
language: python
|
||||||
|
dist: xenial
|
||||||
|
python: 3.7
|
||||||
|
sudo: true
|
||||||
|
env:
|
||||||
|
- TO_TEST=TEST_MOCK
|
||||||
|
|
||||||
|
|
||||||
|
# Install tests
|
||||||
|
- os: linux
|
||||||
|
language: python
|
||||||
|
python: 2.7
|
||||||
|
env:
|
||||||
|
- TO_TEST=INSTALL
|
||||||
|
if: type = cron
|
||||||
|
- os: linux
|
||||||
|
language: python
|
||||||
|
dist: xenial
|
||||||
|
sudo: true
|
||||||
|
python: 3.7
|
||||||
|
env:
|
||||||
|
- TO_TEST=INSTALL
|
||||||
|
if: type = cron
|
||||||
|
- os: osx
|
||||||
|
language: generic
|
||||||
|
python: 2.7
|
||||||
|
env:
|
||||||
|
- TO_TEST=INSTALL
|
||||||
|
if: type = cron
|
||||||
|
- os: osx
|
||||||
|
language: generic
|
||||||
|
python: ">=3.6"
|
||||||
|
env:
|
||||||
|
- TO_TEST=INSTALL
|
||||||
|
if: type = cron
|
||||||
|
|
||||||
|
allow_failures:
|
||||||
|
- python: 3.3
|
||||||
|
|
||||||
# command to run tests
|
# command to run tests
|
||||||
script:
|
script:
|
||||||
- PUBS_TESTS_MODE=MOCK python setup.py test
|
- python --version
|
||||||
- PUBS_TESTS_MODE=COLLECT python setup.py test
|
- if [ "$TO_TEST" = "TEST_MOCK" ] ||
|
||||||
|
[ "$TO_TEST" = "TEST_FULL" ]; then PUBS_TESTS_MODE=MOCK python setup.py test; fi
|
||||||
|
- if [ "$TO_TEST" = "TEST_FULL" ]; then PUBS_TESTS_MODE=COLLECT python setup.py test; fi
|
||||||
|
- if [ "$TO_TEST" = "INSTALL" ]; then pip install -U pip && pip install pubs && pubs --help && pip uninstall -y pubs; fi
|
||||||
|
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
@ -0,0 +1 @@
|
|||||||
|
include readme.md
|
22
changelog.md
22
changelog.md
@ -3,16 +3,30 @@
|
|||||||
|
|
||||||
## Current master
|
## Current master
|
||||||
|
|
||||||
[Full Changelog](https://github.com/pubs/pubs/compare/v0.8.1...master)
|
[Full Changelog](https://github.com/pubs/pubs/compare/v0.8.2...master)
|
||||||
|
|
||||||
|
### Implemented enhancements
|
||||||
|
|
||||||
|
- Add `citekey` filter to `query` ([#193](https://github.com/pubs/pubs/pull/193) by [Shane Stone](https://github.com/shanewstone))
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed bugs
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.8.2](https://github.com/pubs/pubs/compare/v0.8.1...v0.8.2) (2019-01-06)
|
||||||
|
|
||||||
|
Fixes install on python2, and adding old-style arXiv references.
|
||||||
|
|
||||||
|
### Fixed bugs
|
||||||
|
|
||||||
|
- Fixes adding papers with slashes in their citekeys. [(#179)](https://github.com/pubs/pubs/pull/179) (thanks [Amlesh Sivanantham](https://github.com/zamlz) for reporting.)
|
||||||
|
- Fix missing readme.md for python2 pip install. [(#174)](https://github.com/pubs/pubs/pull/174)
|
||||||
|
|
||||||
### Implemented enhancements
|
### Implemented enhancements
|
||||||
|
|
||||||
- [(#45)](https://github.com/pubs/pubs/issues/45) Doc extension visible in pubs list ([#168](https://github.com/pubs/pubs/pull/168))
|
- [(#45)](https://github.com/pubs/pubs/issues/45) Doc extension visible in pubs list ([#168](https://github.com/pubs/pubs/pull/168))
|
||||||
|
|
||||||
|
|
||||||
### Fixed bugs
|
|
||||||
|
|
||||||
|
|
||||||
## [v0.8.1](https://github.com/pubs/pubs/compare/v0.8.0...v0.8.1) (2018-08-28)
|
## [v0.8.1](https://github.com/pubs/pubs/compare/v0.8.0...v0.8.1) (2018-08-28)
|
||||||
|
|
||||||
A hotfix release. All users of 0.8.0 are urged to upgrade.
|
A hotfix release. All users of 0.8.0 are urged to upgrade.
|
||||||
|
@ -19,6 +19,7 @@ six
|
|||||||
|
|
||||||
# those are the additional packages required to run the tests
|
# those are the additional packages required to run the tests
|
||||||
pyfakefs
|
pyfakefs
|
||||||
|
certifi
|
||||||
ddt
|
ddt
|
||||||
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
|
||||||
|
@ -15,7 +15,7 @@ class ReferenceNotFoundError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_bibentry_from_api(id_str, id_type, try_doi=True, ui=None):
|
def get_bibentry_from_api(id_str, id_type, try_doi=True, ui=None, raw=False):
|
||||||
"""Return a bibtex string from various ID methods.
|
"""Return a bibtex string from various ID methods.
|
||||||
|
|
||||||
This is a wrapper around functions that will return a bibtex string given
|
This is a wrapper around functions that will return a bibtex string given
|
||||||
@ -50,6 +50,9 @@ def get_bibentry_from_api(id_str, id_type, try_doi=True, ui=None):
|
|||||||
raise ValueError('id_type must be one of `doi`, `isbn`, or `arxiv`.')
|
raise ValueError('id_type must be one of `doi`, `isbn`, or `arxiv`.')
|
||||||
|
|
||||||
bibentry_raw = id_fns[id_type](id_str, try_doi=try_doi, ui=ui)
|
bibentry_raw = id_fns[id_type](id_str, try_doi=try_doi, ui=ui)
|
||||||
|
if raw:
|
||||||
|
return bibentry_raw
|
||||||
|
|
||||||
bibentry = endecoder.EnDecoder().decode_bibdata(bibentry_raw)
|
bibentry = endecoder.EnDecoder().decode_bibdata(bibentry_raw)
|
||||||
if bibentry is None:
|
if bibentry is None:
|
||||||
raise ReferenceNotFoundError(
|
raise ReferenceNotFoundError(
|
||||||
|
@ -51,11 +51,15 @@ def author_last(author_str):
|
|||||||
return author_str.split(',')[0]
|
return author_str.split(',')[0]
|
||||||
|
|
||||||
|
|
||||||
|
def valid_citekey(citekey):
|
||||||
|
"""Return if a citekey is a valid filename or not"""
|
||||||
|
# FIXME: a bit crude, but efficient for now (and allows unicode citekeys)
|
||||||
|
return not '/' in citekey
|
||||||
|
|
||||||
|
|
||||||
def generate_citekey(bibdata):
|
def generate_citekey(bibdata):
|
||||||
""" Generate a citekey from bib_data.
|
""" Generate a citekey from bib_data.
|
||||||
|
|
||||||
:param generate: if False, return the citekey defined in the file,
|
|
||||||
does not generate a new one.
|
|
||||||
:raise ValueError: if no author nor editor is defined.
|
:raise ValueError: if no author nor editor is defined.
|
||||||
"""
|
"""
|
||||||
citekey, entry = get_entry(bibdata)
|
citekey, entry = get_entry(bibdata)
|
||||||
|
@ -72,6 +72,22 @@ def bibentry_from_editor(conf, ui):
|
|||||||
return bibentry
|
return bibentry
|
||||||
|
|
||||||
|
|
||||||
|
def bibentry_from_api(args, ui, raw=False):
|
||||||
|
try:
|
||||||
|
if args.doi is not None:
|
||||||
|
return apis.get_bibentry_from_api(args.doi, 'doi', ui=ui, raw=raw)
|
||||||
|
elif args.isbn is not None:
|
||||||
|
return apis.get_bibentry_from_api(args.isbn, 'isbn', ui=ui, raw=raw)
|
||||||
|
# TODO distinguish between cases, offer to open the error page in a webbrowser.
|
||||||
|
# TODO offer to confirm/change citekey
|
||||||
|
elif args.arxiv is not None:
|
||||||
|
return apis.get_bibentry_from_api(args.arxiv, 'arxiv', ui=ui, raw=raw)
|
||||||
|
except apis.ReferenceNotFoundError as e:
|
||||||
|
ui.error(str(e))
|
||||||
|
ui.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def command(conf, args):
|
def command(conf, args):
|
||||||
"""
|
"""
|
||||||
:param bibfile: bibtex file (in .bib, .bibml or .yaml format.
|
:param bibfile: bibtex file (in .bib, .bibml or .yaml format.
|
||||||
@ -92,19 +108,7 @@ def command(conf, args):
|
|||||||
if args.doi is None and args.isbn is None and args.arxiv is None:
|
if args.doi is None and args.isbn is None and args.arxiv is None:
|
||||||
bibentry = bibentry_from_editor(conf, ui)
|
bibentry = bibentry_from_editor(conf, ui)
|
||||||
else:
|
else:
|
||||||
bibentry = None
|
bibentry = bibentry_from_api(args, ui)
|
||||||
try:
|
|
||||||
if args.doi is not None:
|
|
||||||
bibentry = apis.get_bibentry_from_api(args.doi, 'doi', ui=ui)
|
|
||||||
elif args.isbn is not None:
|
|
||||||
bibentry = apis.get_bibentry_from_api(args.isbn, 'isbn', ui=ui)
|
|
||||||
# TODO distinguish between cases, offer to open the error page in a webbrowser.
|
|
||||||
# TODO offer to confirm/change citekey
|
|
||||||
elif args.arxiv is not None:
|
|
||||||
bibentry = apis.get_bibentry_from_api(args.arxiv, 'arxiv', ui=ui)
|
|
||||||
except apis.ReferenceNotFoundError as e:
|
|
||||||
ui.error(str(e))
|
|
||||||
ui.exit(1)
|
|
||||||
else:
|
else:
|
||||||
bibentry_raw = content.get_content(bibfile, ui=ui)
|
bibentry_raw = content.get_content(bibfile, ui=ui)
|
||||||
bibentry = decoder.decode_bibdata(bibentry_raw)
|
bibentry = decoder.decode_bibdata(bibentry_raw)
|
||||||
@ -116,7 +120,7 @@ def command(conf, args):
|
|||||||
citekey = args.citekey
|
citekey = args.citekey
|
||||||
if citekey is None:
|
if citekey is None:
|
||||||
base_key = bibstruct.extract_citekey(bibentry)
|
base_key = bibstruct.extract_citekey(bibentry)
|
||||||
citekey = rp.unique_citekey(base_key)
|
citekey = rp.unique_citekey(base_key, bibentry)
|
||||||
elif citekey in rp:
|
elif citekey in rp:
|
||||||
ui.error('citekey already exist {}.'.format(citekey))
|
ui.error('citekey already exist {}.'.format(citekey))
|
||||||
ui.exit(1)
|
ui.exit(1)
|
||||||
|
@ -17,9 +17,9 @@ def command(conf, args):
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
# get modif from user
|
# get modif from user
|
||||||
ui.edit_file(config.get_confpath(), temporary=False)
|
ui.edit_file(conf.filename, temporary=False)
|
||||||
|
|
||||||
new_conf = config.load_conf()
|
new_conf = config.load_conf(path=conf.filename)
|
||||||
try:
|
try:
|
||||||
config.check_conf(new_conf)
|
config.check_conf(new_conf)
|
||||||
ui.message('The configuration file was updated.')
|
ui.message('The configuration file was updated.')
|
||||||
|
@ -14,26 +14,28 @@ from ..content import system_path, read_text_file
|
|||||||
from ..command_utils import add_doc_copy_arguments
|
from ..command_utils import add_doc_copy_arguments
|
||||||
|
|
||||||
|
|
||||||
_ABORT_USE_IGNORE_MSG = "Aborting import. Use --ignore-malformed to ignore."
|
_ABORT_USE_IGNORE_MSG = " Aborting import. Use --ignore-malformed to ignore."
|
||||||
_IGNORING_MSG = " Ignoring it."
|
_IGNORING_MSG = " Ignoring it."
|
||||||
|
|
||||||
|
|
||||||
def parser(subparsers, conf):
|
def parser(subparsers, conf):
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
'import',
|
'import',
|
||||||
help='import paper(s) to the repository')
|
help='import paper(s) to the repository.')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'bibpath',
|
'bibpath',
|
||||||
help='path to bibtex, bibtexml or bibyaml file (or directory)')
|
help=("path to bibtex, bibtexml or bibyaml file, or a directory "
|
||||||
|
"containing such files; will not recurse into subdirectories."))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'keys', nargs='*',
|
'keys', nargs='*',
|
||||||
help="one or several keys to import from the file")
|
help=("one or several keys to import from the file; if not provided,"
|
||||||
|
" all entries will be imported."))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-O', '--overwrite', action='store_true', default=False,
|
'-O', '--overwrite', action='store_true', default=False,
|
||||||
help="Overwrite keys already in the database")
|
help="overwrite keys already in the database.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-i', '--ignore-malformed', action='store_true', default=False,
|
'-i', '--ignore-malformed', action='store_true', default=False,
|
||||||
help="Ignore malformed and unreadable files and entries")
|
help="ignore malformed and unreadable files and entries.")
|
||||||
add_doc_copy_arguments(parser, copy=False)
|
add_doc_copy_arguments(parser, copy=False)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@ -52,7 +54,6 @@ def many_from_path(ui, bibpath, ignore=False):
|
|||||||
|
|
||||||
bibpath = system_path(bibpath)
|
bibpath = system_path(bibpath)
|
||||||
if os.path.isdir(bibpath):
|
if os.path.isdir(bibpath):
|
||||||
print([os.path.splitext(f)[-1][1:] for f in os.listdir(bibpath)])
|
|
||||||
all_files = [os.path.join(bibpath, f) for f in os.listdir(bibpath)
|
all_files = [os.path.join(bibpath, f) for f in os.listdir(bibpath)
|
||||||
if os.path.splitext(f)[-1][1:] == 'bib']
|
if os.path.splitext(f)[-1][1:] == 'bib']
|
||||||
else:
|
else:
|
||||||
|
@ -113,7 +113,7 @@ class EnDecoder(object):
|
|||||||
author for author in entry['author'])
|
author for author in entry['author'])
|
||||||
if 'editor' in entry:
|
if 'editor' in entry:
|
||||||
entry['editor'] = ' and '.join(
|
entry['editor'] = ' and '.join(
|
||||||
editor['name'] for editor in entry['editor'])
|
editor for editor in entry['editor'])
|
||||||
if 'keyword' in entry:
|
if 'keyword' in entry:
|
||||||
entry['keyword'] = ', '.join(
|
entry['keyword'] = ', '.join(
|
||||||
keyword for keyword in entry['keyword'])
|
keyword for keyword in entry['keyword'])
|
||||||
@ -122,7 +122,7 @@ class EnDecoder(object):
|
|||||||
def decode_bibdata(self, bibdata):
|
def decode_bibdata(self, bibdata):
|
||||||
"""Decodes bibdata from string.
|
"""Decodes bibdata from string.
|
||||||
|
|
||||||
If the decoding fails, returns a BibParseError.
|
If the decoding fails, returns a BibDecodingError.
|
||||||
"""
|
"""
|
||||||
if len(bibdata) == 0:
|
if len(bibdata) == 0:
|
||||||
error_msg = 'parsing error: the provided string has length zero.'
|
error_msg = 'parsing error: the provided string has length zero.'
|
||||||
@ -131,15 +131,23 @@ class EnDecoder(object):
|
|||||||
entries = bp.bparser.BibTexParser(
|
entries = bp.bparser.BibTexParser(
|
||||||
bibdata, common_strings=True, customization=customizations,
|
bibdata, common_strings=True, customization=customizations,
|
||||||
homogenize_fields=True).get_entry_dict()
|
homogenize_fields=True).get_entry_dict()
|
||||||
|
|
||||||
# Remove id from bibtexparser attribute which is stored as citekey
|
# Remove id from bibtexparser attribute which is stored as citekey
|
||||||
for e in entries:
|
for e in entries:
|
||||||
entries[e].pop(BP_ID_KEY)
|
entries[e].pop(BP_ID_KEY)
|
||||||
# Convert bibtexparser entrytype key to internal 'type'
|
# Convert bibtexparser entrytype key to internal 'type'
|
||||||
t = entries[e].pop(BP_ENTRYTYPE_KEY)
|
t = entries[e].pop(BP_ENTRYTYPE_KEY)
|
||||||
entries[e][TYPE_KEY] = t
|
entries[e][TYPE_KEY] = t
|
||||||
|
# Temporary fix to #188 (to be fully fixed when the upstream
|
||||||
|
# issue: sciunto-org/python-bibtexparser/#229 is fixed too)
|
||||||
|
if 'editor' in entries[e]:
|
||||||
|
entries[e]['editor'] = [
|
||||||
|
editor['name'] if isinstance(editor, dict) else editor
|
||||||
|
for editor in entries[e]['editor']]
|
||||||
if len(entries) > 0:
|
if len(entries) > 0:
|
||||||
return entries
|
return entries
|
||||||
|
else:
|
||||||
|
raise self.BibDecodingError(('no valid entry found in the provided data: '
|
||||||
|
' {}').format(bibdata), bibdata)
|
||||||
except (pyparsing.ParseException, pyparsing.ParseSyntaxException) as e:
|
except (pyparsing.ParseException, pyparsing.ParseSyntaxException) as e:
|
||||||
error_msg = self._format_parsing_error(e)
|
error_msg = self._format_parsing_error(e)
|
||||||
raise self.BibDecodingError(error_msg, bibdata)
|
raise self.BibDecodingError(error_msg, bibdata)
|
||||||
@ -147,7 +155,6 @@ class EnDecoder(object):
|
|||||||
error_msg = 'parsing error: undefined string in provided data: {}'.format(e)
|
error_msg = 'parsing error: undefined string in provided data: {}'.format(e)
|
||||||
raise self.BibDecodingError(error_msg, bibdata)
|
raise self.BibDecodingError(error_msg, bibdata)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _format_parsing_error(cls, e):
|
def _format_parsing_error(cls, e):
|
||||||
"""Transform a pyparsing exception into an error message
|
"""Transform a pyparsing exception into an error message
|
||||||
|
@ -87,10 +87,14 @@ else:
|
|||||||
super(StdIO, self).__init__(*args, **kwargs)
|
super(StdIO, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def write(self, s):
|
def write(self, s):
|
||||||
|
super(StdIO, self).write(s)
|
||||||
if self.additional_out is not None:
|
if self.additional_out is not None:
|
||||||
|
try:
|
||||||
|
s = s.decode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
self.additional_out.write(s)
|
self.additional_out.write(s)
|
||||||
|
|
||||||
super(StdIO, self).write(s)
|
|
||||||
|
|
||||||
# Only for tests to capture std{out,err}
|
# Only for tests to capture std{out,err}
|
||||||
def _fake_stdio(additional_out=False):
|
def _fake_stdio(additional_out=False):
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import copy
|
import copy
|
||||||
from dateutil.parser import parse as datetime_parse
|
from dateutil.parser import parse as datetime_parse
|
||||||
|
|
||||||
|
from bibtexparser.customization import convert_to_unicode
|
||||||
|
|
||||||
from . import bibstruct
|
from . import bibstruct
|
||||||
from .p3 import ustr
|
from .p3 import ustr
|
||||||
|
|
||||||
@ -102,6 +104,10 @@ class Paper(object):
|
|||||||
def added(self, value):
|
def added(self, value):
|
||||||
self.metadata['added'] = value
|
self.metadata['added'] = value
|
||||||
|
|
||||||
|
def get_unicode_bibdata(self):
|
||||||
|
"""Converts latex in bibdata fields to unicode."""
|
||||||
|
return convert_to_unicode(self.bibdata)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bibentry(bibentry, citekey=None, metadata=None):
|
def from_bibentry(bibentry, citekey=None, metadata=None):
|
||||||
bibentry_key, bibdata = bibstruct.get_entry(bibentry)
|
bibentry_key, bibdata = bibstruct.get_entry(bibentry)
|
||||||
|
@ -64,7 +64,7 @@ def paper_oneliner(p, citekey_only=False):
|
|||||||
if citekey_only:
|
if citekey_only:
|
||||||
return p.citekey
|
return p.citekey
|
||||||
else:
|
else:
|
||||||
bibdesc = bib_oneliner(p.bibdata)
|
bibdesc = bib_oneliner(p.get_unicode_bibdata())
|
||||||
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]
|
||||||
|
@ -60,7 +60,8 @@ def execute(raw_args=sys.argv):
|
|||||||
# an update happened, reload conf.
|
# an update happened, reload conf.
|
||||||
conf = config.load_conf(path=conf_path)
|
conf = config.load_conf(path=conf_path)
|
||||||
except config.ConfigurationNotFound:
|
except config.ConfigurationNotFound:
|
||||||
if len(remaining_args) == 0 or remaining_args[0] == 'init':
|
if (len(remaining_args) == 0 or remaining_args[0] == 'init'
|
||||||
|
or all(arg[0] == '-' for arg in remaining_args)): # only optional arguments
|
||||||
conf = config.load_default_conf()
|
conf = config.load_default_conf()
|
||||||
conf.filename = conf_path
|
conf.filename = conf_path
|
||||||
else:
|
else:
|
||||||
|
@ -6,7 +6,8 @@ from . import bibstruct
|
|||||||
|
|
||||||
|
|
||||||
QUERY_HELP = ('Paper query ("author:Einstein", "title:learning",'
|
QUERY_HELP = ('Paper query ("author:Einstein", "title:learning",'
|
||||||
'"year:2000", "year:2000-2010", or "tags:math")')
|
' "year:2000", "year:2000-2010", "citekey:Einstein_1935",'
|
||||||
|
' or "tags:math")')
|
||||||
|
|
||||||
|
|
||||||
FIELD_ALIASES = {
|
FIELD_ALIASES = {
|
||||||
@ -15,6 +16,7 @@ FIELD_ALIASES = {
|
|||||||
't': 'title',
|
't': 'title',
|
||||||
'tags': 'tag',
|
'tags': 'tag',
|
||||||
'y': 'year',
|
'y': 'year',
|
||||||
|
'key': 'citekey',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -78,6 +80,12 @@ class AuthorFilter(QueryFilter):
|
|||||||
for author in paper.bibdata['author']])
|
for author in paper.bibdata['author']])
|
||||||
|
|
||||||
|
|
||||||
|
class CitekeyFilter(QueryFilter):
|
||||||
|
|
||||||
|
def __call__(self, paper):
|
||||||
|
return self._is_query_in(paper.citekey)
|
||||||
|
|
||||||
|
|
||||||
class TagFilter(QueryFilter):
|
class TagFilter(QueryFilter):
|
||||||
|
|
||||||
def __call__(self, paper):
|
def __call__(self, paper):
|
||||||
@ -137,6 +145,9 @@ def _query_block_to_filter(query_block, case_sensitive=None, strict=False):
|
|||||||
field, value = _get_field_value(query_block)
|
field, value = _get_field_value(query_block)
|
||||||
if field == 'tag':
|
if field == 'tag':
|
||||||
return TagFilter(value, case_sensitive=case_sensitive, strict=strict)
|
return TagFilter(value, case_sensitive=case_sensitive, strict=strict)
|
||||||
|
elif field == 'citekey':
|
||||||
|
return CitekeyFilter(value, case_sensitive=case_sensitive,
|
||||||
|
strict=strict)
|
||||||
elif field == 'author':
|
elif field == 'author':
|
||||||
return AuthorFilter(value, case_sensitive=case_sensitive,
|
return AuthorFilter(value, case_sensitive=case_sensitive,
|
||||||
strict=strict)
|
strict=strict)
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '0.8.1'
|
__version__ = '0.8.2-r1'
|
||||||
|
85
readme.md
85
readme.md
@ -16,20 +16,23 @@ Pubs is built with the following principles in mind:
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
You can install the latest stable version of `pubs` through Pypi, with:
|
You can install the latest stable version of `pubs` through Pypi, with:
|
||||||
|
```
|
||||||
pip install pubs
|
pip install pubs
|
||||||
|
```
|
||||||
|
|
||||||
Alternatively, you can:
|
Alternatively, you can:
|
||||||
|
|
||||||
- install the latest development version with pip:
|
- install the latest development version with pip:
|
||||||
|
```
|
||||||
pip install --upgrade git+https://github.com/pubs/pubs
|
pip install --upgrade git+https://github.com/pubs/pubs
|
||||||
|
```
|
||||||
|
|
||||||
- clone the repository and install it manually:
|
- clone the repository and install it manually:
|
||||||
|
```
|
||||||
git clone https://github.com/pubs/pubs
|
git clone https://github.com/pubs/pubs
|
||||||
cd pubs
|
cd pubs
|
||||||
python setup.py install [--user]
|
python setup.py install [--user]
|
||||||
|
```
|
||||||
|
|
||||||
Arch Linux users can also use the [pubs-git](https://aur.archlinux.org/packages/pubs-git/) AUR package.
|
Arch Linux users can also use the [pubs-git](https://aur.archlinux.org/packages/pubs-git/) AUR package.
|
||||||
|
|
||||||
@ -37,28 +40,34 @@ Arch Linux users can also use the [pubs-git](https://aur.archlinux.org/packages/
|
|||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
Create your library (by default, goes to `~/.pubs/`).
|
Create your library (by default, goes to `~/.pubs/`).
|
||||||
|
```
|
||||||
pubs init
|
pubs init
|
||||||
|
```
|
||||||
|
|
||||||
Import existing data from bibtex (pubs will try to automatically copy documents defined as 'file' in bibtex):
|
Import existing data from bibtex (pubs will try to automatically copy documents defined as 'file' in bibtex):
|
||||||
|
```
|
||||||
pubs import path/to/collection.bib
|
pubs import path/to/collection.bib
|
||||||
|
```
|
||||||
|
|
||||||
or for a .bib file containing a single reference:
|
or for a .bib file containing a single reference:
|
||||||
|
```
|
||||||
pubs add reference.bib -d article.pdf
|
pubs add reference.bib -d article.pdf
|
||||||
|
```
|
||||||
|
|
||||||
pubs can also automatically retrieve the bibtex from a doi:
|
pubs can also automatically retrieve the bibtex from a doi:
|
||||||
|
```
|
||||||
pubs add -D 10.1007/s00422-012-0514-6 -d article.pdf
|
pubs add -D 10.1007/s00422-012-0514-6 -d article.pdf
|
||||||
|
```
|
||||||
|
|
||||||
or an ISBN (dashes are ignored):
|
or an ISBN (dashes are ignored):
|
||||||
|
```
|
||||||
pubs add -I 978-0822324669 -d article.pdf
|
pubs add -I 978-0822324669 -d article.pdf
|
||||||
|
```
|
||||||
|
|
||||||
or an arXiv id (automatically downloading arXiv article is in the works):
|
or an arXiv id (automatically downloading arXiv article is in the works):
|
||||||
|
```
|
||||||
pubs add -X math/9501234 -d article.pdf
|
pubs add -X math/9501234 -d article.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## References always up-to-date
|
## References always up-to-date
|
||||||
@ -66,8 +75,9 @@ or an arXiv id (automatically downloading arXiv article is in the works):
|
|||||||
If you use latex, you can automatize references, by running `pubs export > references.bib` each time you update your library, which also fits well as a `makefile` rule.
|
If you use latex, you can automatize references, by running `pubs export > references.bib` each time you update your library, which also fits well as a `makefile` rule.
|
||||||
|
|
||||||
This ensures that your reference file is always up-to-date; you can cite a paper in your manuscript a soon as you add it in pubs. This means that if you have, for instance, a doi on a webpage, you only need to do:
|
This ensures that your reference file is always up-to-date; you can cite a paper in your manuscript a soon as you add it in pubs. This means that if you have, for instance, a doi on a webpage, you only need to do:
|
||||||
|
```
|
||||||
pubs add -D 10.1007/s00422-012-0514-6
|
pubs add -D 10.1007/s00422-012-0514-6
|
||||||
|
```
|
||||||
|
|
||||||
and then add `\cite{Loeb_2012}` in your manuscript. After exporting the bibliography, the citation will correctly appear in your compiled pdf.
|
and then add `\cite{Loeb_2012}` in your manuscript. After exporting the bibliography, the citation will correctly appear in your compiled pdf.
|
||||||
|
|
||||||
@ -75,23 +85,25 @@ and then add `\cite{Loeb_2012}` in your manuscript. After exporting the bibliogr
|
|||||||
## Document management
|
## Document management
|
||||||
|
|
||||||
You can attach a document to a reference:
|
You can attach a document to a reference:
|
||||||
|
```
|
||||||
pubs add Loeb2012_downloaded.pdf Loeb_2012
|
pubs doc add Loeb2012_downloaded.pdf Loeb_2012
|
||||||
|
```
|
||||||
|
|
||||||
And open your documents automatically from the command line:
|
And open your documents automatically from the command line:
|
||||||
|
```
|
||||||
pubs doc open Loeb_2012
|
pubs doc open Loeb_2012
|
||||||
pubs doc open --with lp Loeb_2012 # Opens the document with `lp` to actually print it.
|
pubs doc open --with lp Loeb_2012 # Opens the document with `lp` to actually print it.
|
||||||
|
```
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
|
||||||
Pubs is designed to interact well with your command line tool chain.
|
Pubs is designed to interact well with your command line tool chain.
|
||||||
You can add custom commands to pubs by defining aliases in your configuration file (make sure that the alias plugin is activated in your configuration by using `pubs conf`).
|
You can add custom commands to pubs by defining aliases in your configuration file (make sure that the alias plugin is activated in your configuration by using `pubs conf`).
|
||||||
|
```ini
|
||||||
[[alias]]
|
[[alias]]
|
||||||
evince = open --with evince
|
evince = open --with evince
|
||||||
count = !pubs list -k "$@" | wc -l
|
count = !pubs list -k "$@" | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
The first command defines a new subcommand: `pubs open --with evince` will be executed when `pubs evince` is typed.
|
The first command defines a new subcommand: `pubs open --with evince` will be executed when `pubs evince` is typed.
|
||||||
The second starts with a bang: `!`, and is treated as a shell command. If other arguments are provided they are passed to the shell command as in a script. In the example above the `count` alias can take arguments that are be passed to the `pubs list -k` command, hence enabling filters like `pubs count year:2012`.
|
The second starts with a bang: `!`, and is treated as a shell command. If other arguments are provided they are passed to the shell command as in a script. In the example above the `count` alias can take arguments that are be passed to the `pubs list -k` command, hence enabling filters like `pubs count year:2012`.
|
||||||
@ -102,14 +114,14 @@ The second starts with a bang: `!`, and is treated as a shell command. If other
|
|||||||
For autocompletion to work, you need the [argcomplete](https://argcomplete.readthedocs.io) Python package, and Bash 4.2 or newer. For activating *bash* or *tsch* completion, consult the [argcomplete documentation](https://argcomplete.readthedocs.io/en/latest/#global-completion).
|
For autocompletion to work, you need the [argcomplete](https://argcomplete.readthedocs.io) Python package, and Bash 4.2 or newer. For activating *bash* or *tsch* completion, consult the [argcomplete documentation](https://argcomplete.readthedocs.io/en/latest/#global-completion).
|
||||||
|
|
||||||
For *zsh* completion, the global activation is not supported but bash completion compatibility can be used for pubs. For that, add the following to your `.zshrc`:
|
For *zsh* completion, the global activation is not supported but bash completion compatibility can be used for pubs. For that, add the following to your `.zshrc`:
|
||||||
|
```shell
|
||||||
# Enable and load bashcompinit
|
# Enable and load bashcompinit
|
||||||
autoload -Uz compinit bashcompinit
|
autoload -Uz compinit bashcompinit
|
||||||
compinit
|
compinit
|
||||||
bashcompinit
|
bashcompinit
|
||||||
# Argcomplete explicit registration for pubs
|
# Argcomplete explicit registration for pubs
|
||||||
eval "$(register-python-argcomplete pubs)"
|
eval "$(register-python-argcomplete pubs)"
|
||||||
|
```
|
||||||
|
|
||||||
## Need more help ?
|
## Need more help ?
|
||||||
|
|
||||||
@ -130,4 +142,5 @@ You can access the self-documented configuration by using `pubs conf`, and all t
|
|||||||
- [Dennis Wilson](https://github.com/d9w)
|
- [Dennis Wilson](https://github.com/d9w)
|
||||||
- [Bill Flynn](https://github.com/wflynny)
|
- [Bill Flynn](https://github.com/wflynny)
|
||||||
- [ksunden](https://github.com/ksunden)
|
- [ksunden](https://github.com/ksunden)
|
||||||
|
- [Shane Stone](https://github.com/shanewstone)
|
||||||
- [Amlesh Sivanantham](http://github.com/zamlz)
|
- [Amlesh Sivanantham](http://github.com/zamlz)
|
9
setup.py
9
setup.py
@ -4,6 +4,7 @@ import unittest
|
|||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
with open('pubs/version.py') as f:
|
with open('pubs/version.py') as f:
|
||||||
exec(f.read()) # defines __version__
|
exec(f.read()) # defines __version__
|
||||||
|
|
||||||
@ -11,11 +12,13 @@ here = os.path.abspath(os.path.dirname(__file__))
|
|||||||
with open(os.path.join(here, 'readme.md'), 'r') as fd:
|
with open(os.path.join(here, 'readme.md'), 'r') as fd:
|
||||||
long_description = fd.read()
|
long_description = fd.read()
|
||||||
|
|
||||||
|
|
||||||
def pubs_test_suite():
|
def pubs_test_suite():
|
||||||
test_loader = unittest.TestLoader()
|
test_loader = unittest.TestLoader()
|
||||||
test_suite = test_loader.discover('tests', pattern='test_*.py')
|
test_suite = test_loader.discover('tests', pattern='test_*.py')
|
||||||
return test_suite
|
return test_suite
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='pubs',
|
name='pubs',
|
||||||
version=__version__,
|
version=__version__,
|
||||||
@ -41,6 +44,8 @@ setup(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
include_package_data=True,
|
||||||
|
|
||||||
install_requires=['pyyaml', 'bibtexparser>=1.0', 'python-dateutil', 'six',
|
install_requires=['pyyaml', 'bibtexparser>=1.0', 'python-dateutil', 'six',
|
||||||
'requests', 'configobj', 'beautifulsoup4', 'feedparser'],
|
'requests', 'configobj', 'beautifulsoup4', 'feedparser'],
|
||||||
extras_require={'autocompletion': ['argcomplete'],
|
extras_require={'autocompletion': ['argcomplete'],
|
||||||
@ -55,8 +60,8 @@ setup(
|
|||||||
'Intended Audience :: Science/Research',
|
'Intended Audience :: Science/Research',
|
||||||
],
|
],
|
||||||
|
|
||||||
test_suite= 'tests',
|
test_suite='tests',
|
||||||
tests_require=['pyfakefs>=3.4', 'mock', 'ddt'],
|
tests_require=['pyfakefs>=3.4', 'mock', 'ddt', '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,
|
||||||
|
41
tests/data/collection.bib
Normal file
41
tests/data/collection.bib
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
@article{Einstein_1935,
|
||||||
|
doi = {10.1103/physrev.47.777},
|
||||||
|
url = {https://doi.org/10.1103%2Fphysrev.47.777},
|
||||||
|
year = 1935,
|
||||||
|
month = {may},
|
||||||
|
publisher = {American Physical Society ({APS})},
|
||||||
|
volume = {47},
|
||||||
|
number = {10},
|
||||||
|
pages = {777--780},
|
||||||
|
author = {A. Einstein and B. Podolsky and N. Rosen},
|
||||||
|
title = {Can Quantum-Mechanical Description of Physical Reality Be Considered Complete?},
|
||||||
|
journal = {Physical Review}
|
||||||
|
}
|
||||||
|
|
||||||
|
@article{Schrodinger_1935,
|
||||||
|
doi = {10.1017/s0305004100013554},
|
||||||
|
url = {https://doi.org/10.1017%2Fs0305004100013554},
|
||||||
|
year = 1935,
|
||||||
|
month = {oct},
|
||||||
|
publisher = {Cambridge University Press ({CUP})},
|
||||||
|
volume = {31},
|
||||||
|
number = {04},
|
||||||
|
pages = {555},
|
||||||
|
author = {E. Schrödinger and M. Born},
|
||||||
|
title = {Discussion of Probability Relations between Separated Systems},
|
||||||
|
journal = {Mathematical Proceedings of the Cambridge Philosophical Society}
|
||||||
|
}
|
||||||
|
|
||||||
|
@article{Bell_1964,
|
||||||
|
doi = {10.1103/physicsphysiquefizika.1.195},
|
||||||
|
url = {https://doi.org/10.1103%2Fphysicsphysiquefizika.1.195},
|
||||||
|
year = 1964,
|
||||||
|
month = {nov},
|
||||||
|
publisher = {American Physical Society ({APS})},
|
||||||
|
volume = {1},
|
||||||
|
number = {3},
|
||||||
|
pages = {195--200},
|
||||||
|
author = {J. S. Bell},
|
||||||
|
title = {On the Einstein Podolsky Rosen paradox},
|
||||||
|
journal = {Physics Physique {\cyrchar\cyrf}{\cyrchar\cyri}{\cyrchar\cyrz}{\cyrchar\cyri}{\cyrchar\cyrk}{\cyrchar\cyra}}
|
||||||
|
}
|
16
tests/data/many_fields.bib
Normal file
16
tests/data/many_fields.bib
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@ARTICLE{Cesar2013,
|
||||||
|
authors = {Jean César},
|
||||||
|
title = {An amazing title},
|
||||||
|
year = {2013},
|
||||||
|
month = "jan",
|
||||||
|
volume = {12},
|
||||||
|
pages = {12-23},
|
||||||
|
journal = {Nice Journal},
|
||||||
|
abstract = {This is an abstract. This line should be long enough to test
|
||||||
|
multilines... and with a french érudit word},
|
||||||
|
comments = {A comment},
|
||||||
|
editors = {Edith Or and Anne Other},
|
||||||
|
keywords = {keyword1, keyword2},
|
||||||
|
links = {http://my.link/to-content},
|
||||||
|
subjects = "Some topic of interest",
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
1. Install the dependencies using:
|
1. Install the dependencies using:
|
||||||
> pip install -r requirements.txt
|
```
|
||||||
|
pip install -r ../dev_requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
2. Run the tests using:
|
2. Run the tests using:
|
||||||
> python -m unittest discover
|
```
|
||||||
|
python setup.py test
|
||||||
|
```
|
||||||
|
|
||||||
If you use nosetest, it will complain about addExpectedFailure, which you can safely disregard.
|
If you use nosetest, it will complain about addExpectedFailure, which you can safely disregard.
|
@ -84,6 +84,18 @@ not_bibtex = """@misc{this looks,
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
bibtex_with_latex = """@article{kjaer2018large,
|
||||||
|
title={A large impact crater beneath Hiawatha Glacier in northwest Greenland},
|
||||||
|
author={Kj{\\ae}r, Kurt H and Larsen, Nicolaj K and Binder, Tobias and Bj{\\o}rk, Anders A and Eisen, Olaf and Fahnestock, Mark A and Funder, Svend and Garde, Adam A and Haack, Henning and Helm, Veit and others},
|
||||||
|
journal={Science advances},
|
||||||
|
volume={4},
|
||||||
|
number={11},
|
||||||
|
pages={eaar8173},
|
||||||
|
year={2018},
|
||||||
|
publisher={American Association for the Advancement of Science}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
sample_conf = """
|
sample_conf = """
|
||||||
[main]
|
[main]
|
||||||
|
|
||||||
|
@ -16,8 +16,12 @@ import mock_requests
|
|||||||
|
|
||||||
|
|
||||||
class APITests(unittest.TestCase):
|
class APITests(unittest.TestCase):
|
||||||
pass
|
|
||||||
|
|
||||||
|
@mock.patch('pubs.apis.requests.get', side_effect=mock_requests.mock_requests_get)
|
||||||
|
def test_readme(self, reqget):
|
||||||
|
apis.doi2bibtex('10.1007/s00422-012-0514-6')
|
||||||
|
apis.isbn2bibtex('978-0822324669')
|
||||||
|
apis.arxiv2bibtex('math/9501234')
|
||||||
|
|
||||||
class TestDOI2Bibtex(APITests):
|
class TestDOI2Bibtex(APITests):
|
||||||
|
|
||||||
@ -45,11 +49,12 @@ class TestDOI2Bibtex(APITests):
|
|||||||
|
|
||||||
class TestISBN2Bibtex(APITests):
|
class TestISBN2Bibtex(APITests):
|
||||||
|
|
||||||
@mock.patch('pubs.apis.requests.get', side_effect=mock_requests.mock_requests_get)
|
# try to avoid triggering 403 status during tests.
|
||||||
def test_unicode(self, reqget):
|
# @mock.patch('pubs.apis.requests.get', side_effect=mock_requests.mock_requests_get)
|
||||||
bib = apis.isbn2bibtex('9782081336742')
|
# def test_unicode(self, reqget):
|
||||||
self.assertIsInstance(bib, ustr)
|
# bib = apis.isbn2bibtex('9782081336742')
|
||||||
self.assertIn('Poincaré, Henri', bib)
|
# self.assertIsInstance(bib, ustr)
|
||||||
|
# self.assertIn('Poincaré, Henri', bib)
|
||||||
|
|
||||||
@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_parses_to_bibtex(self, reqget):
|
def test_parses_to_bibtex(self, reqget):
|
||||||
|
File diff suppressed because one or more lines are too long
@ -26,7 +26,7 @@ class TestEnDecode(unittest.TestCase):
|
|||||||
def test_decode_emptystring(self):
|
def test_decode_emptystring(self):
|
||||||
decoder = endecoder.EnDecoder()
|
decoder = endecoder.EnDecoder()
|
||||||
with self.assertRaises(decoder.BibDecodingError):
|
with self.assertRaises(decoder.BibDecodingError):
|
||||||
entry = decoder.decode_bibdata('')
|
decoder.decode_bibdata('')
|
||||||
|
|
||||||
def test_encode_bibtex_is_unicode(self):
|
def test_encode_bibtex_is_unicode(self):
|
||||||
decoder = endecoder.EnDecoder()
|
decoder = endecoder.EnDecoder()
|
||||||
@ -117,11 +117,21 @@ class TestEnDecode(unittest.TestCase):
|
|||||||
self.assertIn('keyword', entry)
|
self.assertIn('keyword', entry)
|
||||||
self.assertEqual(set(keywords), set(entry['keyword']))
|
self.assertEqual(set(keywords), set(entry['keyword']))
|
||||||
|
|
||||||
|
def test_decode_metadata(self):
|
||||||
|
decoder = endecoder.EnDecoder()
|
||||||
|
entry = decoder.decode_metadata(metadata_raw0)
|
||||||
|
expected = {'docfile': 'docsdir://Page99.pdf',
|
||||||
|
'tags': ['search', 'network'],
|
||||||
|
'added': '2013-11-14 13:14:20',
|
||||||
|
}
|
||||||
|
self.assertEqual(entry, expected)
|
||||||
|
|
||||||
def test_endecode_metadata(self):
|
def test_endecode_metadata(self):
|
||||||
decoder = endecoder.EnDecoder()
|
decoder = endecoder.EnDecoder()
|
||||||
entry = decoder.decode_metadata(metadata_raw0)
|
entry = decoder.decode_metadata(metadata_raw0)
|
||||||
metadata_output0 = decoder.encode_metadata(entry)
|
metadata_output0 = decoder.encode_metadata(entry)
|
||||||
self.assertEqual(set(metadata_raw0.split('\n')), set(metadata_output0.split('\n')))
|
entry_from_encode = decoder.decode_metadata(metadata_output0)
|
||||||
|
self.assertEqual(entry, entry_from_encode)
|
||||||
|
|
||||||
def test_endecode_bibtex_field_order(self):
|
def test_endecode_bibtex_field_order(self):
|
||||||
decoder = endecoder.EnDecoder()
|
decoder = endecoder.EnDecoder()
|
||||||
|
@ -4,7 +4,9 @@ import unittest
|
|||||||
|
|
||||||
import dotdot
|
import dotdot
|
||||||
import fixtures
|
import fixtures
|
||||||
|
import str_fixtures
|
||||||
from pubs.paper import Paper
|
from pubs.paper import Paper
|
||||||
|
from pubs.endecoder import EnDecoder
|
||||||
|
|
||||||
|
|
||||||
class TestAttributes(unittest.TestCase):
|
class TestAttributes(unittest.TestCase):
|
||||||
@ -47,5 +49,20 @@ class TestAttributes(unittest.TestCase):
|
|||||||
Paper(" ", fixtures.doe_bibdata)
|
Paper(" ", fixtures.doe_bibdata)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaperUnicodeBibdata(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_no_latex(self):
|
||||||
|
p = Paper.from_bibentry(fixtures.page_bibentry,
|
||||||
|
metadata=fixtures.page_metadata).deepcopy()
|
||||||
|
self.assertEqual(p.bibdata, p.get_unicode_bibdata())
|
||||||
|
|
||||||
|
def test_latex_converted(self):
|
||||||
|
bib = EnDecoder().decode_bibdata(str_fixtures.bibtex_with_latex)
|
||||||
|
p = Paper.from_bibentry(bib)
|
||||||
|
ubib = p.get_unicode_bibdata()
|
||||||
|
self.assertEqual(ubib['author'][0], u"Kjær, Kurt H")
|
||||||
|
self.assertEqual(ubib['author'][3], u"Bjørk, Anders A")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -4,9 +4,9 @@ from __future__ import unicode_literals
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import dotdot
|
import dotdot
|
||||||
from pubs.query import (AuthorFilter, FieldFilter, YearFilter,
|
from pubs.query import (AuthorFilter, CitekeyFilter, FieldFilter,
|
||||||
_query_block_to_filter, get_paper_filter,
|
YearFilter, _query_block_to_filter,
|
||||||
InvalidQuery)
|
get_paper_filter, InvalidQuery)
|
||||||
|
|
||||||
from pubs.paper import Paper
|
from pubs.paper import Paper
|
||||||
|
|
||||||
@ -43,6 +43,49 @@ class TestAuthorFilter(unittest.TestCase):
|
|||||||
self.assertFalse(AuthorFilter('lawrence')(page_paper))
|
self.assertFalse(AuthorFilter('lawrence')(page_paper))
|
||||||
|
|
||||||
|
|
||||||
|
class TestCitekeyFilter(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_fails_if_no_citekey(self):
|
||||||
|
no_citekey = doe_paper.deepcopy()
|
||||||
|
no_citekey.citekey = ''
|
||||||
|
self.assertFalse(CitekeyFilter('whatever')(no_citekey))
|
||||||
|
|
||||||
|
def test_match_case(self):
|
||||||
|
self.assertTrue(CitekeyFilter('doe201')(doe_paper))
|
||||||
|
self.assertTrue(CitekeyFilter('doe201', case_sensitive=False)(doe_paper))
|
||||||
|
self.assertTrue(CitekeyFilter('Doe201')(doe_paper))
|
||||||
|
|
||||||
|
def test_do_not_match_case(self):
|
||||||
|
self.assertFalse(CitekeyFilter('dOe201')(doe_paper))
|
||||||
|
self.assertFalse(CitekeyFilter('dOe201', case_sensitive=True)(doe_paper))
|
||||||
|
self.assertFalse(CitekeyFilter('doe201', case_sensitive=True)(doe_paper))
|
||||||
|
self.assertTrue(CitekeyFilter('dOe201', case_sensitive=False)(doe_paper))
|
||||||
|
|
||||||
|
def test_latex_enc(self):
|
||||||
|
latexenc_paper = doe_paper.deepcopy()
|
||||||
|
latexenc_paper.citekey = "{G}r{\\\"u}n2013"
|
||||||
|
self.assertTrue(CitekeyFilter('Grün')(latexenc_paper))
|
||||||
|
self.assertTrue(CitekeyFilter('Gr{\\\"u}n')(latexenc_paper))
|
||||||
|
|
||||||
|
def test_normalize_unicode(self):
|
||||||
|
latexenc_paper = doe_paper.deepcopy()
|
||||||
|
latexenc_paper.citekey = "Jalape\u00f1o2013"
|
||||||
|
self.assertTrue(CitekeyFilter("Jalapen\u0303o")(latexenc_paper))
|
||||||
|
|
||||||
|
def test_strict(self):
|
||||||
|
latexenc_paper = doe_paper.deepcopy()
|
||||||
|
latexenc_paper.citekey = "Jalape\u00f1o2013"
|
||||||
|
self.assertFalse(CitekeyFilter("Jalapen\u0303o", strict=True)(latexenc_paper))
|
||||||
|
latexenc_paper.citekey = "{G}ros2013"
|
||||||
|
self.assertFalse(CitekeyFilter("Gros", strict=True)(latexenc_paper))
|
||||||
|
|
||||||
|
def test_strict_implies_case(self):
|
||||||
|
latexenc_paper = doe_paper.deepcopy()
|
||||||
|
latexenc_paper.citekey = "Gros2013"
|
||||||
|
self.assertFalse(
|
||||||
|
CitekeyFilter("gros", case_sensitive=False, strict=True)(latexenc_paper))
|
||||||
|
|
||||||
|
|
||||||
class TestCheckTag(unittest.TestCase):
|
class TestCheckTag(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -29,10 +29,12 @@ class TestCitekeyGeneration(TestRepo):
|
|||||||
|
|
||||||
def test_generated_key_is_unique(self):
|
def test_generated_key_is_unique(self):
|
||||||
self.repo.push_paper(Paper.from_bibentry(fixtures.doe_bibentry))
|
self.repo.push_paper(Paper.from_bibentry(fixtures.doe_bibentry))
|
||||||
c = self.repo.unique_citekey('Doe2013')
|
c = self.repo.unique_citekey('Doe2013', fixtures.doe_bibentry)
|
||||||
self.repo.push_paper(Paper.from_bibentry(fixtures.doe_bibentry,
|
self.repo.push_paper(Paper.from_bibentry(fixtures.doe_bibentry,
|
||||||
citekey='Doe2013a'))
|
citekey='Doe2013a'))
|
||||||
c = self.repo.unique_citekey('Doe2013')
|
c = self.repo.unique_citekey('Doe2013', fixtures.doe_bibentry)
|
||||||
|
self.assertEqual(c, 'Doe2013b')
|
||||||
|
c = self.repo.unique_citekey('bla/bla', fixtures.doe_bibentry)
|
||||||
self.assertEqual(c, 'Doe2013b')
|
self.assertEqual(c, 'Doe2013b')
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,14 +6,16 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import mock
|
|
||||||
|
|
||||||
import six
|
import six
|
||||||
import ddt
|
import ddt
|
||||||
|
import certifi
|
||||||
|
import mock
|
||||||
from pyfakefs.fake_filesystem import FakeFileOpen
|
from pyfakefs.fake_filesystem import FakeFileOpen
|
||||||
|
|
||||||
import dotdot
|
import dotdot
|
||||||
import fake_env
|
import fake_env
|
||||||
|
import mock_requests
|
||||||
|
|
||||||
from pubs import pubs_cmd, color, content, uis, p3, endecoder
|
from pubs import pubs_cmd, color, content, uis, p3, endecoder
|
||||||
from pubs.config import conf
|
from pubs.config import conf
|
||||||
@ -95,6 +97,14 @@ class CommandTestCase(fake_env.TestFakeFs):
|
|||||||
3. the expected output on stdout, verified with assertEqual.
|
3. the expected output on stdout, verified with assertEqual.
|
||||||
4. the expected output on stderr, verified with assertEqual.
|
4. the expected output on stderr, verified with assertEqual.
|
||||||
"""
|
"""
|
||||||
|
def normalize(s):
|
||||||
|
s = color.undye(s)
|
||||||
|
try:
|
||||||
|
s = s.decode('utf-8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
return s
|
||||||
|
|
||||||
try:
|
try:
|
||||||
outs = []
|
outs = []
|
||||||
for cmd in cmds:
|
for cmd in cmds:
|
||||||
@ -119,8 +129,8 @@ class CommandTestCase(fake_env.TestFakeFs):
|
|||||||
capture_wrap = fake_env.capture(pubs_cmd.execute,
|
capture_wrap = fake_env.capture(pubs_cmd.execute,
|
||||||
verbose=PRINT_OUTPUT)
|
verbose=PRINT_OUTPUT)
|
||||||
_, stdout, stderr = capture_wrap(actual_cmd.split())
|
_, stdout, stderr = capture_wrap(actual_cmd.split())
|
||||||
actual_out = color.undye(stdout)
|
actual_out = normalize(stdout)
|
||||||
actual_err = color.undye(stderr)
|
actual_err = normalize(stderr)
|
||||||
if expected_out is not None:
|
if expected_out is not None:
|
||||||
self.assertEqual(p3.u_maybe(actual_out), p3.u_maybe(expected_out))
|
self.assertEqual(p3.u_maybe(actual_out), p3.u_maybe(expected_out))
|
||||||
if expected_err is not None:
|
if expected_err is not None:
|
||||||
@ -153,9 +163,10 @@ class DataCommandTestCase(CommandTestCase):
|
|||||||
super(DataCommandTestCase, self).setUp(nsec_stat=nsec_stat)
|
super(DataCommandTestCase, self).setUp(nsec_stat=nsec_stat)
|
||||||
self.fs.add_real_directory(os.path.join(self.rootpath, 'data'), read_only=False)
|
self.fs.add_real_directory(os.path.join(self.rootpath, 'data'), read_only=False)
|
||||||
self.fs.add_real_directory(os.path.join(self.rootpath, 'bibexamples'), read_only=False)
|
self.fs.add_real_directory(os.path.join(self.rootpath, 'bibexamples'), read_only=False)
|
||||||
|
# add certificate for web querries
|
||||||
|
self.fs.add_real_file(certifi.where(), read_only=True)
|
||||||
|
self.fs.add_real_file(mock_requests._data_filepath, read_only=False)
|
||||||
|
|
||||||
# fake_env.copy_dir(self.fs, os.path.join(os.path.dirname(__file__), 'data'), 'data')
|
|
||||||
# fake_env.copy_dir(self.fs, os.path.join(os.path.dirname(__file__), 'bibexamples'), 'bibexamples')
|
|
||||||
|
|
||||||
def assertFileContentEqual(self, path, expected_content):
|
def assertFileContentEqual(self, path, expected_content):
|
||||||
self.assertTrue(os.path.isfile(path))
|
self.assertTrue(os.path.isfile(path))
|
||||||
@ -276,7 +287,7 @@ class TestAdd(URLContentTestCase):
|
|||||||
def test_add_utf8_citekey(self):
|
def test_add_utf8_citekey(self):
|
||||||
correct = ["",
|
correct = ["",
|
||||||
("added to pubs:\n"
|
("added to pubs:\n"
|
||||||
"[hausdorff1949grundzüge] Hausdorff, Felix \"Grundzüge der Mengenlehre\" (1949) \n"),
|
"[hausdorff1949grundzüge] Hausdorff, Felix \"Grundzüge der Mengenlehre\" (1949) \n"),
|
||||||
"The 'hausdorff1949grundzüge' citekey has been renamed into 'アスキー'\n",
|
"The 'hausdorff1949grundzüge' citekey has been renamed into 'アスキー'\n",
|
||||||
"The 'アスキー' citekey has been renamed into 'Ḽơᶉëᶆ_ȋṕšᶙṁ'\n"
|
"The 'アスキー' citekey has been renamed into 'Ḽơᶉëᶆ_ȋṕšᶙṁ'\n"
|
||||||
]
|
]
|
||||||
@ -493,6 +504,18 @@ class TestList(DataCommandTestCase):
|
|||||||
outs = self.execute_cmds(cmds)
|
outs = self.execute_cmds(cmds)
|
||||||
self.assertEqual(1 + 1, len(outs[-1].split('\n')))
|
self.assertEqual(1 + 1, len(outs[-1].split('\n')))
|
||||||
|
|
||||||
|
def test_list_with_citekey_query(self):
|
||||||
|
cmds = ['pubs init',
|
||||||
|
'pubs import data/',
|
||||||
|
'pubs list citekey:Page99',
|
||||||
|
'pubs list key:eiNstein_1935',
|
||||||
|
'pubs list --ignore-case key:eiNstein_1935',
|
||||||
|
]
|
||||||
|
outs = self.execute_cmds(cmds)
|
||||||
|
self.assertEqual(1, len(outs[2].splitlines()))
|
||||||
|
self.assertEqual(0, len(outs[3].splitlines()))
|
||||||
|
self.assertEqual(1, len(outs[4].splitlines()))
|
||||||
|
|
||||||
|
|
||||||
class TestTag(DataCommandTestCase):
|
class TestTag(DataCommandTestCase):
|
||||||
|
|
||||||
@ -849,7 +872,7 @@ class TestUsecase(DataCommandTestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
outs = self.execute_cmds(cmds)
|
outs = self.execute_cmds(cmds)
|
||||||
self.assertEqual(4 + 1, len(outs[-1].split('\n')))
|
self.assertEqual(9, len(outs[-1].split('\n')))
|
||||||
|
|
||||||
def test_import_one(self):
|
def test_import_one(self):
|
||||||
cmds = ['pubs init',
|
cmds = ['pubs init',
|
||||||
@ -973,7 +996,7 @@ class TestUsecase(DataCommandTestCase):
|
|||||||
alt_conf = os.path.expanduser('~/.alt_conf')
|
alt_conf = os.path.expanduser('~/.alt_conf')
|
||||||
cmds = ['pubs -c ' + alt_conf + ' init',
|
cmds = ['pubs -c ' + alt_conf + ' init',
|
||||||
'pubs --config ' + alt_conf + ' import data/ Page99',
|
'pubs --config ' + alt_conf + ' import data/ Page99',
|
||||||
'pubs list -c ' + alt_conf
|
'pubs list -c ' + alt_conf,
|
||||||
]
|
]
|
||||||
outs = self.execute_cmds(cmds)
|
outs = self.execute_cmds(cmds)
|
||||||
# check if pubs works as expected
|
# check if pubs works as expected
|
||||||
@ -982,6 +1005,11 @@ class TestUsecase(DataCommandTestCase):
|
|||||||
self.assertFalse(os.path.isfile(self.default_conf_path))
|
self.assertFalse(os.path.isfile(self.default_conf_path))
|
||||||
self.assertTrue(os.path.isfile(alt_conf))
|
self.assertTrue(os.path.isfile(alt_conf))
|
||||||
|
|
||||||
|
with open(alt_conf, 'r') as fd:
|
||||||
|
conf_text = fd.read()
|
||||||
|
outs = self.execute_cmds([('pubs conf -c ' + alt_conf, conf_text)])
|
||||||
|
|
||||||
|
|
||||||
def test_statistics(self):
|
def test_statistics(self):
|
||||||
cmds = ['pubs init',
|
cmds = ['pubs init',
|
||||||
'pubs statistics',
|
'pubs statistics',
|
||||||
@ -1002,10 +1030,10 @@ class TestUsecase(DataCommandTestCase):
|
|||||||
self.assertEqual(lines[2], 'Total tags: 3, 2 (50%) of papers have at least one tag')
|
self.assertEqual(lines[2], 'Total tags: 3, 2 (50%) of papers have at least one tag')
|
||||||
|
|
||||||
def test_add_no_extension(self):
|
def test_add_no_extension(self):
|
||||||
# This tests checks that a paper which document has no
|
"""This tests checks that a paper which document has no extension does
|
||||||
# extension does not raise issues when listing. This test might
|
not raise issues when listing. This test might be removed if decided to
|
||||||
# be removed if decided to prevent such documents. It would then need
|
prevent such documents. It would then need to be replaced by a check
|
||||||
# to be replaced by a check that this is prevented.
|
that this is prevented."""
|
||||||
self.fs.add_real_file(os.path.join(self.rootpath, 'data', 'pagerank.pdf'),
|
self.fs.add_real_file(os.path.join(self.rootpath, 'data', 'pagerank.pdf'),
|
||||||
target_path=os.path.join('data', 'no-ext'))
|
target_path=os.path.join('data', 'no-ext'))
|
||||||
correct = ['Initializing pubs in /pubs\n',
|
correct = ['Initializing pubs in /pubs\n',
|
||||||
@ -1019,6 +1047,26 @@ class TestUsecase(DataCommandTestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(correct, self.execute_cmds(cmds, capture_output=True))
|
self.assertEqual(correct, self.execute_cmds(cmds, capture_output=True))
|
||||||
|
|
||||||
|
@mock.patch('pubs.apis.requests.get', side_effect=mock_requests.mock_requests_get)
|
||||||
|
def test_readme(self, reqget):
|
||||||
|
"""Test that the readme example work."""
|
||||||
|
self.fs.add_real_file(os.path.join(self.rootpath, 'data/pagerank.pdf'), target_path='data/Loeb_2012.pdf')
|
||||||
|
self.fs.add_real_file(os.path.join(self.rootpath, 'data/pagerank.pdf'), target_path='data/oyama2000the.pdf')
|
||||||
|
self.fs.add_real_file(os.path.join(self.rootpath, 'data/pagerank.pdf'), target_path='data/Knuth1995.pdf')
|
||||||
|
|
||||||
|
cmds = ['pubs init',
|
||||||
|
'pubs import data/collection.bib',
|
||||||
|
'pubs add data/pagerank.bib -d data/pagerank.pdf',
|
||||||
|
#'pubs add -D 10.1007/s00422-012-0514-6 -d data/pagerank.pdf',
|
||||||
|
'pubs add -I 978-0822324669 -d data/oyama2000the.pdf',
|
||||||
|
'pubs add -X math/9501234 -d data/Knuth1995.pdf',
|
||||||
|
'pubs add -D 10.1007/s00422-012-0514-6',
|
||||||
|
'pubs doc add data/Loeb_2012.pdf Loeb_2012',
|
||||||
|
]
|
||||||
|
self.execute_cmds(cmds, capture_output=True)
|
||||||
|
# self.assertEqual(correct, self.execute_cmds(cmds, capture_output=True))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
class TestCache(DataCommandTestCase):
|
class TestCache(DataCommandTestCase):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user