Merge branch 'master' into jma/arxiv

main
Olivier Mangin 7 years ago committed by GitHub
commit 6e72b0e2f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,72 @@
# Changelog
## Current master
[Full Changelog](https://github.com/pubs/pubs/compare/v0.7.0...master)
### Implemented enhancements
- Better dialog after editing paper [(#142)](https://github.com/pubs/pubs/issues/142)
- Add a command to open urls ([#139](https://github.com/pubs/pubs/issues/139) by [ksunden](https://github.com/ksunden))
- More robust cache on version change [(#138)](https://github.com/pubs/pubs/issues/138)
- Allow utf8 citekeys [(#133)](https://github.com/pubs/pubs/issues/133)
- Adds tag list completion in `pubs add -t ` [(#130)](https://github.com/pubs/pubs/issues/130)
- Wider Travis coverage ([#107](https://github.com/pubs/pubs/issues/107) and [#108](https://github.com/pubs/pubs/issues/108))
- Uses bibtexparser bwriter instead of internal encoder and adds `--ignore-fields` option to export. [(#106)](https://github.com/pubs/pubs/issues/106)
- Configurable alias descriptions ([#104](https://github.com/pubs/pubs/issues/104) by [wflynny](https://github.com/wflynny))
- Support year ranges in query [(#102)](https://github.com/pubs/pubs/issues/102)
### Fixed bugs
- [[#149]](https://github.com/pubs/pubs/issues/149) More robust handling of parsing and citekey errors [(#87)](https://github.com/pubs/pubs/pull/87)
- [[#148]](https://github.com/pubs/pubs/issues/148) Fix compatibility with Pyfakefs 3.7 [(#151)](https://github.com/pubs/pubs/pull/151)
- [[#95]](https://github.com/pubs/pubs/issues/95) Error message when editor is missing [(#141)](https://github.com/pubs/pubs/issues/141)
- Fixes tests for printing help on `--help` and without argument. [(#137)](https://github.com/pubs/pubs/issues/137)
- [[#126]](https://github.com/pubs/pubs/issues/126) Removes journal customization [(#127)](https://github.com/pubs/pubs/issues/127)
- Fixes Travis failure on installing python3 for OSX [(#125)](https://github.com/pubs/pubs/issues/125)
- [[#119]](https://github.com/pubs/pubs/issues/119) Removes link and DOI customization. [(#124)](https://github.com/pubs/pubs/issues/124)
- [[#122]](https://github.com/pubs/pubs/issues/122) Fixes common strings [(#123)](https://github.com/pubs/pubs/issues/123)
- [[#28]](https://github.com/pubs/pubs/issues/28) allow utf8 in citekeys [(#120)](https://github.com/pubs/pubs/issues/120)
- Fixes field orders to use 'url' and fixes broken test. [(#118)](https://github.com/pubs/pubs/issues/118)
- [[#25]](https://github.com/pubs/pubs/issues/25) Fix bibtex testcase [(#117)](https://github.com/pubs/pubs/issues/117)
- [[#103]](https://github.com/pubs/pubs/issues/103) Fixes unicode comparison [(#116)](https://github.com/pubs/pubs/issues/116)
- [[#95]](https://github.com/pubs/pubs/issues/95) robust handling of DOIs ([#105](https://github.com/pubs/pubs/issues/105) by [wflynny](https://github.com/wflynny))
- [[#99]](https://github.com/pubs/pubs/issues/99) Print help when no subcommand is provided ([#100](https://github.com/pubs/pubs/issues/100) by [wflynny](https://github.com/wflynny))
- Fix defaults not used in config. [(#97)](https://github.com/pubs/pubs/issues/97)
- Fixes content not read from urls because of call to `os.abspath` [(#96)](https://github.com/pubs/pubs/issues/96)
- [[#93]](https://github.com/pubs/pubs/issues/93) actually save the modifications on `edit -m`. [(#94)](https://github.com/pubs/pubs/issues/94)
- [[#88]](https://github.com/pubs/pubs/issues/88) Adds proper escaping for
arguments in alias plugin. [(#91)](https://github.com/pubs/pubs/issues/91)
## [v0.7.0](https://github.com/pubs/pubs/compare/v0.6.0...v0.7.0) (2017-08-06)
[Full Changelog](https://github.com/pubs/pubs/compare/v0.6.0...v0.7.0)

@ -11,6 +11,7 @@ from .. import templates
from .. import apis from .. import apis
from .. import pretty from .. import pretty
from .. import utils from .. import utils
from .. import endecoder
from ..completion import CommaSeparatedTagsCompletion from ..completion import CommaSeparatedTagsCompletion
@ -58,12 +59,13 @@ def bibentry_from_editor(conf, ui, rp):
bibstruct.verify_bibdata(bibentry) bibstruct.verify_bibdata(bibentry)
# REFACTOR Generate citykey # REFACTOR Generate citykey
again = False again = False
except ValueError:
except endecoder.EnDecoder.BibDecodingError:
again = ui.input_yn( again = ui.input_yn(
question='Invalid bibfile. Edit again ?', question='Invalid bibfile. Edit again?',
default='y') default='y')
if not again: if not again:
ui.exit(0) ui.exit()
return bibentry return bibentry

@ -54,12 +54,24 @@ 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 '
'edited.'.format(citekey)))
else: else:
new_paper = Paper.from_bibentry(content, new_paper = Paper.from_bibentry(content,
metadata=paper.metadata) metadata=paper.metadata)
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 '
'as `{}`.'.format(citekey, new_paper.citekey)))
else:
ui.info(('Paper `{}` was successfully edited.'.format(
citekey)))
break break
except coder.BibDecodingError:
if not ui.input_yn(question="Error parsing bibdata. Edit again?"):
ui.error("Aborting, paper not updated.")
ui.exit()
except repo.CiteKeyCollision: except repo.CiteKeyCollision:
options = ['overwrite', 'edit again', 'abort'] options = ['overwrite', 'edit again', 'abort']
choice = options[ui.input_choice( choice = options[ui.input_choice(
@ -71,6 +83,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)))
break break
# else edit again # else edit again
# Also handle malformed bibtex and metadata # Also handle malformed bibtex and metadata

@ -13,6 +13,10 @@ from ..uis import get_ui
from ..content import system_path, read_text_file from ..content import system_path, read_text_file
_ABORT_USE_IGNORE_MSG = "Aborting import. Use --ignore-malformed to ignore."
_IGNORING_MSG = " Ignoring it."
def parser(subparsers, conf): def parser(subparsers, conf):
parser = subparsers.add_parser('import', parser = subparsers.add_parser('import',
help='import paper(s) to the repository') help='import paper(s) to the repository')
@ -24,10 +28,12 @@ def parser(subparsers, conf):
help="one or several keys to import from the file") help="one or several keys to import from the file")
parser.add_argument('-O', '--overwrite', action='store_true', default=False, parser.add_argument('-O', '--overwrite', action='store_true', default=False,
help="Overwrite keys already in the database") help="Overwrite keys already in the database")
parser.add_argument('-i', '--ignore-malformed', action='store_true', default=False,
help="Ignore malformed and unreadable files and entries")
return parser return parser
def many_from_path(bibpath): def many_from_path(ui, bibpath, ignore=False):
"""Extract list of papers found in bibliographic files in path. """Extract list of papers found in bibliographic files in path.
The behavior is to: The behavior is to:
@ -49,16 +55,31 @@ def many_from_path(bibpath):
biblist = [] biblist = []
for filepath in all_files: for filepath in all_files:
biblist.append(coder.decode_bibdata(read_text_file(filepath))) try:
biblist.append(coder.decode_bibdata(read_text_file(filepath)))
except coder.BibDecodingError:
error = "Could not parse bibtex at {}.".format(filepath)
if ignore:
ui.warning(error + _IGNORING_MSG)
else:
ui.error(error + _ABORT_USE_IGNORE_MSG)
ui.exit()
papers = {} papers = {}
for b in biblist: for b in biblist:
for k, b in b.items(): for k, b in b.items():
if k in papers:
ui.warning('Duplicated citekey {}. Keeping the last one.'.format(k))
try: try:
papers[k] = Paper(k, b) papers[k] = Paper(k, b)
papers[k].added = datetime.datetime.now() papers[k].added = datetime.datetime.now()
except ValueError as e: except ValueError as e:
papers[k] = e error = 'Could not load entry for citekey {} ({}).'.format(k, e)
if ignore:
ui.warning(error + _IGNORING_MSG)
else:
ui.error(error + _ABORT_USE_IGNORE_MSG)
ui.exit()
return papers return papers
@ -75,20 +96,17 @@ def command(conf, args):
rp = repo.Repository(conf) rp = repo.Repository(conf)
# Extract papers from bib # Extract papers from bib
papers = many_from_path(bibpath) papers = many_from_path(ui, bibpath, ignore=args.ignore_malformed)
keys = args.keys or papers.keys() keys = args.keys or papers.keys()
for k in keys: for k in keys:
p = papers[k] p = papers[k]
if isinstance(p, Exception): rp.push_paper(p, overwrite=args.overwrite)
ui.error('Could not load entry for citekey {}.'.format(k)) ui.info('{} imported.'.format(color.dye_out(p.citekey, 'citekey')))
docfile = bibstruct.extract_docfile(p.bibdata)
if docfile is None:
ui.warning("No file for {}.".format(p.citekey))
else: else:
rp.push_paper(p, overwrite=args.overwrite) rp.push_doc(p.citekey, docfile, copy=copy)
ui.info('{} imported.'.format(color.dye_out(p.citekey, 'citekey'))) # FIXME should move the file if configured to do so.
docfile = bibstruct.extract_docfile(p.bibdata)
if docfile is None:
ui.warning("No file for {}.".format(p.citekey))
else:
rp.push_doc(p.citekey, docfile, copy=copy)
#FIXME should move the file if configured to do so.
rp.close() rp.close()

@ -45,7 +45,11 @@ class DataBroker(object):
def pull_bibentry(self, citekey): def pull_bibentry(self, citekey):
bibdata_raw = self.filebroker.pull_bibfile(citekey) bibdata_raw = self.filebroker.pull_bibfile(citekey)
return self.endecoder.decode_bibdata(bibdata_raw) try:
return self.endecoder.decode_bibdata(bibdata_raw)
except self.endecoder.BibDecodingError as e:
e.message = "Unable to decode bibtex for paper {}.".format(citekey)
raise e
def push_metadata(self, citekey, metadata): def push_metadata(self, citekey, metadata):
metadata_raw = self.endecoder.encode_metadata(metadata) metadata_raw = self.endecoder.encode_metadata(metadata)

@ -66,6 +66,16 @@ class EnDecoder(object):
* encode_bibdata will try to recognize exceptions * encode_bibdata will try to recognize exceptions
""" """
class BibDecodingError(Exception):
message = "Could not parse provided bibdata:\n---\n{}\n---"
def __init__(self, bibdata):
self.data = bibdata
def __str__(self):
return self.message.format(self.data)
bwriter = bp.bwriter.BibTexWriter() bwriter = bp.bwriter.BibTexWriter()
bwriter.display_order = BIBFIELD_ORDER bwriter.display_order = BIBFIELD_ORDER
@ -103,7 +113,10 @@ class EnDecoder(object):
return entry return entry
def decode_bibdata(self, bibdata): def decode_bibdata(self, bibdata):
"""""" """Decodes bibdata from string.
If the decoding fails, returns a BibParseError.
"""
try: try:
entries = bp.bparser.BibTexParser( entries = bp.bparser.BibTexParser(
bibdata, common_strings=True, bibdata, common_strings=True,
@ -121,4 +134,5 @@ class EnDecoder(object):
except Exception: except Exception:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
raise ValueError('could not parse provided bibdata:\n{}'.format(bibdata)) raise self.BibDecodingError(bibdata)
# TODO: filter exceptions from pyparsing and pass reason upstream

@ -141,6 +141,13 @@ class Repository(object):
pass pass
def rename_paper(self, paper, new_citekey=None, old_citekey=None): def rename_paper(self, paper, new_citekey=None, old_citekey=None):
"""Move a paper from a citekey to another one.
Even if the new and old citekey are the same, the paper instance is
pushed to disk.
:return: True if a rename happened, False if not.
"""
if old_citekey is None: if old_citekey is None:
old_citekey = paper.citekey old_citekey = paper.citekey
if new_citekey is None: if new_citekey is None:
@ -149,6 +156,7 @@ class Repository(object):
# check if new_citekey is not the same as paper.citekey # check if new_citekey is not the same as paper.citekey
if old_citekey == new_citekey: if old_citekey == new_citekey:
self.push_paper(paper, overwrite=True, event=False) self.push_paper(paper, overwrite=True, event=False)
return False
else: else:
# check if new_citekey does not exists # check if new_citekey does not exists
if new_citekey in self: if new_citekey in self:
@ -171,6 +179,7 @@ class Repository(object):
self.remove_paper(old_citekey, event=False) self.remove_paper(old_citekey, event=False)
# send event # send event
events.RenameEvent(paper, old_citekey).send() events.RenameEvent(paper, old_citekey).send()
return True
def push_doc(self, citekey, docfile, copy=None): def push_doc(self, citekey, docfile, copy=None):
p = self.pull_paper(citekey) p = self.pull_paper(citekey)

@ -11,11 +11,11 @@ import subprocess
from . import color from . import color
from . import config from . import config
from .p3 import _get_raw_stdout, _get_raw_stderr, input, ustr from .p3 import _get_raw_stdout, _get_raw_stderr, input, ustr
from .content import check_file, read_text_file, write_file, system_path from .content import check_file, read_text_file, write_file
DEBUG = False # unhandled exceptions traces are printed DEBUG = False # unhandled exceptions traces are printed
DEBUG_ALL_TRACES = False # handled exceptions traces are printed DEBUG_ALL_TRACES = False # handled exceptions traces are printed
# package-shared ui that can be accessed using : # package-shared ui that can be accessed using :
# from uis import get_ui # from uis import get_ui
# ui = get_ui() # ui = get_ui()
@ -38,40 +38,14 @@ def _get_encoding(conf):
def _get_local_editor(): def _get_local_editor():
"""Get the editor from environment variables. """Get the editor from environment variables.
Use nano as a default. Use vi as a default.
""" """
return os.environ.get('EDITOR', 'nano') return os.environ.get('EDITOR', 'vi')
def _editor_input(editor, initial='', suffix='.tmp'):
"""Use an editor to get input"""
str_initial = initial.encode('utf-8') # TODO: make it a configuration item
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
tfile_name = temp_file.name
temp_file.write(str_initial)
cmd = shlex.split(editor) # this enable editor command with option, e.g. gvim -f
cmd.append(tfile_name)
subprocess.call(cmd)
content = read_text_file(tfile_name)
os.remove(tfile_name)
return content
def _edit_file(editor, path_to_file, temporary=True):
if temporary:
check_file(path_to_file, fail=True)
content = read_text_file(path_to_file)
content = _editor_input(editor, content)
write_file(path_to_file, content)
else:
cmd = editor.split() # this enable editor command with option, e.g. gvim -f
cmd.append(system_path(path_to_file))
subprocess.call(cmd)
def get_ui(): def get_ui():
if _ui is None: if _ui is None:
return PrintUI(config.load_default_conf()) # no editor support. (#FIXME?) return PrintUI(config.load_default_conf()) # no editor support. (#FIXME?)
return _ui return _ui
@ -111,7 +85,7 @@ class PrintUI(object):
kwargs['file'] = self._stderr kwargs['file'] = self._stderr
print('{}: {}'.format(color.dye_err('error', 'error'), message), **kwargs) print('{}: {}'.format(color.dye_err('error', 'error'), message), **kwargs)
if DEBUG_ALL_TRACES: # if an exception has been raised, print the trace. if DEBUG_ALL_TRACES: # if an exception has been raised, print the trace.
if sys.exc_info()[0] is not None: if sys.exc_info()[0] is not None:
traceback.print_exception(*sys.exc_info) traceback.print_exception(*sys.exc_info)
@ -130,6 +104,7 @@ class PrintUI(object):
self.exit() self.exit()
return True # never happens return True # never happens
class InputUI(PrintUI): class InputUI(PrintUI):
"""UI class. Stores configuration parameters and system information. """UI class. Stores configuration parameters and system information.
""" """
@ -144,7 +119,7 @@ class InputUI(PrintUI):
except EOFError: except EOFError:
self.error('Standard input ended while waiting for answer.') self.error('Standard input ended while waiting for answer.')
self.exit(1) self.exit(1)
return ustr(data) #.decode('utf-8') return ustr(data) #.decode('utf-8')
def input_choice_ng(self, options, option_chars=None, default=None, question=''): def input_choice_ng(self, options, option_chars=None, default=None, question=''):
"""Ask the user to chose between a set of options. The user is asked """Ask the user to chose between a set of options. The user is asked
@ -184,7 +159,6 @@ class InputUI(PrintUI):
pass pass
self.message('Incorrect option.', option_str) self.message('Incorrect option.', option_str)
def input_choice(self, options, option_chars, default=None, question=''): def input_choice(self, options, option_chars, default=None, question=''):
"""Ask the user to chose between a set of options. The user is asked """Ask the user to chose between a set of options. The user is asked
to input a char corresponding to the option he chooses. to input a char corresponding to the option he chooses.
@ -223,7 +197,41 @@ class InputUI(PrintUI):
return [True, False][answer] return [True, False][answer]
def editor_input(self, initial="", suffix='.tmp'): def editor_input(self, initial="", suffix='.tmp'):
return _editor_input(self.editor, initial=initial, suffix=suffix) """Use an editor to get input"""
str_initial = initial.encode('utf-8') # TODO: make it a configuration item
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
tfile_name = temp_file.name
temp_file.write(str_initial)
self._call_editor(tfile_name)
content = read_text_file(tfile_name)
os.remove(tfile_name)
return content
def edit_file(self, path, temporary): def edit_file(self, path, temporary):
_edit_file(self.editor, path, temporary=temporary) if temporary:
check_file(path, fail=True)
content = read_text_file(path)
content = self.editor_input(content)
write_file(path, content)
else:
self._call_editor(path)
def _call_editor(self, path):
"""Call the editor, and checks that no error were raised by the OS"""
cmd = shlex.split(self.editor) # this enable editor command with option, e.g. gvim -f
cmd.append(path)
try:
subprocess.call(cmd)
except OSError as e:
if e.errno == os.errno.ENOENT:
self.error(("Error while calling editor '{}'. The editor may "
"not be present. You can change the text editor "
"that pubs uses by setting the $EDITOR environment "
"variable, or by running `pubs conf` and setting "
"the `edit_cmd` field."
).format(self.editor))
# handle file not found error.
self.exit()
else:
# Something else went wrong while trying to run `wget`
self.handle_exception(e)

@ -125,3 +125,4 @@ You can access the self-documented configuration by using `pubs conf`, and all t
- [Tyler Earnest](https://github.com/tmearnest) - [Tyler Earnest](https://github.com/tmearnest)
- [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)

@ -74,12 +74,21 @@ class FakeInput():
def as_global(self): def as_global(self):
for md in self.module_list: for md in self.module_list:
md.input = self md.input = self
md._editor_input = self if md.__name__ == 'pubs.uis':
md._edit_file = self.input_to_file md.InputUI.editor_input = self
# if mdname.endswith('files'): md.InputUI.edit_file = self.input_to_file
# md.editor_input = self # Do not catch UnexpectedInput
original_handler = md.InputUI.handle_exception
def input_to_file(self, _, path_to_file, temporary=True): def handler(ui, exc):
if isinstance(exc, self.UnexpectedInput):
raise
else:
original_handler(ui, exc)
md.InputUI.handle_exception = handler
def input_to_file(self, path_to_file, temporary=True):
content.write_file(path_to_file, self()) content.write_file(path_to_file, self())
def add_input(self, inp): def add_input(self, inp):
@ -98,11 +107,16 @@ class TestFakeFs(fake_filesystem_unittest.TestCase):
def setUp(self): def setUp(self):
self.rootpath = os.path.abspath(os.path.dirname(__file__)) self.rootpath = os.path.abspath(os.path.dirname(__file__))
self.homepath = os.path.expanduser('~')
self.setUpPyfakefs() self.setUpPyfakefs()
self.fs.CreateDirectory(os.path.expanduser('~')) self.reset_fs()
self.fs.CreateDirectory(self.rootpath)
os.chdir(self.rootpath)
def reset_fs(self): def reset_fs(self):
self._stubber.tearDown() # renew the filesystem """Reset the fake filesystem"""
self.setUp() for dir_name in self.fs.listdir('/'):
if dir_name not in ['var', 'tmp']:
self.fs.remove_object(os.path.join('/', dir_name))
self.fs.create_dir(os.path.expanduser('~'))
self.fs.create_dir(self.rootpath)
os.chdir(self.rootpath)

@ -1,5 +1,5 @@
# those are the additional packages required to run the tests # those are the additional packages required to run the tests
six six
pyfakefs==3.3 pyfakefs
ddt ddt
mock mock

@ -69,7 +69,7 @@ bibtex_no_citekey = """@Manual{,
} }
""" """
bibtex_month= """@inproceedings{Goyal2017, bibtex_month = """@inproceedings{Goyal2017,
author = {Goyal, Anirudh and Sordoni, Alessandro and C{\^{o}}t{\'{e}}, Marc-Alexandre and Ke, Nan Rosemary and Bengio, Yoshua}, author = {Goyal, Anirudh and Sordoni, Alessandro and C{\^{o}}t{\'{e}}, Marc-Alexandre and Ke, Nan Rosemary and Bengio, Yoshua},
title = {Z-Forcing: Training Stochastic Recurrent Networks}, title = {Z-Forcing: Training Stochastic Recurrent Networks},
year = {2017}, year = {2017},
@ -78,6 +78,12 @@ bibtex_month= """@inproceedings{Goyal2017,
} }
""" """
not_bibtex = """@misc{this looks,
like = a = bibtex file but
, is not a real one!
"""
sample_conf = """ sample_conf = """
[main] [main]

@ -32,7 +32,7 @@ class TestDOI2Bibtex(unittest.TestCase):
def test_parse_fails_on_incorrect_DOI(self): def test_parse_fails_on_incorrect_DOI(self):
bib = doi2bibtex('999999') bib = doi2bibtex('999999')
with self.assertRaises(ValueError): with self.assertRaises(EnDecoder.BibDecodingError):
self.endecoder.decode_bibdata(bib) self.endecoder.decode_bibdata(bib)
@ -56,7 +56,7 @@ class TestISBN2Bibtex(unittest.TestCase):
def test_parse_fails_on_incorrect_ISBN(self): def test_parse_fails_on_incorrect_ISBN(self):
bib = doi2bibtex('9' * 13) bib = doi2bibtex('9' * 13)
with self.assertRaises(ValueError): with self.assertRaises(EnDecoder.BibDecodingError):
self.endecoder.decode_bibdata(bib) self.endecoder.decode_bibdata(bib)

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import unittest import unittest
import copy import copy

@ -2,7 +2,7 @@
import unittest import unittest
import dotdot import dotdot
from pubs.config import conf # from pubs.config import conf
# class TestConfig(unittest.TestCase): # class TestConfig(unittest.TestCase):
# #

@ -5,8 +5,7 @@ import os
import dotdot import dotdot
import fake_env import fake_env
from pubs import content, filebroker, databroker, datacache from pubs import content, databroker, datacache
from pubs.config import conf
import str_fixtures import str_fixtures
from pubs import endecoder from pubs import endecoder
@ -35,7 +34,7 @@ class TestDataBroker(fake_env.TestFakeFs):
self.assertEqual(db.pull_metadata('citekey1'), page99_metadata) self.assertEqual(db.pull_metadata('citekey1'), page99_metadata)
pulled = db.pull_bibentry('citekey1')['Page99'] pulled = db.pull_bibentry('citekey1')['Page99']
for key, value in pulled.items(): for key in pulled.keys():
self.assertEqual(pulled[key], page99_bibentry['Page99'][key]) self.assertEqual(pulled[key], page99_bibentry['Page99'][key])
self.assertEqual(db.pull_bibentry('citekey1'), page99_bibentry) self.assertEqual(db.pull_bibentry('citekey1'), page99_bibentry)
@ -91,4 +90,4 @@ class TestDataBroker(fake_env.TestFakeFs):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main(verbosity=2)

@ -3,7 +3,6 @@ import unittest
import time import time
import dotdot import dotdot
import fake_env
from pubs.datacache import CacheEntrySet from pubs.datacache import CacheEntrySet

@ -52,7 +52,7 @@ class TestEnDecode(unittest.TestCase):
self.assertEqual(bibraw1, bibraw2) self.assertEqual(bibraw1, bibraw2)
def test_endecode_bibtex(self): def test_endecode_bibtex_converts_month_string(self):
"""Test if `month=dec` is correctly recognized and transformed into """Test if `month=dec` is correctly recognized and transformed into
`month={December}`""" `month={December}`"""
decoder = endecoder.EnDecoder() decoder = endecoder.EnDecoder()
@ -147,6 +147,11 @@ class TestEnDecode(unittest.TestCase):
self.assertIn('author', entry1) self.assertIn('author', entry1)
self.assertIn('institution', entry1) self.assertIn('institution', entry1)
def test_endecodes_raises_exception(self):
decoder = endecoder.EnDecoder()
with self.assertRaises(decoder.BibDecodingError):
decoder.decode_bibdata("@misc{I am not a correct bibtex{{}")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@ -1,10 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import os
import dotdot import dotdot
import fake_env
from pubs import endecoder, pretty, color, config from pubs import endecoder, pretty, color, config

@ -15,14 +15,12 @@ from pyfakefs.fake_filesystem import FakeFileOpen
import dotdot import dotdot
import fake_env import fake_env
from pubs import pubs_cmd, update, color, content, filebroker, uis, p3, endecoder from pubs import pubs_cmd, color, content, uis, p3, endecoder
from pubs.config import conf from pubs.config import conf
import configobj
import str_fixtures import str_fixtures
import fixtures import fixtures
from pubs.commands import init_cmd, import_cmd
# makes the tests very noisy # makes the tests very noisy
PRINT_OUTPUT = False PRINT_OUTPUT = False
@ -45,9 +43,8 @@ class FakeSystemExit(Exception):
"Exited with code: {}.".format(self.code), *args) "Exited with code: {}.".format(self.code), *args)
# code for fake fs class TestInput(unittest.TestCase):
"""Test that the fake input mechanisms work correctly in the tests"""
class TestFakeInput(unittest.TestCase):
def test_input(self): def test_input(self):
input = fake_env.FakeInput(['yes', 'no']) input = fake_env.FakeInput(['yes', 'no'])
@ -65,13 +62,16 @@ class TestFakeInput(unittest.TestCase):
color.input() color.input()
def test_editor_input(self): def test_editor_input(self):
sample_conf = conf.load_default_conf()
ui = uis.InputUI(sample_conf)
other_input = fake_env.FakeInput(['yes', 'no'], other_input = fake_env.FakeInput(['yes', 'no'],
module_list=[uis, color]) module_list=[uis])
other_input.as_global() other_input.as_global()
self.assertEqual(uis._editor_input(), 'yes') self.assertEqual(ui.editor_input('fake_editor'), 'yes')
self.assertEqual(uis._editor_input(), 'no') self.assertEqual(ui.editor_input('fake_editor'), 'no')
with self.assertRaises(fake_env.FakeInput.UnexpectedInput): with self.assertRaises(fake_env.FakeInput.UnexpectedInput):
color.input() ui.editor_input()
class CommandTestCase(fake_env.TestFakeFs): class CommandTestCase(fake_env.TestFakeFs):
@ -82,7 +82,6 @@ class CommandTestCase(fake_env.TestFakeFs):
def setUp(self, nsec_stat=True): def setUp(self, nsec_stat=True):
super(CommandTestCase, self).setUp() super(CommandTestCase, self).setUp()
os.stat_float_times(nsec_stat) os.stat_float_times(nsec_stat)
# self.fs = fake_env.create_fake_fs([content, filebroker, conf, init_cmd, import_cmd, configobj, update], nsec_stat=nsec_stat)
self.default_pubs_dir = os.path.expanduser('~/.pubs') self.default_pubs_dir = os.path.expanduser('~/.pubs')
self.default_conf_path = os.path.expanduser('~/.pubsrc') self.default_conf_path = os.path.expanduser('~/.pubsrc')
@ -129,7 +128,7 @@ class CommandTestCase(fake_env.TestFakeFs):
outs.append(color.undye(actual_out)) outs.append(color.undye(actual_out))
else: else:
pubs_cmd.execute(actual_cmd.split()) pubs_cmd.execute(actual_cmd.split())
except fake_env.FakeInput.UnexpectedInput as e: except fake_env.FakeInput.UnexpectedInput:
self.fail('Unexpected input asked by command: {}.'.format( self.fail('Unexpected input asked by command: {}.'.format(
actual_cmd)) actual_cmd))
return outs return outs
@ -178,7 +177,7 @@ class URLContentTestCase(DataCommandTestCase):
return p3.urlparse(url).path return p3.urlparse(url).path
def url_exists(self, url): def url_exists(self, url):
return self.fs.Exists(self._url_to_path(url)) return self.fs.exists(self._url_to_path(url))
def url_to_byte_content(self, url, ui=None): def url_to_byte_content(self, url, ui=None):
path = self._url_to_path(url) path = self._url_to_path(url)
@ -198,7 +197,7 @@ class TestAlone(CommandTestCase):
def test_alone_misses_command(self): def test_alone_misses_command(self):
with self.assertRaises(FakeSystemExit) as cm: with self.assertRaises(FakeSystemExit) as cm:
self.execute_cmds(['pubs']) self.execute_cmds(['pubs'])
self.assertEqual(cm.exception.code, 2) self.assertEqual(cm.exception.code, 2)
def test_alone_prints_help(self): def test_alone_prints_help(self):
# capturing the output of `pubs --help` is difficult because argparse # capturing the output of `pubs --help` is difficult because argparse
@ -335,11 +334,20 @@ class TestAdd(URLContentTestCase):
def test_add_no_citekey_fails(self): def test_add_no_citekey_fails(self):
# See #113 # See #113
cmds = ['pubs init', cmds = ['pubs init',
('pubs add', [str_fixtures.bibtex_no_citekey]), ('pubs add', [str_fixtures.bibtex_no_citekey, 'n']),
] ]
with self.assertRaises(FakeSystemExit): with self.assertRaises(FakeSystemExit):
self.execute_cmds(cmds) self.execute_cmds(cmds)
def test_add_edit_fails(self):
cmds = ['pubs init',
('pubs add',
['@misc{I am not a correct bibtex{{}', 'n']),
]
with self.assertRaises(FakeSystemExit) as cm:
self.execute_cmds(cmds)
self.assertEqual(cm.exception.code, 1)
class TestList(DataCommandTestCase): class TestList(DataCommandTestCase):
@ -355,8 +363,8 @@ class TestList(DataCommandTestCase):
def test_list_several_no_date(self): def test_list_several_no_date(self):
self.execute_cmds(['pubs init -p testrepo']) self.execute_cmds(['pubs init -p testrepo'])
os.chdir('/') # weird fix for shutil.rmtree invocation. os.chdir('/') # weird fix for shutil.rmtree invocation.
shutil.rmtree(self.rootpath + '/testrepo') shutil.rmtree(os.path.join(self.rootpath, 'testrepo'))
os.chdir(self.rootpath) os.chdir(self.rootpath)
self.fs.add_real_directory(os.path.join(self.rootpath, 'testrepo'), read_only=False) self.fs.add_real_directory(os.path.join(self.rootpath, 'testrepo'), read_only=False)
@ -680,16 +688,40 @@ class TestUsecase(DataCommandTestCase):
self.assertFileContentEqual(os.path.expanduser('~/.pubsrc'), self.assertFileContentEqual(os.path.expanduser('~/.pubsrc'),
str_fixtures.sample_conf) str_fixtures.sample_conf)
def test_editor_abort(self): def test_editor_aborts(self):
with self.assertRaises(FakeSystemExit): with self.assertRaises(FakeSystemExit):
cmds = ['pubs init', cmds = ['pubs init',
('pubs add', ['abc', 'n']),
('pubs add', ['abc', 'y', 'abc', 'n']),
'pubs add data/pagerank.bib', 'pubs add data/pagerank.bib',
('pubs edit Page99', ['', 'a']), ('pubs edit Page99', ['', 'n']),
]
self.execute_cmds(cmds)
def test_editor_succeeds_on_second_edit(self):
cmds = ['pubs init',
'pubs add data/pagerank.bib',
('pubs edit Page99', [
'@misc{Page99, title="TTT" author="X. YY"}', 'y',
'@misc{Page99, title="TTT", author="X. YY"}', '']),
('pubs list', [], '[Page99] YY, X. "TTT" \n')
]
self.execute_cmds(cmds)
def test_add_aborts(self):
with self.assertRaises(FakeSystemExit):
cmds = ['pubs init',
('pubs add New', ['']),
] ]
self.execute_cmds(cmds) self.execute_cmds(cmds)
def test_add_succeeds_on_second_edit(self):
cmds = ['pubs init',
('pubs add', [
'', 'y',
'@misc{New, title="TTT", author="X. YY"}', '']),
('pubs list', [], '[New] YY, X. "TTT" \n')
]
self.execute_cmds(cmds)
def test_editor_success(self): def test_editor_success(self):
cmds = ['pubs init', cmds = ['pubs init',
('pubs add', [str_fixtures.bibtex_external0]), ('pubs add', [str_fixtures.bibtex_external0]),
@ -791,6 +823,25 @@ class TestUsecase(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_import_fails_without_ignore(self):
with FakeFileOpen(self.fs)('data/fake.bib', 'w') as f:
f.write(str_fixtures.not_bibtex)
cmds = ['pubs init',
'pubs import data/ Page99',
]
with self.assertRaises(FakeSystemExit):
self.execute_cmds(cmds)
def test_import_ignores(self):
with FakeFileOpen(self.fs)('data/fake.bib', 'w') as f:
f.write(str_fixtures.not_bibtex)
cmds = ['pubs init',
'pubs import --ignore-malformed data/ Page99',
'pubs list'
]
outs = self.execute_cmds(cmds)
self.assertEqual(1 + 1, len(outs[-1].split('\n')))
def test_update(self): def test_update(self):
cmds = ['pubs init', cmds = ['pubs init',
'pubs add data/pagerank.bib', 'pubs add data/pagerank.bib',
@ -920,4 +971,4 @@ class TestCache(DataCommandTestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main(verbosity=2)

Loading…
Cancel
Save