Merge branch 'GulliblePsychologist-argparser' into develop

This commit is contained in:
Griatch 2021-03-27 23:45:28 +01:00
commit cc352f7723
38 changed files with 12246 additions and 1728 deletions

View file

@ -0,0 +1,382 @@
# The Inline Function Parser
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
lead to a call to a Python function you control. The inline function call will be replaced by
the return from the function.
```python
from evennia.utils.funcparser import FuncParser
def _power_callable(*args, **kwargs):
"""This will be callable as $square(number, power=<num>) in string"""
pow = int(kwargs.get('power', 2))
return float(args[0]) ** pow
parser = FuncParser({"pow": _power_callable})
```
Next, just pass a string into the parser, optionally containing `$func(...)` markers:
```python
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("$pow(4)")
16
```
To show a `$func()` verbatim in your code without parsing it, escape it as either `$$func()` or `\$func()`:
```python
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
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
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
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#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
You can apply inline function parsing to any string. The
[FuncParser](api:evennia.utils.funcparser.FuncParser) is found in `evennia.utils.funcparser.py`.
```python
from evennia.utils import funcparser
parser = FuncParser(callables, **default_kwargs)
parsed_string = parser.parser(input_string, raise_errors=False,
escape=False, strip=False,
return_str=True, **reserved_kwargs)
# callables can also be passed as paths to modules
parser = FuncParser(["game.myfuncparser_callables", "game.more_funcparser_callables"])
```
Here, `callables` points to a collection of normal Python functions (see next section) for you to make
available to the parser as you parse strings with it. It can either be
- A `dict` of `{"functionname": callable, ...}`. This allows you do pick and choose exactly which callables
to include and how they should be named. Do you want a callable to be available under more than one name?
Just add it multiple times to the dict, with a different key.
- A `module` or (more commonly) a `python-path` to a module. This module can define a dict
`FUNCPARSER_CALLABLES = {"funcname": callable, ...}` - this will be imported and used like ther `dict` above.
If no such variable is defined, _every_ top-level function in the module (whose name doesn't start with
an underscore `_`) will be considered a suitable callable. The name of the function will be the `$funcname`
by which it can be called.
- A `list` of modules/paths. This allows you to pull in modules from many sources for your parsing.
The other arguments to the parser:
- `raise_errors` - By default, any errors from a callable will be quietly ignored and the result
will be that the failing function call will show verbatim. If `raise_errors` is set,
then parsing will stop and whatever exception happened will be raised. It'd be up to you to handle
this properly.
- `escape` - Returns a string where every `$func(...)` has been escaped as `\$func()`.
- `strip` - Remove all `$func(...)` calls from string (as if each returned `''`).
- `return_str` - When `True` (default), `parser` always returns a string. If `False`, it may return
the return value of a single function call in the string. This is the same as using the `.parse_to_any`
method.
- The `**default/reserved_keywords` are optional and allow you to pass custom data into _every_ function
call. This is great for including things like the current session or config options. Defaults can be
replaced if the user gives the same-named kwarg in the string's function call. Reserved kwargs are always passed,
ignoring defaults or what the user passed. In addition, the `funcparser` and `raise_errors`
reserved kwargs are always passed - the first is a back-reference to the `FuncParser` instance and the second
is the `raise_errors` boolean passed into `FuncParser.parse`.
Here's an example of using the default/reserved keywords:
```python
def _test(*args, **kwargs):
# do stuff
return something
parser = funcparser.FuncParser({"test": _test}, mydefault=2)
result = parser.parse("$test(foo, bar=4)", myreserved=[1, 2, 3])
```
Here the callable will be called as
```python
_test('foo', bar='4', mydefault=2, myreserved=[1, 2, 3],
funcparser=<FuncParser>, raise_errrors=False)
```
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
All callables made available to the parser must have the following signature:
```python
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 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).
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
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 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.
> 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:
```python
parser.parse(above_string)
"There's a 22% chance of survival."
```
However, if you use the `parse_to_any` (or `parse(..., return_str=True)`) 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)%")
"22%"
parser.parse_to_any("$toint($eval(10 * 2.2)")
22
```
### 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 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
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):
"""
A callable with a lot of custom options
$process(expression, local, extra=34, extra2=foo)
"""
args, kwargs = safe_convert_to_type(
(('py', 'py'), {'extra1': int, 'extra2': str}),
*args, **kwargs)
# args/kwargs should be correct types now
```
In other words,
```python
args, kwargs = safe_convert_to_type(
(tuple_of_arg_converters, dict_of_kwarg_converters), *args, **kwargs)
```
Each converter should be a callable taking one argument - this will be the arg/kwarg-value to convert. 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.
```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.
```
## Default 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
more to them when you create your `FuncParser` instance to have those callables be available.
### `evennia.utils.funcparser.FUNCPARSER_CALLABLES`
These are the 'base' callables.
- `$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)) -
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)) -
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
integers. Without argument, will return a float between 0 and 1.
- `$randint([start, [end]])` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_randint)) -
works like the `randint()` python function and always returns an integer.
- `$choice(list)` ([code](api:evennia.utils.funcparser#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](api:evennia.utils.funcparser#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](api:evennia.utils.funcparser#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](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_space)) -
this will insert `num` spaces.
- `$just(string, width=40, align=c, indent=2)` ([code](api:evennia.utils.funcparser#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](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`.
### `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:
```python
parser.parse_to_any(string, caller=<object or account>, access="control", ...)`
```
The `caller` is required, it's the the object to do the access-check for. The `access` kwarg is the
[lock type](./Locks) to check, default being `"control"`.
- `$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
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](api:evennia.objects.objects#evennia.objects.objects.DefaultObject.msg_contents) method
by default.
These all require extra kwargs be passed into the parser:
```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.
- `$you([key])` ([code](api:evennia.utils.funcparser#evennia.utils.funcparser.funcparser_callable_you)) -
if no `key` is given, this represents the `caller`, otherwise an object from `mapping`
will be used. As this message is sent to different recipients, the `receiver` will change and this will
be replaced either with the string `you` (if you and the receiver is the same entity) or with the
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)` ([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
Here's an example of including the default callables together with two custom ones.
```python
from evennia.utils import funcparser
from evennia.utils import gametime
def _dashline(*args, **kwargs):
if args:
return f"\n-------- {args[0]} --------"
return ''
def _uptime(*args, **kwargs):
return gametime.uptime()
callables = {
"dashline": _dashline,
"uptime": _uptime,
**funcparser.FUNCPARSER_CALLABLES,
**funcparser.ACTOR_STANCE_CALLABLES,
**funcparser.SEARCHING_CALLABLES
}
parser = funcparser.FuncParser(callables)
string = "This is the current uptime:$dashline($toint($uptime()) seconds)"
result = parser.parse(string)
```
Above we define two callables `_dashline` and `_uptime` and map them to names `"dashline"` and `"uptime"`,
which is what we then can call as `$header` and `$uptime` in the string. We also have access to
all the defaults (like `$toint()`).
The parsed result of the above would be something like this:
This is the current uptime:
------- 343 seconds -------

View file

@ -3,7 +3,7 @@
Whether due to abuse, blatant breaking of your rules, or some other reason, you will eventually find
no other recourse but to kick out a particularly troublesome player. The default command set has
admin tools to handle this, primarily `@ban`, `@unban`, and `@boot`.
admin tools to handle this, primarily `ban`, `unban`, and `boot`.
## Creating a ban
@ -16,7 +16,7 @@ have tried to be nice. Now you just want this troll gone.
The easiest recourse is to block the account YouSuck from ever connecting again.
@ban YouSuck
ban YouSuck
This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and
next time they try to log in with this name the server will not let them!
@ -24,12 +24,12 @@ next time they try to log in with this name the server will not let them!
You can also give a reason so you remember later why this was a good thing (the banned account will
never see this)
@ban YouSuck:This is just a troll.
ban YouSuck:This is just a troll.
If you are sure this is just a spam account, you might even consider deleting the player account
outright:
@delaccount YouSuck
accounts/delete YouSuck
Generally, banning the name is the easier and safer way to stop the use of an account -- if you
change your mind you can always remove the block later whereas a deletion is permanent.
@ -49,7 +49,7 @@ the `who` command, which will show you something like this:
The "Host" bit is the IP address from which the account is connecting. Use this to define the ban
instead of the name:
@ban 237.333.0.223
ban 237.333.0.223
This will stop YouSuckMore connecting from their computer. Note however that IP address might change
easily - either due to how the player's Internet Service Provider operates or by the user simply
@ -58,7 +58,7 @@ groups of three digits in the address. So if you figure out that !YouSuckMore ma
237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea
to put down a ban like this to include any number in that subnet:
@ban 237.333.0.*
ban 237.333.0.*
You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly
locked regardless of where they connect from.
@ -71,16 +71,16 @@ blocking out innocent players who just happen to connect from the same subnet as
YouSuck is not really noticing all this banning yet though - and won't until having logged out and
trying to log back in again. Let's help the troll along.
@boot YouSuck
boot YouSuck
Good riddance. You can give a reason for booting too (to be echoed to the player before getting
kicked out).
@boot YouSuck:Go troll somewhere else.
boot YouSuck:Go troll somewhere else.
### Lifting a ban
Use the `@unban` (or `@ban`) command without any arguments and you will see a list of all currently
Use the `unban` (or `ban`) command without any arguments and you will see a list of all currently
active bans:
Active bans
@ -90,7 +90,7 @@ active bans:
Use the `id` from this list to find out which ban to lift.
@unban 2
unban 2
Cleared ban 2: 237.333.0.*
@ -132,7 +132,7 @@ case) the lock to fail.
- **type thomas = FlowerPot** -- Turn an annoying player into a flower pot (assuming you have a
`FlowerPot` typeclass ready)
- **userpassword thomas = fooBarFoo** -- Change a user's password
- **delaccount thomas** -- Delete a player account (not recommended, use **ban** instead)
- **accounts/delete thomas** -- Delete a player account (not recommended, use **ban** instead)
- **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are
cached
@ -141,4 +141,4 @@ cached
- **reset** -- Restarts the server, kicking all connections
- **shutdown** -- Stops the server cold without it auto-starting again
- **py** -- Executes raw Python code, allows for direct inspection of the database and account
objects on the fly. For advanced users.
objects on the fly. For advanced users.

View file

@ -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.

View file

@ -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 `<strong>` 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.

View file

@ -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 `<strong>` 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.
# 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
```

View file

@ -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

View file

@ -0,0 +1,7 @@
evennia.utils.funcparser
===============================
.. automodule:: evennia.utils.funcparser
:members:
:undoc-members:
:show-inheritance:

View file

@ -1,7 +0,0 @@
evennia.utils.inlinefuncs
================================
.. automodule:: evennia.utils.inlinefuncs
:members:
:undoc-members:
:show-inheritance:

View file

@ -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

View file

@ -0,0 +1,7 @@
evennia.utils.verb\_conjugation.conjugate
================================================
.. automodule:: evennia.utils.verb_conjugation.conjugate
:members:
:undoc-members:
:show-inheritance:

View file

@ -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

View file

@ -0,0 +1,7 @@
evennia.utils.verb\_conjugation.tests
============================================
.. automodule:: evennia.utils.verb_conjugation.tests
:members:
:undoc-members:
:show-inheritance:

View file

@ -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)
@ -29,6 +29,7 @@
- [Components/EvEditor](Components/EvEditor)
- [Components/EvMenu](Components/EvMenu)
- [Components/EvMore](Components/EvMore)
- [Components/FuncParser](Components/FuncParser)
- [Components/Help System](Components/Help-System)
- [Components/Inputfuncs](Components/Inputfuncs)
- [Components/Locks](Components/Locks)
@ -52,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)