diff --git a/pubs/commands/tag_cmd.py b/pubs/commands/tag_cmd.py index 29e1078..2cd8339 100644 --- a/pubs/commands/tag_cmd.py +++ b/pubs/commands/tag_cmd.py @@ -19,33 +19,29 @@ The different use cases are : import re -from ..repo import Repository, InvalidReference - +from ..repo import Repository 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 = [] - for s in 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 +58,7 @@ def _parse_tag_seq(s): tags.append(s[last:]) return tags + def _tag_groups(tags): plus_tags, minus_tags = [], [] for tag in tags: @@ -79,7 +76,6 @@ def command(conf, args): citekeyOrTag = args.citekeyOrTag tags = args.tags - rp = Repository(conf) if citekeyOrTag is None: @@ -87,20 +83,21 @@ def command(conf, 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)), '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 @@ -108,4 +105,4 @@ def command(conf, 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/pubs/uis.py b/pubs/uis.py index ac566bf..c0c472d 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/pubs/update.py b/pubs/update.py index 71c582c..96ca080 100644 --- a/pubs/update.py +++ b/pubs/update.py @@ -1,6 +1,6 @@ import shutil -import StringIO +import io from . import config from . import uis from . import color @@ -81,7 +81,7 @@ def update(conf, code_version, repo_version, path=None): # comparing potential changes with open(path, 'r') as f: old_conf_text = f.read() - new_conf_text = StringIO.StringIO() + new_conf_text = io.BytesIO() default_conf.write(outfile=new_conf_text) if new_conf_text.getvalue() != old_conf_text: 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 diff --git a/tests/fake_env.py b/tests/fake_env.py index c5299cf..fd627c2 100644 --- a/tests/fake_env.py +++ b/tests/fake_env.py @@ -103,6 +103,8 @@ class FakeIO(object): fakefs_stringio = self.fake_open.Call(*args, **kwargs) return UnicodeStringIOWrapper(fakefs_stringio) + BytesIO = real_io.BytesIO + StringIO = real_io.StringIO def create_fake_fs(module_list): diff --git a/tests/test_tag.py b/tests/test_tag.py index 7e08d43..0e6db53 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -2,20 +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_tag_parsing(self): + def test_parse_tags(self): + 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')) - 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_tag_seq('-war+math+romance'))) + self.assertEqual(({'math', 'romance'}, {'war'}), + _tag_groups(_parse_tag_seq(':war+math+romance'))) + self.assertEqual(({'math', 'romance'}, {'war'}), + _tag_groups(_parse_tag_seq('+math+romance-war'))) + self.assertEqual(({'math', 'romance'}, {'war'}), + _tag_groups(_parse_tag_seq('math+romance-war'))) if __name__ == '__main__': diff --git a/tests/test_usecase.py b/tests/test_usecase.py index 38e31cf..9ec2b3e 100644 --- a/tests/test_usecase.py +++ b/tests/test_usecase.py @@ -7,8 +7,7 @@ import os import dotdot import fake_env -from pubs import pubs_cmd -from pubs import color, content, filebroker, uis, p3, endecoder +from pubs import pubs_cmd, update, color, content, filebroker, uis, p3, endecoder from pubs.config import conf import configobj @@ -58,7 +57,7 @@ class CommandTestCase(unittest.TestCase): maxDiff = 1000000 def setUp(self): - self.fs = fake_env.create_fake_fs([content, filebroker, conf, init_cmd, import_cmd, configobj]) + self.fs = fake_env.create_fake_fs([content, filebroker, conf, init_cmd, import_cmd, configobj, update]) self.default_pubs_dir = self.fs['os'].path.expanduser('~/.pubs') def execute_cmds(self, cmds, capture_output=CAPTURE_OUTPUT): @@ -118,7 +117,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') @@ -251,6 +250,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):