From c9d9e9c6f834b3ad668121e7d74e782d834b571c Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Mar 2021 23:43:46 +0100 Subject: [PATCH] Clean up docs and more funcparser fixes --- docs/source/Components/FuncParser.md | 88 ++--- docs/source/Concepts/Clickable-Links.md | 21 + docs/source/Concepts/Colors.md | 183 +++++++++ docs/source/Concepts/TextTags.md | 359 +----------------- docs/source/Evennia-API.md | 1 + docs/source/api/evennia.utils.funcparser.rst | 7 + docs/source/api/evennia.utils.inlinefuncs.rst | 7 - docs/source/api/evennia.utils.rst | 3 +- ...ennia.utils.verb_conjugation.conjugate.rst | 7 + .../api/evennia.utils.verb_conjugation.rst | 15 + .../evennia.utils.verb_conjugation.tests.rst | 7 + docs/source/toc.md | 4 +- evennia/commands/default/building.py | 4 +- .../game_template/server/conf/inlinefuncs.py | 40 +- evennia/prototypes/protfuncs.py | 7 +- evennia/server/deprecations.py | 15 + evennia/server/sessionhandler.py | 14 +- evennia/settings_default.py | 34 +- evennia/utils/funcparser.py | 118 +++--- evennia/utils/tests/test_funcparser.py | 13 +- evennia/utils/utils.py | 3 +- 21 files changed, 438 insertions(+), 512 deletions(-) create mode 100644 docs/source/Concepts/Clickable-Links.md create mode 100644 docs/source/Concepts/Colors.md create mode 100644 docs/source/api/evennia.utils.funcparser.rst delete mode 100644 docs/source/api/evennia.utils.inlinefuncs.rst create mode 100644 docs/source/api/evennia.utils.verb_conjugation.conjugate.rst create mode 100644 docs/source/api/evennia.utils.verb_conjugation.rst create mode 100644 docs/source/api/evennia.utils.verb_conjugation.tests.rst diff --git a/docs/source/Components/FuncParser.md b/docs/source/Components/FuncParser.md index d0d19329ff..696b20c3be 100644 --- a/docs/source/Components/FuncParser.md +++ b/docs/source/Components/FuncParser.md @@ -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=) 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=, raise_errrors=False) +_test('foo', bar='4', mydefault=2, myreserved=[1, 2, 3], + 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=, 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 ------- -``` \ No newline at end of file + This is the current uptime: + ------- 343 seconds ------- \ No newline at end of file diff --git a/docs/source/Concepts/Clickable-Links.md b/docs/source/Concepts/Clickable-Links.md new file mode 100644 index 0000000000..b29065e9e2 --- /dev/null +++ b/docs/source/Concepts/Clickable-Links.md @@ -0,0 +1,21 @@ +## Clickable links + +Evennia supports clickable links for clients that supports it. This marks certain text so it can be +clicked by a mouse and trigger a given Evennia command. To support clickable links, Evennia requires +the webclient or an third-party telnet client with [MXP](http://www.zuggsoft.com/zmud/mxp.htm) +support (*Note: Evennia only supports clickable links, no other MXP features*). + +- `|lc` to start the link, by defining the command to execute. +- `|lt` to continue with the text to show to the user (the link text). +- `|le` to end the link text and the link definition. + +All elements must appear in exactly this order to make a valid link. For example, + +``` +"If you go |lcnorth|ltto the north|le you will find a cottage." +``` + +This will display as "If you go __to the north__ you will find a cottage." where clicking the link +will execute the command `north`. If the client does not support clickable links, only the link text +will be shown. + diff --git a/docs/source/Concepts/Colors.md b/docs/source/Concepts/Colors.md new file mode 100644 index 0000000000..64b2f9a319 --- /dev/null +++ b/docs/source/Concepts/Colors.md @@ -0,0 +1,183 @@ +# Colors + +*Note that the Documentation does not display colour the way it would look on the screen.* + +Color can be a very useful tool for your game. It can be used to increase readability and make your +game more appealing visually. + +Remember however that, with the exception of the webclient, you generally don't control the client +used to connect to the game. There is, for example, one special tag meaning "yellow". But exactly +*which* hue of yellow is actually displayed on the user's screen depends on the settings of their +particular mud client. They could even swap the colours around or turn them off altogether if so +desired. Some clients don't even support color - text games are also played with special reading +equipment by people who are blind or have otherwise diminished eyesight. + +So a good rule of thumb is to use colour to enhance your game but don't *rely* on it to display +critical information. If you are coding the game, you can add functionality to let users disable +colours as they please, as described [here](../Howto/Manually-Configuring-Color). + +To see which colours your client support, use the default `@color` command. This will list all +available colours for ANSI and Xterm256 along with the codes you use for them. You can find a list +of all the parsed `ANSI`-colour codes in `evennia/utils/ansi.py`. + +### ANSI colours + +Evennia supports the `ANSI` standard for text. This is by far the most supported MUD-color standard, +available in all but the most ancient mud clients. The ANSI colours are **r**ed, **g**reen, +**y**ellow, **b**lue, **m**agenta, **c**yan, **w**hite and black. They are abbreviated by their +first letter except for black which is abbreviated with the letter **x**. In ANSI there are "bright" +and "normal" (darker) versions of each color, adding up to a total of 16 colours to use for +foreground text. There are also 8 "background" colours. These have no bright alternative in ANSI +(but Evennia uses the [Xterm256](./TextTags#xterm256-colours) extension behind the scenes to offer +them anyway). + +To colour your text you put special tags in it. Evennia will parse these and convert them to the +correct markup for the client used. If the user's client/console/display supports ANSI colour, they +will see the text in the specified colour, otherwise the tags will be stripped (uncolored text). +This works also for non-terminal clients, such as the webclient. For the webclient, Evennia will +translate the codes to HTML RGB colors. + +Here is an example of the tags in action: + + |rThis text is bright red.|n This is normal text. + |RThis is a dark red text.|n This is normal text. + |[rThis text has red background.|n This is normal text. + |b|[yThis is bright blue text on yellow background.|n This is normal text. + +- `|n` - this tag will turn off all color formatting, including background colors. +- `|#`- markup marks the start of foreground color. The case defines if the text is "bright" or +"normal". So `|g` is a bright green and `|G` is "normal" (darker) green. +- `|[#` is used to add a background colour to the text. The case again specifies if it is "bright" +or "normal", so `|[c` starts a bright cyan background and `|[C` a darker cyan background. +- `|!#` is used to add foreground color without any enforced brightness/normal information. + These are normal-intensity and are thus always given as uppercase, such as + `|!R` for red. The difference between e.g. `|!R` and `|R` is that + `|!R` will "inherit" the brightness setting from previously set color tags, whereas `|R` will +always reset to the normal-intensity red. The `|#` format contains an implicit `|h`/`|H` tag in it: +disabling highlighting when switching to a normal color, and enabling it for bright ones. So `|btest +|!Rtest2` will result in a bright red `test2` since the brightness setting from `|b` "bleeds over". +You could use this to for example quickly switch the intensity of a multitude of color tags. There +is no background-color equivalent to `|!` style tags. +- `|h` is used to make any following foreground ANSI colors bright (it has no effect on Xterm +colors). This is only relevant to use with `|!` type tags and will be valid until the next `|n`, +`|H` or normal (upper-case) `|#` tag. This tag will never affect background colors, those have to be +set bright/normal explicitly. Technically, `|h|!G` is identical to `|g`. +- `|H` negates the effects `|h` and returns all ANSI foreground colors (`|!` and `|` types) to +'normal' intensity. It has no effect on background and Xterm colors. + +> Note: The ANSI standard does not actually support bright backgrounds like `|[r` - the standard +only supports "normal" intensity backgrounds. To get around this Evennia instead implements these +as [Xterm256 colours](./TextTags#xterm256-colours) behind the scenes. If the client does not support +Xterm256 the ANSI colors will be used instead and there will be no visible difference between using +upper- and lower-case background tags. + +If you want to display an ANSI marker as output text (without having any effect), you need to escape +it by preceding its `|` with another `|`: + +``` +say The ||r ANSI marker changes text color to bright red. +``` + +This will output the raw `|r` without any color change. This can also be necessary if you are doing +ansi art that uses `|` with a letter directly following it. + +Use the command + + @color ansi + +to get a list of all supported ANSI colours and the tags used to produce them. + +A few additional ANSI codes are supported: + +- `|/` A line break. You cannot put the normal Python `\n` line breaks in text entered inside the +game (Evennia will filter this for security reasons). This is what you use instead: use the `|/` +marker to format text with line breaks from the game command line. +- `` This will translate into a `TAB` character. This will not always show (or show differently) to +the client since it depends on their local settings. It's often better to use multiple spaces. +- `|_` This is a space. You can usually use the normal space character, but if the space is *at the +end of the line*, Evennia will likely crop it. This tag will not be cropped but always result in a +space. +- `|*` This will invert the current text/background colours. Can be useful to mark things (but see +below). + +##### Caveats of `|*` + +The `|*` tag (inverse video) is an old ANSI standard and should usually not be used for more than to +mark short snippets of text. If combined with other tags it comes with a series of potentially +confusing behaviors: + +* The `|*` tag will only work once in a row:, ie: after using it once it won't have an effect again +until you declare another tag. This is an example: + + ``` + Normal text, |*reversed text|*, still reversed text. + ``` + + that is, it will not reverse to normal at the second `|*`. You need to reset it manually: + + ``` + Normal text, |*reversed text|n, normal again. + ``` + +* The `|*` tag does not take "bright" colors into account: + + ``` + |RNormal red, |hnow brightened. |*BG is normal red. + ``` + + So `|*` only considers the 'true' foreground color, ignoring any highlighting. Think of the bright +state (`|h`) as something like like `` in HTML: it modifies the _appearance_ of a normal +foreground color to match its bright counterpart, without changing its normal color. +* Finally, after a `|*`, if the previous background was set to a dark color (via `|[`), `|!#`) will +actually change the background color instead of the foreground: + + ``` + |*reversed text |!R now BG is red. + ``` +For a detailed explanation of these caveats, see the [Understanding Color Tags](Understanding-Color- +Tags) tutorial. But most of the time you might be better off to simply avoid `|*` and mark your text +manually instead. + +### Xterm256 Colours + +The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background. +While this offers many more possibilities than traditional ANSI colours, be wary that too many text +colors will be confusing to the eye. Also, not all clients support Xterm256 - these will instead see +the closest equivalent ANSI color. You can mix Xterm256 tags with ANSI tags as you please. + + |555 This is pure white text.|n This is normal text. + |230 This is olive green text. + |[300 This text has a dark red background. + |005|[054 This is dark blue text on a bright cyan background. + |=a This is a greyscale value, equal to black. + |=m This is a greyscale value, midway between white and black. + |=z This is a greyscale value, equal to white. + |[=m This is a background greyscale value. + +- `|###` - markup consists of three digits, each an integer from 0 to 5. The three digits describe +the amount of **r**ed, **g**reen and **b**lue (RGB) components used in the colour. So `|500` means +maximum red and none of the other colours - the result is a bright red. `|520` is red with a touch +of green - the result is orange. As opposed to ANSI colors, Xterm256 syntax does not worry about +bright/normal intensity, a brighter (lighter) color is just achieved by upping all RGB values with +the same amount. +- `|[###` - this works the same way but produces a coloured background. +- `|=#` - markup produces the xterm256 gray scale tones, where `#` is a letter from `a` (black) to +`z` (white). This offers many more nuances of gray than the normal `|###` markup (which only has +four gray tones between solid black and white (`|000`, `|111`, `|222`, `|333` and `|444`)). +- `|[=#` - this works in the same way but produces background gray scale tones. + +If you have a client that supports Xterm256, you can use + + @color xterm256 + +to get a table of all the 256 colours and the codes that produce them. If the table looks broken up +into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement. +You can use the `@options` command to see if xterm256 is active for you. This depends on if your +client told Evennia what it supports - if not, and you know what your client supports, you may have +to activate some features manually. + +## More reading + +There is an [Understanding Color Tags](../Howto/Understanding-Color-Tags) tutorial which expands on the +use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context. + diff --git a/docs/source/Concepts/TextTags.md b/docs/source/Concepts/TextTags.md index 6c2c6fae3e..94d13fe22e 100644 --- a/docs/source/Concepts/TextTags.md +++ b/docs/source/Concepts/TextTags.md @@ -1,340 +1,19 @@ -# TextTags - - -This documentation details the various text tags supported by Evennia, namely *colours*, *command -links* and *inline functions*. - -There is also an [Understanding Color Tags](../Howto/Understanding-Color-Tags) tutorial which expands on the -use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context. - -## Coloured text - -*Note that the Documentation does not display colour the way it would look on the screen.* - -Color can be a very useful tool for your game. It can be used to increase readability and make your -game more appealing visually. - -Remember however that, with the exception of the webclient, you generally don't control the client -used to connect to the game. There is, for example, one special tag meaning "yellow". But exactly -*which* hue of yellow is actually displayed on the user's screen depends on the settings of their -particular mud client. They could even swap the colours around or turn them off altogether if so -desired. Some clients don't even support color - text games are also played with special reading -equipment by people who are blind or have otherwise diminished eyesight. - -So a good rule of thumb is to use colour to enhance your game but don't *rely* on it to display -critical information. If you are coding the game, you can add functionality to let users disable -colours as they please, as described [here](../Howto/Manually-Configuring-Color). - -To see which colours your client support, use the default `@color` command. This will list all -available colours for ANSI and Xterm256 along with the codes you use for them. You can find a list -of all the parsed `ANSI`-colour codes in `evennia/utils/ansi.py`. - -### ANSI colours - -Evennia supports the `ANSI` standard for text. This is by far the most supported MUD-color standard, -available in all but the most ancient mud clients. The ANSI colours are **r**ed, **g**reen, -**y**ellow, **b**lue, **m**agenta, **c**yan, **w**hite and black. They are abbreviated by their -first letter except for black which is abbreviated with the letter **x**. In ANSI there are "bright" -and "normal" (darker) versions of each color, adding up to a total of 16 colours to use for -foreground text. There are also 8 "background" colours. These have no bright alternative in ANSI -(but Evennia uses the [Xterm256](./TextTags#xterm256-colours) extension behind the scenes to offer -them anyway). - -To colour your text you put special tags in it. Evennia will parse these and convert them to the -correct markup for the client used. If the user's client/console/display supports ANSI colour, they -will see the text in the specified colour, otherwise the tags will be stripped (uncolored text). -This works also for non-terminal clients, such as the webclient. For the webclient, Evennia will -translate the codes to HTML RGB colors. - -Here is an example of the tags in action: - - |rThis text is bright red.|n This is normal text. - |RThis is a dark red text.|n This is normal text. - |[rThis text has red background.|n This is normal text. - |b|[yThis is bright blue text on yellow background.|n This is normal text. - -- `|n` - this tag will turn off all color formatting, including background colors. -- `|#`- markup marks the start of foreground color. The case defines if the text is "bright" or -"normal". So `|g` is a bright green and `|G` is "normal" (darker) green. -- `|[#` is used to add a background colour to the text. The case again specifies if it is "bright" -or "normal", so `|[c` starts a bright cyan background and `|[C` a darker cyan background. -- `|!#` is used to add foreground color without any enforced brightness/normal information. - These are normal-intensity and are thus always given as uppercase, such as - `|!R` for red. The difference between e.g. `|!R` and `|R` is that - `|!R` will "inherit" the brightness setting from previously set color tags, whereas `|R` will -always reset to the normal-intensity red. The `|#` format contains an implicit `|h`/`|H` tag in it: -disabling highlighting when switching to a normal color, and enabling it for bright ones. So `|btest -|!Rtest2` will result in a bright red `test2` since the brightness setting from `|b` "bleeds over". -You could use this to for example quickly switch the intensity of a multitude of color tags. There -is no background-color equivalent to `|!` style tags. -- `|h` is used to make any following foreground ANSI colors bright (it has no effect on Xterm -colors). This is only relevant to use with `|!` type tags and will be valid until the next `|n`, -`|H` or normal (upper-case) `|#` tag. This tag will never affect background colors, those have to be -set bright/normal explicitly. Technically, `|h|!G` is identical to `|g`. -- `|H` negates the effects `|h` and returns all ANSI foreground colors (`|!` and `|` types) to -'normal' intensity. It has no effect on background and Xterm colors. - -> Note: The ANSI standard does not actually support bright backgrounds like `|[r` - the standard -only supports "normal" intensity backgrounds. To get around this Evennia instead implements these -as [Xterm256 colours](./TextTags#xterm256-colours) behind the scenes. If the client does not support -Xterm256 the ANSI colors will be used instead and there will be no visible difference between using -upper- and lower-case background tags. - -If you want to display an ANSI marker as output text (without having any effect), you need to escape -it by preceding its `|` with another `|`: - -``` -say The ||r ANSI marker changes text color to bright red. -``` - -This will output the raw `|r` without any color change. This can also be necessary if you are doing -ansi art that uses `|` with a letter directly following it. - -Use the command - - @color ansi - -to get a list of all supported ANSI colours and the tags used to produce them. - -A few additional ANSI codes are supported: - -- `|/` A line break. You cannot put the normal Python `\n` line breaks in text entered inside the -game (Evennia will filter this for security reasons). This is what you use instead: use the `|/` -marker to format text with line breaks from the game command line. -- `` This will translate into a `TAB` character. This will not always show (or show differently) to -the client since it depends on their local settings. It's often better to use multiple spaces. -- `|_` This is a space. You can usually use the normal space character, but if the space is *at the -end of the line*, Evennia will likely crop it. This tag will not be cropped but always result in a -space. -- `|*` This will invert the current text/background colours. Can be useful to mark things (but see -below). - -##### Caveats of `|*` - -The `|*` tag (inverse video) is an old ANSI standard and should usually not be used for more than to -mark short snippets of text. If combined with other tags it comes with a series of potentially -confusing behaviors: - -* The `|*` tag will only work once in a row:, ie: after using it once it won't have an effect again -until you declare another tag. This is an example: - - ``` - Normal text, |*reversed text|*, still reversed text. - ``` - - that is, it will not reverse to normal at the second `|*`. You need to reset it manually: - - ``` - Normal text, |*reversed text|n, normal again. - ``` - -* The `|*` tag does not take "bright" colors into account: - - ``` - |RNormal red, |hnow brightened. |*BG is normal red. - ``` - - So `|*` only considers the 'true' foreground color, ignoring any highlighting. Think of the bright -state (`|h`) as something like like `` in HTML: it modifies the _appearance_ of a normal -foreground color to match its bright counterpart, without changing its normal color. -* Finally, after a `|*`, if the previous background was set to a dark color (via `|[`), `|!#`) will -actually change the background color instead of the foreground: - - ``` - |*reversed text |!R now BG is red. - ``` -For a detailed explanation of these caveats, see the [Understanding Color Tags](Understanding-Color- -Tags) tutorial. But most of the time you might be better off to simply avoid `|*` and mark your text -manually instead. - -### Xterm256 Colours - -The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background. -While this offers many more possibilities than traditional ANSI colours, be wary that too many text -colors will be confusing to the eye. Also, not all clients support Xterm256 - these will instead see -the closest equivalent ANSI color. You can mix Xterm256 tags with ANSI tags as you please. - - |555 This is pure white text.|n This is normal text. - |230 This is olive green text. - |[300 This text has a dark red background. - |005|[054 This is dark blue text on a bright cyan background. - |=a This is a greyscale value, equal to black. - |=m This is a greyscale value, midway between white and black. - |=z This is a greyscale value, equal to white. - |[=m This is a background greyscale value. - -- `|###` - markup consists of three digits, each an integer from 0 to 5. The three digits describe -the amount of **r**ed, **g**reen and **b**lue (RGB) components used in the colour. So `|500` means -maximum red and none of the other colours - the result is a bright red. `|520` is red with a touch -of green - the result is orange. As opposed to ANSI colors, Xterm256 syntax does not worry about -bright/normal intensity, a brighter (lighter) color is just achieved by upping all RGB values with -the same amount. -- `|[###` - this works the same way but produces a coloured background. -- `|=#` - markup produces the xterm256 gray scale tones, where `#` is a letter from `a` (black) to -`z` (white). This offers many more nuances of gray than the normal `|###` markup (which only has -four gray tones between solid black and white (`|000`, `|111`, `|222`, `|333` and `|444`)). -- `|[=#` - this works in the same way but produces background gray scale tones. - -If you have a client that supports Xterm256, you can use - - @color xterm256 - -to get a table of all the 256 colours and the codes that produce them. If the table looks broken up -into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement. -You can use the `@options` command to see if xterm256 is active for you. This depends on if your -client told Evennia what it supports - if not, and you know what your client supports, you may have -to activate some features manually. - -## Clickable links - -Evennia supports clickable links for clients that supports it. This marks certain text so it can be -clicked by a mouse and trigger a given Evennia command. To support clickable links, Evennia requires -the webclient or an third-party telnet client with [MXP](http://www.zuggsoft.com/zmud/mxp.htm) -support (*Note: Evennia only supports clickable links, no other MXP features*). - - - `|lc` to start the link, by defining the command to execute. - - `|lt` to continue with the text to show to the user (the link text). - - `|le` to end the link text and the link definition. - -All elements must appear in exactly this order to make a valid link. For example, - -``` -"If you go |lcnorth|ltto the north|le you will find a cottage." -``` - -This will display as "If you go __to the north__ you will find a cottage." where clicking the link -will execute the command `north`. If the client does not support clickable links, only the link text -will be shown. - -## Inline functions - -> Note: Inlinefuncs are **not** activated by default. To use them you need to add -`INLINEFUNC_ENABLED=True` to your settings file. - -Evennia has its own inline text formatting language, known as *inlinefuncs*. It allows the builder -to include special function calls in code. They are executed dynamically by each session that -receives them. - -To add an inlinefunc, you embed it in a text string like this: - -``` -"A normal string with $funcname(arg, arg, ...) embedded inside it." -``` - -When this string is sent to a session (with the `msg()` method), these embedded inlinefuncs will be -parsed. Their return value (which always is a string) replace their call location in the finalized -string. The interesting thing with this is that the function called will have access to which -session is seeing the string, meaning the string can end up looking different depending on who is -looking. It could of course also vary depending on other factors like game time. - -Any number of comma-separated arguments can be given (or none). No keywords are supported. You can -also nest inlinefuncs by letting an argument itself also be another `$funcname(arg, arg, ...)` call -(down to any depth of nesting). Function call resolution happens as in all programming languages -inside-out, with the nested calls replacing the argument with their return strings before calling he -parent. - -``` - > say "This is $pad(a center-padded text, 30,c,-) of width 30." - You say, "This is ---- a center-padded text----- of width 30." -``` - -A special case happens if wanting to use an inlinefunc argument that itself includes a comma - this -would be parsed as an argument separator. To escape commas you can either escape each comma manually -with a backslash `\,`, or you can embed the entire string in python triple-quotes `"""` or `'''` - -this will escape the entire argument, including commas and any nested inlinefunc calls within. - -Only certain functions are available to use as inlinefuncs and the game developer may add their own -functions as needed. - -### New inlinefuncs - -To add new inlinefuncs, edit the file `mygame/server/conf/inlinefuncs.py`. - -*All globally defined functions in this module* are considered inline functions by the system. The -only exception is functions whose name starts with an underscore `_`. An inlinefunc must be of the -following form: - -```python -def funcname(*args, **kwargs): - # ... - return modified_text -``` - -where `*args` denotes all the arguments this function will accept as an `$inlinefunc`. The inline -function is expected to clean arguments and check that they are valid. If needed arguments are not -given, default values should be used. The function should always return a string (even if it's -empty). An inlinefunc should never cause a traceback regardless of the input (but it could log -errors if desired). - -Note that whereas the function should accept `**kwargs`, keyword inputs are *not* usable in the call -to the inlinefunction. The `kwargs` part is instead intended for Evennia to be able to supply extra -information. Currently Evennia sends a single keyword to every inline function and that is -`session`, which holds the [serversession](../Components/Sessions) this text is targeted at. Through the session -object, a lot of dynamic possibilities are opened up for your inline functions. - -The `settings.INLINEFUNC_MODULES` configuration option is a list that decides which modules should -be parsed for inline function definitions. This will include `mygame/server/conf/inlinefuncs.py` but -more could be added. The list is read from left to right so if you want to overload default -functions you just have to put your custom module-paths later in the list and name your functions -the same as default ones. - -Here is an example, the `crop` default inlinefunction: - -```python -from evennia.utils import utils - -def crop(*args, **kwargs): - """ - Inlinefunc. Crops ingoing text to given widths. - Args: - text (str, optional): Text to crop. - width (str, optional): Will be converted to an integer. Width of - crop in characters. - suffix (str, optional): End string to mark the fact that a part - of the string was cropped. Defaults to `[...]`. - Kwargs: - session (Session): Session performing the crop. - Example: - `$crop(text, 50, [...])` - - """ - text, width, suffix = "", 78, "[...]" - nargs = len(args) - if nargs > 0: - text = args[0] - if nargs > 1: - width = int(args[1]) if args[1].strip().isdigit() else 78 - if nargs > 2: - suffix = args[2] - return utils.crop(text, width=width, suffix=suffix) -``` -Another example, making use of the Session: - -```python -def charactername(*args, **kwargs): - """ - Inserts the character name of whomever sees the string - (so everyone will see their own name). Uses the account - name for OOC communications. - - Example: - say "This means YOU, $charactername()!" - - """ - session = kwargs["session"] - if session.puppet: - return kwargs["session"].puppet.key - else: - return session.account.key -``` - -Evennia itself offers the following default inline functions (mostly as examples): - -* `crop(text, width, suffix)` - See above. -* `pad(text, width, align, fillchar)` - this pads the text to `width` (default 78), alignment ("c", -"l" or "r", defaulting to "c") and fill-in character (defaults to space). Example: `$pad(40,l,-)` -* `clr(startclr, text, endclr)` - A programmatic way to enter colored text for those who don't want -to use the normal `|c` type color markers for some reason. The `color` argument is the same as the -color markers except without the actual pre-marker, so `|r` would be just `r`. If `endclr` is not -given, it defaults to resetting the color (`n`). Example: `$clr(b, A blue text)` -* `space(number)` - Inserts the given number of spaces. If no argument is given, use 4 spaces. \ No newline at end of file +# In-text tags parsed by Evennia + +Evennia understands various extra information embedded in text: + +- [Colors](./Colors) - Using `|r`, `|n` etc can be used to mark parts of text with a color. The color will + become ANSI/XTerm256 color tags for Telnet connections and CSS information for the webclient. +- [Clickable links](./Clickable-Links) - This allows you to provide a text the user can click to execute an + in-game command. This is on the form `|lc command |lt text |le`. +- [FuncParser callables](../Components/FuncParser) - These are full-fledged function calls on the form `$funcname(args, kwargs)` + that lead to calls to Python functions. The parser can be run with different available callables in different + circumstances. The parser is run on all outgoing messages if `settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED=True` + (disabled by default). + +```toctree:: + + Colors.md + Clickable-Links.md + ../Components/FuncParser.md +``` \ No newline at end of file diff --git a/docs/source/Evennia-API.md b/docs/source/Evennia-API.md index 421cfa660c..ab405079a2 100644 --- a/docs/source/Evennia-API.md +++ b/docs/source/Evennia-API.md @@ -77,6 +77,7 @@ The flat API is defined in `__init__.py` [viewable here](github:evennia/__init__ - [evennia.EvForm](api:evennia.utils.evform#evennia.utils.evform.EvForm) - text form creator - Evennia.EvMore - text paginator - [evennia.EvEditor](api:evennia.utils.eveditor#evennia.utils.eveditor.EvEditor) - in game text line editor ([docs](Components/EvEditor)) +- [evennia.utils.funcparser.Funcparser](api:evennia.utils.funcparser) - inline parsing of functions ([docs](Components/FuncParser)) ### Global singleton handlers diff --git a/docs/source/api/evennia.utils.funcparser.rst b/docs/source/api/evennia.utils.funcparser.rst new file mode 100644 index 0000000000..151811e406 --- /dev/null +++ b/docs/source/api/evennia.utils.funcparser.rst @@ -0,0 +1,7 @@ +evennia.utils.funcparser +=============================== + +.. automodule:: evennia.utils.funcparser + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/evennia.utils.inlinefuncs.rst b/docs/source/api/evennia.utils.inlinefuncs.rst deleted file mode 100644 index f11abd4776..0000000000 --- a/docs/source/api/evennia.utils.inlinefuncs.rst +++ /dev/null @@ -1,7 +0,0 @@ -evennia.utils.inlinefuncs -================================ - -.. automodule:: evennia.utils.inlinefuncs - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/evennia.utils.rst b/docs/source/api/evennia.utils.rst index 12894e85f0..32653d5c5f 100644 --- a/docs/source/api/evennia.utils.rst +++ b/docs/source/api/evennia.utils.rst @@ -21,8 +21,8 @@ evennia.utils evennia.utils.evmenu evennia.utils.evmore evennia.utils.evtable + evennia.utils.funcparser evennia.utils.gametime - evennia.utils.inlinefuncs evennia.utils.logger evennia.utils.optionclasses evennia.utils.optionhandler @@ -38,3 +38,4 @@ evennia.utils :maxdepth: 6 evennia.utils.idmapper + evennia.utils.verb_conjugation diff --git a/docs/source/api/evennia.utils.verb_conjugation.conjugate.rst b/docs/source/api/evennia.utils.verb_conjugation.conjugate.rst new file mode 100644 index 0000000000..5f60d2e589 --- /dev/null +++ b/docs/source/api/evennia.utils.verb_conjugation.conjugate.rst @@ -0,0 +1,7 @@ +evennia.utils.verb\_conjugation.conjugate +================================================ + +.. automodule:: evennia.utils.verb_conjugation.conjugate + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/evennia.utils.verb_conjugation.rst b/docs/source/api/evennia.utils.verb_conjugation.rst new file mode 100644 index 0000000000..ab6df6f377 --- /dev/null +++ b/docs/source/api/evennia.utils.verb_conjugation.rst @@ -0,0 +1,15 @@ +evennia.utils.verb\_conjugation +======================================= + +.. automodule:: evennia.utils.verb_conjugation + :members: + :undoc-members: + :show-inheritance: + + + +.. toctree:: + :maxdepth: 6 + + evennia.utils.verb_conjugation.conjugate + evennia.utils.verb_conjugation.tests diff --git a/docs/source/api/evennia.utils.verb_conjugation.tests.rst b/docs/source/api/evennia.utils.verb_conjugation.tests.rst new file mode 100644 index 0000000000..a61eb6e472 --- /dev/null +++ b/docs/source/api/evennia.utils.verb_conjugation.tests.rst @@ -0,0 +1,7 @@ +evennia.utils.verb\_conjugation.tests +============================================ + +.. automodule:: evennia.utils.verb_conjugation.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/toc.md b/docs/source/toc.md index e216d8b87c..fa1a27bec0 100644 --- a/docs/source/toc.md +++ b/docs/source/toc.md @@ -1,5 +1,5 @@ # Toc - +- [API root](api/evennia-api.rst) - [Coding/Coding Introduction](Coding/Coding-Introduction) - [Coding/Coding Overview](Coding/Coding-Overview) - [Coding/Continuous Integration](Coding/Continuous-Integration) @@ -53,6 +53,8 @@ - [Concepts/Banning](Concepts/Banning) - [Concepts/Bootstrap & Evennia](Concepts/Bootstrap-&-Evennia) - [Concepts/Building Permissions](Concepts/Building-Permissions) +- [Concepts/Clickable Links](Concepts/Clickable-Links) +- [Concepts/Colors](Concepts/Colors) - [Concepts/Concepts Overview](Concepts/Concepts-Overview) - [Concepts/Custom Protocols](Concepts/Custom-Protocols) - [Concepts/Guest Logins](Concepts/Guest-Logins) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 5ea451b0af..cca069c298 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -2384,7 +2384,7 @@ class CmdExamine(ObjManipCommand): """ global _FUNCPARSER if not _FUNCPARSER: - _FUNCPARSER = funcparser.FuncParser(settings.INLINEFUNC_MODULES) + _FUNCPARSER = funcparser.FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES) if attr is None: return "No such attribute was found." @@ -3464,7 +3464,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): "Python structures are allowed. \nMake sure to use correct " "Python syntax. Remember especially to put quotes around all " "strings inside lists and dicts.|n For more advanced uses, embed " - "inlinefuncs in the strings." + "funcparser callables ($funcs) in the strings." ) else: string = "Expected {}, got {}.".format(expect, type(prototype)) diff --git a/evennia/game_template/server/conf/inlinefuncs.py b/evennia/game_template/server/conf/inlinefuncs.py index 1190597677..c1ba9dfb08 100644 --- a/evennia/game_template/server/conf/inlinefuncs.py +++ b/evennia/game_template/server/conf/inlinefuncs.py @@ -1,17 +1,17 @@ """ -Inlinefunc +Outgoing callables to apply with the FuncParser on outgoing messages. -Inline functions allow for direct conversion of text users mark in a -special way. Inlinefuncs are deactivated by default. To activate, add +The functions in this module will become available as $funcname(args, kwargs) +in all outgoing strings if you add - INLINEFUNC_ENABLED = True + FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = True -to your settings file. The default inlinefuncs are found in -evennia.utils.inlinefunc. +to your settings file. The default inlinefuncs are found at the bottom of +`evennia.utils.funcparser`. In text, usage is straightforward: -$funcname([arg1,[arg2,...]]) +$funcname(arg1, arg2, ..., key=val, key2=val2, ...) Example 1 (using the "pad" inlinefunc): say This is $pad("a center-padded text", 50,c,-) of width 50. @@ -26,26 +26,14 @@ Example 2 (using nested "pad" and "time" inlinefuncs): To add more inline functions, add them to this module, using the following call signature: - def funcname(text, *args, **kwargs) - -where `text` is always the part between {funcname(args) and -{/funcname and the *args are taken from the appropriate part of the -call. If no {/funcname is given, `text` will be the empty string. - -It is important that the inline function properly clean the -incoming `args`, checking their type and replacing them with sane -defaults if needed. If impossible to resolve, the unmodified text -should be returned. The inlinefunc should never cause a traceback. - -While the inline function should accept **kwargs, the keyword is -never accepted as a valid call - this is only intended to be used -internally by Evennia, notably to send the `session` keyword to -the function; this is the session of the object viewing the string -and can be used to customize it to each session. + def funcname(*args, **kwargs) + ... """ -# def capitalize(text, *args, **kwargs): -# "Silly capitalize example. Used as {capitalize() ... {/capitalize" +# def capitalize(*args, **kwargs): +# "Silly capitalize example. Used as $capitalize +# if not args: +# return '' # session = kwargs.get("session") -# return text.capitalize() +# return args[0].capitalize() diff --git a/evennia/prototypes/protfuncs.py b/evennia/prototypes/protfuncs.py index bde784f60e..c84ab579c8 100644 --- a/evennia/prototypes/protfuncs.py +++ b/evennia/prototypes/protfuncs.py @@ -45,7 +45,12 @@ def protfunc_callable_protkey(*args, **kwargs): prototype = kwargs.get("prototype", {}) prot_value = prototype[args[0]] - return funcparser.funcparser_callable_eval(prot_value, **kwargs) + try: + return funcparser.funcparser_callable_eval(prot_value, **kwargs) + except funcparser.ParsingError: + return prot_value + + # this is picked up by FuncParser diff --git a/evennia/server/deprecations.py b/evennia/server/deprecations.py index fd6a7fbc85..c4a79feac2 100644 --- a/evennia/server/deprecations.py +++ b/evennia/server/deprecations.py @@ -59,6 +59,21 @@ def check_errors(settings): "Update your settings file (see evennia/settings_default.py " "for more info)." ) + depstring = ( + "settings.{} was renamed to {}. Update your settings file (the FuncParser " + "replaces and generalizes that which inlinefuncs used to do).") + if hasattr(settings, "INLINEFUNC_ENABLED"): + raise DeprecationWarning(depstring.format( + "settings.INLINEFUNC_ENABLED", "FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLE")) + if hasattr(settings, "INLINEFUNC_STACK_MAXSIZE"): + raise DeprecationWarning(depstring.format( + "settings.INLINEFUNC_STACK_MAXSIZE", "FUNCPARSER_MAX_NESTING")) + if hasattr(settings, "INLINEFUNC_MODULES"): + raise DeprecationWarning(depstring.format( + "settings.INLINEFUNC_MODULES", "FUNCPARSER_OUTGOING_MESSAGES_MODULES")) + if hasattr(settings, "PROTFUNC_MODULES"): + raise DeprecationWarning(depstring.format( + "settings.PROTFUNC_MODULES", "FUNCPARSER_PROTOTYPE_VALUE_MODULES")) gametime_deprecation = ( "The settings TIME_SEC_PER_MIN, TIME_MIN_PER_HOUR," diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index dafa4c2245..6f1aeebe5b 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -28,10 +28,9 @@ from evennia.utils.utils import ( from evennia.server.portal import amp from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT -# from evennia.utils.inlinefuncs import parse_inlinefunc from codecs import decode as codecs_decode -_INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED +_FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED # delayed imports _AccountDB = None @@ -154,7 +153,8 @@ class SessionHandler(dict): def clean_senddata(self, session, kwargs): """ - Clean up data for sending across the AMP wire. Also apply INLINEFUNCS. + Clean up data for sending across the AMP wire. Also apply the + FuncParser using callables from `settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES`. Args: session (Session): The relevant session instance. @@ -170,14 +170,14 @@ class SessionHandler(dict): Returns: kwargs (dict): A cleaned dictionary of cmdname:[[args],{kwargs}] pairs, where the keys, args and kwargs have all been converted to - send-safe entities (strings or numbers), and inlinefuncs have been + send-safe entities (strings or numbers), and funcparser parsing has been applied. """ global _FUNCPARSER if not _FUNCPARSER: from evennia.utils.funcparser import FuncParser - _FUNCPARSER = FuncParser(settings.INLINEFUNC_MODULES, raise_errors=True) + _FUNCPARSER = FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULE, raise_errors=True) options = kwargs.pop("options", None) or {} raw = options.get("raw", False) @@ -210,8 +210,8 @@ class SessionHandler(dict): elif isinstance(data, (str, bytes)): data = _utf8(data) - if _INLINEFUNC_ENABLED and not raw and isinstance(self, ServerSessionHandler): - # only parse inlinefuncs on the outgoing path (sessionhandler->) + if _FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED and not raw and isinstance(self, ServerSessionHandler): + # only apply funcparser on the outgoing path (sessionhandler->) # data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) data = _FUNCPARSER.parse(data, strip=strip_inlinefunc, session=session) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 5fe08f02c1..d848cb5796 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -598,23 +598,31 @@ TIME_GAME_EPOCH = None TIME_IGNORE_DOWNTIMES = False ###################################################################### -# Inlinefunc, PrototypeFuncs +# FuncParser +# +# Strings parsed with the FuncParser can contain 'callables' on the +# form $funcname(args,kwargs), which will lead to actual Python functions +# being executed. ###################################################################### -# Evennia supports inline function preprocessing. This allows users -# to supply inline calls on the form $func(arg, arg, ...) to do -# session-aware text formatting and manipulation on the fly. If -# disabled, such inline functions will not be parsed. -INLINEFUNC_ENABLED = False -# This defined how deeply nested inlinefuncs can be. Set to <=0 to -# disable (not recommended, this is a safeguard against infinite loops). -INLINEFUNC_STACK_MAXSIZE = 20 +# This changes the start-symbol for the funcparser callable. Note that +# this will make a lot of documentation invalid and there may also be +# other unexpected side effects, so change with caution. +FUNCPARSER_START_CHAR = '$' +# The symbol to use to escape Func +FUNCPARSER_ESCAPE_CHAR = '\\' +# This is the global max nesting-level for nesting functions in +# the funcparser. This protects against infinite loops. +FUNCPARSER_MAX_NESTING = 20 +# Activate funcparser for all outgoing strings. The current Session +# will be passed into the parser (used to be called inlinefuncs) +FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = False # Only functions defined globally (and not starting with '_') in # these modules will be considered valid inlinefuncs. The list # is loaded from left-to-right, same-named functions will overload -INLINEFUNC_MODULES = ["evennia.utils.funcparser", "server.conf.inlinefuncs"] -# Module holding handlers for ProtFuncs. These allow for embedding -# functional code in prototypes and has the same syntax as inlinefuncs. -PROTOTYPEFUNC_MODULES = ["evennia.prototypes.protfuncs", "server.conf.prototypefuncs"] +FUNCPARSER_OUTGOING_MESSAGES_MODULES = ["evennia.utils.funcparser", "server.conf.inlinefuncs"] +# Prototype values are also parsed with FuncParser. These modules +# define which $func callables are available to use in prototypes. +FUNCPARSER_PROTOTYPE_PARSING_MODULES = ["evennia.prototypes.protfuncs", "server.conf.prototypefuncs"] ###################################################################### # Global Scripts diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index e281467d09..2f161578f0 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -2,10 +2,13 @@ Generic function parser for functions embedded in a string, on the form `$funcname(*args, **kwargs)`, for example: - "A string $foo() with $bar(a, b, c, $moo(), d=23) etc." +``` +"A string $foo() with $bar(a, b, c, $moo(), d=23) etc." +``` -Each arg/kwarg can also be another nested function. These will be executed from -the deepest-nested first and used as arguments for the higher-level function. +Each arg/kwarg can also be another nested function. These will be executed +inside-out and their return will used as arguments for the enclosing function +(so the same as for regular Python function execution). This is the base for all forms of embedded func-parsing, like inlinefuncs and protfuncs. Each function available to use must be registered as a 'safe' @@ -23,7 +26,6 @@ def funcname(*args, **kwargs): # it must always accept *args and **kwargs. ... return something - ``` Usage: @@ -38,6 +40,7 @@ result = parser.parse("String with $funcname() in it") The `FuncParser` also accepts a direct dict mapping of `{'name': callable, ...}`. +--- """ import re @@ -53,20 +56,21 @@ from evennia.utils.utils import ( from evennia.utils import search from evennia.utils.verb_conjugation.conjugate import verb_actor_stance_components -_CLIENT_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH -_MAX_NESTING = 20 +# setup -_ESCAPE_CHAR = "\\" -_START_CHAR = "$" +_CLIENT_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH +_MAX_NESTING = settings.FUNCPARSER_MAX_NESTING +_START_CHAR = settings.FUNCPARSER_START_CHAR +_ESCAPE_CHAR = settings.FUNCPARSER_ESCAPE_CHAR @dataclasses.dataclass -class ParsedFunc: +class _ParsedFunc: """ Represents a function parsed from the string """ - prefix: str = "$" + prefix: str = _START_CHAR funcname: str = "" args: list = dataclasses.field(default_factory=list) kwargs: dict = dataclasses.field(default_factory=dict) @@ -98,7 +102,8 @@ class ParsingError(RuntimeError): class FuncParser: """ - Sets up a parser for strings containing $funcname(*args, **kwargs) substrings. + Sets up a parser for strings containing `$funcname(*args, **kwargs)` + substrings. """ @@ -113,26 +118,23 @@ class FuncParser: Args: callables (str, module, list or dict): Where to find - 'safe' functions to make available in the parser. These modules - can have a dict `FUNCPARSER_CALLABLES = {"funcname": callable, ...}`. - If no such dict exists, all callables in provided modules (whose names - don't start with an underscore) will be loaded as callables. Each - callable will will be available to call as `$funcname(*args, **kwags)` - during parsing. If `callables` is a `str`, this should be the path - to such a module. A `list` can either be a list of paths or module - objects. If a `dict`, this should be a direct mapping - `{"funcname": callable, ...}` to use. + 'safe' functions to make available in the parser. If a `dict`, + it should be a direct mapping `{"funcname": callable, ...}`. If + one or mode modules or module-paths, the module(s) are first checked + for a dict `FUNCPARSER_CALLABLES = {"funcname", callable, ...}`. If + no such variable exists, all callables in the module (whose name does + not start with an underscore) will be made available to the parser. start_char (str, optional): A character used to identify the beginning of a parseable function. Default is `$`. escape_char (str, optional): Prepend characters with this to have - them not count as a function. Default is `\\`. + them not count as a function. Default is the backtick, `\\\\`. max_nesting (int, optional): How many levels of nested function calls are allowed, to avoid exploitation. Default is 20. **default_kwargs: These kwargs will be passed into all callables. These - kwargs can be overridden both by kwargs passed direcetly to `.parse` _and_ + 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 + user. To guarantee a call always gets a particular kwarg, pass it into `.parse` as `**reserved_kwargs` instead. """ @@ -194,7 +196,7 @@ class FuncParser: Execute a parsed function Args: - parsedfunc (ParsedFunc): This dataclass holds the parsed details + parsedfunc (_ParsedFunc): This dataclass holds the parsed details of the function. raise_errors (bool, optional): Raise errors. Otherwise return the string with the function unparsed. @@ -254,9 +256,9 @@ class FuncParser: def parse(self, string, raise_errors=False, escape=False, strip=False, return_str=True, **reserved_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 - will be eligible for parsing, others will remain un-parsed. + 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 will be eligible for parsing. Args: string (str): The string to parse. @@ -357,7 +359,7 @@ class FuncParser: callstack.append(curr_func) # start a new func - curr_func = ParsedFunc(prefix=char, fullstr=char) + curr_func = _ParsedFunc(prefix=char, fullstr=char) continue if not curr_func: @@ -592,16 +594,16 @@ def funcparser_callable_eval(*args, **kwargs): incoming string into a python object. If it fails, the return will be same as the input. - Args + Args: string (str): The string to parse. Only simple literals or operators are allowed. Returns: any: The string parsed into its Python form, or the same as input. - Example: - `$py(1)` - `$py([1,2,3,4])` - `$py(3 + 4)` + Examples: + - `$py(1) -> 1` + - `$py([1,2,3,4] -> [1, 2, 3]` + - `$py(3 + 4) -> 7` """ args, kwargs = safe_convert_to_types(("py", {}) , *args, **kwargs) @@ -652,22 +654,22 @@ def _apply_operation_two_elements(*args, operator="+", **kwargs): def funcparser_callable_add(*args, **kwargs): - """Usage: $add(val1, val2) -> val1 + val2""" + """Usage: `$add(val1, val2) -> val1 + val2`""" return _apply_operation_two_elements(*args, operator='+', **kwargs) def funcparser_callable_sub(*args, **kwargs): - """Usage: $sub(val1, val2) -> val1 - val2""" + """Usage: ``$sub(val1, val2) -> val1 - val2`""" return _apply_operation_two_elements(*args, operator='-', **kwargs) def funcparser_callable_mult(*args, **kwargs): - """Usage: $mult(val1, val2) -> val1 * val2""" + """Usage: `$mult(val1, val2) -> val1 * val2`""" return _apply_operation_two_elements(*args, operator='*', **kwargs) def funcparser_callable_div(*args, **kwargs): - """Usage: $mult(val1, val2) -> val1 / val2""" + """Usage: `$mult(val1, val2) -> val1 / val2`""" return _apply_operation_two_elements(*args, operator='/', **kwargs) @@ -686,8 +688,8 @@ def funcparser_callable_round(*args, **kwargs): any: The rounded value or inp if inp was not a number. Examples: - - `$round(3.5434343, 3)` - gives 3.543 - - `$round($random(), 2)` - rounds random result, e.g 0.22 + - `$round(3.5434343, 3) -> 3.543` + - `$round($random(), 2)` - rounds random result, e.g `0.22` """ if not args: @@ -738,7 +740,7 @@ def funcparser_callable_random(*args, **kwargs): try: if isinstance(minval, float) or isinstance(maxval, float): - return minval + maxval * random.random() + return minval + ((maxval - minval) * random.random()) else: return random.randint(minval, maxval) except Exception: @@ -794,8 +796,8 @@ def funcparser_callable_pad(*args, **kwargs): fillchar (str, optional): Character used for padding. Defaults to a space. Example: - - `$pad(text, 12, l, ' ')` - - `$pad(text, width=12, align=c, fillchar=-)` + - `$pad(text, 12, r, ' ') -> " text"` + - `$pad(text, width=12, align=c, fillchar=-) -> "----text----"` """ if not args: @@ -829,8 +831,8 @@ def funcparser_callable_crop(*args, **kwargs): of the string was cropped. Defaults to `[...]`. Example: - `$crop(text, 78, [...])` - `$crop(text, width=78, suffix='[...]')` + - `$crop(A long text, 10, [...]) -> "A lon[...]"` + - `$crop(text, width=11, suffix='[...]) -> "A long[...]"` """ if not args: @@ -875,8 +877,8 @@ def funcparser_callable_justify(*args, **kwargs): str: The justified text. Examples: - - $just(text, width=40) - - $just(text, align=r, indent=2) + - `$just(text, width=40)` + - `$just(text, align=r, indent=2)` """ if not args: @@ -925,9 +927,9 @@ def funcparser_callable_clr(*args, **kwargs): color (str, optional): If given, Example: - - `$clr(r, text, n)` - - `$clr(r, text)` - - `$clr(text, start=r, end=n)` + - `$clr(r, text, n) -> "|rtext|n"` + - `$clr(r, text) -> "|rtext|n` + - `$clr(text, start=r, end=n) -> "|rtext|n"` """ if not args: @@ -961,7 +963,7 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs): Args: query (str): The key or dbref to search for. - Kwargs: + Keyword Args: return_list (bool): If set, return a list of objects with 0, 1 or more matches to `query`. Defaults to False. type (str): One of 'obj', 'account', 'script' @@ -1037,7 +1039,7 @@ def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, cap Replaces with you for the caller of the string, with the display_name of the caller for others. - Kwargs: + Keyword Args: caller (Object): The 'you' in the string. This is used unless another you-key is passed to the callable in combination with `mapping`. receiver (Object): The recipient of the string. @@ -1093,10 +1095,9 @@ def funcparser_callable_You(*args, you=None, receiver=None, mapping=None, capita def funcparser_callable_conjugate(*args, caller=None, receiver=None, **kwargs): """ - $conj(verb) - Conjugate a verb according to if it should be 2nd or third person. - Kwargs: + + Keyword Args: caller (Object): The object who represents 'you' in the string. receiver (Object): The recipient of the string. @@ -1107,13 +1108,12 @@ def funcparser_callable_conjugate(*args, caller=None, receiver=None, **kwargs): ParsingError: If `you` and `recipient` were not both supplied. Notes: - Note that it will not capitalized. - This assumes that the active party (You) is the one performing the verb. + Note that the verb will not be capitalized. It also + assumes that the active party (You) is the one performing the verb. This automatic conjugation will fail if the active part is another person - than 'you'. - The you/receiver should be passed to the parser directly. + than 'you'. The caller/receiver must be passed to the parser directly. - Exampels: + Examples: This is often used in combination with the $you/You( callables. - `With a grin, $you() $conj(jump)` diff --git a/evennia/utils/tests/test_funcparser.py b/evennia/utils/tests/test_funcparser.py index bde79b24b8..3fc41aee93 100644 --- a/evennia/utils/tests/test_funcparser.py +++ b/evennia/utils/tests/test_funcparser.py @@ -348,18 +348,19 @@ class TestDefaultCallables(TestCase): def test_random(self): string = "$random(1,10)" - ret = self.parser.parse_to_any(string, raise_errors=True) - self.assertTrue(1 <= ret <= 10) + for i in range(100): + ret = self.parser.parse_to_any(string, raise_errors=True) + self.assertTrue(1 <= ret <= 10) string = "$random()" - ret = self.parser.parse_to_any(string, raise_errors=True) - self.assertTrue(0 <= ret <= 1) + for i in range(100): + ret = self.parser.parse_to_any(string, raise_errors=True) + self.assertTrue(0 <= ret <= 1) string = "$random(1.0, 3.0)" - for i in range(1000): + for i in range(100): ret = self.parser.parse_to_any(string, raise_errors=True) self.assertTrue(isinstance(ret, float)) - print("ret:", ret) self.assertTrue(1.0 <= ret <= 3.0) def test_randint(self): diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index c4cc1cccf9..d8cab2d586 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1080,7 +1080,8 @@ def repeat(interval, callback, persistent=True, idstring="", stop=False, store_key (tuple, optional): This is only used in combination with `stop` and should be the return given from the original `repeat` call. If this is given, all other args except `stop` are ignored. - *args, **kwargs: Used as arguments to `callback`. + *args: Used as arguments to `callback`. + **kwargs: Keyword-arguments to pass to `callback`. Returns: tuple or None: The tuple is the `store_key` - the identifier for the