Cleaned up Components page
This commit is contained in:
parent
bc092b8b2b
commit
3b6f16f529
29 changed files with 969 additions and 1735 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# The Inline Function Parser
|
||||
# FuncParser inline text parsing
|
||||
|
||||
The [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 lead to a call to a Python function you control. The inline function call will be replaced by the return from the function.
|
||||
|
||||
|
|
@ -36,30 +36,20 @@ 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
|
||||
## Working with FuncParser
|
||||
|
||||
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.md) 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.md) 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
|
||||
- _Outgoing messages_. All messages sent from the server is processed through FuncParser and every callable is provided the [Session](./Sessions.md) 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.md) 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](evennia.objects.objects.DefaultObject.msg_contents) method,
|
||||
the outgoing string is parsed for special `$You()` and `$conj()` callables to decide if a given recipient
|
||||
- _Actor-stance in messages to others_. In the [Object.msg_contents](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.
|
||||
|
||||
```{important}
|
||||
The inline-function parser is not intended as a 'softcode' programming language. It does not
|
||||
have things like loops and conditionals, for example. While you could in principle extend it to
|
||||
do very advanced things and allow builders a lot of power, all-out coding is something
|
||||
Evennia expects you to do in a proper text editor, outside of the game, not from inside it.
|
||||
```
|
||||
|
||||
## Using the FuncParser
|
||||
The inline-function parser is not intended as a 'softcode' programming language. It does not have things like loops and conditionals, for example. While you could in principle extend it to do very advanced things and allow builders a lot of power, all-out coding is something Evennia expects you to do in a proper text editor, outside of the game, not from inside it.
|
||||
```
|
||||
|
||||
You can apply inline function parsing to any string. The
|
||||
[FuncParser](evennia.utils.funcparser.FuncParser) is imported as `evennia.utils.funcparser`.
|
||||
|
|
@ -129,8 +119,7 @@ _test('foo', bar='4', mydefault=2, myreserved=[1, 2, 3],
|
|||
funcparser=<FuncParser>, raise_errors=False)
|
||||
```
|
||||
|
||||
The `mydefault=2` kwarg could be overwritten if we made the call as `$test(mydefault=...)`
|
||||
but `myreserved=[1, 2, 3]` will _always_ be sent as-is and will override a call `$test(myreserved=...)`.
|
||||
The `mydefault=2` kwarg could be overwritten if we made the call as `$test(mydefault=...)` but `myreserved=[1, 2, 3]` will _always_ be sent as-is and will override a call `$test(myreserved=...)`.
|
||||
The `funcparser`/`raise_errors` kwargs are also always included as reserved kwargs.
|
||||
|
||||
## Defining custom callables
|
||||
|
|
@ -143,21 +132,16 @@ def funcname(*args, **kwargs):
|
|||
return something
|
||||
```
|
||||
|
||||
> The `*args` and `**kwargs` must always be included. If you are unsure how `*args` and `**kwargs` work in Python,
|
||||
> [read about them here](https://www.digitalocean.com/community/tutorials/how-to-use-args-and-kwargs-in-python-3).
|
||||
> The `*args` and `**kwargs` must always be included. If you are unsure how `*args` and `**kwargs` work in Python, [read about them here](https://www.digitalocean.com/community/tutorials/how-to-use-args-and-kwargs-in-python-3).
|
||||
|
||||
The input from the innermost `$funcname(...)` call in your callable will always be a `str`. Here's
|
||||
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 number so that we can convert it to an integer. We must also properly handle invalid
|
||||
inputs (like non-numbers).
|
||||
What will enter the `$toint` callable (as `args[0]`) is the _string_ `"22.0"`. The function is responsible 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
|
||||
created the parser.
|
||||
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 created the parser.
|
||||
|
||||
However, if you _nest_ functions, the return of the innermost function may be something other than
|
||||
a string. Let's introduce the `$eval` function, which evaluates simple expressions using
|
||||
|
|
@ -170,20 +154,16 @@ Since the `$eval` is the innermost call, it will get a string as input - the str
|
|||
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.
|
||||
|
||||
> 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.
|
||||
> 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:
|
||||
In these examples, the result will be embedded in the larger string, so the result of the entire parsing will be a string:
|
||||
|
||||
```python
|
||||
parser.parse(above_string)
|
||||
"There's a 22% chance of survival."
|
||||
```
|
||||
|
||||
However, if you use the `parse_to_any` (or `parse(..., return_str=False)`) and
|
||||
_don't add any extra string around the outermost function call_,
|
||||
you'll get the return type of the outermost callable back:
|
||||
However, if you use the `parse_to_any` (or `parse(..., return_str=False)`) and _don't add any extra string around the outermost function call_, you'll get the return type of the outermost callable back:
|
||||
|
||||
```python
|
||||
parser.parse_to_any("$toint($eval(10 * 2.2)")
|
||||
|
|
@ -245,9 +225,7 @@ 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](evennia.utils.utils.safe_convert_to_types). This function
|
||||
automates the conversion of simple data types in a safe way:
|
||||
In `evennia.utils.utils` is a helper called [safe_convert_to_types](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
|
||||
|
|
@ -265,44 +243,23 @@ def _process_callable(*args, **kwargs):
|
|||
|
||||
```
|
||||
|
||||
In other words, in the callable `$process(expression, local, extra1=..,
|
||||
extra2=...)`, the first argument will be handled by the 'py' converter
|
||||
(described below), the second will passed through regular Python `str`,
|
||||
kwargs will be handled by `int` and `str` respectively. You can supply
|
||||
your own converter function as long as it takes one argument and returns
|
||||
the converted result.
|
||||
|
||||
In other words,
|
||||
In other words, in the callable `$process(expression, local, extra1=.., extra2=...)`, the first argument will be handled by the 'py' converter (described below), the second will passed through regular Python `str`, kwargs will be handled by `int` and `str` respectively. You can supply your own converter function as long as it takes one argument and returns the converted result.
|
||||
|
||||
```python
|
||||
args, kwargs = safe_convert_to_type(
|
||||
(tuple_of_arg_converters, dict_of_kwarg_converters), *args, **kwargs)
|
||||
```
|
||||
|
||||
The special converter `"py"` will try to convert a string argument to a Python structure with the help of the
|
||||
following tools (which you may also find useful to experiment with on your own):
|
||||
The special converter `"py"` will try to convert a string argument to a Python structure with the help of the following tools (which you may also find useful to experiment with on your own):
|
||||
|
||||
- [ast.literal_eval](https://docs.python.org/3.8/library/ast.html#ast.literal_eval) is an in-built Python
|
||||
function. It
|
||||
_only_ supports strings, bytes, numbers, tuples, lists, dicts, sets, booleans and `None`. That's
|
||||
it - no arithmetic or modifications of data is allowed. This is good for converting individual values and
|
||||
lists/dicts from the input line to real Python objects.
|
||||
- [simpleeval](https://pypi.org/project/simpleeval/) is a third-party tool included with Evennia. This
|
||||
allows for evaluation of simple (and thus safe) expressions. One can operate on numbers and strings
|
||||
with `+-/*` as well as do simple comparisons like `4 > 3` and more. It does _not_ accept more complex
|
||||
containers like lists/dicts etc, so this and `literal_eval` are complementary to each other.
|
||||
- [ast.literal_eval](https://docs.python.org/3.8/library/ast.html#ast.literal_eval) is an in-built Python function. It _only_ supports strings, bytes, numbers, tuples, lists, dicts, sets, booleans and `None`. That's it - no arithmetic or modifications of data is allowed. This is good for converting individual values and lists/dicts from the input line to real Python objects.
|
||||
- [simpleeval](https://pypi.org/project/simpleeval/) is a third-party tool included with Evennia. This allows for evaluation of simple (and thus safe) expressions. One can operate on numbers and strings with `+-/*` as well as do simple comparisons like `4 > 3` and more. It does _not_ accept more complex 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 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 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.
|
||||
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 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.
|
||||
```
|
||||
|
||||
## Default callables
|
||||
## Default funcparser callables
|
||||
|
||||
These are some example callables you can import and add your parser. They are divided into
|
||||
global-level dicts in `evennia.utils.funcparser`. Just import the dict(s) and merge/add one or
|
||||
|
|
@ -312,47 +269,27 @@ more to them when you create your `FuncParser` instance to have those callables
|
|||
|
||||
These are the 'base' callables.
|
||||
|
||||
- `$eval(expression)` ([code](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](evennia.utils.funcparser.funcparser_callable_toint)) -
|
||||
always converts an output to an integer, if possible.
|
||||
- `$eval(expression)` ([code](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](evennia.utils.funcparser.funcparser_callable_toint)) - always converts an output to an integer, if possible.
|
||||
- `$add/sub/mult/div(obj1, obj2)` ([code](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](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](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
|
||||
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](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](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
|
||||
integers. Without argument, will return a float between 0 and 1.
|
||||
- `$randint([start, [end]])` ([code](evennia.utils.funcparser.funcparser_callable_randint)) -
|
||||
works like the `randint()` python function and always returns an integer.
|
||||
- `$choice(list)` ([code](evennia.utils.funcparser.funcparser_callable_choice)) -
|
||||
the input will automatically be parsed the same way as `$eval` and is expected to be an iterable. A random
|
||||
element of this list will be returned.
|
||||
- `$pad(text[, width, align, fillchar])` ([code](evennia.utils.funcparser.funcparser_callable_pad)) -
|
||||
this will pad content. `$pad("Hello", 30, c, -)` will lead to a text centered in a 30-wide block surrounded by `-`
|
||||
characters.
|
||||
- `$crop(text, width=78, suffix='[...]')` ([code](evennia.utils.funcparser.funcparser_callable_crop)) -
|
||||
this will crop a text longer than the width, by default ending it with a `[...]`-suffix that also fits within
|
||||
the width. If no width is given, the client width or `settings.DEFAULT_CLIENT_WIDTH` will be used.
|
||||
- `$space(num)` ([code](evennia.utils.funcparser.funcparser_callable_space)) -
|
||||
this will insert `num` spaces.
|
||||
- `$just(string, width=40, align=c, indent=2)` ([code](evennia.utils.funcparser.funcparser_callable_justify)) -
|
||||
justifies the text to a given width, aligning it left/right/center or 'f' for full (spread text across width).
|
||||
- `$randint([start, [end]])` ([code](evennia.utils.funcparser.funcparser_callable_randint)) - works like the `randint()` python function and always returns an integer.
|
||||
- `$choice(list)` ([code](evennia.utils.funcparser.funcparser_callable_choice)) - the input will automatically be parsed the same way as `$eval` and is expected to be an iterable. A random element of this list will be returned.
|
||||
- `$pad(text[, width, align, fillchar])` ([code](evennia.utils.funcparser.funcparser_callable_pad)) - this will pad content. `$pad("Hello", 30, c, -)` will lead to a text centered in a 30-wide block surrounded by `-` characters.
|
||||
- `$crop(text, width=78, suffix='[...]')` ([code](evennia.utils.funcparser.funcparser_callable_crop)) - this will crop a text longer than the width, by default ending it with a `[...]`-suffix that also fits within the width. If no width is given, the client width or `settings.DEFAULT_CLIENT_WIDTH` will be used.
|
||||
- `$space(num)` ([code](evennia.utils.funcparser.funcparser_callable_space)) - this will insert `num` spaces.
|
||||
- `$just(string, width=40, align=c, indent=2)` ([code](evennia.utils.funcparser.funcparser_callable_justify)) - justifies the text to a given width, aligning it left/right/center or 'f' for full (spread text across width).
|
||||
- `$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](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`.
|
||||
- `$clr(startcolor, text[, endcolor])` ([code](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`.
|
||||
|
||||
### `evennia.utils.funcparser.SEARCHING_CALLABLES`
|
||||
|
||||
These are callables that requires access-checks in order to search for objects. So they require some
|
||||
extra reserved kwargs to be passed when running the parser:
|
||||
|
||||
These are callables that requires access-checks in order to search for objects. So they require some extra reserved kwargs to be passed when running the parser:
|
||||
```python
|
||||
|
||||
parser.parse_to_any(string, caller=<object or account>, access="control", ...)
|
||||
|
|
@ -361,30 +298,23 @@ 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.md) to check, default being `"control"`.
|
||||
|
||||
- `$search(query,type=account|script,return_list=False)` ([code](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
|
||||
search for `account` or `script` instead. By default this will return nothing if there are more than one
|
||||
match; if `return_list` is `True` a list of 0, 1 or more matches will be returned instead.
|
||||
- `$search(query,type=account|script,return_list=False)` ([code](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 search for `account` or `script` instead. By default this will return nothing if there are more than one match; if `return_list` is `True` a list of 0, 1 or more matches will be returned instead.
|
||||
- `$obj(query)`, `$dbref(query)` - legacy aliases for `$search`.
|
||||
- `$objlist(query)` - legacy alias for `$search`, always returning a list.
|
||||
|
||||
|
||||
### `evennia.utils.funcparser.ACTOR_STANCE_CALLABLES`
|
||||
|
||||
These are used to implement actor-stance emoting. They are used by the
|
||||
[DefaultObject.msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method
|
||||
by default. You can read a lot more about this on the page
|
||||
These are used to implement actor-stance emoting. They are used by the [DefaultObject.msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method by default. You can read a lot more about this on the page
|
||||
[Change messages per receiver](../Concepts/Change-Message-Per-Receiver.md).
|
||||
|
||||
On the parser side, all these inline functions require extra kwargs be passed into the parser
|
||||
(done by `msg_contents` by default):
|
||||
On the parser side, all these inline functions require extra kwargs be passed into the parser (done by `msg_contents` by default):
|
||||
|
||||
```python
|
||||
parser.parse(string, caller=<obj>, receiver=<obj>, mapping={'key': <obj>, ...})
|
||||
```
|
||||
|
||||
Here the `caller` is the one sending the message and `receiver` the one to see it. The `mapping` contains
|
||||
references to other objects accessible via these callables.
|
||||
Here the `caller` is the one sending the message and `receiver` the one to see it. The `mapping` contains references to other objects accessible via these callables.
|
||||
|
||||
- `$you([key])` ([code](evennia.utils.funcparser.funcparser_callable_you)) -
|
||||
if no `key` is given, this represents the `caller`, otherwise an object from `mapping`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue