From 6a0df97c1669897babbdaee47cbb99db24d3789a Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 29 Oct 2022 18:28:30 +0200 Subject: [PATCH] Stop escape ' in funcparser; req double quotes. Resolve #2737. --- docs/source/Components/FuncParser.md | 33 +++++++++--------- evennia/utils/funcparser.py | 26 ++------------- evennia/utils/tests/test_funcparser.py | 46 +++++++++++++++----------- 3 files changed, 47 insertions(+), 58 deletions(-) diff --git a/docs/source/Components/FuncParser.md b/docs/source/Components/FuncParser.md index 3bc8260bd2..eddba3fe5a 100644 --- a/docs/source/Components/FuncParser.md +++ b/docs/source/Components/FuncParser.md @@ -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 diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index f401ed40ce..911b6c39d1 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -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 diff --git a/evennia/utils/tests/test_funcparser.py b/evennia/utils/tests/test_funcparser.py index 713b8ebb2b..c276fb20da 100644 --- a/evennia/utils/tests/test_funcparser.py +++ b/evennia/utils/tests/test_funcparser.py @@ -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)"