Merge branch 'main' into evadventure_work

This commit is contained in:
Griatch 2023-04-20 18:46:53 +02:00
commit ea58f3e1a6
8 changed files with 168 additions and 153 deletions

View file

@ -2,12 +2,15 @@
## Main branch
- Feature: Better ANSI color fallbacks (InspectorCaracal)
- Feature: Add support for saving `deque` with `maxlen` to Attributes (before
`maxlen` was ignored).
- Fix: More unit tests for scripts (Storsorken)
- Tools: More unit tests for scripts (Storsorken)
- Fix: Components contrib had issues with inherited typeclasses (ChrisLR)
- Fix: f-string fix in clothing contrib (aMiss-aWry)
- Docs: Made separate doc pages for Exits, Characters and Rooms. Expanded on how
to change the description of an in-game object with templating.
- Docs: Fixed a multitude of doc issues.
- Docs: A multitude of doc issues and typos fixed.
## Evennia 1.2.1

View file

@ -3,4 +3,4 @@
You can find the latest updated installation instructions and
requirements
[here](https://www.evennia.com/docs/1.0-dev/Setup/Installation.html)
[here](https://www.evennia.com/docs/latest/Setup/Installation.html)

View file

@ -278,33 +278,24 @@ it
### Return values from the node
Each function must return two variables, `text` and `options`.
Each node function must return two variables, `text` and `options`.
#### text
The `text` variable is a string or tuple. This text is what will be displayed when the user reaches
this node. If this is a tuple, then the first element of the tuple will be considered the displayed
text and the second the help-text to display when the user enters the `help` command on this node.
The `text` variable is a string or tuple. This text is what will be displayed when the user reaches this node. If this is a tuple, then the first element of the tuple will be considered the displayed text and the second the help-text to display when the user enters the `help` command on this node.
```python
text = ("This is the text to display", "This is the help text for this node")
```
Returning a `None` text is allowed and simply leads to a node with no text and only options. If the
help text is not given, the menu will give a generic error message when using `help`.
Returning a `None` text is allowed and simply leads to a node with no text and only options. If the help text is not given, the menu will give a generic error message when using `help`.
#### options
The `options` list describe all the choices available to the user when viewing this node. If
`options` is
returned as `None`, it means that this node is an *Exit node* - any text is displayed and then the
menu immediately exits, running the `exit_cmd` if given.
The `options` list describe all the choices available to the user when viewing this node. If `options` is returned as `None`, it means that this node is an *Exit node* - any text is displayed and then the menu immediately exits, running the `exit_cmd` if given.
Otherwise, `options` should be a list (or tuple) of dictionaries, one for each option. If only one
option is
available, a single dictionary can also be returned. This is how it could look:
Otherwise, `options` should be a list (or tuple) of dictionaries, one for each option. If only one option is available, a single dictionary can also be returned. This is how it could look:
```python
@ -338,14 +329,7 @@ Defend: Hold back and defend yourself
##### option-key 'key'
The option's `key` is what the user should enter in order to choose that option. If given as a
tuple, the
first string of that tuple will be what is shown on-screen while the rest are aliases for picking
that option. In the above example, the user could enter "Attack" (or "attack", it's not
case-sensitive), "a" or "att" in order to attack the goblin. Aliasing is useful for adding custom
coloring to the choice. The first element of the aliasing tuple should then be the colored version,
followed by a version without color - since otherwise the user would have to enter the color codes
to select that choice.
The option's `key` is what the user should enter in order to choose that option. If given as a tuple, the first string of that tuple will be what is shown on-screen while the rest are aliases for picking that option. In the above example, the user could enter "Attack" (or "attack", it's not case-sensitive), "a" or "att" in order to attack the goblin. Aliasing is useful for adding custom coloring to the choice. The first element of the aliasing tuple should then be the colored version, followed by a version without color - since otherwise the user would have to enter the color codes to select that choice.
Note that the `key` is *optional*. If no key is given, it will instead automatically be replaced
with a running number starting from `1`. If removing the `key` part of each option, the resulting
@ -361,12 +345,9 @@ ________________________________
```
Whether you want to use a key or rely on numbers is mostly
a matter of style and the type of menu.
Whether you want to use a key or rely on numbers is mostly a matter of style and the type of menu.
EvMenu accepts one important special `key` given only as `"_default"`. This key is used when a user
enters something that does not match any other fixed keys. It is particularly useful for getting
user input:
EvMenu accepts one important special `key` given only as `"_default"`. This key is used when a user enters something that does not match any other fixed keys. It is particularly useful for getting user input:
```python
def node_readuser(caller, raw_string, **kwargs):
@ -385,15 +366,12 @@ A `"_default"` option does not show up in the menu, so the above will just be a
#### option-key 'desc'
This simply contains the description as to what happens when selecting the menu option. For
`"_default"` options or if the `key` is already long or descriptive, it is not strictly needed. But
usually it's better to keep the `key` short and put more detail in `desc`.
This simply contains the description as to what happens when selecting the menu option. For `"_default"` options or if the `key` is already long or descriptive, it is not strictly needed. But usually it's better to keep the `key` short and put more detail in `desc`.
#### option-key 'goto'
This is the operational part of the option and fires only when the user chooses said option. Here
are three ways to write it
This is the operational part of the option and fires only when the user chooses said option. Here are three ways to write it
```python
@ -422,63 +400,33 @@ def node_select(caller, raw_string, **kwargs):
```
As seen above, `goto` could just be pointing to a single `nodename` string - the name of the node to
go to. When given like this, EvMenu will look for a node named like this and call its associated
function as
As seen above, `goto` could just be pointing to a single `nodename` string - the name of the node to go to. When given like this, EvMenu will look for a node named like this and call its associated function as
```python
nodename(caller, raw_string, **kwargs)
```
Here, `raw_string` is always the input the user entered to make that choice and `kwargs` are the
same as those `kwargs` that already entered the *current* node (they are passed on).
Here, `raw_string` is always the input the user entered to make that choice and `kwargs` are the same as those `kwargs` that already entered the *current* node (they are passed on).
Alternatively the `goto` could point to a "goto-callable". Such callables are usually defined in the
same
module as the menu nodes and given names starting with `_` (to avoid being parsed as nodes
themselves). These callables will be called the same as a node function - `callable(caller,
raw_string, **kwargs)`, where `raw_string` is what the user entered on this node and `**kwargs` is
forwarded from the node's own input.
Alternatively the `goto` could point to a "goto-callable". Such callables are usually defined in the same module as the menu nodes and given names starting with `_` (to avoid being parsed as nodes themselves). These callables will be called the same as a node function - `callable(caller, raw_string, **kwargs)`, where `raw_string` is what the user entered on this node and `**kwargs` is forwarded from the node's own input.
The `goto` option key could also point to a tuple `(callable, kwargs)` - this allows for customizing
the kwargs passed into the goto-callable, for example you could use the same callable but change the
kwargs passed into it depending on which option was actually chosen.
The `goto` option key could also point to a tuple `(callable, kwargs)` - this allows for customizing the kwargs passed into the goto-callable, for example you could use the same callable but change the kwargs passed into it depending on which option was actually chosen.
The "goto callable" must either return a string `"nodename"` or a tuple `("nodename", mykwargs)`.
This will lead to the next node being called as either `nodename(caller, raw_string, **kwargs)` or
`nodename(caller, raw_string, **mykwargs)` - so this allows changing (or replacing) the options
going
into the next node depending on what option was chosen.
The "goto callable" must either return a string `"nodename"` or a tuple `("nodename", mykwargs)`. This will lead to the next node being called as either `nodename(caller, raw_string, **kwargs)` or `nodename(caller, raw_string, **mykwargs)` - so this allows changing (or replacing) the options going into the next node depending on what option was chosen.
There is one important case - if the goto-callable returns `None` for a `nodename`, *the current
node will run again*, possibly with different kwargs. This makes it very easy to re-use a node over
and over, for example allowing different options to update some text form being passed and
manipulated for every iteration.
> The EvMenu also supports the `exec` option key. This allows for running a callable *before* the
> goto-callable. This functionality comes from a time before goto could be a callable and is
> *deprecated* as of Evennia 0.8. Use `goto` for all functionality where you'd before use `exec`.
There is one important case - if the goto-callable returns `None` for a `nodename`, *the current node will run again*, possibly with different kwargs. This makes it very easy to re-use a node over and over, for example allowing different options to update some text form being passed and manipulated for every iteration.
### Temporary storage
When the menu starts, the EvMenu instance is stored on the caller as `caller.ndb._evmenu`. Through
this object you can in principle reach the menu's internal state if you know what you are doing.
This is also a good place to store temporary, more global variables that may be cumbersome to keep
passing from node to node via the `**kwargs`. The `_evmnenu` will be deleted automatically when the
menu closes, meaning you don't need to worry about cleaning anything up.
When the menu starts, the EvMenu instance is stored on the caller as `caller.ndb._evmenu`. Through this object you can in principle reach the menu's internal state if you know what you are doing. This is also a good place to store temporary, more global variables that may be cumbersome to keep passing from node to node via the `**kwargs`. The `_evmnenu` will be deleted automatically when the menu closes, meaning you don't need to worry about cleaning anything up.
If you want *permanent* state storage, it's instead better to use an Attribute on `caller`. Remember
that this will remain after the menu closes though, so you need to handle any needed cleanup
yourself.
If you want *permanent* state storage, it's instead better to use an Attribute on `caller`. Remember that this will remain after the menu closes though, so you need to handle any needed cleanup yourself.
### Customizing Menu formatting
The `EvMenu` display of nodes, options etc are controlled by a series of formatting methods on the
`EvMenu` class. To customize these, simply create a new child class of `EvMenu` and override as
needed. Here is an example:
The `EvMenu` display of nodes, options etc are controlled by a series of formatting methods on the `EvMenu` class. To customize these, simply create a new child class of `EvMenu` and override as needed. Here is an example:
```python
from evennia.utils.evmenu import EvMenu
@ -542,8 +490,7 @@ See `evennia/utils/evmenu.py` for the details of their default implementations.
## EvMenu templating language
In evmenu.py are two helper functions `parse_menu_template` and `template2menu`
that is used to parse a _menu template_ string into an EvMenu:
In evmenu.py are two helper functions `parse_menu_template` and `template2menu` that is used to parse a _menu template_ string into an EvMenu:
evmenu.template2menu(caller, menu_template, goto_callables)
@ -875,6 +822,68 @@ auto-created by the `list_node` decorator.
## Example Menus
Here is a diagram to help visualize the flow of data from node to node, including goto-callables in-between:
```
┌─
│ def nodeA(caller, raw_string, **kwargs):
│ text = "Choose how to operate on 2 and 3."
│ options = (
│ {
│ "key": "A",
│ "desc": "Multiply 2 with 3",
│ "goto": (_callback, {"type": "mult", "a": 2, "b": 3})
│ }, ───────────────────┬────────────
│ { │
│ "key": "B", └───────────────┐
│ "desc": "Add 2 and 3", │
Node A│ "goto": (_callback, {"type": "add", "a": 2, "b": 3}) │
│ }, ─────────────────┬───────────── │
│ { │ │
│ "key": "C", │ │
│ "desc": "Show the value 5", │ │
│ "goto": ("node_B", {"c": 5}) │ │
│ } ───────┐ │ │
│ ) └──────────┼─────────────────┼───┐
│ return text, options │ │ │
└─ ┌──────────┘ │ │
│ │ │
│ ┌──────────────────────────┘ │
┌─ ▼ ▼ │
│ def _callback(caller, raw_string, **kwargs): │
│ if kwargs["type"] == "mult": │
│ return "node_B", {"c": kwargs["a"] * kwargs["b"]} │
Goto- │ ───────────────┬──────────────── │
callable│ │ │
│ └───────────────────┐ │
│ │ │
│ elif kwargs["type"] == "add": │ │
│ return "node_B", {"c": kwargs["a"] + kwargs["b"]} │ │
└─ ────────┬─────────────────────── │ │
│ │ │
│ ┌────────────────────────┼──────────┘
│ │ │
│ │ ┌──────────────────────┘
┌─ ▼ ▼ ▼
│ def nodeB(caller, raw_string, **kwargs):
Node B│ text = "Result of operation: " + kwargs["c"]
│ return text, {}
└─
┌─
Menu │ EvMenu(caller, {"node_A": nodeA, "node_B": nodeB}, startnode="node_A")
Start│
└─
```
Above we create a very simple/stupid menu (in the `EvMenu` call at the end) where we map the node identifier `"node_A"` to the Python function `nodeA` and `"node_B"` to the function `nodeB`.
We start the menu in `"node_A"` where we get three options A, B and C. Options A and B will route via a a goto-callable `_callback` that either multiples or adds the numbers 2 and 3 together before continuing to `"node_B"`. Option C routes directly to `"node_B"`, passing the number 5.
In every step, we pass a dict which becomes the ingoing `**kwargs` in the next step. If we didn't pass anything (it's optional), the next step's `**kwargs` would just be empty.
More examples:
- **[Simple branching menu](./EvMenu.md#example-simple-branching-menu)** - choose from options
- **[Dynamic goto](./EvMenu.md#example-dynamic-goto)** - jumping to different nodes based on response
- **[Set caller properties](./EvMenu.md#example-set-caller-properties)** - a menu that changes things

View file

@ -293,7 +293,7 @@ the `Character` class in `mygame/typeclasses/characters.py`).
There are thus two ways to weave your new Character class into Evennia:
1. Change `mygame/server/conf/settings.py` and add `BASE_CHARACTER_CLASS = "evadventure.characters.EvAdventureCharacter"`.
1. Change `mygame/server/conf/settings.py` and add `BASE_CHARACTER_TYPECLASS = "evadventure.characters.EvAdventureCharacter"`.
2. Or, change `typeclasses.characters.Character` to inherit from `EvAdventureCharacter`.
You must always reload the server for changes like this to take effect.

View file

@ -7,8 +7,7 @@ This page gives an overview of the supported SQL databases as well as instructio
- PostgreSQL
- MySQL / MariaDB
Since Evennia uses [Django](https://djangoproject.com), most of our notes are based off of what we know from the community and their documentation. While the information below may be useful, you can always find the most up-to-date and "correct" information at Django's [Notes about supported
Databases](https://docs.djangoproject.com/en/4.1/ref/databases/#ref-databases) page.
Since Evennia uses [Django](https://djangoproject.com), most of our notes are based off of what we know from the community and their documentation. While the information below may be useful, you can always find the most up-to-date and "correct" information at Django's [Notes about supported Databases](https://docs.djangoproject.com/en/4.1/ref/databases/#ref-databases) page.
## SQLite3 (default)
@ -16,8 +15,7 @@ Databases](https://docs.djangoproject.com/en/4.1/ref/databases/#ref-databases) p
SQLite stores the database in a single file (`mygame/server/evennia.db3`). This means it's very easy to reset this database - just delete (or move) that `evennia.db3` file and run `evennia migrate` again! No server process is needed and the administrative overhead and resource consumption is tiny. It is also very fast since it's run in-memory. For the vast majority of Evennia installs it will probably be all that's ever needed.
SQLite will generally be much faster than MySQL/PostgreSQL but its performance comes with two
drawbacks:
SQLite will generally be much faster than MySQL/PostgreSQL but its performance comes with two drawbacks:
* SQLite [ignores length constraints by design](https://www.sqlite.org/faq.html#q9); it is possible to store very large strings and numbers in fields that technically should not accept them. This is not something you will notice; your game will read and write them and function normally, but this *can* create some data migration problems requiring careful thought if you do need to change databases later.
* SQLite can scale well to storage of millions of objects, but if you end up with a thundering herd of users trying to access your MUD and web site at the same time, or you find yourself writing long- running functions to update large numbers of objects on a live game, either will yield errors and interference. SQLite does not work reliably with multiple concurrent threads or processes accessing its records. This has to do with file-locking clashes of the database file. So for a production server making heavy use of process- or thread pools, a proper database is a more appropriate choice.
@ -28,8 +26,7 @@ This is installed and configured as part of Evennia. The database file is create
evennia migrate
without changing any database options. An optional requirement is the `sqlite3` client program - this is required if you want to inspect the database data manually. A shortcut for using it with the
evennia database is `evennia dbshell`. Linux users should look for the `sqlite3` package for their distro while Mac/Windows should get the [sqlite-tools package from this page](https://sqlite.org/download.html).
without changing any database options. An optional requirement is the `sqlite3` client program - this is required if you want to inspect the database data manually. A shortcut for using it with the evennia database is `evennia dbshell`. Linux users should look for the `sqlite3` package for their distro while Mac/Windows should get the [sqlite-tools package from this page](https://sqlite.org/download.html).
To inspect the default Evennia database (once it's been created), go to your game dir and do
@ -48,9 +45,7 @@ If you want to reset your SQLite3 database, see [here](./Updating-Evennia.md#sql
## PostgreSQL
[PostgreSQL](https://www.postgresql.org/) is an open-source database engine, recommended by Django.
While not as fast as SQLite for normal usage, it will scale better than SQLite, especially if your
game has an very large database and/or extensive web presence through a separate server process.
[PostgreSQL](https://www.postgresql.org/) is an open-source database engine, recommended by Django. While not as fast as SQLite for normal usage, it will scale better than SQLite, especially if your game has an very large database and/or extensive web presence through a separate server process.
### Install and initial setup of PostgreSQL
@ -67,9 +62,7 @@ Next, start the postgres client:
```{warning}
With the `--password` argument, Postgres should prompt you for a password.
If it won't, replace that with `-p yourpassword` instead. Do not use the `-p` argument unless you
have to since the resulting command, and your password, will be logged in the shell history.
With the `--password` argument, Postgres should prompt you for a password. If it won't, replace that with `-p yourpassword` instead. Do not use the `-p` argument unless you have to since the resulting command, and your password, will be logged in the shell history.
```
This will open a console to the postgres service using the psql client.
@ -87,6 +80,9 @@ ALTER ROLE evennia SET default_transaction_isolation TO 'read committed';
ALTER ROLE evennia SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE evennia TO evennia;
-- For Postgres 10+
ALTER DATABASE evennia owner to evennia;
-- Other useful commands:
-- \l (list all databases and permissions)
-- \q (exit)
@ -125,8 +121,7 @@ to populate your database. Should you ever want to inspect the database directly
as a shortcut to get into the postgres command line for the right database and user.
With the database setup you should now be able to start start Evennia normally with your new
database.
With the database setup you should now be able to start start Evennia normally with your new database.
### Resetting PostgreSQL
@ -213,7 +208,7 @@ Now your Evennia installation should be able to connect and talk with a remote s
First, install and setup MariaDB or MySQL for your specific server. Linux users should look for the `mysql-server` or `mariadb-server` packages for their respective distributions. Windows/Mac users will find what they need from the [MySQL downloads](https://www.mysql.com/downloads/) or [MariaDB downloads](https://mariadb.org/download/) pages. You also need the respective database clients (`mysql`, `mariadb-client`), so you can setup the database itself. When you install the server you should usually be asked to set up the database root user and password.
You will finally also need a Python interface to allow Evennia to talk to the database. Django recommends the `mysqlclient` one. Install this into the evennia virtualenv with `pip install mysqlclient`.
Finally, you will also need a Python interface to allow Evennia to talk to the database. Django recommends the `mysqlclient` one. Install this into the evennia virtualenv with `pip install mysqlclient`.
Start the database client (this is named the same for both mysql and mariadb):
@ -221,8 +216,7 @@ Start the database client (this is named the same for both mysql and mariadb):
mysql -u root -p
```
You should get to enter your database root password (set this up when you installed the database
server).
You should get to enter your database root password (set this up when you installed the database server).
Inside the database client interface:

View file

@ -36,9 +36,11 @@ class ComponentProperty:
raise Exception("Cannot set a class property")
def __set_name__(self, owner, name):
class_components = getattr(owner, "_class_components", None)
# Retrieve the class_components set on the direct class only
class_components = owner.__dict__.get("_class_components")
if not class_components:
class_components = []
# Create a new list, including inherited class components
class_components = list(getattr(owner, "_class_components", []))
setattr(owner, "_class_components", class_components)
class_components.append((self.component_name, self.values))

View file

@ -42,6 +42,10 @@ class CharacterWithComponents(ComponentHolderMixin, DefaultCharacter):
test_b = ComponentProperty("test_b", my_int=3, my_list=[1, 2, 3])
class InheritedTCWithComponents(CharacterWithComponents):
test_c = ComponentProperty("test_c")
class TestComponents(EvenniaTest):
character_typeclass = CharacterWithComponents
@ -49,6 +53,14 @@ class TestComponents(EvenniaTest):
assert self.char1.test_a
assert self.char1.test_b
def test_inherited_typeclass_does_not_include_child_class_components(self):
char_with_c = create.create_object(
InheritedTCWithComponents, key="char_with_c", location=self.room1, home=self.room1
)
assert self.char1.test_a
assert not self.char1.cmp.get('test_c')
assert char_with_c.test_c
def test_character_instances_components_properly(self):
assert isinstance(self.char1.test_a, ComponentTestA)
assert isinstance(self.char1.test_b, ComponentTestB)

View file

@ -332,7 +332,7 @@ class ANSIParser(object):
colval = 134 + ord(letter)
# ansi fallback logic expects r,g,b values in [0..5] range
gray = (ord(letter) - 97) / 5.0
gray = round((ord(letter) - 97) / 5.0)
red, green, blue = gray, gray, gray
if use_xterm256:
@ -347,62 +347,57 @@ class ANSIParser(object):
else:
# xterm256 not supported, convert the rgb value to ansi instead
if red == green == blue and red < 3:
if background:
return ANSI_BACK_BLACK
elif red >= 1:
return ANSI_HILITE + ANSI_BLACK
else:
return ANSI_NORMAL + ANSI_BLACK
elif red == green == blue:
if background:
return ANSI_BACK_WHITE
elif red >= 4:
return ANSI_HILITE + ANSI_WHITE
else:
return ANSI_NORMAL + ANSI_WHITE
elif red > green and red > blue:
if background:
return ANSI_BACK_RED
elif red >= 3:
return ANSI_HILITE + ANSI_RED
else:
return ANSI_NORMAL + ANSI_RED
elif red == green and red > blue:
if background:
return ANSI_BACK_YELLOW
elif red >= 3:
return ANSI_HILITE + ANSI_YELLOW
else:
return ANSI_NORMAL + ANSI_YELLOW
elif red == blue and red > green:
if background:
return ANSI_BACK_MAGENTA
elif red >= 3:
return ANSI_HILITE + ANSI_MAGENTA
else:
return ANSI_NORMAL + ANSI_MAGENTA
elif green > blue:
if background:
return ANSI_BACK_GREEN
elif green >= 3:
return ANSI_HILITE + ANSI_GREEN
else:
return ANSI_NORMAL + ANSI_GREEN
elif green == blue:
if background:
return ANSI_BACK_CYAN
elif green >= 3:
return ANSI_HILITE + ANSI_CYAN
else:
return ANSI_NORMAL + ANSI_CYAN
else: # mostly blue
if background:
return ANSI_BACK_BLUE
elif blue >= 3:
return ANSI_HILITE + ANSI_BLUE
else:
return ANSI_NORMAL + ANSI_BLUE
rgb = (red, green, blue)
def _convert_for_ansi(val):
return int((val+1)//2)
# greys
if (max(rgb) - min(rgb)) <= 1:
match rgb:
case (0,0,0):
return ANSI_BACK_BLACK if background else ANSI_NORMAL + ANSI_BLACK
case ((1|2), (1|2), (1|2)):
return ANSI_BACK_BLACK if background else ANSI_HILITE + ANSI_BLACK
case ((2|3), (2|3), (2|3)):
return ANSI_BACK_WHITE if background else ANSI_NORMAL + ANSI_WHITE
case ((3|4), (3|4), (3|4)):
return ANSI_BACK_WHITE if background else ANSI_NORMAL + ANSI_WHITE
case ((4|5), (4|5), (4|5)):
return ANSI_BACK_WHITE if background else ANSI_HILITE + ANSI_WHITE
match tuple(_convert_for_ansi(c) for c in rgb):
# red
case ((2|3), (0|1), (0|1)):
return ANSI_BACK_RED if background else ANSI_HILITE + ANSI_RED
case ((1|2), 0, 0):
return ANSI_BACK_RED if background else ANSI_NORMAL + ANSI_RED
# green
case ((0|1), (2|3), (0|1)):
return ANSI_BACK_GREEN if background else ANSI_HILITE + ANSI_GREEN
case ((0 | 1), 1, 0) if green > red:
return ANSI_BACK_GREEN if background else ANSI_NORMAL + ANSI_GREEN
# blue
case ((0|1), (0|1), (2|3)):
return ANSI_BACK_BLUE if background else ANSI_HILITE + ANSI_BLUE
case (0, 0, 1):
return ANSI_BACK_BLUE if background else ANSI_NORMAL + ANSI_BLUE
# cyan
case ((0|1|2), (2|3), (2|3)) if red == min(rgb):
return ANSI_BACK_CYAN if background else ANSI_HILITE + ANSI_CYAN
case (0, (1|2), (1|2)):
return ANSI_BACK_CYAN if background else ANSI_NORMAL + ANSI_CYAN
# yellow
case ((2|3), (2|3), (0|1|2)) if blue == min(rgb):
return ANSI_BACK_YELLOW if background else ANSI_HILITE + ANSI_YELLOW
case ((2|1), (2|1), (0|1)):
return ANSI_BACK_YELLOW if background else ANSI_NORMAL + ANSI_YELLOW
# magenta
case ((2|3), (0|1|2), (2|3)) if green == min(rgb):
return ANSI_BACK_MAGENTA if background else ANSI_HILITE + ANSI_MAGENTA
case ((1|2), 0, (1|2)):
return ANSI_BACK_MAGENTA if background else ANSI_NORMAL + ANSI_MAGENTA
def strip_raw_codes(self, string):
"""