1""" 2This command is all about tags. 3The different use cases are : 41. > pubs tag 5 Returns the list of all tags 62. > pubs tag citekey 7 Return the list of tags of the given citekey 83. > pubs tag citekey math 9 Add 'math' to the list of tags of the given citekey 104. > pubs tag citekey :math 11 Remove 'math' for the list of tags of the given citekey 125. > pubs tag citekey math+romance-war 13 Add 'math' and 'romance' tags to the given citekey, and remove the 'war' tag 146. > pubs tag math 15 If 'math' is not a citekey, then display all papers with the tag 'math' 167. > pubs tag -war+math+romance 17 display all papers with the tag 'math', 'romance' but not 'war' 18""" 19from __future__ import unicode_literals 20 21import re 22 23from ..repo import Repository 24from ..uis import get_ui 25from .. import pretty 26from .. import color 27from ..utils import resolve_citekey 28from ..completion import CiteKeyOrTagCompletion, TagModifierCompletion 29from ..events import TagEvent 30 31 32def parser(subparsers, conf): 33 parser = subparsers.add_parser('tag', help="add, remove and show tags") 34 parser.add_argument('citekeyOrTag', nargs='?', default=None, 35 help='citekey or tag.').completer = CiteKeyOrTagCompletion(conf) 36 parser.add_argument('tags', nargs='?', default=None, 37 help='If the previous argument was a citekey, then ' 38 'a list of tags separated by + and -.' 39 ).completer = TagModifierCompletion(conf) 40 # TODO find a way to display clear help for multiple command semantics, 41 # indistinguisable for argparse. (fabien, 201306) 42 return parser 43 44 45def _parse_tag_seq(s): 46 """Transform 'math-ai' in ['+math', '-ai']""" 47 tags = [] 48 if s[0] == ':': 49 s = '-' + s[1:] 50 if s[0] not in ['+', '-']: 51 s = '+' + s 52 last = 0 53 for m in re.finditer(r'[+-]', s): 54 if m.start() == last: 55 if last != 0: 56 raise ValueError('could not match tag expression') 57 else: 58 tags.append(s[last:(m.start())]) 59 last = m.start() 60 if last == len(s): 61 raise ValueError('could not match tag expression') 62 else: 63 tags.append(s[last:]) 64 return tags 65 66 67def _tag_groups(tags): 68 plus_tags, minus_tags = [], [] 69 for tag in tags: 70 if tag[0] == '+': 71 plus_tags.append(tag[1:]) 72 else: 73 assert tag[0] == '-' 74 minus_tags.append(tag[1:]) 75 return set(plus_tags), set(minus_tags) 76 77 78def command(conf, args): 79 """Add, remove and show tags""" 80 81 ui = get_ui() 82 citekeyOrTag = args.citekeyOrTag 83 tags = args.tags 84 85 rp = Repository(conf) 86 87 if citekeyOrTag is None: 88 ui.message(color.dye_out(' '.join(sorted(rp.get_tags())), 'tag')) 89 else: 90 not_citekey = False 91 try: 92 citekeyOrTag = resolve_citekey(repo=rp, citekey=citekeyOrTag, ui=ui, exit_on_fail=True) 93 except SystemExit: 94 not_citekey = True 95 if not not_citekey: 96 p = rp.pull_paper(citekeyOrTag) 97 if tags is None: 98 ui.message(color.dye_out(' '.join(sorted(p.tags)), 'tag')) 99 else: 100 add_tags, remove_tags = _tag_groups(_parse_tag_seq(tags)) 101 for tag in add_tags: 102 p.add_tag(tag) 103 for tag in remove_tags: 104 p.remove_tag(tag) 105 rp.push_paper(p, overwrite=True, event=False) 106 TagEvent(citekeyOrTag).send() 107 elif tags is not None: 108 ui.error(ui.error('No entry found for citekey {}.'.format(citekeyOrTag))) 109 ui.exit() 110 else: 111 ui.info('Assuming {} to be a tag.'.format(color.dye_out(citekeyOrTag))) 112 # case where we want to find papers with specific tags 113 included, excluded = _tag_groups(_parse_tag_seq(citekeyOrTag)) 114 papers_list = [] 115 for p in rp.all_papers(): 116 if (p.tags.issuperset(included) and 117 len(p.tags.intersection(excluded)) == 0): 118 papers_list.append(p) 119 120 ui.message('\n'.join(pretty.paper_oneliner(p) 121 for p in papers_list)) 122 123 rp.close() 124