From 5480f79d8d85fb49329df3b9babe158858530556 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 4 Dec 2015 23:20:19 -0500 Subject: [PATCH 1/4] =?UTF-8?q?Fixes=20=CB=87pubs=20tag=20citekey=20:somet?= =?UTF-8?q?ag=CB=87=20not=20working.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also adds a few tests for the tag command. --- pubs/commands/tag_cmd.py | 21 +++++++----- tests/test_tag.py | 15 +++++--- tests/test_usecase.py | 74 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 15 deletions(-) diff --git a/pubs/commands/tag_cmd.py b/pubs/commands/tag_cmd.py index 34485b8..cb259d3 100644 --- a/pubs/commands/tag_cmd.py +++ b/pubs/commands/tag_cmd.py @@ -19,23 +19,25 @@ The different use cases are : import re -from ..repo import Repository, InvalidReference +from ..repo import Repository from ..configs import config from ..uis import get_ui from .. import pretty from .. import color + def parser(subparsers): parser = subparsers.add_parser('tag', help="add, remove and show tags") - parser.add_argument('citekeyOrTag', nargs='?', default = None, + parser.add_argument('citekeyOrTag', nargs='?', default=None, help='citekey or tag.') - parser.add_argument('tags', nargs='*', default = None, + parser.add_argument('tags', nargs='*', default=None, help='If the previous argument was a citekey, then ' - 'then a list of tags separated by a +.') + 'a list of tags separated by a +.') # TODO find a way to display clear help for multiple command semantics, # indistinguisable for argparse. (fabien, 201306) return parser + def _parse_tags(list_tags): """Transform 'math-ai network -search' in ['+math', '-ai', '+network', '-search']""" tags = [] @@ -43,9 +45,12 @@ def _parse_tags(list_tags): tags += _parse_tag_seq(s) return tags + def _parse_tag_seq(s): """Transform 'math-ai' in ['+math', '-ai']""" tags = [] + if s[0] == ':': + s = '-' + s[1:] if s[0] not in ['+', '-']: s = '+' + s last = 0 @@ -62,6 +67,7 @@ def _parse_tag_seq(s): tags.append(s[last:]) return tags + def _tag_groups(tags): plus_tags, minus_tags = [], [] for tag in tags: @@ -72,6 +78,7 @@ def _tag_groups(tags): minus_tags.append(tag[1:]) return set(plus_tags), set(minus_tags) + def command(args): """Add, remove and show tags""" @@ -79,7 +86,6 @@ def command(args): citekeyOrTag = args.citekeyOrTag tags = args.tags - rp = Repository(config()) if citekeyOrTag is None: @@ -88,8 +94,7 @@ def command(args): if rp.databroker.exists(citekeyOrTag): p = rp.pull_paper(citekeyOrTag) if tags == []: - ui.message(color.dye_out(' '.join(sorted(p.tags)), - color.tag)) + ui.message(color.dye_out(' '.join(sorted(p.tags)), color.tag)) else: add_tags, remove_tags = _tag_groups(_parse_tags(tags)) for tag in add_tags: @@ -109,4 +114,4 @@ def command(args): papers_list.append(p) ui.message('\n'.join(pretty.paper_oneliner(p) - for p in papers_list)) + for p in papers_list)) diff --git a/tests/test_tag.py b/tests/test_tag.py index 7e08d43..409ebc5 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -6,16 +6,21 @@ from pubs.commands.tag_cmd import _parse_tags, _tag_groups class TestTag(unittest.TestCase): - def test_tag_parsing(self): - + def test_parse_tags(self): self.assertEqual(['+abc', '+def9'], _parse_tags([ 'abc+def9'])) self.assertEqual(['+abc', '-def9'], _parse_tags([ 'abc-def9'])) self.assertEqual(['-abc', '-def9'], _parse_tags(['-abc-def9'])) self.assertEqual(['+abc', '-def9'], _parse_tags(['+abc-def9'])) - self.assertEqual(({'math', 'romance'}, {'war'}), _tag_groups(_parse_tags(['-war+math+romance']))) - self.assertEqual(({'math', 'romance'}, {'war'}), _tag_groups(_parse_tags(['+math+romance-war']))) - self.assertEqual(({'math', 'romance'}, {'war'}), _tag_groups(_parse_tags(['math+romance-war']))) + def test_tag_groups(self): + self.assertEqual(({'math', 'romance'}, {'war'}), + _tag_groups(_parse_tags(['-war+math+romance']))) + self.assertEqual(({'math', 'romance'}, {'war'}), + _tag_groups(_parse_tags([':war+math+romance']))) + self.assertEqual(({'math', 'romance'}, {'war'}), + _tag_groups(_parse_tags(['+math+romance-war']))) + self.assertEqual(({'math', 'romance'}, {'war'}), + _tag_groups(_parse_tags(['math+romance-war']))) if __name__ == '__main__': diff --git a/tests/test_usecase.py b/tests/test_usecase.py index 5464d21..7345918 100644 --- a/tests/test_usecase.py +++ b/tests/test_usecase.py @@ -116,7 +116,7 @@ class DataCommandTestCase(CommandTestCase): """ def setUp(self): - CommandTestCase.setUp(self) + super(DataCommandTestCase, self).setUp() fake_env.copy_dir(self.fs, os.path.join(os.path.dirname(__file__), 'data'), 'data') @@ -249,6 +249,76 @@ class TestList(DataCommandTestCase): self.assertEqual(0 + 1, len(outs[-1].split('\n'))) +class TestTag(DataCommandTestCase): + + def setUp(self): + super(TestTag, self).setUp() + init = ['pubs init', + 'pubs add data/pagerank.bib', + 'pubs add -k Turing1950 data/turing1950.bib', + ] + self.execute_cmds(init) + + def test_add_tag(self): + cmds = ['pubs tag Page99 search', + 'pubs tag Turing1950 ai', + 'pubs list', + ] + correct = ['', + '', + '[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) | search\n' + + '[Turing1950] Turing, Alan M "Computing machinery and intelligence" Mind (1950) | ai\n', + ] + out = self.execute_cmds(cmds) + self.assertEqual(out, correct) + + def test_add_tags(self): + """Adds several tags at once. + Also checks that tags printed in alphabetic order. + """ + cmds = ['pubs tag Page99 search+network', + 'pubs list', + ] + correct = ['', + '[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) | network,search\n' + + '[Turing1950] Turing, Alan M "Computing machinery and intelligence" Mind (1950) \n', + ] + out = self.execute_cmds(cmds) + self.assertEqual(out, correct) + + def test_remove_tag(self): + cmds = ['pubs tag Page99 search+network', + 'pubs tag Page99 :network', + 'pubs list', + ] + correct = ['', + '', + '[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) | search\n' + + '[Turing1950] Turing, Alan M "Computing machinery and intelligence" Mind (1950) \n', + ] + out = self.execute_cmds(cmds) + self.assertEqual(out, correct) + + def test_add_remove_tag(self): + cmds = ['pubs tag Page99 a', + 'pubs tag Page99 b-a', + 'pubs list', + ] + correct = ['', + '', + '[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) | b\n' + + '[Turing1950] Turing, Alan M "Computing machinery and intelligence" Mind (1950) \n', + ] + out = self.execute_cmds(cmds) + self.assertEqual(out, correct) + + def test_wrong_citekey(self): + cmds = ['pubs tag Page999 a', + ] + with self.assertRaises(SystemExit): + self.execute_cmds(cmds) + + class TestUsecase(DataCommandTestCase): def test_first(self): @@ -438,7 +508,7 @@ class TestUsecase(DataCommandTestCase): ] self.execute_cmds(cmds) self.assertFalse(self.fs['os'].path.exists('/data/pagerank.pdf')) - + if __name__ == '__main__': unittest.main() From 681ae65c9ee8626bbc62a902c189195bacd738b0 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 4 Dec 2015 23:38:11 -0500 Subject: [PATCH 2/4] Fixes issue #35. pubs tag non-existing-citekey some-tag Know fails as expected. The commit however removes the option of listing tags as separate arguments. --- pubs/commands/tag_cmd.py | 21 +++++++-------------- pubs/uis.py | 2 ++ tests/test_tag.py | 18 +++++++++--------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/pubs/commands/tag_cmd.py b/pubs/commands/tag_cmd.py index cb259d3..7e07b94 100644 --- a/pubs/commands/tag_cmd.py +++ b/pubs/commands/tag_cmd.py @@ -30,7 +30,7 @@ def parser(subparsers): parser = subparsers.add_parser('tag', help="add, remove and show tags") parser.add_argument('citekeyOrTag', nargs='?', default=None, help='citekey or tag.') - parser.add_argument('tags', nargs='*', default=None, + parser.add_argument('tags', nargs='?', default=None, help='If the previous argument was a citekey, then ' 'a list of tags separated by a +.') # TODO find a way to display clear help for multiple command semantics, @@ -38,14 +38,6 @@ def parser(subparsers): return parser -def _parse_tags(list_tags): - """Transform 'math-ai network -search' in ['+math', '-ai', '+network', '-search']""" - tags = [] - for s in list_tags: - tags += _parse_tag_seq(s) - return tags - - def _parse_tag_seq(s): """Transform 'math-ai' in ['+math', '-ai']""" tags = [] @@ -93,20 +85,21 @@ def command(args): else: if rp.databroker.exists(citekeyOrTag): p = rp.pull_paper(citekeyOrTag) - if tags == []: + if tags is None: ui.message(color.dye_out(' '.join(sorted(p.tags)), color.tag)) else: - add_tags, remove_tags = _tag_groups(_parse_tags(tags)) + add_tags, remove_tags = _tag_groups(_parse_tag_seq(tags)) for tag in add_tags: p.add_tag(tag) for tag in remove_tags: p.remove_tag(tag) rp.push_paper(p, overwrite=True) + elif tags is not None: + ui.error(ui.error('no entry found for citekey {}.'.format(citekeyOrTag))) + ui.exit() else: # case where we want to find papers with specific tags - all_tags = [citekeyOrTag] - all_tags += tags - included, excluded = _tag_groups(_parse_tags(all_tags)) + included, excluded = _tag_groups(_parse_tag_seq(citekeyOrTag)) papers_list = [] for p in rp.all_papers(): if (p.tags.issuperset(included) and diff --git a/pubs/uis.py b/pubs/uis.py index c019105..df878a3 100644 --- a/pubs/uis.py +++ b/pubs/uis.py @@ -27,11 +27,13 @@ def _get_encoding(conf): return enc or 'utf-8' return conf.get('terminal-encoding', enc or 'utf-8') + def get_ui(): if _ui is None: return PrintUI() # no editor support. (#FIXME?) return _ui + def init_ui(conf): global _ui _ui = InputUI(conf) diff --git a/tests/test_tag.py b/tests/test_tag.py index 409ebc5..0e6db53 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -2,25 +2,25 @@ import unittest import dotdot -from pubs.commands.tag_cmd import _parse_tags, _tag_groups +from pubs.commands.tag_cmd import _parse_tag_seq, _tag_groups class TestTag(unittest.TestCase): def test_parse_tags(self): - self.assertEqual(['+abc', '+def9'], _parse_tags([ 'abc+def9'])) - self.assertEqual(['+abc', '-def9'], _parse_tags([ 'abc-def9'])) - self.assertEqual(['-abc', '-def9'], _parse_tags(['-abc-def9'])) - self.assertEqual(['+abc', '-def9'], _parse_tags(['+abc-def9'])) + self.assertEqual(['+abc', '+def9'], _parse_tag_seq('abc+def9')) + self.assertEqual(['+abc', '-def9'], _parse_tag_seq('abc-def9')) + self.assertEqual(['-abc', '-def9'], _parse_tag_seq('-abc-def9')) + self.assertEqual(['+abc', '-def9'], _parse_tag_seq('+abc-def9')) def test_tag_groups(self): self.assertEqual(({'math', 'romance'}, {'war'}), - _tag_groups(_parse_tags(['-war+math+romance']))) + _tag_groups(_parse_tag_seq('-war+math+romance'))) self.assertEqual(({'math', 'romance'}, {'war'}), - _tag_groups(_parse_tags([':war+math+romance']))) + _tag_groups(_parse_tag_seq(':war+math+romance'))) self.assertEqual(({'math', 'romance'}, {'war'}), - _tag_groups(_parse_tags(['+math+romance-war']))) + _tag_groups(_parse_tag_seq('+math+romance-war'))) self.assertEqual(({'math', 'romance'}, {'war'}), - _tag_groups(_parse_tags(['math+romance-war']))) + _tag_groups(_parse_tag_seq('math+romance-war'))) if __name__ == '__main__': From 4e204a41f2ec3ca116a1f6b3d3d798c576c57e90 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Sat, 5 Dec 2015 00:37:59 -0500 Subject: [PATCH 3/4] Some more examples to README. --- readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/readme.md b/readme.md index 90e3333..f741495 100644 --- a/readme.md +++ b/readme.md @@ -52,6 +52,17 @@ This ensure that your reference file is always up-to-date; you can cite a paper and then add `\cite{Loeb_2012}` in your manuscript. After running the bash script, the citation will correctly appear in your compiled pdf. +Customization +------------- +Pubs is designed to interact well with your command line tool chain. You can add custom commands to pubs by defining aliases in your config file. Here are a few examples. + + [alias] + print = open -w lp + count = !pubs list -k | wc -l + +For more advanced functionalities, pubs also support plugins. Actually *alias* is itself a plugin! + + Requirements ------------ - python >= 2.7 or >= 3.3 From bd3659a432dc6bcf9d93c4bfa47abb36819c450f Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Sun, 6 Dec 2015 00:58:29 -0500 Subject: [PATCH 4/4] Fixes missing update of dye in open command. --- pubs/commands/open_cmd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubs/commands/open_cmd.py b/pubs/commands/open_cmd.py index 7f47a4a..9fb0afc 100644 --- a/pubs/commands/open_cmd.py +++ b/pubs/commands/open_cmd.py @@ -39,7 +39,7 @@ def command(args): cmd = with_command.split() cmd.append(docpath) subprocess.Popen(cmd) - ui.message('{} opened.'.format(color.dye(docpath, color.filepath))) + ui.message('{} opened.'.format(color.dye_out(docpath, color.filepath))) except OSError: ui.error("Command does not exist: %s." % with_command) ui.exit(127)