diff --git a/API.md b/API.md
index abdc8425..79c1e6eb 100644
--- a/API.md
+++ b/API.md
@@ -2258,6 +2258,9 @@ Date/time values can be of following form:
Some fields, such as user names, can take wildcards (`*`).
+You can escape special characters such as `:` and `-` by prepending them with a
+backslash: `\\`.
+
**Example**
Searching for posts with following query:
@@ -2266,3 +2269,8 @@ Searching for posts with following query:
will show flash files tagged as sea, that were liked by seven people at most,
uploaded by user Pirate.
+
+Searching for posts with `re:zero` will show an error message about unknown
+named token.
+
+Searching for posts with `re\:zero` will show posts tagged with `re:zero`.
diff --git a/client/html/help_search_general.tpl b/client/html/help_search_general.tpl
index a7b05d83..6a5cfd58 100644
--- a/client/html/help_search_general.tpl
+++ b/client/html/help_search_general.tpl
@@ -80,6 +80,9 @@ take following form:
,desc
to control the sort direction, which can be also controlled
by negating the whole token.
+You can escape special characters such as :
and -
+by prepending them with a backslash: \\
.
+
Example
Searching for posts with following query:
@@ -89,3 +92,8 @@ by negating the whole token.
will show flash files tagged as sea, that were liked by seven people at
most, uploaded by user Pirate.
+Searching for posts with re:zero
will show an error message
+about unknown named token.
+
+Searching for posts with re\:zero
will show posts tagged with
+re:zero
.
diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py
index 34e86537..1dd85149 100644
--- a/server/szurubooru/search/configs/post_search_config.py
+++ b/server/szurubooru/search/configs/post_search_config.py
@@ -10,15 +10,6 @@ from szurubooru.search.configs.base_search_config import (
BaseSearchConfig, Filter)
-def _enum_transformer(available_values: Dict[str, Any], value: str) -> str:
- try:
- return available_values[value.lower()]
- except KeyError:
- raise errors.SearchError(
- 'Invalid value: %r. Possible values: %r.' % (
- value, list(sorted(available_values.keys()))))
-
-
def _type_transformer(value: str) -> str:
available_values = {
'image': model.Post.TYPE_IMAGE,
@@ -31,7 +22,7 @@ def _type_transformer(value: str) -> str:
'flash': model.Post.TYPE_FLASH,
'swf': model.Post.TYPE_FLASH,
}
- return _enum_transformer(available_values, value)
+ return search_util.enum_transformer(available_values, value)
def _safety_transformer(value: str) -> str:
@@ -41,7 +32,7 @@ def _safety_transformer(value: str) -> str:
'questionable': model.Post.SAFETY_SKETCHY,
'unsafe': model.Post.SAFETY_UNSAFE,
}
- return _enum_transformer(available_values, value)
+ return search_util.enum_transformer(available_values, value)
def _create_score_filter(score: int) -> Filter:
diff --git a/server/szurubooru/search/configs/util.py b/server/szurubooru/search/configs/util.py
index c6b9b783..ac4bd8ee 100644
--- a/server/szurubooru/search/configs/util.py
+++ b/server/szurubooru/search/configs/util.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional, Union, Callable
+from typing import Any, Optional, Union, Dict, Callable
import sqlalchemy as sa
from szurubooru import db, errors
from szurubooru.func import util
@@ -8,27 +8,62 @@ from szurubooru.search.configs.base_search_config import Filter
Number = Union[int, float]
+WILDCARD = '(--wildcard--)' # something unlikely to be used by the users
+
+
+def unescape(text: str, make_wildcards_special: bool = False) -> str:
+ output = ''
+ i = 0
+ while i < len(text):
+ if text[i] == '\\':
+ try:
+ char = text[i+1]
+ i += 1
+ except IndexError:
+ raise errors.SearchError(
+ 'Unterminated escape sequence (did you forget to escape '
+ 'the ending backslash?)')
+ if char not in '*\\:-.,':
+ raise errors.SearchError(
+ 'Unknown escape sequence (did you forget to escape '
+ 'the backslash?)')
+ elif text[i] == '*' and make_wildcards_special:
+ char = WILDCARD
+ else:
+ char = text[i]
+ output += char
+ i += 1
+ return output
def wildcard_transformer(value: str) -> str:
return (
- value
+ unescape(value, make_wildcards_special=True)
.replace('\\', '\\\\')
.replace('%', '\\%')
.replace('_', '\\_')
- .replace('*', '%'))
+ .replace(WILDCARD, '%'))
+
+
+def enum_transformer(available_values: Dict[str, Any], value: str) -> str:
+ try:
+ return available_values[unescape(value.lower())]
+ except KeyError:
+ raise errors.SearchError(
+ 'Invalid value: %r. Possible values: %r.' % (
+ value, list(sorted(available_values.keys()))))
def integer_transformer(value: str) -> int:
- return int(value)
+ return int(unescape(value))
def float_transformer(value: str) -> float:
for sep in list('/:'):
if sep in value:
a, b = value.split(sep, 1)
- return float(a) / float(b)
- return float(value)
+ return float(unescape(a)) / float(unescape(b))
+ return float(unescape(value))
def apply_num_criterion_to_column(
@@ -84,23 +119,23 @@ def apply_str_criterion_to_column(
for value in criterion.values:
expr = expr | column.ilike(transformer(value))
elif isinstance(criterion, criteria.RangedCriterion):
- expr = column.ilike(transformer(criterion.original_text))
+ raise errors.SearchError(
+ 'Ranged criterion is invalid in this context. '
+ 'Did you forget to escape the dots?')
else:
assert False
return expr
def create_str_filter(
- column: SaColumn,
- transformer: Callable[[str], str]=wildcard_transformer
+ column: SaColumn, transformer: Callable[[str], str]=wildcard_transformer
) -> Filter:
def wrapper(
query: SaQuery,
criterion: Optional[criteria.BaseCriterion],
negated: bool) -> SaQuery:
assert criterion
- expr = apply_str_criterion_to_column(
- column, criterion, transformer)
+ expr = apply_str_criterion_to_column(column, criterion, transformer)
if negated:
expr = ~expr
return query.filter(expr)
diff --git a/server/szurubooru/search/parser.py b/server/szurubooru/search/parser.py
index 6369bc6e..50d8fb0e 100644
--- a/server/szurubooru/search/parser.py
+++ b/server/szurubooru/search/parser.py
@@ -1,17 +1,20 @@
import re
-from typing import List
+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 ',' in value:
- return criteria.ArrayCriterion(
- original_value, value.split(','))
- if '..' in value:
- low, high = value.split('..', 1)
+ if re.search(r'(?