Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ parenthesis. DjangoQL is case-sensitive.

- model fields: exactly as they are defined in Python code. Access
nested properties via ``.``, for example ``author.last_name``;
- strings must be double-quoted. Single quotes are not supported.
To escape a double quote use ``\"``;
- strings can be enclosed in either double quotes or single quotes.
To escape a quote, use ``\"`` for double quotes or ``\'`` for single quotes.
You can also use single quotes to enclose strings containing double quotes,
and vice versa;
- boolean and null values: ``True``, ``False``, ``None``. Please note
that they can be combined only with equality operators, so you can
write ``published = False or date_published = None``, but
Expand Down
10 changes: 7 additions & 3 deletions djangoql/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ def find_column(self, t):

re_line_terminators = r'\n\r\u2028\u2029'

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

re_int_value = r'(-?0|-?[1-9][0-9]*)'
re_fraction_part = r'\.[0-9]+'
Expand Down Expand Up @@ -106,7 +107,10 @@ def find_column(self, t):

@TOKEN(r'\"(' + re_escaped_char +
'|' + re_escaped_unicode +
'|' + re_string_char + r')*\"')
'|' + re_string_char_double + r')*\"|' +
r'\'(' + re_escaped_char +
'|' + re_escaped_unicode +
'|' + re_string_char_single + r')*\'')
def t_STRING_VALUE(self, t):
t.value = t.value[1:-1] # cut leading and trailing quotes ""
return t
Expand Down
27 changes: 27 additions & 0 deletions test_project/core/tests/test_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,33 @@ def test_string(self):
[('STRING_VALUE', s.strip('"'))],
)

def test_string_single_quotes(self):
for s in ("''", u"''", "'42'", r"'\t\n\u0042 ^'"):
self.assert_output(
self.lexer.input(s),
[('STRING_VALUE', s.strip("'"))],
)

def test_string_mixed_quotes(self):
self.assert_output(
self.lexer.input("""'He said "hello"'"""),
[('STRING_VALUE', 'He said "hello"')],
)
self.assert_output(
self.lexer.input('''"It's working"'''),
[('STRING_VALUE', "It's working")],
)

def test_string_escaped_quotes(self):
self.assert_output(
self.lexer.input(r"'It\'s working'"),
[('STRING_VALUE', r"It\'s working")],
)
self.assert_output(
self.lexer.input(r'"He said \"hello\""'),
[('STRING_VALUE', r'He said \"hello\"')],
)

def test_illegal_chars(self):
for s in ('"', '^'):
try:
Expand Down
25 changes: 25 additions & 0 deletions test_project/core/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,31 @@ def test_escaped_chars(self):
self.parser.parse(u'options = "\\u041f \\u0438 \\u0429"'),
)

def test_single_quoted_strings(self):
self.assertEqual(
Expression(Name('gender'), Comparison('='), Const('female')),
self.parser.parse("gender = 'female'"),
)
self.assertEqual(
Expression(Name('name'), Comparison('~'), Const('He said "hello"')),
self.parser.parse("""name ~ 'He said "hello"'"""),
)
self.assertEqual(
Expression(Name('name'), Comparison('~'), Const("It's working")),
self.parser.parse('''name ~ "It's working"'''),
)

def test_escaped_chars_single_quotes(self):
self.assertEqual(
Expression(Name('name'), Comparison('~'),
Const(u"It's working")),
self.parser.parse(u"name ~ 'It\\'s working'"),
)
self.assertEqual(
Expression(Name('options'), Comparison('='), Const(u'П и Щ')),
self.parser.parse(u"options = '\\u041f \\u0438 \\u0429'"),
)

def test_numbers(self):
self.assertEqual(
Expression(Name('pk'), Comparison('>'), Const(5)),
Expand Down
Loading