import re from szurubooru import errors from szurubooru.search import criteria, tokens def _create_criterion(original_value, value): if '..' in value: low, high = value.split('..', 1) if not low and not high: raise errors.SearchError('Empty ranged value') return criteria.RangedCriterion(original_value, low, high) if ',' in value: return criteria.ArrayCriterion( original_value, value.split(',')) return criteria.PlainCriterion(original_value, value) def _parse_anonymous(value, negated): criterion = _create_criterion(value, value) return tokens.AnonymousToken(criterion, negated) def _parse_named(key, value, negated): 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, negated): return tokens.SpecialToken(value, negated) def _parse_sort(value, negated): if value.count(',') == 0: direction_str = None elif value.count(',') == 1: value, direction_str = value.split(',') else: raise errors.SearchError('Too many commas in sort style token.') try: direction = { 'asc': tokens.SortToken.SORT_ASC, 'desc': tokens.SortToken.SORT_DESC, '': tokens.SortToken.SORT_DEFAULT, None: tokens.SortToken.SORT_DEFAULT, }[direction_str] except KeyError: raise errors.SearchError( 'Unknown search direction: %r.' % direction_str) if negated: direction = { 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, }[direction] return tokens.SortToken(value, direction) class SearchQuery(): def __init__(self): self.anonymous_tokens = [] self.named_tokens = [] self.special_tokens = [] self.sort_tokens = [] def __hash__(self): return hash(( tuple(self.anonymous_tokens), tuple(self.named_tokens), tuple(self.special_tokens), tuple(self.sort_tokens))) class Parser(object): def parse(self, query_text): query = SearchQuery() for chunk in re.split(r'\s+', (query_text or '').lower()): if not chunk: continue negated = False while chunk[0] == '-': chunk = chunk[1:] negated = not negated if ':' in chunk and chunk[0] != ':': key, value = chunk.split(':', 2) 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