diff --git a/.ackrc b/.ackrc new file mode 100644 index 0000000..1617d11 --- /dev/null +++ b/.ackrc @@ -0,0 +1 @@ +--ignore-directory=is:build diff --git a/.gitignore b/.gitignore index 0dfdfda..114bd34 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ build/ dist/ *~ *.pyc -tests/paper_test papers.egg-info .DS_Store diff --git a/papers/commands/add_cmd.py b/papers/commands/add_cmd.py index 57f4399..31cbcf5 100644 --- a/papers/commands/add_cmd.py +++ b/papers/commands/add_cmd.py @@ -39,7 +39,7 @@ def command(args): bibstr = '' while cont: try: - bibstr = files.editor_input(config, bibstr, suffix='.yaml') + bibstr = files.editor_input(config().edit_cmd, bibstr, suffix='.yaml') key, bib = get_bibentry_from_string(bibstr) cont = False except Exception: @@ -47,7 +47,7 @@ def command(args): question='Invalid bibfile. Edit again ?', default='y') if not cont: - ui.exit() + ui.exit(0) p = Paper(bibentry=bib, citekey=key) else: p = Paper.load(bibfile) diff --git a/papers/commands/edit_cmd.py b/papers/commands/edit_cmd.py index c2f8bc6..513ea4e 100644 --- a/papers/commands/edit_cmd.py +++ b/papers/commands/edit_cmd.py @@ -16,18 +16,18 @@ def parser(subparsers): def command(args): - ui = args.ui - meta = args.meta + ui = args.ui + meta = args.meta reference = args.reference rp = repo.Repository(config()) key = parse_reference(ui, rp, reference) paper = rp.get_paper(key) - filepath = rp._bibfile(key) - if meta: - filepath = rp._metafile(key) + filepath = rp._metafile(key) if meta else rp._bibfile(key) + with open(filepath) as f: content = f.read() + while True: # Get new content from user content = editor_input(config().edit_cmd, content) @@ -46,10 +46,10 @@ def command(args): except repo.CiteKeyCollision: options = ['overwrite', 'edit again', 'abort'] choice = options[ui.input_choice( - options, - ['o', 'e', 'a'], + options, ['o', 'e', 'a'], question='A paper already exist with this citekey.' )] + if choice == 'abort': break elif choice == 'overwrite': diff --git a/papers/commands/import_cmd.py b/papers/commands/import_cmd.py index ce13f2c..cb81fb9 100644 --- a/papers/commands/import_cmd.py +++ b/papers/commands/import_cmd.py @@ -22,7 +22,7 @@ def command(args): ui = args.ui bibpath = args.bibpath - copy = agrs.copy + copy = args.copy if copy is None: copy = config().import_copy diff --git a/papers/configs.py b/papers/configs.py index 391b1b0..25b80e4 100644 --- a/papers/configs.py +++ b/papers/configs.py @@ -15,17 +15,18 @@ DFT_PLUGINS = '' DFT_CONFIG = {'papers_dir' : os.path.expanduser('~/.papers'), 'doc_dir' : 'doc', - 'import_copy' : 'yes', - 'import_move' : 'no', - 'color' : 'yes', + 'import_copy' : True, + 'import_move' : False, + 'color' : True, 'version' : 3, + 'version_warning' : True, 'open_cmd' : 'open', 'edit_cmd' : DFT_EDIT_CMD, 'plugins' : DFT_PLUGINS } -BOOLEANS = {'import_copy', 'import_move', 'color'} +BOOLEANS = {'import_copy', 'import_move', 'color', 'version_warning'} # package-shared config that can be accessed using : diff --git a/papers/paper.py b/papers/paper.py index a5885db..d1018f3 100644 --- a/papers/paper.py +++ b/papers/paper.py @@ -271,7 +271,10 @@ class Paper(object): self.tags.discard(tag) -class PaperInRepo(Paper): # TODO document why this class exists (fabien, 2013/06) +class PaperInRepo(Paper): + """Extend paper class with command specific to the case where the paper + lives in a repository. + """ def __init__(self, repo, *args, **kwargs): Paper.__init__(self, *args, **kwargs) diff --git a/papers/papers_cmd.py b/papers/papers_cmd.py index 5118bfb..cd1e138 100644 --- a/papers/papers_cmd.py +++ b/papers/papers_cmd.py @@ -9,6 +9,7 @@ from .ui import UI from . import configs from . import commands from . import plugins +from .__init__ import __version__ cmds = collections.OrderedDict([ ('init', commands.init_cmd), @@ -25,6 +26,26 @@ cmds = collections.OrderedDict([ ('update', commands.update_cmd), ]) +def _update_check(config, ui): + if config.version_warning: + code_version = int(__version__) + repo_version = int(config.version) + + if repo_version > code_version: + ui.print_('warning: your repository was generated with an newer version' + ' of papers (v{}) than the one you are using (v{}).\n'.format( + repo_version, code_version) + + ' you should not use papers until you install the ' + 'newest version. (use version_warning in you papersrc ' + 'to bypass this error)') + sys.exit() + elif repo_version < code_version: + ui.print_( + 'warning: your repository version (v{})'.format(repo_version) + + 'must be updated to version {}.\n'.format(code_version) + + "run 'papers update'.") + sys.exit() + def execute(raw_args = sys.argv): # loading config @@ -34,6 +55,8 @@ def execute(raw_args = sys.argv): ui = UI(config) + _update_check(config, ui) + # Extend with plugin commands plugins.load_plugins(ui, config.plugins.split()) for p in plugins.get_plugins().values(): diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..77eb33e --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,2 @@ +tmpdir* +*.bak diff --git a/tests/fixtures.py b/tests/fixtures.py index 86bc7b6..3098283 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -16,3 +16,34 @@ doe2013.bibentry.fields['title'] = u'Nice title.' doe2013.bibentry.fields['year'] = u'2013' doe2013.bibentry.persons['author'] = [Person(u'John Doe')] doe2013.citekey = doe2013.generate_citekey() + + +pagerankbib = """ +@techreport{Page99, + number = {1999-66}, + month = {November}, + author = {Lawrence Page and Sergey Brin and Rajeev Motwani and Terry Winograd}, + note = {Previous number = SIDL-WP-1999-0120}, + title = {The PageRank Citation Ranking: Bringing Order to the Web.}, + type = {Technical Report}, + publisher = {Stanford InfoLab}, + year = {1999}, +institution = {Stanford InfoLab}, + url = {http://ilpubs.stanford.edu:8090/422/}, +} +""" + +pagerankbib_generated = """@techreport{ + Page99, + author = "Page, Lawrence and Brin, Sergey and Motwani, Rajeev and Winograd, Terry", + publisher = "Stanford InfoLab", + title = "The PageRank Citation Ranking: Bringing Order to the Web.", + url = "http://ilpubs.stanford.edu:8090/422/", + number = "1999-66", + month = "November", + note = "Previous number = SIDL-WP-1999-0120", + year = "1999", + institution = "Stanford InfoLab" +} + +""" \ No newline at end of file diff --git a/tests/test.sh b/tests/test.sh index 7bfba55..c9e2521 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -rm -Rf paper_test/*; -papers init -p paper_test/; +rm -Rf tmpdir/*; +papers init -p tmpdir/; papers add -d data/pagerank.pdf -b data/pagerank.bib; papers list; papers tag; @@ -8,4 +8,4 @@ papers tag Page99 network+search; papers tag Page99; papers tag search; papers tag 0; -rm -Rf paper_test/*; +#rm -Rf tmpdir/*; diff --git a/tests/test_usecase.py b/tests/test_usecase.py index c7db347..2a348ee 100644 --- a/tests/test_usecase.py +++ b/tests/test_usecase.py @@ -1,6 +1,7 @@ import sys, os, shutil, glob import unittest import pkgutil +import re import testenv import fake_filesystem @@ -8,9 +9,11 @@ import fake_filesystem_shutil import fake_filesystem_glob from papers import papers_cmd -from papers import color +from papers import color, files from papers.p3 import io, input +import fixtures + # code for fake fs @@ -28,7 +31,9 @@ def _mod_list(): path=papers.__path__, prefix=papers.__name__+'.', onerror=lambda x: None): - ml.append(__import__(modname, fromlist = 'dummy')) + # HACK to not load textnote + if not modname.startswith('papers.plugs.texnote'): + ml.append((modname, __import__(modname, fromlist = 'dummy'))) return ml mod_list = _mod_list() @@ -43,28 +48,37 @@ def _create_fake_fs(): fake_glob = fake_filesystem_glob.FakeGlobModule(fake_fs) fake_fs.CreateDirectory(fake_os.path.expanduser('~')) - __builtins__['open'] = fake_open - __builtins__['file'] = fake_open + + try: + __builtins__.open = fake_open + __builtins__.file = fake_open + except AttributeError: + __builtins__['open'] = fake_open + __builtins__['file'] = fake_open sys.modules['os'] = fake_os sys.modules['shutil'] = fake_shutil sys.modules['glob'] = fake_glob - for md in mod_list: + for mdname, md in mod_list: md.os = fake_os md.shutil = fake_shutil + md.open = fake_open + md.file = fake_open return fake_fs def _copy_data(fs): """Copy all the data directory into the fake fs""" - for filename in real_os.listdir('data/'): - filepath = 'data/' + filename - if real_os.path.isfile(filepath): - with real_open(filepath, 'r') as f: - fs.CreateFile(filepath, contents = f.read()) - if real_os.path.isdir(filepath): - fs.CreateDirectory(filepath) + datadir = real_os.path.join(real_os.path.dirname(__file__), 'data') + for filename in real_os.listdir(datadir): + real_path = real_os.path.join(datadir, filename) + fake_path = fake_os.path.join('data', filename) + if real_os.path.isfile(real_path): + with real_open(real_path, 'r') as f: + fs.CreateFile(fake_path, contents = f.read()) + if real_os.path.isdir(real_path): + fs.CreateDirectory(fake_path) # redirecting output @@ -104,13 +118,16 @@ class FakeInput(): self._cursor = 0 def as_global(self): - for md in mod_list: + for mdname, md in mod_list: md.input = self + md.editor_input = self + # if mdname.endswith('files'): + # md.editor_input = self def add_input(self, inp): self.inputs.append(inp) - def __call__(self): + def __call__(self, *args, **kwargs): inp = self.inputs[self._cursor] self._cursor += 1 return inp @@ -118,7 +135,7 @@ class FakeInput(): # putting it all together -def _execute_cmds(cmds, fs = None): +def _execute_cmds(cmds, test_instance = None, fs = None): """ Execute a list of commands, and capture their output A command can be a string, or a tuple of size 2 or 3. @@ -133,24 +150,26 @@ def _execute_cmds(cmds, fs = None): fs = _create_fake_fs() _copy_data(fs) + outs = [] for cmd in cmds: - if hasattr('__iter__', cmd): + if hasattr(cmd, '__iter__'): if len(cmd) == 2: - input = FakeInput(cmd[2]) + input = FakeInput(cmd[1]) input.as_global() _, stdout, stderr = redirect(papers_cmd.execute)(cmd[0].split()) if len(cmd) == 3: actual_out = color.undye(stdout.getvalue()) correct_out = color.undye(cmd[2]) - self.assertEqual(actual_out, correct_out) + if test_instance is not None: + test_instance.assertEqual(actual_out, correct_out) else: assert type(cmd) == str _, stdout, stderr = redirect(papers_cmd.execute)(cmd.split()) - print stderr + assert(stderr.getvalue() == '') outs.append(color.undye(stdout.getvalue())) return outs @@ -208,7 +227,7 @@ class TestInput(unittest.TestCase): with self.assertRaises(IndexError): input() - def test_input(self): + def test_input2(self): other_input = FakeInput(['yes', 'no']) other_input.as_global() self.assertEqual(color.input(), 'yes') @@ -216,6 +235,14 @@ class TestInput(unittest.TestCase): with self.assertRaises(IndexError): color.input() + def test_editor_input(self): + other_input = FakeInput(['yes', 'no']) + other_input.as_global() + self.assertEqual(files.editor_input(), 'yes') + self.assertEqual(files.editor_input(), 'no') + with self.assertRaises(IndexError): + color.input() + class TestUsecase(unittest.TestCase): @@ -264,10 +291,94 @@ class TestUsecase(unittest.TestCase): 'papers add -b data/10.1371%2Fjournal.pone.0038236.bib', 'papers list', 'papers attach Page99 data/pagerank.pdf', - 'papers remove -f Page99', + ('papers remove Page99', ['y']), 'papers remove -f turing1950computing', ] _execute_cmds(cmds) + def test_editor_abort(self): + + with self.assertRaises(SystemExit): + cmds = ['papers init', + ('papers add', ['abc', 'n']), + ('papers add', ['abc', 'y', 'abc', 'n']), + 'papers add -b data/pagerank.bib', + ('papers edit Page99', ['', 'a']), + ] + + _execute_cmds(cmds) + + def test_editor_success(self): + cmds = ['papers init', + ('papers add', [fixtures.pagerankbib]), + ('papers remove Page99', ['y']), + ] + + _execute_cmds(cmds) + + def test_edit(self): + bib = fixtures.pagerankbib + bib1 = re.sub('year = \{1999\}', 'year = {2007}', bib) + bib2 = re.sub('Lawrence Page', 'Lawrence Ridge', bib1) + bib3 = re.sub('Page99', 'Ridge07', bib2) + + line = '0: [Page99] L. Page et al. "The PageRank Citation Ranking Bringing Order to the Web" (1999) \n' + line1 = re.sub('1999', '2007', line) + line2 = re.sub('L. Page', 'L. Ridge', line1) + line3 = re.sub('Page99', 'Ridge07', line2) + + cmds = ['papers init', + 'papers add -b data/pagerank.bib', + ('papers list', [], line), + ('papers edit Page99', [bib1]), + ('papers list', [], line1), + ('papers edit Page99', [bib2]), + ('papers list', [], line2), + ('papers edit Page99', [bib3]), + ('papers list', [], line3), + ] + + _execute_cmds(cmds, test_instance = self) + + def test_export(self): + cmds = ['papers init', + ('papers add', [fixtures.pagerankbib]), + 'papers export Page99', + ('papers export Page99 -f bibtex', [], fixtures.pagerankbib_generated), + 'papers export Page99 -f bibyaml', + ] + + _execute_cmds(cmds) + + def test_import(self): + cmds = ['papers init', + 'papers import data/', + 'papers list' + ] + + outs = _execute_cmds(cmds) + self.assertEqual(4+1, len(outs[-1].split('\n'))) + + def test_open(self): + cmds = ['papers init', + 'papers add -b data/pagerank.bib', + 'papers open Page99' + ] + + with self.assertRaises(SystemExit): + _execute_cmds(cmds) + + with self.assertRaises(SystemExit): + cmds[-1] == 'papers open Page8' + _execute_cmds(cmds) + + def test_update(self): + cmds = ['papers init', + 'papers add -b data/pagerank.bib', + 'papers update' + ] + + with self.assertRaises(SystemExit): + _execute_cmds(cmds)