Stop escape ' in funcparser; req double quotes. Resolve #2737.

This commit is contained in:
Griatch 2022-10-29 18:28:30 +02:00
parent a92214354b
commit 6a0df97c16
3 changed files with 47 additions and 58 deletions

View file

@ -213,28 +213,31 @@ Sometimes you want to include commas in the argument without it breaking the
argument list.
```python
"There is a $format(beautiful meadow, with dandelions) to the west."
"The $format(forest's smallest meadow, with dandelions) is to the west."
```
You can escape in various ways.
- Prepending with the escape character `\`
- Prepending special characters like `,` and `=` with the escape character `\`
```python
"There is a $format(beautiful meadow\, with dandelions) to the west."
```
- Wrapping your strings in quotes. This works like Python, and you can nest
double and single quotes inside each other if so needed. The result will
be a verbatim string that contains everything but the outermost quotes.
```python
"The $format(forest's smallest meadow\, with dandelions) is to the west."
```
```python
"There is a $format('beautiful meadow, with dandelions') to the west."
```
- If you want verbatim quotes in your string, you can escape them too.
- Wrapping your strings in double quotes. Unlike in raw Python, you
can't escape with single quotes `'` since these could also be apostrophes (like
`forest's` above). The result will be a verbatim string that contains
everything but the outermost double quotes.
```python
"There is a $format('beautiful meadow, with \'dandelions\'') to the west."
```
```python
'The $format("forest's smallest meadow, with dandelions") is to the west.'
```
- If you want verbatim double-quotes to appear in your string, you can escape
them with `\"` in turn.
```python
'The $format("forest's smallest meadow, with \"dandelions\"') is to the west.'
```
### Safe convertion of inputs

View file

@ -85,7 +85,6 @@ class _ParsedFunc:
# state storage
fullstr: str = ""
infuncstr: str = ""
single_quoted: int = -1
double_quoted: int = -1
current_kwarg: str = ""
open_lparens: int = 0
@ -319,7 +318,6 @@ class FuncParser:
# parsing state
callstack = []
single_quoted = -1
double_quoted = -1
open_lparens = 0 # open (
open_lsquare = 0 # open [
@ -367,14 +365,12 @@ class FuncParser:
# store state for the current func and stack it
curr_func.current_kwarg = current_kwarg
curr_func.infuncstr = infuncstr
curr_func.single_quoted = single_quoted
curr_func.double_quoted = double_quoted
curr_func.open_lparens = open_lparens
curr_func.open_lsquare = open_lsquare
curr_func.open_lcurly = open_lcurly
current_kwarg = ""
infuncstr = ""
single_quoted = -1
double_quoted = -1
open_lparens = 0
open_lsquare = 0
@ -403,24 +399,7 @@ class FuncParser:
infuncstr += str(exec_return)
exec_return = ""
if char == "'" and double_quoted < 0: # note that this is the same as "\'"
# a single quote - flip status
if single_quoted == 0:
infuncstr = infuncstr[1:]
single_quoted = -1
elif single_quoted > 0:
prefix = infuncstr[0:single_quoted]
infuncstr = prefix + infuncstr[single_quoted + 1 :]
single_quoted = -1
else:
infuncstr += char
infuncstr = infuncstr.strip()
single_quoted = len(infuncstr) - 1
literal_infuncstr = True
continue
if char == '"' and single_quoted < 0: # note that this is the same as '\"'
if char == '"': # note that this is the same as '\"'
# a double quote = flip status
if double_quoted == 0:
infuncstr = infuncstr[1:]
@ -437,7 +416,7 @@ class FuncParser:
continue
if double_quoted >= 0 or single_quoted >= 0:
if double_quoted >= 0:
# inside a string definition - this escapes everything else
infuncstr += char
continue
@ -551,7 +530,6 @@ class FuncParser:
infuncstr = curr_func.infuncstr + str(exec_return)
exec_return = ""
curr_func.infuncstr = ""
single_quoted = curr_func.single_quoted
double_quoted = curr_func.double_quoted
open_lparens = curr_func.open_lparens
open_lsquare = curr_func.open_lsquare

View file

@ -134,8 +134,8 @@ class TestFuncParser(TestCase):
("$foo() Test noargs5", "_test() Test noargs5"),
("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"),
("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"),
(r"Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, too)"),
("Test args4 $foo('')", "Test args4 _test()"),
(r'Test args3 $bar(foo, bar, " too")', "Test args3 _test(foo, bar, too)"),
("Test args4 $foo('')", "Test args4 _test('')"), # ' treated as literal
('Test args4 $foo("")', "Test args4 _test()"),
("Test args5 $foo(\(\))", "Test args5 _test(())"),
("Test args6 $foo(\()", "Test args6 _test(()"),
@ -143,16 +143,16 @@ class TestFuncParser(TestCase):
("Test args8 $foo())", "Test args8 _test())"),
("Test args9 $foo(=)", "Test args9 _test(=)"),
("Test args10 $foo(\,)", "Test args10 _test(,)"),
("Test args10 $foo(',')", "Test args10 _test(,)"),
(r'Test args10 $foo(",")', "Test args10 _test(,)"),
("Test args11 $foo(()", "Test args11 $foo(()"), # invalid syntax
(
"Test kwarg1 $bar(foo=1, bar='foo', too=ere)",
r'Test kwarg1 $bar(foo=1, bar="foo", too=ere)',
"Test kwarg1 _test(foo=1, bar=foo, too=ere)",
),
("Test kwarg2 $bar(foo,bar,too=ere)", "Test kwarg2 _test(foo, bar, too=ere)"),
("test kwarg3 $foo(foo = bar, bar = ere )", "test kwarg3 _test(foo=bar, bar=ere)"),
(
r"test kwarg4 $foo(foo =\' bar \',\" bar \"= ere )",
r"test kwarg4 $foo(foo =' bar ',\" bar \"= ere )",
"test kwarg4 _test(foo=' bar ', \" bar \"=ere)",
),
(
@ -202,7 +202,7 @@ class TestFuncParser(TestCase):
("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"),
("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"),
("Test eval3 $eval(\"'21' + 'foo' + 'bar'\")", "Test eval3 21foobar"),
(r"Test eval4 $eval(\'21\' + \'$repl()\' + \"''\" + str(10 // 2))", "Test eval4 21rr5"),
(r"Test eval4 $eval('21' + '$repl()' + \"\" + str(10 // 2))", "Test eval4 21rr5"),
(
r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))",
"Test eval5 21$repl()5",
@ -519,10 +519,10 @@ class TestDefaultCallables(TestCase):
("There is $an(thing) here", "There is a thing here"),
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
('$crop("spider\'s silk", 5)', "spide"),
("$crop(spider's silk, 5)", "spide"),
("$an(apple)", "an apple"),
# These two are broken because of https://github.com/evennia/evennia/issues/2912
# ("$round(2.9) apples", "3.0 apples"),
# ("$round(2.967, 1) apples", "3.0 apples"),
("$round(2.9) apples", "3.0 apples"),
("$round(2.967, 1) apples", "3.0 apples"),
# Degenerate cases
("$int2str() apples", " apples"),
("$int2str(x) apples", "x apples"),
@ -616,6 +616,24 @@ class TestDefaultCallables(TestCase):
ret = self.parser.parse_to_any(string)
self.assertIn(ret, (1, 2))
def test_choice_quotes(self):
"""
Test choice, but also commas embedded.
"""
string = "$choice(spider's, devil's, mummy's, zombie's)"
ret = self.parser.parse(string)
self.assertIn(ret, ("spider's", "devil's", "mummy's", "zombie's"))
string = '$choice("Tiamat, queen of dragons", "Dracula, lord of the night")'
ret = self.parser.parse(string)
self.assertIn(ret, ("Tiamat, queen of dragons", "Dracula, lord of the night"))
# single quotes are ignored, so this becomes many entries
string = "$choice('Tiamat, queen of dragons', 'Dracula, lord of the night')"
ret = self.parser.parse(string)
self.assertIn(ret, ("'Tiamat", "queen of dragons'", "'Dracula", " lord of the night'"))
def test_randint(self):
string = "$randint(1.0, 3.0)"
ret = self.parser.parse_to_any(string, raise_errors=True)
@ -649,16 +667,6 @@ class TestDefaultCallables(TestCase):
)
def test_escaped(self):
self.assertEqual(
self.parser.parse(
"this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5)"
" text., 80)"
),
"this should be escaped, and instead, cropped with text. "
" ",
)
def test_escaped2(self):
raw_str = (
'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5)'
" text., 80)"