2016-06-03 10:13:43 +00:00
|
|
|
import re
|
2020-06-05 22:03:37 +00:00
|
|
|
|
2016-06-03 10:13:43 +00:00
|
|
|
from szurubooru import errors
|
|
|
|
from szurubooru.search import criteria, tokens
|
2017-04-24 19:51:49 +00:00
|
|
|
from szurubooru.search.configs import util
|
2020-06-05 22:03:37 +00:00
|
|
|
from szurubooru.search.query import SearchQuery
|
2016-06-03 10:13:43 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _create_criterion(
|
2020-06-05 22:03:37 +00:00
|
|
|
original_value: str, value: str
|
|
|
|
) -> criteria.BaseCriterion:
|
|
|
|
if re.search(r"(?<!\\),", value):
|
|
|
|
values = re.split(r"(?<!\\),", value)
|
2017-04-24 19:51:49 +00:00
|
|
|
if any(not term.strip() for term in values):
|
2020-06-05 22:03:37 +00:00
|
|
|
raise errors.SearchError("Empty compound value")
|
2017-04-24 19:51:49 +00:00
|
|
|
return criteria.ArrayCriterion(original_value, values)
|
2020-06-05 22:03:37 +00:00
|
|
|
if re.search(r"(?<!\\)\.(?<!\\)\.", value):
|
|
|
|
low, high = re.split(r"(?<!\\)\.(?<!\\)\.", value, 1)
|
2016-06-03 10:13:43 +00:00
|
|
|
if not low and not high:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise errors.SearchError("Empty ranged value")
|
2016-06-03 10:13:43 +00:00
|
|
|
return criteria.RangedCriterion(original_value, low, high)
|
|
|
|
return criteria.PlainCriterion(original_value, value)
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _parse_anonymous(value: str, negated: bool) -> tokens.AnonymousToken:
|
2016-06-03 10:13:43 +00:00
|
|
|
criterion = _create_criterion(value, value)
|
|
|
|
return tokens.AnonymousToken(criterion, negated)
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _parse_named(key: str, value: str, negated: bool) -> tokens.NamedToken:
|
2016-06-03 10:13:43 +00:00
|
|
|
original_value = value
|
2020-06-05 22:03:37 +00:00
|
|
|
if key.endswith("-min"):
|
2016-06-03 10:13:43 +00:00
|
|
|
key = key[:-4]
|
2020-06-05 22:03:37 +00:00
|
|
|
value += ".."
|
|
|
|
elif key.endswith("-max"):
|
2016-06-03 10:13:43 +00:00
|
|
|
key = key[:-4]
|
2020-06-05 22:03:37 +00:00
|
|
|
value = ".." + value
|
2016-06-03 10:13:43 +00:00
|
|
|
criterion = _create_criterion(original_value, value)
|
|
|
|
return tokens.NamedToken(key, criterion, negated)
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _parse_special(value: str, negated: bool) -> tokens.SpecialToken:
|
2016-06-03 10:13:43 +00:00
|
|
|
return tokens.SpecialToken(value, negated)
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _parse_sort(value: str, negated: bool) -> tokens.SortToken:
|
2020-06-05 22:03:37 +00:00
|
|
|
if value.count(",") == 0:
|
2016-08-14 12:22:53 +00:00
|
|
|
order_str = None
|
2020-06-05 22:03:37 +00:00
|
|
|
elif value.count(",") == 1:
|
|
|
|
value, order_str = value.split(",")
|
2016-06-03 10:13:43 +00:00
|
|
|
else:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise errors.SearchError("Too many commas in sort style token.")
|
2016-06-03 10:13:43 +00:00
|
|
|
try:
|
2016-08-14 12:22:53 +00:00
|
|
|
order = {
|
2020-06-05 22:03:37 +00:00
|
|
|
"asc": tokens.SortToken.SORT_ASC,
|
|
|
|
"desc": tokens.SortToken.SORT_DESC,
|
|
|
|
"": tokens.SortToken.SORT_DEFAULT,
|
2016-06-03 10:13:43 +00:00
|
|
|
None: tokens.SortToken.SORT_DEFAULT,
|
2016-08-14 12:22:53 +00:00
|
|
|
}[order_str]
|
2016-06-03 10:13:43 +00:00
|
|
|
except KeyError:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise errors.SearchError("Unknown search direction: %r." % order_str)
|
2016-06-03 10:13:43 +00:00
|
|
|
if negated:
|
2016-08-14 12:22:53 +00:00
|
|
|
order = {
|
2020-06-05 22:03:37 +00:00
|
|
|
tokens.SortToken.SORT_ASC: tokens.SortToken.SORT_DESC,
|
|
|
|
tokens.SortToken.SORT_DESC: tokens.SortToken.SORT_ASC,
|
|
|
|
tokens.SortToken.SORT_DEFAULT: tokens.SortToken.SORT_NEGATED_DEFAULT, # noqa: E501
|
|
|
|
tokens.SortToken.SORT_NEGATED_DEFAULT: tokens.SortToken.SORT_DEFAULT, # noqa: E501
|
2016-08-14 12:22:53 +00:00
|
|
|
}[order]
|
|
|
|
return tokens.SortToken(value, order)
|
|
|
|
|
2016-06-03 10:13:43 +00:00
|
|
|
|
2016-10-22 12:43:52 +00:00
|
|
|
class Parser:
|
2017-02-04 00:08:12 +00:00
|
|
|
def parse(self, query_text: str) -> SearchQuery:
|
2016-06-03 10:13:43 +00:00
|
|
|
query = SearchQuery()
|
2020-06-05 22:03:37 +00:00
|
|
|
for chunk in re.split(r"\s+", (query_text or "").lower()):
|
2016-06-03 10:13:43 +00:00
|
|
|
if not chunk:
|
|
|
|
continue
|
|
|
|
negated = False
|
2020-06-05 22:03:37 +00:00
|
|
|
if chunk[0] == "-":
|
2016-06-03 10:13:43 +00:00
|
|
|
chunk = chunk[1:]
|
2017-04-24 17:55:02 +00:00
|
|
|
negated = True
|
|
|
|
if not chunk:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise errors.SearchError("Empty negated token.")
|
|
|
|
match = re.match(r"^(.*?)(?<!\\):(.*)$", chunk)
|
2016-09-26 20:06:18 +00:00
|
|
|
if match:
|
|
|
|
key, value = list(match.groups())
|
2017-04-24 19:51:49 +00:00
|
|
|
key = util.unescape(key)
|
2020-06-05 22:03:37 +00:00
|
|
|
if key == "sort":
|
|
|
|
query.sort_tokens.append(_parse_sort(value, negated))
|
|
|
|
elif key == "special":
|
|
|
|
query.special_tokens.append(_parse_special(value, negated))
|
2016-06-03 10:13:43 +00:00
|
|
|
else:
|
|
|
|
query.named_tokens.append(
|
2020-06-05 22:03:37 +00:00
|
|
|
_parse_named(key, value, negated)
|
|
|
|
)
|
2016-06-03 10:13:43 +00:00
|
|
|
else:
|
2020-06-05 22:03:37 +00:00
|
|
|
query.anonymous_tokens.append(_parse_anonymous(chunk, negated))
|
2016-06-03 10:13:43 +00:00
|
|
|
return query
|