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

This commit is contained in:
Griatch 2022-10-29 18:28:30 +02:00
parent c741b2b898
commit b684194795
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. argument list.
```python ```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. You can escape in various ways.
- Prepending with the escape character `\` - Prepending special characters like `,` and `=` with the escape character `\`
```python ```python
"There is a $format(beautiful meadow\, with dandelions) to the west." "The $format(forest's smallest meadow\, with dandelions) is 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 - Wrapping your strings in double quotes. Unlike in raw Python, you
"There is a $format('beautiful meadow, with dandelions') to the west." 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
- If you want verbatim quotes in your string, you can escape them too. everything but the outermost double quotes.
```python ```python
"There is a $format('beautiful meadow, with \'dandelions\'') to the west." '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 ### Safe convertion of inputs

View file

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

View file

@ -134,8 +134,8 @@ class TestFuncParser(TestCase):
("$foo() Test noargs5", "_test() Test noargs5"), ("$foo() Test noargs5", "_test() Test noargs5"),
("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"), ("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"),
("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"), ("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)"), (r'Test args3 $bar(foo, bar, " too")', "Test args3 _test(foo, bar, too)"),
("Test args4 $foo('')", "Test args4 _test()"), ("Test args4 $foo('')", "Test args4 _test('')"), # ' treated as literal
('Test args4 $foo("")', "Test args4 _test()"), ('Test args4 $foo("")', "Test args4 _test()"),
("Test args5 $foo(\(\))", "Test args5 _test(())"), ("Test args5 $foo(\(\))", "Test args5 _test(())"),
("Test args6 $foo(\()", "Test args6 _test(()"), ("Test args6 $foo(\()", "Test args6 _test(()"),
@ -143,16 +143,16 @@ class TestFuncParser(TestCase):
("Test args8 $foo())", "Test args8 _test())"), ("Test args8 $foo())", "Test args8 _test())"),
("Test args9 $foo(=)", "Test args9 _test(=)"), ("Test args9 $foo(=)", "Test args9 _test(=)"),
("Test args10 $foo(\,)", "Test args10 _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 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 kwarg1 _test(foo=1, bar=foo, too=ere)",
), ),
("Test kwarg2 $bar(foo,bar,too=ere)", "Test kwarg2 _test(foo, bar, 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)"), ("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)", "test kwarg4 _test(foo=' bar ', \" bar \"=ere)",
), ),
( (
@ -202,7 +202,7 @@ class TestFuncParser(TestCase):
("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"), ("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"),
("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"), ("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"),
("Test eval3 $eval(\"'21' + 'foo' + 'bar'\")", "Test eval3 21foobar"), ("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))", r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))",
"Test eval5 21$repl()5", "Test eval5 21$repl()5",
@ -519,10 +519,10 @@ class TestDefaultCallables(TestCase):
("There is $an(thing) here", "There is a thing here"), ("There is $an(thing) here", "There is a thing here"),
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"), ("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
('$crop("spider\'s silk", 5)', "spide"), ('$crop("spider\'s silk", 5)', "spide"),
("$crop(spider's silk, 5)", "spide"),
("$an(apple)", "an apple"), ("$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.9) apples", "3.0 apples"), ("$round(2.967, 1) apples", "3.0 apples"),
# ("$round(2.967, 1) apples", "3.0 apples"),
# Degenerate cases # Degenerate cases
("$int2str() apples", " apples"), ("$int2str() apples", " apples"),
("$int2str(x) apples", "x apples"), ("$int2str(x) apples", "x apples"),
@ -616,6 +616,24 @@ class TestDefaultCallables(TestCase):
ret = self.parser.parse_to_any(string) ret = self.parser.parse_to_any(string)
self.assertIn(ret, (1, 2)) 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): def test_randint(self):
string = "$randint(1.0, 3.0)" string = "$randint(1.0, 3.0)"
ret = self.parser.parse_to_any(string, raise_errors=True) ret = self.parser.parse_to_any(string, raise_errors=True)
@ -649,16 +667,6 @@ class TestDefaultCallables(TestCase):
) )
def test_escaped(self): 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 = ( raw_str = (
'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5)' 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5)'
" text., 80)" " text., 80)"