Add simple_parser dependency. Extend/test new funcparser
This commit is contained in:
parent
377a25f9e8
commit
f445f34356
3 changed files with 319 additions and 223 deletions
|
|
@ -56,14 +56,18 @@ class ParsedFunc:
|
||||||
args: list = dataclasses.field(default_factory=list)
|
args: list = dataclasses.field(default_factory=list)
|
||||||
kwargs: dict = dataclasses.field(default_factory=dict)
|
kwargs: dict = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
|
# state storage
|
||||||
|
fullstr: str = ""
|
||||||
|
infuncstr: str = ""
|
||||||
|
single_quoted: bool = False
|
||||||
|
double_quoted: bool = False
|
||||||
|
current_kwarg: str = ""
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
return self.funcname[len(self.prefix):], self.args, self.kwargs
|
return self.funcname, self.args, self.kwargs
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
argstr = ", ".join(str(arg) for arg in self.args)
|
return self.fullstr + self.infuncstr
|
||||||
kwargstr = ", " + ", ".join(
|
|
||||||
f"{key}={val}" for key, val in self.kwargs.items()) if self.kwargs else ""
|
|
||||||
return f"{self.prefix}{self.funcname}({argstr}{kwargstr})"
|
|
||||||
|
|
||||||
|
|
||||||
class ParsingError(RuntimeError):
|
class ParsingError(RuntimeError):
|
||||||
|
|
@ -84,7 +88,7 @@ class FuncParser:
|
||||||
start_char=_START_CHAR,
|
start_char=_START_CHAR,
|
||||||
escape_char=_ESCAPE_CHAR,
|
escape_char=_ESCAPE_CHAR,
|
||||||
max_nesting=_MAX_NESTING,
|
max_nesting=_MAX_NESTING,
|
||||||
**kwargs):
|
**default_kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize the parser.
|
Initialize the parser.
|
||||||
|
|
||||||
|
|
@ -104,15 +108,19 @@ class FuncParser:
|
||||||
them not count as a function. Default is `\\`.
|
them not count as a function. Default is `\\`.
|
||||||
max_nesting (int, optional): How many levels of nested function calls
|
max_nesting (int, optional): How many levels of nested function calls
|
||||||
are allowed, to avoid exploitation.
|
are allowed, to avoid exploitation.
|
||||||
**kwargs: If given - these kwargs will always be passed to _every_
|
**default_kwargs: These kwargs will be passed into all callables. These
|
||||||
callable parsed and executed by this parser instance.
|
kwargs can be overridden both by kwargs passed direcetly to `.parse` _and_
|
||||||
|
by kwargs given directly in the string `$funcname` call. They are
|
||||||
|
suitable for global defaults that is intended to be changed by the
|
||||||
|
user. To _guarantee_ a call always gets a particular kwarg, pass it
|
||||||
|
into `.parse` as `**reserved_kwargs` instead.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(safe_callables, dict):
|
if isinstance(safe_callables, dict):
|
||||||
callables = {**safe_callables}
|
callables = {**safe_callables}
|
||||||
else:
|
else:
|
||||||
# load all modules/paths in sequence. Later-added will override earlier
|
# load all modules/paths in sequence. Later-added will override
|
||||||
# same-named callables (allows for overriding evennia defaults)
|
# earlier same-named callables (allows for overriding evennia defaults)
|
||||||
callables = {}
|
callables = {}
|
||||||
for safe_callable in make_iter(safe_callables):
|
for safe_callable in make_iter(safe_callables):
|
||||||
# callables_from_module handles both paths and module instances
|
# callables_from_module handles both paths and module instances
|
||||||
|
|
@ -121,7 +129,7 @@ class FuncParser:
|
||||||
self.callables = callables
|
self.callables = callables
|
||||||
self.escape_char = escape_char
|
self.escape_char = escape_char
|
||||||
self.start_char = start_char
|
self.start_char = start_char
|
||||||
self.default_kwargs = kwargs
|
self.default_kwargs = default_kwargs
|
||||||
|
|
||||||
def validate_callables(self, callables):
|
def validate_callables(self, callables):
|
||||||
"""
|
"""
|
||||||
|
|
@ -145,7 +153,7 @@ class FuncParser:
|
||||||
assert mapping.varargs, f"Parse-func callable '{funcname}' does not support *args."
|
assert mapping.varargs, f"Parse-func callable '{funcname}' does not support *args."
|
||||||
assert mapping.varkw, f"Parse-func callable '{funcname}' does not support **kwargs."
|
assert mapping.varkw, f"Parse-func callable '{funcname}' does not support **kwargs."
|
||||||
|
|
||||||
def execute(self, parsedfunc, raise_errors=False):
|
def execute(self, parsedfunc, raise_errors=False, **reserved_kwargs):
|
||||||
"""
|
"""
|
||||||
Execute a parsed function
|
Execute a parsed function
|
||||||
|
|
||||||
|
|
@ -154,15 +162,28 @@ class FuncParser:
|
||||||
of the function.
|
of the function.
|
||||||
raise_errors (bool, optional): Raise errors. Otherwise return the
|
raise_errors (bool, optional): Raise errors. Otherwise return the
|
||||||
string with the function unparsed.
|
string with the function unparsed.
|
||||||
|
**reserved_kwargs: These kwargs are _guaranteed_ to always be passed into
|
||||||
|
the callable on every call. It will override any default kwargs
|
||||||
|
_and_ also a same-named kwarg given manually in the $funcname
|
||||||
|
call. This is often used by Evennia to pass required data into
|
||||||
|
the callable, for example the current Session for inlinefuncs.
|
||||||
Returns:
|
Returns:
|
||||||
any: The result of the execution. If this is a nested function, it
|
any: The result of the execution. If this is a nested function, it
|
||||||
can be anything, otherwise it will be converted to a string later.
|
can be anything, otherwise it will be converted to a string later.
|
||||||
Always a string on un-raised error (the unparsed function string).
|
Always a string on un-raised error (the unparsed function string).
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ParsingError, any: A `ParsingError` if the function could not be found, otherwise
|
ParsingError, any: A `ParsingError` if the function could not be
|
||||||
error from function definition. Only raised if `raise_errors` is `True`
|
found, otherwise error from function definition. Only raised if
|
||||||
|
`raise_errors` is `True`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The kwargs passed into the callable will be a mixture of the
|
||||||
|
`default_kwargs` passed into `FuncParser.__init__`, kwargs given
|
||||||
|
directly in the `$funcdef` string, and the `reserved_kwargs` this
|
||||||
|
function gets from `.parse()`. For colliding keys, funcdef-defined
|
||||||
|
kwargs will override default kwargs while reserved kwargs will always
|
||||||
|
override the other two.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
funcname, args, kwargs = parsedfunc.get()
|
funcname, args, kwargs = parsedfunc.get()
|
||||||
|
|
@ -175,6 +196,9 @@ class FuncParser:
|
||||||
f"(available: {available})")
|
f"(available: {available})")
|
||||||
return str(parsedfunc)
|
return str(parsedfunc)
|
||||||
|
|
||||||
|
# build kwargs in the proper priority order
|
||||||
|
kwargs = {**self.default_kwargs, **kwargs, **reserved_kwargs}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return str(func(*args, **kwargs))
|
return str(func(*args, **kwargs))
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -183,7 +207,7 @@ class FuncParser:
|
||||||
raise
|
raise
|
||||||
return str(parsedfunc)
|
return str(parsedfunc)
|
||||||
|
|
||||||
def parse(self, string, raise_errors=False, **kwargs):
|
def parse(self, string, raise_errors=False, **reserved_kwargs):
|
||||||
"""
|
"""
|
||||||
Use parser to parse a string that may or may not have `$funcname(*args, **kwargs)`
|
Use parser to parse a string that may or may not have `$funcname(*args, **kwargs)`
|
||||||
- style tokens in it. Only the callables used to initiate the parser
|
- style tokens in it. Only the callables used to initiate the parser
|
||||||
|
|
@ -191,12 +215,16 @@ class FuncParser:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
string (str): The string to parse.
|
string (str): The string to parse.
|
||||||
raise_errors (bool, optional): By default, a failing parse just means not parsing the
|
raise_errors (bool, optional): By default, a failing parse just
|
||||||
string but leaving it as-is. If this is `True`, errors (like not closing brackets)
|
means not parsing the string but leaving it as-is. If this is
|
||||||
will lead to an ParsingError.
|
`True`, errors (like not closing brackets) will lead to an
|
||||||
**kwargs: If given, these are extra options to pass as `**kwargs` into each
|
ParsingError.
|
||||||
parsed callable. These will override any same-named kwargs given earlier
|
**reserved_kwargs: If given, these are guaranteed to _always_ pass
|
||||||
to `FuncParser.__init__`.
|
as part of each parsed callable's **kwargs. These override
|
||||||
|
same-named default options given in `__init__` as well as any
|
||||||
|
same-named kwarg given in the string function. This is because
|
||||||
|
it is often used by Evennia to pass necessary kwargs into each
|
||||||
|
callable (like the current Session object for inlinefuncs).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The parsed string, or the same string on error (if `raise_errors` is `False`)
|
str: The parsed string, or the same string on error (if `raise_errors` is `False`)
|
||||||
|
|
@ -205,224 +233,176 @@ class FuncParser:
|
||||||
ParsingError: If a problem is encountered and `raise_errors` is True.
|
ParsingError: If a problem is encountered and `raise_errors` is True.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
callables = self.callables
|
|
||||||
# prepare kwargs to pass into callables
|
|
||||||
callable_kwargs = {**self.default_kwargs}
|
|
||||||
callable_kwargs.update(kwargs)
|
|
||||||
|
|
||||||
start_char = self.start_char
|
start_char = self.start_char
|
||||||
escape_char = self.escape_char
|
escape_char = self.escape_char
|
||||||
|
|
||||||
|
# replace e.g. $$ with \$ so we only need to handle one escape method
|
||||||
|
string = string.replace(start_char + start_char, escape_char + start_char)
|
||||||
|
|
||||||
# parsing state
|
# parsing state
|
||||||
callstack = []
|
callstack = []
|
||||||
|
|
||||||
single_quoted = False
|
single_quoted = False
|
||||||
double_quoted = False
|
double_quoted = False
|
||||||
|
open_lparens = 0
|
||||||
escaped = False
|
escaped = False
|
||||||
current_kwarg = None
|
current_kwarg = ""
|
||||||
|
|
||||||
curr_func = None
|
curr_func = None
|
||||||
fullstr = ''
|
fullstr = '' # final string
|
||||||
workstr = ''
|
infuncstr = '' # string parts inside the current level of $funcdef (including $)
|
||||||
|
|
||||||
#from evennia import set_trace;set_trace()
|
|
||||||
|
|
||||||
for char in string:
|
for char in string:
|
||||||
|
|
||||||
if escaped:
|
if escaped:
|
||||||
# always store escaped characters verbatim
|
# always store escaped characters verbatim
|
||||||
workstr += char
|
if curr_func:
|
||||||
|
infuncstr += char
|
||||||
|
else:
|
||||||
|
fullstr += char
|
||||||
escaped = False
|
escaped = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if char == escape_char:
|
if char == escape_char:
|
||||||
# don't store the escape-char itself
|
# don't store the escape-char itself
|
||||||
escaped = True
|
escaped = True
|
||||||
continue
|
continue
|
||||||
if char == "'":
|
|
||||||
# a single quote - flip status
|
|
||||||
single_quoted = not single_quoted
|
|
||||||
continue
|
|
||||||
if char == '"':
|
|
||||||
# a double quote = flip status
|
|
||||||
double_quoted = not double_quoted
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not (double_quoted or single_quoted):
|
if char == start_char:
|
||||||
# not in a string escape
|
# start a new function definition (not escaped as $$)
|
||||||
if char == start_char:
|
|
||||||
# start a new function
|
|
||||||
if curr_func:
|
|
||||||
# nested func
|
|
||||||
if len(callstack) >= _MAX_NESTING:
|
|
||||||
# stack full - ignore this function
|
|
||||||
if raise_errors:
|
|
||||||
raise ParsingError("Only allows for parsing nesting function defs "
|
|
||||||
f"to a max depth of {_MAX_NESTING}.")
|
|
||||||
workstr += char
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
# store what we have and stack it
|
|
||||||
if current_kwarg:
|
|
||||||
curr_func.kwargs[current_kwarg] = workstr
|
|
||||||
current_kwarg = None
|
|
||||||
else:
|
|
||||||
curr_func.args.append(workstr)
|
|
||||||
workstr = ''
|
|
||||||
callstack.append(curr_func)
|
|
||||||
else:
|
|
||||||
# entering a funcdef, flush workstr
|
|
||||||
fullstr += workstr
|
|
||||||
workstr = char
|
|
||||||
# start a new func
|
|
||||||
curr_func = ParsedFunc(prefix=char)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if curr_func:
|
if curr_func:
|
||||||
# currently parsing a func
|
# we are starting a nested funcdef
|
||||||
if char == '(':
|
if len(callstack) > _MAX_NESTING:
|
||||||
# end of a funcdef
|
# stack full - ignore this function
|
||||||
curr_func.funcname = workstr
|
if raise_errors:
|
||||||
workstr = ''
|
raise ParsingError("Only allows for parsing nesting function defs "
|
||||||
|
f"to a max depth of {_MAX_NESTING}.")
|
||||||
|
infuncstr += char
|
||||||
continue
|
continue
|
||||||
if char == '=':
|
else:
|
||||||
# beginning of a keyword argument
|
# store state for the current func and stack it
|
||||||
current_kwarg = workstr
|
curr_func.current_kwarg = current_kwarg
|
||||||
curr_func.kwargs[current_kwarg] = None
|
curr_func.infuncstr = infuncstr
|
||||||
workstr = ''
|
curr_func.open_lparens = open_lparens
|
||||||
continue
|
curr_func.single_quoted = single_quoted
|
||||||
if char in (',', ')'):
|
curr_func.double_quoted = double_quoted
|
||||||
# end current arg/kwarg one way or another
|
current_kwarg = ""
|
||||||
if current_kwarg:
|
infuncstr = ""
|
||||||
curr_func.kwargs[current_kwarg] = workstr
|
open_lparens = 0
|
||||||
current_kwarg = None
|
single_quoted = False
|
||||||
else:
|
double_quoted = False
|
||||||
curr_func.args.append(workstr)
|
callstack.append(curr_func)
|
||||||
workstr = ''
|
|
||||||
|
|
||||||
if char == ')':
|
# start a new func
|
||||||
# closing the function list - this means we have a
|
curr_func = ParsedFunc(prefix=char, fullstr=char)
|
||||||
# ready function def to run.
|
continue
|
||||||
|
|
||||||
workstr += self.execute(curr_func, raise_errors=raise_errors)
|
if not curr_func:
|
||||||
|
# a normal piece of string
|
||||||
|
fullstr += char
|
||||||
|
continue
|
||||||
|
|
||||||
curr_func = None
|
# in a function def (can be nested)
|
||||||
if callstack:
|
|
||||||
# get a new func from stack, if any
|
|
||||||
curr_func = callstack.pop(0)
|
|
||||||
else:
|
|
||||||
fullstr += workstr
|
|
||||||
workstr = ''
|
|
||||||
continue
|
|
||||||
|
|
||||||
workstr += char
|
if char == "'": # note that this is the same as "\'"
|
||||||
|
# a single quote - flip status
|
||||||
|
single_quoted = not single_quoted
|
||||||
|
infuncstr += char
|
||||||
|
continue
|
||||||
|
|
||||||
fullstr += workstr
|
if char == '"': # note that this is the same as '\"'
|
||||||
|
# a double quote = flip status
|
||||||
|
double_quoted = not double_quoted
|
||||||
|
infuncstr += char
|
||||||
|
continue
|
||||||
|
|
||||||
|
if double_quoted or single_quoted:
|
||||||
|
# inside a string escape
|
||||||
|
infuncstr += char
|
||||||
|
continue
|
||||||
|
|
||||||
|
# special characters detected inside function def
|
||||||
|
if char == '(':
|
||||||
|
if not curr_func.funcname:
|
||||||
|
# end of a funcdef name
|
||||||
|
curr_func.funcname = infuncstr
|
||||||
|
curr_func.fullstr += infuncstr + char
|
||||||
|
infuncstr = ''
|
||||||
|
else:
|
||||||
|
# just a random left-parenthesis
|
||||||
|
infuncstr += char
|
||||||
|
# track the open left-parenthesis
|
||||||
|
open_lparens += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if char == '=':
|
||||||
|
# beginning of a keyword argument
|
||||||
|
current_kwarg = infuncstr.strip()
|
||||||
|
curr_func.kwargs[current_kwarg] = ""
|
||||||
|
curr_func.fullstr += infuncstr + char
|
||||||
|
infuncstr = ''
|
||||||
|
continue
|
||||||
|
|
||||||
|
if char in (',', ')'):
|
||||||
|
# commas and right-parens may indicate arguments ending
|
||||||
|
|
||||||
|
if open_lparens > 1:
|
||||||
|
# inside an unclosed, nested ( - this is neither
|
||||||
|
# closing the function-def nor indicating a new arg
|
||||||
|
# at the funcdef level
|
||||||
|
infuncstr += char
|
||||||
|
open_lparens -= 1 if char == ')' else 0
|
||||||
|
continue
|
||||||
|
|
||||||
|
# end current arg/kwarg one way or another
|
||||||
|
if current_kwarg:
|
||||||
|
curr_func.kwargs[current_kwarg] = infuncstr.strip()
|
||||||
|
current_kwarg = ""
|
||||||
|
elif infuncstr.strip():
|
||||||
|
curr_func.args.append(infuncstr.strip())
|
||||||
|
|
||||||
|
# we need to store the full string so we can print it 'raw' in
|
||||||
|
# case this funcdef turns out to e.g. lack an ending paranthesis
|
||||||
|
curr_func.fullstr += infuncstr + char
|
||||||
|
infuncstr = ''
|
||||||
|
|
||||||
|
if char == ')':
|
||||||
|
# closing the function list - this means we have a
|
||||||
|
# ready function-def to run.
|
||||||
|
open_lparens = 0
|
||||||
|
infuncstr = self.execute(
|
||||||
|
curr_func, raise_errors=raise_errors, **reserved_kwargs)
|
||||||
|
|
||||||
|
curr_func = None
|
||||||
|
if callstack:
|
||||||
|
# unnest the higher-level funcdef from stack
|
||||||
|
# and continue where we were
|
||||||
|
curr_func = callstack.pop()
|
||||||
|
current_kwarg = curr_func.current_kwarg
|
||||||
|
infuncstr = curr_func.infuncstr + infuncstr
|
||||||
|
curr_func.infuncstr = ''
|
||||||
|
open_lparens = curr_func.open_lparens
|
||||||
|
single_quoted = curr_func.single_quoted
|
||||||
|
double_quoted = curr_func.double_quoted
|
||||||
|
else:
|
||||||
|
# back to the top-level string
|
||||||
|
curr_func = None
|
||||||
|
fullstr += infuncstr
|
||||||
|
infuncstr = ''
|
||||||
|
continue
|
||||||
|
|
||||||
|
# no special char
|
||||||
|
infuncstr += char
|
||||||
|
|
||||||
|
if curr_func:
|
||||||
|
# if there is a still open funcdef or defs remaining in callstack,
|
||||||
|
# these are malformed (no closing bracket) and we should get their
|
||||||
|
# strings as-is.
|
||||||
|
callstack.append(curr_func)
|
||||||
|
for _ in range(len(callstack)):
|
||||||
|
infuncstr = str(callstack.pop()) + infuncstr
|
||||||
|
|
||||||
|
# add the last bit to the finished string and return
|
||||||
|
fullstr += infuncstr
|
||||||
return fullstr
|
return fullstr
|
||||||
|
|
||||||
|
|
||||||
#def parse_arguments(s, **kwargs):
|
|
||||||
# """
|
|
||||||
# This method takes a string and parses it as if it were an argument list to a function.
|
|
||||||
# It supports both positional and named arguments.
|
|
||||||
#
|
|
||||||
# Values are automatically converted to int or float if possible.
|
|
||||||
# Values surrounded by single or double quotes are treated as strings.
|
|
||||||
# Any other value is wrapped in a "FunctionArgument" class for later processing.
|
|
||||||
#
|
|
||||||
# Args:
|
|
||||||
# s (str): The string to convert.
|
|
||||||
#
|
|
||||||
# Returns:
|
|
||||||
# (list, dict): A tuple containing a list of arguments (list) and named arguments (dict).
|
|
||||||
# """
|
|
||||||
# global _ARG_ESCAPE_SIGN
|
|
||||||
#
|
|
||||||
# args_list = []
|
|
||||||
# args_dict = {}
|
|
||||||
#
|
|
||||||
# # State (general)
|
|
||||||
# inside = (False, None) # Are we inside a quoted string? What is the quoted character?
|
|
||||||
# skip = False # Skip the current parameter?
|
|
||||||
# escape = False # Was the escape key used?
|
|
||||||
# is_string = False # Have we been inside a quoted string?
|
|
||||||
# temp = "" # Buffer
|
|
||||||
# key = None # Key (for named parameter)
|
|
||||||
#
|
|
||||||
# def _parse_value(temp):
|
|
||||||
# ret = temp.strip()
|
|
||||||
# if not is_string:
|
|
||||||
# try:
|
|
||||||
# ret = int(ret)
|
|
||||||
# except ValueError:
|
|
||||||
# try:
|
|
||||||
# ret = float(ret)
|
|
||||||
# except ValueError:
|
|
||||||
# if ret != "":
|
|
||||||
# return FunctionArgument(ret)
|
|
||||||
#
|
|
||||||
# return ret
|
|
||||||
#
|
|
||||||
# def _add_value(skip, key, args_list, args_dict, temp):
|
|
||||||
# if not skip:
|
|
||||||
# # Record value based on whether named parameters mode is set or not.
|
|
||||||
# if key is not None:
|
|
||||||
# args_dict[key] = _parse_value(temp)
|
|
||||||
# key = None
|
|
||||||
# else:
|
|
||||||
# args_list.append(_parse_value(temp))
|
|
||||||
#
|
|
||||||
# for c in s:
|
|
||||||
# if c == _ARG_ESCAPE_SIGN:
|
|
||||||
# # Escape sign used.
|
|
||||||
# if escape:
|
|
||||||
# # Already escaping: print escape sign itself.
|
|
||||||
# temp += _ARG_ESCAPE_SIGN
|
|
||||||
# escape = False
|
|
||||||
# else:
|
|
||||||
# # Enter escape mode.
|
|
||||||
# escape = True
|
|
||||||
# elif escape:
|
|
||||||
# # Escape mode: print whatever comes after the symbol.
|
|
||||||
# escape = False
|
|
||||||
# temp += c
|
|
||||||
# elif inside[0] is True:
|
|
||||||
# # Inside single quotes or double quotes
|
|
||||||
# # Wait for the end symbol, allow everything else through, allow escape sign for typing quotes in strings
|
|
||||||
# if c == inside[1]:
|
|
||||||
# # Leaving single/double quoted area
|
|
||||||
# inside = (False, None)
|
|
||||||
# else:
|
|
||||||
# temp += c
|
|
||||||
# elif c == "\"" or c == "'":
|
|
||||||
# # Entering single/double quoted area
|
|
||||||
# inside = (True, c)
|
|
||||||
# is_string = True
|
|
||||||
# continue
|
|
||||||
# elif c == "=":
|
|
||||||
# if is_string:
|
|
||||||
# # Invalid syntax because we don't allow named parameters to be quoted.
|
|
||||||
# return None
|
|
||||||
# elif key is None:
|
|
||||||
# # Named parameters mode and equals sign encountered. Record key and continue with value.
|
|
||||||
# key = temp.strip()
|
|
||||||
# temp = ""
|
|
||||||
# elif c == ",":
|
|
||||||
# # Comma encountered outside of quoted area.
|
|
||||||
#
|
|
||||||
# _add_value(skip, key, args_list, args_dict, temp)
|
|
||||||
#
|
|
||||||
# # Reset
|
|
||||||
# temp = ""
|
|
||||||
# skip = False
|
|
||||||
# is_string = False
|
|
||||||
# key = None
|
|
||||||
# else:
|
|
||||||
# # Any other character: add to buffer.
|
|
||||||
# temp += c
|
|
||||||
#
|
|
||||||
# if inside[0] is True:
|
|
||||||
# # Invalid syntax because we are inside a quoted area.
|
|
||||||
# return None
|
|
||||||
# else:
|
|
||||||
# _add_value(skip, key, args_list, args_dict, temp)
|
|
||||||
#
|
|
||||||
# return args_list, args_dict
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ Test the funcparser module.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from simpleeval import simple_eval
|
||||||
from parameterized import parameterized
|
from parameterized import parameterized
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
@ -18,11 +20,37 @@ def _test_callable(*args, **kwargs):
|
||||||
", ".join(f"{key}={val}" for key, val in kwargs.items()))
|
", ".join(f"{key}={val}" for key, val in kwargs.items()))
|
||||||
return f"_test({argstr}{kwargstr})"
|
return f"_test({argstr}{kwargstr})"
|
||||||
|
|
||||||
|
def _repl_callable(*args, **kwargs):
|
||||||
|
if args:
|
||||||
|
return f"r{args[0]}r"
|
||||||
|
return "rr"
|
||||||
|
|
||||||
|
def _double_callable(*args, **kwargs):
|
||||||
|
if args:
|
||||||
|
try:
|
||||||
|
return int(args[0]) * 2
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return 'N/A'
|
||||||
|
|
||||||
|
def _eval_callable(*args, **kwargs):
|
||||||
|
if args:
|
||||||
|
return simple_eval(args[0])
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def _clr_callable(*args, **kwargs):
|
||||||
|
clr, string, *rest = args
|
||||||
|
return f"|{clr}{string}|n"
|
||||||
|
|
||||||
|
|
||||||
_test_callables = {
|
_test_callables = {
|
||||||
"foo": _test_callable,
|
"foo": _test_callable,
|
||||||
"bar": _test_callable,
|
"bar": _test_callable,
|
||||||
"with spaces": _test_callable,
|
"with spaces": _test_callable,
|
||||||
|
"repl": _repl_callable,
|
||||||
|
"double": _double_callable,
|
||||||
|
"eval": _eval_callable,
|
||||||
|
"clr": _clr_callable,
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestFuncParser(TestCase):
|
class TestFuncParser(TestCase):
|
||||||
|
|
@ -30,7 +58,6 @@ class TestFuncParser(TestCase):
|
||||||
Test the FuncParser class
|
Test the FuncParser class
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
self.parser = funcparser.FuncParser(
|
self.parser = funcparser.FuncParser(
|
||||||
|
|
@ -38,24 +65,112 @@ class TestFuncParser(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@parameterized.expand([
|
@parameterized.expand([
|
||||||
("This is a normal string", "This is a normal string"),
|
("Test normal string", "Test normal string"),
|
||||||
("This is $foo()", "This is _test()"),
|
("Test noargs1 $foo()", "Test noargs1 _test()"),
|
||||||
("This is $bar() etc.", "This is _test() etc."),
|
("Test noargs2 $bar() etc.", "Test noargs2 _test() etc."),
|
||||||
("This is $with spaces() etc.", "This is _test() etc."),
|
("Test noargs3 $with spaces() etc.", "Test noargs3 _test() etc."),
|
||||||
("Two $foo(), $bar() and $foo", "Two _test(), _test() and $foo"),
|
("Test noargs4 $foo(), $bar() and $foo", "Test noargs4 _test(), _test() and $foo"),
|
||||||
|
("$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')"),
|
||||||
|
("Test args4 $foo('')", "Test args4 _test('')"),
|
||||||
|
("Test args4 $foo(\"\")", "Test args4 _test(\"\")"),
|
||||||
|
("Test args5 $foo(\(\))", "Test args5 _test(())"),
|
||||||
|
("Test args6 $foo(\()", "Test args6 _test(()"),
|
||||||
|
("Test args7 $foo(())", "Test args7 _test(())"),
|
||||||
|
("Test args8 $foo())", "Test args8 _test())"),
|
||||||
|
("Test args9 $foo(=)", "Test args9 _test(=)"),
|
||||||
|
("Test args10 $foo(\,)", "Test args10 _test(,)"),
|
||||||
|
("Test args10 $foo(',')", "Test args10 _test(',')"),
|
||||||
|
("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 $bar(foo,bar,too=ere)",
|
||||||
"Test kwarg2 _test(foo, bar, too=ere)"),
|
"Test kwarg2 _test(foo, bar, too=ere)"),
|
||||||
("Test nest1 $foo($bar(foo, bar, too=ere))",
|
("test kwarg3 $foo(foo = bar, bar = ere )",
|
||||||
|
"test kwarg3 _test(foo=bar, bar=ere)"),
|
||||||
|
("test kwarg4 $foo(foo =' bar ',\" bar \"= ere )",
|
||||||
|
"test kwarg4 _test(foo=' bar ', \" bar \"=ere)"),
|
||||||
|
("Test nest1 $foo($bar(foo,bar,too=ere))",
|
||||||
"Test nest1 _test(_test(foo, bar, too=ere))"),
|
"Test nest1 _test(_test(foo, bar, too=ere))"),
|
||||||
|
("Test nest2 $foo(bar,$repl(a),$repl()=$repl(),a=b) etc",
|
||||||
|
"Test nest2 _test(bar, rar, rr=rr, a=b) etc"),
|
||||||
|
("Test nest3 $foo(bar,$repl($repl($repl(c))))",
|
||||||
|
"Test nest3 _test(bar, rrrcrrr)"),
|
||||||
|
("Test nest4 $foo($bar(a,b),$bar(a,$repl()),$bar())",
|
||||||
|
"Test nest4 _test(_test(a, b), _test(a, rr), _test())"),
|
||||||
|
("Test escape1 \\$repl(foo)", "Test escape1 $repl(foo)"),
|
||||||
|
("Test escape2 \"This is $foo() and $bar($bar())\", $repl()",
|
||||||
|
"Test escape2 \"This is _test() and _test(_test())\", rr"),
|
||||||
|
("Test escape3 'This is $foo() and $bar($bar())', $repl()",
|
||||||
|
"Test escape3 'This is _test() and _test(_test())', rr"),
|
||||||
|
("Test escape4 $$foo() and $$bar(a,b), $repl()",
|
||||||
|
"Test escape4 $foo() and $bar(a,b), rr"),
|
||||||
|
("Test with color |r$foo(a,b)|n is ok",
|
||||||
|
"Test with color |r_test(a, b)|n is ok"),
|
||||||
|
("Test malformed1 This is $foo( and $bar(",
|
||||||
|
"Test malformed1 This is $foo( and $bar("),
|
||||||
|
("Test malformed2 This is $foo( and $bar()",
|
||||||
|
"Test malformed2 This is $foo( and _test()"),
|
||||||
|
("Test malformed3 $", "Test malformed3 $"),
|
||||||
|
("Test malformed4 This is $dummy(a, b) and $bar(",
|
||||||
|
"Test malformed4 This is $dummy(a, b) and $bar("),
|
||||||
|
("Test malformed5 This is $foo(a=b and $bar(",
|
||||||
|
"Test malformed5 This is $foo(a=b and $bar("),
|
||||||
|
("Test malformed6 This is $foo(a=b, and $repl()",
|
||||||
|
"Test malformed6 This is $foo(a=b, and rr"),
|
||||||
|
("Test nonstr 4x2 = $double(4)", "Test nonstr 4x2 = 8"),
|
||||||
|
("Test nonstr 4x2 = $double(foo)", "Test nonstr 4x2 = N/A"),
|
||||||
|
("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 eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"),
|
||||||
|
("Test eval3 $eval('21' + 'foo' + 'bar')", "Test eval3 21foobar"),
|
||||||
|
("Test eval4 $eval('21' + '$repl()' + '' + str(10 // 2))", "Test eval4 21rr5"),
|
||||||
|
("Test eval5 $eval('21' + '\$repl()' + '' + str(10 // 2))", "Test eval5 21$repl()5"),
|
||||||
|
("Test eval6 $eval('$repl(a)' + '$repl(b)')", "Test eval6 rarrbr"),
|
||||||
])
|
])
|
||||||
def test_parse(self, string, expected):
|
def test_parse(self, string, expected):
|
||||||
"""
|
"""
|
||||||
Test parsing of string.
|
Test parsing of string.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ret = self.parser.parse(string, raise_errors=True)
|
t0 = time.time()
|
||||||
self.assertEqual(expected, ret, "Parsing mismatch")
|
# from evennia import set_trace;set_trace()
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
t1 = time.time()
|
||||||
|
print(f"time: {(t1-t0)*1000} ms")
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_parse_raise(self):
|
||||||
|
string = "Test invalid $dummy()"
|
||||||
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
self.parser.parse(string, raise_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_kwargs_overrides(self):
|
||||||
|
"""
|
||||||
|
Test so default kwargs are added and overridden properly
|
||||||
|
"""
|
||||||
|
# default kwargs passed on initializations
|
||||||
|
parser = funcparser.FuncParser(
|
||||||
|
_test_callables,
|
||||||
|
test='foo'
|
||||||
|
)
|
||||||
|
ret = parser.parse("This is a $foo() string")
|
||||||
|
self.assertEqual("This is a _test(test=foo) string", ret)
|
||||||
|
|
||||||
|
# override in the string itself
|
||||||
|
|
||||||
|
ret = parser.parse("This is a $foo(test=bar,foo=moo) string")
|
||||||
|
self.assertEqual("This is a _test(test=bar, foo=moo) string", ret)
|
||||||
|
|
||||||
|
# parser kwargs override the other types
|
||||||
|
|
||||||
|
ret = parser.parse("This is a $foo(test=bar,foo=moo) string", test="override", foo="bar")
|
||||||
|
self.assertEqual("This is a _test(test=override, foo=bar) string", ret)
|
||||||
|
|
||||||
|
# non-overridden kwargs shine through
|
||||||
|
|
||||||
|
ret = parser.parse("This is a $foo(foo=moo) string", foo="bar")
|
||||||
|
self.assertEqual("This is a _test(test=foo, foo=bar) string", ret)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ django-sekizai
|
||||||
inflect >= 5.2.0
|
inflect >= 5.2.0
|
||||||
autobahn >= 17.9.3
|
autobahn >= 17.9.3
|
||||||
lunr == 0.5.6
|
lunr == 0.5.6
|
||||||
|
simpleeval <= 1.0
|
||||||
|
|
||||||
# try to resolve dependency issue in py3.7
|
# try to resolve dependency issue in py3.7
|
||||||
attrs >= 19.2.0
|
attrs >= 19.2.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue