gallery.accords-library.com/server/szurubooru/search/parser.py

105 lines
3.7 KiB
Python
Raw Normal View History

import re
from typing import Match, List
from szurubooru import errors
from szurubooru.search import criteria, tokens
from szurubooru.search.query import SearchQuery
from szurubooru.search.configs import util
def _create_criterion(
original_value: str, value: str) -> criteria.BaseCriterion:
if re.search(r'(?<!\\),', value):
values = re.split(r'(?<!\\),', value)
if any(not term.strip() for term in values):
raise errors.SearchError('Empty compound value')
return criteria.ArrayCriterion(original_value, values)
if re.search(r'(?<!\\)\.(?<!\\)\.', value):
low, high = re.split(r'(?<!\\)\.(?<!\\)\.', value, 1)
if not low and not high:
raise errors.SearchError('Empty ranged value')
return criteria.RangedCriterion(original_value, low, high)
return criteria.PlainCriterion(original_value, value)
def _parse_anonymous(value: str, negated: bool) -> tokens.AnonymousToken:
criterion = _create_criterion(value, value)
return tokens.AnonymousToken(criterion, negated)
def _parse_named(key: str, value: str, negated: bool) -> tokens.NamedToken:
original_value = value
if key.endswith('-min'):
key = key[:-4]
value += '..'
elif key.endswith('-max'):
key = key[:-4]
value = '..' + value
criterion = _create_criterion(original_value, value)
return tokens.NamedToken(key, criterion, negated)
def _parse_special(value: str, negated: bool) -> tokens.SpecialToken:
return tokens.SpecialToken(value, negated)
def _parse_sort(value: str, negated: bool) -> tokens.SortToken:
if value.count(',') == 0:
order_str = None
elif value.count(',') == 1:
value, order_str = value.split(',')
else:
raise errors.SearchError('Too many commas in sort style token.')
try:
order = {
'asc': tokens.SortToken.SORT_ASC,
'desc': tokens.SortToken.SORT_DESC,
'': tokens.SortToken.SORT_DEFAULT,
None: tokens.SortToken.SORT_DEFAULT,
}[order_str]
except KeyError:
raise errors.SearchError(
'Unknown search direction: %r.' % order_str)
if negated:
order = {
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,
tokens.SortToken.SORT_NEGATED_DEFAULT:
tokens.SortToken.SORT_DEFAULT,
}[order]
return tokens.SortToken(value, order)
class Parser:
def parse(self, query_text: str) -> SearchQuery:
query = SearchQuery()
for chunk in re.split(r'\s+', (query_text or '').lower()):
if not chunk:
continue
negated = False
if chunk[0] == '-':
chunk = chunk[1:]
negated = True
if not chunk:
raise errors.SearchError('Empty negated token.')
match = re.match(r'^(.*?)(?<!\\):(.*)$', chunk)
if match:
key, value = list(match.groups())
key = util.unescape(key)
if key == 'sort':
query.sort_tokens.append(
_parse_sort(value, negated))
elif key == 'special':
query.special_tokens.append(
_parse_special(value, negated))
else:
query.named_tokens.append(
_parse_named(key, value, negated))
else:
query.anonymous_tokens.append(
_parse_anonymous(chunk, negated))
return query