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