|
@@ -4,6 +4,7 @@ frontend into trees that can be used by the database backend. |
|
|
""" |
|
|
""" |
|
|
|
|
|
|
|
|
from __future__ import unicode_literals |
|
|
from __future__ import unicode_literals |
|
|
|
|
|
from re import IGNORECASE, search |
|
|
from shlex import split |
|
|
from shlex import split |
|
|
|
|
|
|
|
|
from dateutil.parser import parse as parse_date |
|
|
from dateutil.parser import parse as parse_date |
|
@@ -35,10 +36,35 @@ class _QueryParser(object): |
|
|
self._parse_variable: ["v", "var", "variable"] |
|
|
self._parse_variable: ["v", "var", "variable"] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def _parse_literal(self, literal): |
|
|
|
|
|
"""Parse part of a search query into a string or regular expression.""" |
|
|
|
|
|
if literal.startswith(("r:", "re:", "regex:", "regexp:")): |
|
|
|
|
|
return Regex(literal.split(":", 1)[1]) |
|
|
|
|
|
return String(literal) |
|
|
|
|
|
|
|
|
def _parse_language(self, term): |
|
|
def _parse_language(self, term): |
|
|
"""Parse part of a query into a language node and return it.""" |
|
|
"""Parse part of a query into a language node and return it.""" |
|
|
## TODO: look up language ID |
|
|
|
|
|
return Language(0) |
|
|
|
|
|
|
|
|
term = self._parse_literal(term) |
|
|
|
|
|
if isinstance(term, Regex): |
|
|
|
|
|
langs = [i for i, lang in enumerate(LANGS) |
|
|
|
|
|
if search(term.regex, lang, IGNORECASE)] |
|
|
|
|
|
if not langs: |
|
|
|
|
|
err = "No languages found for regex: %r" % term.regex |
|
|
|
|
|
raise QueryParseException(err) |
|
|
|
|
|
node = Language(langs.pop()) |
|
|
|
|
|
while langs: |
|
|
|
|
|
node = BinaryOp(Language(langs.pop()), BinaryOp.OR, node) |
|
|
|
|
|
return node |
|
|
|
|
|
|
|
|
|
|
|
needle = term.string.lower() |
|
|
|
|
|
for i, lang in enumerate(LANGS): |
|
|
|
|
|
if lang.lower() == needle: |
|
|
|
|
|
return Language(i) |
|
|
|
|
|
for i, lang in enumerate(LANGS): |
|
|
|
|
|
if lang.lower().startswith(needle): |
|
|
|
|
|
return Language(i) |
|
|
|
|
|
err = "No languages found for string: %r" % term.string |
|
|
|
|
|
raise QueryParseException(err) |
|
|
|
|
|
|
|
|
def _parse_author(self, term): |
|
|
def _parse_author(self, term): |
|
|
"""Parse part of a query into an author node and return it.""" |
|
|
"""Parse part of a query into an author node and return it.""" |
|
@@ -53,11 +79,11 @@ class _QueryParser(object): |
|
|
relation = Date.AFTER |
|
|
relation = Date.AFTER |
|
|
dtstr = term.split(":", 1)[1] |
|
|
dtstr = term.split(":", 1)[1] |
|
|
else: |
|
|
else: |
|
|
raise QueryParseException('Bad relation for date: "%s"' % term) |
|
|
|
|
|
|
|
|
raise QueryParseException("Bad relation for date node: %r" % term) |
|
|
try: |
|
|
try: |
|
|
dt = parse_date(dtstr) |
|
|
dt = parse_date(dtstr) |
|
|
except (TypeError, ValueError): |
|
|
except (TypeError, ValueError): |
|
|
raise QueryParseException('Bad datetime for date: "%s"' % dtstr) |
|
|
|
|
|
|
|
|
raise QueryParseException("Bad datetime for date node: %r" % dtstr) |
|
|
return Date(type_, relation, dt) |
|
|
return Date(type_, relation, dt) |
|
|
|
|
|
|
|
|
def _parse_modified(self, term): |
|
|
def _parse_modified(self, term): |
|
@@ -84,12 +110,6 @@ class _QueryParser(object): |
|
|
"""Parse part of a query into a variable node and return it.""" |
|
|
"""Parse part of a query into a variable node and return it.""" |
|
|
return Symbol(Symbol.VARIABLE, self._parse_literal(term)) |
|
|
return Symbol(Symbol.VARIABLE, self._parse_literal(term)) |
|
|
|
|
|
|
|
|
def _parse_literal(self, literal): |
|
|
|
|
|
"""Parse part of a search query into a string or regular expression.""" |
|
|
|
|
|
if literal.startswith(("r:", "re:", "regex:", "regexp:")): |
|
|
|
|
|
return Regex(literal.split(":", 1)[1]) |
|
|
|
|
|
return String(literal) |
|
|
|
|
|
|
|
|
|
|
|
def _parse_term(self, term): |
|
|
def _parse_term(self, term): |
|
|
"""Parse a query term into a tree node and return it.""" |
|
|
"""Parse a query term into a tree node and return it.""" |
|
|
if ":" in term and not term[0] == ":": |
|
|
if ":" in term and not term[0] == ":": |
|
|