Clean up docs and more funcparser fixes

This commit is contained in:
Griatch 2021-03-27 23:43:46 +01:00
parent c65c68e4c2
commit c9d9e9c6f8
21 changed files with 438 additions and 512 deletions

View file

@ -1,7 +1,5 @@
# The Inline Function Parser
## Introduction
The [FuncParser](api:evennia.utils.funcparser#evennia.utils.funcparser.FuncParser) extracts and executes
'inline functions'
embedded in a string on the form `$funcname(args, kwargs)`. Under the hood, this will
@ -11,26 +9,25 @@ the return from the function.
```python
from evennia.utils.funcparser import FuncParser
def _square_callable(*args, **kwargs):
"""This will be callable as $square(number) in string"""
return float(args[0]) ** 2
def _power_callable(*args, **kwargs):
"""This will be callable as $square(number, power=<num>) in string"""
pow = int(kwargs.get('power', 2))
return float(args[0]) ** pow
parser = FuncParser({"square": _square_callable})
parser = FuncParser({"pow": _power_callable})
```
Next, just pass a string into the parser, optionally containing `$func(...)` markers:
```python
parser.parse("We have that 4 x 4 is $square(4).")
"We have that 4 x 4 is 16."
parser.parse("We have that 4 x 4 x 4 is $pow(4, power=3).")
"We have that 4 x 4 x 4 is 64."
```
Normally the return is always converted to a string but you can also get the actual data type from the call:
```python
parser.parse_to_any("$square(4)")
parser.parse_to_any("$pow(4)")
16
```
@ -38,8 +35,8 @@ To show a `$func()` verbatim in your code without parsing it, escape it as eithe
```python
parser.parse("This is an escaped $$square(4) and so is this \$square(3)")
"This is an escaped $square(4) and so is this $square(3)"
parser.parse("This is an escaped $$pow(4) and so is this \$pow(3)")
"This is an escaped $pow(4) and so is this $pow(3)"
```
## Uses in default Evennia
@ -47,14 +44,14 @@ parser.parse("This is an escaped $$square(4) and so is this \$square(3)")
The FuncParser can be applied to any string. Out of the box it's applied in a few situations:
- _Outgoing messages_. All messages sent from the server is processed through FuncParser and every
callable is provided the [Session](Sessions) of the object receiving the message. This potentially
callable is provided the [Session](./Sessions) of the object receiving the message. This potentially
allows a message to be modified on the fly to look different for different recipients.
- _Prototype values_. A [Prototype](Prototypes) dict's values are run through the parser such that every
- _Prototype values_. A [Prototype](./Prototypes) dict's values are run through the parser such that every
callable gets a reference to the rest of the prototype. In the Prototype ORM, this would allow builders
to safely call functions to set non-string values to prototype values, get random values, reference
other fields of the prototype, and more.
- _Actor-stance in messages to others_. In the
[Object.msg_contents](api:evennia.objects.objects#objects.objects.DefaultObject.msg_contents) method,
[Object.msg_contents](api:evennia.objects.objects#DefaultObject.msg_contents) method,
the outgoing string is parsed for special `$You()` and `$conj()` callables to decide if a given recipient
should see "You" or the character's name.
@ -115,20 +112,18 @@ The other arguments to the parser:
Here's an example of using the default/reserved keywords:
```python
def _test(*args, **kwargs):
# do stuff
return something
parser = funcparser.FuncParser({"test": _test}, mydefault=2)
result = parser.parse("$test(foo, bar=4)", myreserved=[1, 2, 3])
```
Here the callable will be called as
```python
_test('foo', bar='4', mydefault=2, myreserved=[1, 2, 3],
funcparser=<FuncParser>, raise_errrors=False)
_test('foo', bar='4', mydefault=2, myreserved=[1, 2, 3],
funcparser=<FuncParser>, raise_errrors=False)
```
The `mydefault=2` kwarg could be overwritten if we made the call as `$test(mydefault=...)`
@ -154,8 +149,8 @@ an example of an `$toint` function; it converts numbers to integers.
"There's a $toint(22.0)% chance of survival."
What will enter the `$toint` callable (as `args[0]`) is the _string_ `"22.0"`. The function is responsible
for converting this to a float so it can operate on it. And also to properly handle invalid inputs (like
non-numbers). Common is to just return the input as-is or return the empty string.
for converting this to a number so that we can convert it to an integer. We must also properly handle invalid
inputs (like non-numbers).
If you want to mark an error, raise `evennia.utils.funcparser.ParsingError`. This stops the entire parsing
of the string and may or may not raise the exception depending on what you set `raise_errors` to when you
@ -167,12 +162,12 @@ Python's `literal_eval` and/or `simple_eval`.
"There's a $toint($eval(10 * 2.2))% chance of survival."
Since the `$eval` is the innermost call, it will get a sring as input - the string `"10 * 2.2"`.
Since the `$eval` is the innermost call, it will get a string as input - the string `"10 * 2.2"`.
It evaluates this and returns the `float` `22.0`. This time the outermost `$toint` will be called with
this `float` instead of with a string.
> Since you don't know in which order users will nest or not nest your callables, it's important to
> safely validate your inputs. See the next section for useful tools to do this.
> It's important to safely validate your inputs since users may end up nesting your callables in any order.
> See the next section for useful tools to help with this.
In these examples, the result will be embedded in the larger string, so the result of the entire parsing
will be a string:
@ -195,16 +190,16 @@ parser.parse_to_any("$toint($eval(10 * 2.2)")
### Safe convertion of inputs
Since you don't know in which order users may use your callables, they should always check the types
of its inputs and convert it to type the callable needs. Note also that this limits what inputs you can
support since some things (such as complex classes/callables etc) are just not safe/possible to
convert from string representation.
of its inputs and convert to the type the callable needs. Note also that when converting from strings,
there are limits what inputs you can support. This is because FunctionParser strings are often used by
non-developer players/builders and some things (such as complex classes/callables etc) are just not
safe/possible to convert from string representation.
In `evennia.utils.utils` is a helper called
[safe_convert_to_types](api.evennia.utils.utils#evennia.utils.utils.safe_convert_to_types). This function
[safe_convert_to_types](api:evennia.utils.utils#evennia.utils.utils.safe_convert_to_types). This function
automates the conversion of simple data types in a safe way:
```python
from evennia.utils.utils import safe_convert_to_types
def _process_callable(*args, **kwargs):
@ -225,7 +220,6 @@ def _process_callable(*args, **kwargs):
In other words,
```python
args, kwargs = safe_convert_to_type(
(tuple_of_arg_converters, dict_of_kwarg_converters), *args, **kwargs)
```
@ -245,12 +239,12 @@ following tools (which you may also find useful to experiment with on your own):
containers like lists/dicts etc, so this and `literal_eval` are complementary to each other.
```warning::
It may be tempting to run use Python's in-built `eval()` or `exec()` functions as converters since these
are able to convert any valid Python source code to Python. NEVER DO THIS unless you really, really
know ONLY developers will ever send input to the callable. The parser is intended
It may be tempting to run use Python's in-built ``eval()`` or ``exec()`` functions as converters since
these are able to convert any valid Python source code to Python. NEVER DO THIS unless you really, really
know that ONLY developers will ever modify the string going into the callable. The parser is intended
for untrusted users (if you were trusted you'd have access to Python already). Letting untrusted users
pass strings to eval/exec is a MAJOR security risk. It allows the caller to effectively run arbitrary
Python code on your server. This is the way to maliciously deleted hard drives. Just don't do it and
pass strings to ``eval``/``exec`` is a MAJOR security risk. It allows the caller to run arbitrary
Python code on your server. This is the path to maliciously deleted hard drives. Just don't do it and
sleep better at night.
```
@ -264,16 +258,16 @@ more to them when you create your `FuncParser` instance to have those callables
These are the 'base' callables.
- `$eval(expression)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_eval) -
- `$eval(expression)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_eval)) -
this uses `literal_eval` and `simple_eval` (see previous section) attemt to convert a string expression
to a python object. This handles e.g. lists of literals `[1, 2, 3]` and simple expressions like `"1 + 2"`.
- `$toint(number)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_toint)) -
always converts an output to an integer, if possible.
- `$add/sub/mult/div(obj1, obj2)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_add))) -
- `$add/sub/mult/div(obj1, obj2)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_add)) -
this adds/subtracts/multiplies and divides to elements together. While simple addition could be done with
`$eval`, this could for example be used also to add two lists together, which is not possible with `eval`;
for example `$add($eval([1,2,3]), $eval([4,5,6])) -> [1, 2, 3, 4, 5, 6]`.
- `$round(float, significant)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_round) -
- `$round(float, significant)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_round)) -
rounds an input float into the number of provided significant digits. For example `$round(3.54343, 3) -> 3.543`.
- `$random([start, [end]])` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_random)) -
this works like the Python `random()` function, but will randomize to an integer value if both start/end are
@ -296,7 +290,7 @@ These are the 'base' callables.
- `$ljust` - shortcut to justify-left. Takes all other kwarg of `$just`.
- `$rjust` - shortcut to right justify.
- `$cjust` - shortcut to center justify.
- `$clr(startcolor, text[, endcolor])`([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_clr)) -
- `$clr(startcolor, text[, endcolor])` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_clr)) -
color text. The color is given with one or two characters without the preceeding `|`. If no endcolor is
given, the string will go back to neutral, so `$clr(r, Hello)` is equivalent to `|rHello|n`.
@ -310,7 +304,7 @@ parser.parse_to_any(string, caller=<object or account>, access="control", ...)`
```
The `caller` is required, it's the the object to do the access-check for. The `access` kwarg is the
[lock type](Locks) to check, default being `"control"`.
[lock type](./Locks) to check, default being `"control"`.
- `$search(query,type=account|script,return_list=False)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_search)) -
this will look up and try to match an object by key or alias. Use the `type` kwarg to
@ -342,9 +336,9 @@ references to other objects accessible via these callables.
result of `you_obj.get_display_name(looker=receiver)`. This allows for a single string to echo differently
depending on who sees it, and also to reference other people in the same way.
- `$You([key])` - same as `$you` but always capitalized.
- `$conj(verb)` - conjugates a verb between 2nd person presens to 3rd person presence depending on who
sees the string. For example `$You() $conj(smiles).` will show as "You smile." and "Tom smiles." depending
on who sees it. This makes use of the tools in [evennia.utils.verb_conjugation](api.evennia.utils.verb_conjugation)
- `$conj(verb)` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_conjugate)) -- conjugates a verb between 4nd person presens to 3rd person presence depending on who
sees the string. For example `"$You() $conj(smiles)".` will show as "You smile." and "Tom smiles." depending
on who sees it. This makes use of the tools in [evennia.utils.verb_conjugation](api:evennia.utils.verb_conjugation)
to do this, and only works for English verbs.
### Example
@ -384,7 +378,5 @@ all the defaults (like `$toint()`).
The parsed result of the above would be something like this:
```
This is the current uptime:
------- 343 seconds -------
```
This is the current uptime:
------- 343 seconds -------