Changed parser to escape properly using alternating " or '

This commit is contained in:
ChrisLR 2022-06-06 16:23:36 -04:00
parent 0c62046224
commit f47beb6751
2 changed files with 72 additions and 35 deletions

View file

@ -84,8 +84,8 @@ class _ParsedFunc:
# state storage # state storage
fullstr: str = "" fullstr: str = ""
infuncstr: str = "" infuncstr: str = ""
single_quoted: bool = False single_quoted: int = -1
double_quoted: bool = False double_quoted: int = -1
current_kwarg: str = "" current_kwarg: str = ""
open_lparens: int = 0 open_lparens: int = 0
open_lsquate: int = 0 open_lsquate: int = 0
@ -318,8 +318,8 @@ class FuncParser:
# parsing state # parsing state
callstack = [] callstack = []
single_quoted = False single_quoted = -1
double_quoted = False double_quoted = -1
open_lparens = 0 # open ( open_lparens = 0 # open (
open_lsquare = 0 # open [ open_lsquare = 0 # open [
open_lcurly = 0 # open { open_lcurly = 0 # open {
@ -330,6 +330,7 @@ class FuncParser:
curr_func = None curr_func = None
fullstr = "" # final string fullstr = "" # final string
infuncstr = "" # string parts inside the current level of $funcdef (including $) infuncstr = "" # string parts inside the current level of $funcdef (including $)
literal_infuncstr = False
for char in string: for char in string:
@ -373,12 +374,13 @@ class FuncParser:
curr_func.open_lcurly = open_lcurly curr_func.open_lcurly = open_lcurly
current_kwarg = "" current_kwarg = ""
infuncstr = "" infuncstr = ""
single_quoted = False single_quoted = -1
double_quoted = False double_quoted = -1
open_lparens = 0 open_lparens = 0
open_lsquare = 0 open_lsquare = 0
open_lcurly = 0 open_lcurly = 0
exec_return = "" exec_return = ""
literal_infuncstr = False
callstack.append(curr_func) callstack.append(curr_func)
# start a new func # start a new func
@ -401,19 +403,41 @@ class FuncParser:
infuncstr += str(exec_return) infuncstr += str(exec_return)
exec_return = "" exec_return = ""
if char == "'": # note that this is the same as "\'" if char == "'" and double_quoted < 0: # note that this is the same as "\'"
# a single quote - flip status # a single quote - flip status
single_quoted = not single_quoted 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 += char
infuncstr = infuncstr.strip()
single_quoted = len(infuncstr) - 1
literal_infuncstr = True
continue continue
if char == '"': # note that this is the same as '\"' if char == '"' and single_quoted < 0: # note that this is the same as '\"'
# a double quote = flip status # a double quote = flip status
double_quoted = not double_quoted if double_quoted == 0:
infuncstr = infuncstr[1:]
double_quoted = -1
elif double_quoted > 0:
prefix = infuncstr[0:double_quoted]
infuncstr = prefix + infuncstr[double_quoted + 1:]
double_quoted = -1
else:
infuncstr += char infuncstr += char
infuncstr = infuncstr.strip()
double_quoted = len(infuncstr) - 1
literal_infuncstr = True
continue continue
if double_quoted or single_quoted: if double_quoted >= 0 or single_quoted >= 0:
# inside a string definition - this escapes everything else # inside a string definition - this escapes everything else
infuncstr += char infuncstr += char
continue continue
@ -477,12 +501,15 @@ class FuncParser:
else: else:
curr_func.args.append(exec_return) curr_func.args.append(exec_return)
else: else:
if not literal_infuncstr:
infuncstr = infuncstr.strip()
# store a string instead # store a string instead
if current_kwarg: if current_kwarg:
curr_func.kwargs[current_kwarg] = infuncstr.strip() curr_func.kwargs[current_kwarg] = infuncstr
elif infuncstr.strip(): elif literal_infuncstr or infuncstr.strip():
# don't store the empty string # don't store the empty string
curr_func.args.append(infuncstr.strip()) curr_func.args.append(infuncstr)
# note that at this point either exec_return or infuncstr will # note that at this point either exec_return or infuncstr will
# be empty. We need to store the full string so we can print # be empty. We need to store the full string so we can print
@ -493,6 +520,7 @@ class FuncParser:
current_kwarg = "" current_kwarg = ""
exec_return = "" exec_return = ""
infuncstr = "" infuncstr = ""
literal_infuncstr = False
if char == ")": if char == ")":
# closing the function list - this means we have a # closing the function list - this means we have a
@ -536,6 +564,7 @@ class FuncParser:
if return_str: if return_str:
exec_return = "" exec_return = ""
infuncstr = "" infuncstr = ""
literal_infuncstr = False
continue continue
infuncstr += char infuncstr += char

View file

@ -44,6 +44,7 @@ def _double_callable(*args, **kwargs):
def _eval_callable(*args, **kwargs): def _eval_callable(*args, **kwargs):
if args: if args:
return simple_eval(args[0]) return simple_eval(args[0])
return "" return ""
@ -113,25 +114,25 @@ 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)"),
("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()"),
('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(()"),
("Test args7 $foo(())", "Test args7 _test(())"), ("Test args7 $foo(())", "Test args7 _test(())"),
("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(',')"), ("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)", "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)"),
( (
"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)",
), ),
( (
@ -180,22 +181,24 @@ class TestFuncParser(TestCase):
("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"), ("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"),
("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"),
("Test eval4 $eval('21' + '$repl()' + '' + str(10 // 2))", "Test eval4 21rr5"), (r"Test eval4 $eval(\'21\' + \'$repl()\' + \"''\" + str(10 // 2))", "Test eval4 21rr5"),
("Test eval5 $eval('21' + '\$repl()' + '' + str(10 // 2))", "Test eval5 21$repl()5"), (r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))", "Test eval5 21$repl()5"),
("Test eval6 $eval('$repl(a)' + '$repl(b)')", "Test eval6 rarrbr"), ("Test eval6 $eval(\"'$repl(a)' + '$repl(b)'\")", "Test eval6 rarrbr"),
("Test type1 $typ([1,2,3,4])", "Test type1 <class 'list'>"), ("Test type1 $typ([1,2,3,4])", "Test type1 <class 'list'>"),
("Test type2 $typ((1,2,3,4))", "Test type2 <class 'tuple'>"), ("Test type2 $typ((1,2,3,4))", "Test type2 <class 'tuple'>"),
("Test type3 $typ({1,2,3,4})", "Test type3 <class 'set'>"), ("Test type3 $typ({1,2,3,4})", "Test type3 <class 'set'>"),
("Test type4 $typ({1:2,3:4})", "Test type4 <class 'dict'>"), ("Test type4 $typ({1:2,3:4})", "Test type4 <class 'dict'>"),
("Test type5 $typ(1), $typ(1.0)", "Test type5 <class 'int'>, <class 'float'>"), ("Test type5 $typ(1), $typ(1.0)", "Test type5 <class 'int'>, <class 'float'>"),
("Test type6 $typ('1'), $typ(\"1.0\")", "Test type6 <class 'str'>, <class 'str'>"), ("Test type6 $typ(\"'1'\"), $typ('\"1.0\"')", "Test type6 <class 'str'>, <class 'str'>"),
("Test add1 $add(1, 2)", "Test add1 3"), ("Test add1 $add(1, 2)", "Test add1 3"),
("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"), ("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"),
("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"), ("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"),
("Test literal2 $typ($lit(1))", "Test literal2 <class 'int'>"), ("Test literal2 $typ($lit(1))", "Test literal2 <class 'int'>"),
("Test literal3 $typ($lit(1)aaa)", "Test literal3 <class 'str'>"), ("Test literal3 $typ($lit(1)aaa)", "Test literal3 <class 'str'>"),
("Test literal4 $typ(aaa$lit(1))", "Test literal4 <class 'str'>"), ("Test literal4 $typ(aaa$lit(1))", "Test literal4 <class 'str'>"),
("Test spider's thread", "Test spider's thread"),
] ]
) )
def test_parse(self, string, expected): def test_parse(self, string, expected):
@ -258,7 +261,11 @@ class TestFuncParser(TestCase):
self.assertEqual([1, 2, 3, 4], ret) self.assertEqual([1, 2, 3, 4], ret)
self.assertTrue(isinstance(ret, list)) self.assertTrue(isinstance(ret, list))
ret = self.parser.parse_to_any("$lit('')") ret = self.parser.parse_to_any("$lit(\"''\")")
self.assertEqual("", ret)
self.assertTrue(isinstance(ret, str))
ret = self.parser.parse_to_any(r"$lit(\'\')")
self.assertEqual("", ret) self.assertEqual("", ret)
self.assertTrue(isinstance(ret, str)) self.assertTrue(isinstance(ret, str))
@ -390,7 +397,8 @@ class TestDefaultCallables(TestCase):
("Some $rjust(Hello, 30)", "Some Hello"), ("Some $rjust(Hello, 30)", "Some Hello"),
("Some $rjust(Hello, width=30)", "Some Hello"), ("Some $rjust(Hello, width=30)", "Some Hello"),
("Some $cjust(Hello, 30)", "Some Hello "), ("Some $cjust(Hello, 30)", "Some Hello "),
("Some $eval('-'*20)Hello", "Some --------------------Hello"), ("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
("$crop(\"spider's silk\", 5)", "spide"),
] ]
) )
def test_other_callables(self, string, expected): def test_other_callables(self, string, expected):
@ -455,18 +463,18 @@ class TestDefaultCallables(TestCase):
self.parser.parse( self.parser.parse(
"this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)" "this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)"
), ),
"this should be '''escaped,''' and '''instead,''' cropped with text. ", "this should be escaped, and instead, cropped with text. ",
) )
def test_escaped2(self): def test_escaped2(self):
raw_str = 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'
expected = 'this should be escaped, and instead, cropped with text. '
result = self.parser.parse(raw_str)
self.assertEqual( self.assertEqual(
self.parser.parse( result,
'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)' expected,
),
'this should be """escaped,""" and """instead,""" cropped with text. ',
) )
class TestCallableSearch(test_resources.BaseEvenniaTest): class TestCallableSearch(test_resources.BaseEvenniaTest):
""" """
Test the $search(query) callable Test the $search(query) callable