Merge branch 'master' into jma/arxiv
This commit is contained in:
commit
6e72b0e2f7
72
changelog.md
Normal file
72
changelog.md
Normal file
@ -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)
|
||||||
|
80
pubs/uis.py
80
pubs/uis.py
@ -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)
|
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)
|
||||||
|
|
||||||
|
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…
x
Reference in New Issue
Block a user