Skip to content

Commit 4e95317

Browse files
author
Bodyhealer
committed
add full support for regular expressions
1 parent 3e74881 commit 4e95317

File tree

9 files changed

+33
-10
lines changed

9 files changed

+33
-10
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ parenthesis. DjangoQL is case-sensitive.
133133
- comparison operators: ``=``, ``!=``, ``<``, ``<=``, ``>``, ``>=``
134134
- work as you expect. ``~`` and ``!~`` - test whether or not a string contains
135135
a substring (translated into ``__icontains``);
136+
- ``regex`` - find based on regular expression (translated into ``__iregex``);
136137
- test a value vs. list: ``in``, ``not in``. Example:
137138
``pk in (2, 3)``.
138139

djangoql/lexer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def find_column(self, t):
5555

5656
re_escaped_char = r'\\[\"\\/bfnrt]'
5757
re_escaped_unicode = r'\\u[0-9A-Fa-f]{4}'
58-
re_string_char = r'[^\"\\' + re_line_terminators + u']'
58+
re_string_char = r'[^\"' + re_line_terminators + u']'
5959

6060
re_int_value = r'(-?0|-?[1-9][0-9]*)'
6161
re_fraction_part = r'\.[0-9]+'
@@ -84,6 +84,7 @@ def find_column(self, t):
8484
'LESS_EQUAL',
8585
'CONTAINS',
8686
'NOT_CONTAINS',
87+
'REGEX',
8788
]
8889

8990
t_COMMA = ','
@@ -137,6 +138,10 @@ def t_NOT(self, t):
137138
def t_IN(self, t):
138139
return t
139140

141+
@TOKEN('regex' + not_followed_by_name)
142+
def t_REGEX(self, t):
143+
return t
144+
140145
@TOKEN('True' + not_followed_by_name)
141146
def t_TRUE(self, t):
142147
return t

djangoql/parser.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def p_comparison_string(self, p):
9393
comparison_string : comparison_equality
9494
| comparison_greater_less
9595
| comparison_contains
96+
| comparison_regex
9697
"""
9798
p[0] = p[1]
9899

@@ -119,6 +120,12 @@ def p_comparison_contains(self, p):
119120
"""
120121
p[0] = Comparison(operator=p[1])
121122

123+
def p_comparison_regex(self, p):
124+
"""
125+
comparison_regex : REGEX
126+
"""
127+
p[0] = Comparison(operator=p[1])
128+
122129
def p_comparison_in_list(self, p):
123130
"""
124131
comparison_in_list : IN

djangoql/schema.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def get_operator(self, operator):
101101
'<': '__lt',
102102
'<=': '__lte',
103103
'~': '__icontains',
104+
'regex': '__iregex',
104105
'in': '__in',
105106
}.get(operator)
106107
if op is not None:
@@ -124,11 +125,12 @@ def get_lookup(self, path, operator, value):
124125
be ['author', 'groups']. 'name' is not included, because it's the
125126
current field instance itself.
126127
:param operator: a string with comparison operator. It could be one of
127-
the following: '=', '!=', '>', '>=', '<', '<=', '~', '!~', 'in',
128-
'not in'. Depending on the field type, some operators may be
129-
excluded. '~' and '!~' can be applied to StrField only and aren't
130-
allowed for any other fields. BoolField can't be used with less or
131-
greater operators, '>', '>=', '<' and '<=' are excluded for it.
128+
the following: '=', '!=', '>', '>=', '<', '<=', '~', '!~', 'regex',
129+
'in', 'not in'. Depending on the field type, some operators
130+
may be excluded. '~' and '!~' can be applied to StrField only and
131+
aren't allowed for any other fields. BoolField can't be used with
132+
less or greater operators, '>', '>=', '<' and '<=' are excluded
133+
for it.
132134
:param value: value passed for comparison
133135
:return: Q-object
134136
"""
@@ -277,7 +279,7 @@ def get_lookup(self, path, operator, value):
277279
# and resulting comparison would look like
278280
# 'created LIKE %2017-01-30 00:00:00%'
279281
# which is not what we want for this case.
280-
val = value if operator in ('~', '!~') else self.get_lookup_value(value)
282+
val = value if operator in ('~', '!~', 'regex') else self.get_lookup_value(value)
281283

282284
q = models.Q(**{'%s%s' % (search, op): val})
283285
return ~q if invert else q

djangoql/static/djangoql/js/completion.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

djangoql/static/djangoql/js/completion.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

djangoql/templates/djangoql/syntax_help.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ <h2 id="comparison-operators">Comparison operators</h2>
242242
<td>does not contain a substring</td>
243243
<td>username !~ "test"</td>
244244
</tr>
245+
<tr>
246+
<td>regex</td>
247+
<td>find based on regular expression</td>
248+
<td>email regex "\w+@\w+\.com"</td>
249+
</tr>
245250
<tr>
246251
<td>&gt;</td>
247252
<td>greater</td>

test_project/core/tests/test_lexer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def test_entity_props(self):
5353
pass
5454

5555
def test_reserved_words(self):
56-
reserved = ('True', 'False', 'None', 'or', 'and', 'in')
56+
reserved = ('True', 'False', 'None', 'or', 'and', 'in', 'regex')
5757
for word in reserved:
5858
self.assert_output(self.lexer.input(word), [(word.upper(), word)])
5959
# A word made of reserved words should be treated as a name

test_project/core/tests/test_schema.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ def test_invalid_config(self):
142142
def test_validation_pass(self):
143143
samples = [
144144
'first_name = "Lolita"',
145+
'first_name regex "^Lol"',
146+
'first_name regex "ita$"',
147+
r'first_name regex "\w+i\w+a"',
145148
'groups.id < 42',
146149
'groups = None', # that's ok to compare a model to None
147150
'groups != None',

0 commit comments

Comments
 (0)