~nmu-sscheel/gtg/rework-task-editor

« back to all changes in this revision

Viewing changes to GTG/core/search.py

  • Committer: Bertrand Rousseau
  • Date: 2012-05-09 22:33:25 UTC
  • mfrom: (1178 trunk)
  • mto: This revision was merged to the branch mainline in revision 1179.
  • Revision ID: bertrand.rousseau@gmail.com-20120509223325-a53d8nwo0x9g93bc
Merge nimit branch and trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
  * João Ascenso, GSoC 2011
25
25
  * Izidor Matušov, Jan/Feb 2012
26
26
 
27
 
You can search by entring a query in a simple language. Function parse_search_query()
28
 
parse the query and return internal representation which is used for filtering
29
 
in search_filter() function. If the query is malformed, the exception InvalidQuery is
30
 
raised.
 
27
You can search by entring a query in a simple language. Function
 
28
parse_search_query() parse the query and return internal representation which
 
29
is used for filtering in search_filter() function. If the query is malformed,
 
30
the exception InvalidQuery is raised.
31
31
 
32
32
The query language consists of several elements:
33
33
  * commands
34
34
    * !not <elem> -- the next element will be negated
35
 
    * <elem> !or <elem> -- return True if the former or the later element are true
36
 
    * !after <date> -- show tasks which could be done after this date 
 
35
    * <elem> !or <elem> -- return True if at least on of elements is true
 
36
    * !after <date> -- show tasks which could be done after this date
37
37
    * !before <date> -- show tasks which must be done before this date
38
38
    * !today -- show tasks with due_date == today
39
39
    * !tomorrow -- show tasks with due_date == tomorrow
41
41
    * !now -- show tasks with due_date == now
42
42
    * !soon -- show tasks with due_date == soon
43
43
    * !someday -- show tasks with due_date == someday
 
44
    * !notag -- show tasks without tags
44
45
  * tags -- show tasks with this tag
45
46
  * word -- show tasks which contains this word
46
 
  * "literal" -- basically the same as word but allows the space and special charasters
47
 
        inside. Literal must be inside "quotes".
 
47
  * "literal" -- basically the same as word but allows the space and special
 
48
        characters inside. Literal must be inside "quotes".
48
49
  * date -- date which could be parsed with Date.parse()
49
50
 
50
51
Elements are supposed to be in conjuction, i.e. they are interpreted as
67
68
A special command is "or" which contains subcommands and returns Ture if
68
69
at least one subcommand returns True.
69
70
 
70
 
search_filter() could be easily plugged in Liblarch and filter only suitable tasks.
 
71
search_filter() could be easily plugged in Liblarch and filter only suitable
 
72
tasks.
71
73
 
72
74
For more information see unittests:
73
75
  * GTG/tests/test_search_query.py -- parsing query
92
94
  "now": _("now"),
93
95
  "soon": _("soon"),
94
96
  "someday": _("someday"),
 
97
  "notag": _("notag"),
95
98
}
96
99
 
97
100
# transform keywords and their translations into a list of possible commands
103
106
    KEYWORDS[key] = possible_words
104
107
 
105
108
# Generate list of possible commands
106
 
search_commands = []
107
 
for keyword in KEYWORDS:
108
 
    for command in KEYWORDS[keyword]:
109
 
        command = '!' + command
110
 
        if command not in search_commands:
111
 
            search_commands.append(command)
 
109
SEARCH_COMMANDS = []
 
110
for key in KEYWORDS:
 
111
    for key_command in KEYWORDS[key]:
 
112
        key_command = '!' + key_command
 
113
        if key_command not in SEARCH_COMMANDS:
 
114
            SEARCH_COMMANDS.append(key_command)
 
115
 
112
116
 
113
117
class InvalidQuery(Exception):
 
118
    """ Exception which is raised during parsing of
 
119
    search query if it is invalid """
114
120
    pass
115
121
 
116
122
TOKENS_RE = re.compile(r"""
117
123
            (?P<command>!\S+(?=\s)?) |
118
124
            (?P<tag>@\S+(?=\s)?) |
119
125
            (?P<date>\d{4}-\d{2}-\d{2}|\d{8}|\d{4}) |
120
 
            (?P<literal>".+?") | 
 
126
            (?P<literal>".+?") |
121
127
            (?P<word>(?![!"@])\S+(?=\s)?) |
122
128
            (?P<space>(\s+))
123
129
            """, re.VERBOSE)
124
130
 
 
131
 
125
132
def _tokenize_query(query):
126
133
    """ Split query into a sequence of tokens (type, value)
127
134
 
132
139
    """
133
140
    pos = 0
134
141
    while True:
135
 
        m = TOKENS_RE.match(query, pos)
136
 
        if not m: 
 
142
        match = TOKENS_RE.match(query, pos)
 
143
        if not match:
137
144
            break
138
 
        pos = m.end()
139
 
        token_type = m.lastgroup
140
 
        token_value = m.group(token_type)
 
145
        pos = match.end()
 
146
        token_type = match.lastgroup
 
147
        token_value = match.group(token_type)
141
148
        if token_type != 'space':
142
149
            yield token_type, token_value
143
150
    if pos != len(query):
144
151
        raise InvalidQuery('tokenizer stopped at pos %r of %r left of "%s"' % (
145
152
            pos, len(query), query[pos:pos+10]))
146
153
 
 
154
 
147
155
def parse_search_query(query):
148
156
    """ Parse query into parameters for search filter
149
157
 
165
173
 
166
174
        if require_date:
167
175
            if token not in ['date', 'word', 'literal']:
168
 
                raise InvalidQuery("Unexpected token '%s' after '%s'" % (token, require_date))
 
176
                raise InvalidQuery("Unexpected token '%s' after '%s'" % (
 
177
                            token, require_date))
169
178
 
170
179
            value = value.strip('"')
171
180
            try:
191
200
                        raise InvalidQuery("!or cann't follow !not")
192
201
 
193
202
                    if commands == []:
194
 
                        raise InvalidQuery("Or is not allowed at the beginning of query")
 
203
                        raise InvalidQuery( \
 
204
                            "Or is not allowed at the beginning of query")
195
205
 
196
206
                    if commands[-1][0] != "or":
197
207
                        commands.append(("or", True, [commands.pop()]))
230
240
 
231
241
    return {'q': commands}
232
242
 
 
243
 
233
244
def search_filter(task, parameters=None):
234
245
    """ Check if task satisfies all search parameters """
235
246
 
237
248
        return False
238
249
 
239
250
    def check_commands(commands_list):
240
 
        # Check if contian values
 
251
        """ Execute search commands
 
252
 
 
253
        This method is recursive for !or and !and """
 
254
 
241
255
        def fulltext_search(task, word):
 
256
            """ check if task contains the word """
242
257
            word = word.lower()
243
258
            text = task.get_excerpt(strip_tags=False).lower()
244
259
            title = task.get_title().lower()
256
271
            'now': lambda task, v: task.get_due_date() == Date.now(),
257
272
            'soon': lambda task, v: task.get_due_date() == Date.soon(),
258
273
            'someday': lambda task, v: task.get_due_date() == Date.someday(),
 
274
            'notag': lambda task, v: task.get_tags() == [],
259
275
        }
260
276
 
261
277
        for command in commands_list: