mirror of
https://github.com/evennia/evennia.git
synced 2026-04-01 21:47:17 +02:00
Add lock-handling to FileHelp, add warnings to sethelp
This commit is contained in:
parent
69adf76469
commit
2e67f9d700
7 changed files with 377 additions and 187 deletions
|
|
@ -51,6 +51,11 @@ Up requirements to Django 3.2+
|
|||
to the user and respond to their input. This complements the existing `get_input` helper.
|
||||
- Allow sending messages with `page/tell` without a `=` if target name contains no spaces.
|
||||
- New FileHelpStorage system allows adding help entries via external files.
|
||||
- `sethelp` command now warns if shadowing other help-types when creating a new
|
||||
entry.
|
||||
- Help command now uses `view` lock to determine if cmd/entry shows in index and
|
||||
`read` lock to determine if it can be read. It used to be `view` in the role
|
||||
of the latter. Migration swaps these around.
|
||||
- In modules given by `settings.PROTOTYPE_MODULES`, spawner will now first look for a global
|
||||
list `PROTOTYPE_LIST` of dicts before loading all dicts in the module as prototypes.
|
||||
- New Channel-System using the `channel` command and nicks. Removed the `ChannelHandler` and the
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ match it will provide suggestsions, first from alternative topics and then by
|
|||
finding mentions of the search term in help entries.
|
||||
|
||||
|
||||
help theatre
|
||||
|
||||
```
|
||||
------------------------------------------------------------------------------
|
||||
Help for The theatre (aliases: the hub, curtains)
|
||||
|
||||
The theatre is at the centre of the city, both literally and figuratively ...
|
||||
The theatre is at the centre of the city, both literally and figuratively ...
|
||||
(A lot more text about it follows ...)
|
||||
|
||||
Subtopics:
|
||||
|
|
@ -19,6 +21,9 @@ Subtopics:
|
|||
theatre/dramatis personae
|
||||
------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
help evennia
|
||||
|
||||
```
|
||||
------------------------------------------------------------------------------
|
||||
No help found
|
||||
|
|
@ -51,43 +56,48 @@ Use the `/edit` switch to open the EvEditor for more convenient in-game writing
|
|||
(but note that devs can also create help entries outside the game using their
|
||||
regular code editor, see below).
|
||||
|
||||
> You can also create help entries as Python modules, outside of the game. These
|
||||
> can not be modified from in-game.
|
||||
|
||||
## Sources of help entries
|
||||
|
||||
Evennia collects help entries from three sources:
|
||||
|
||||
- _Auto-generated command help_ - this is literally the doc-strings of the [Command classes](./Commands).
|
||||
The idea is that the command docs are easier to maintain and keep up-to-date if
|
||||
the developer can change them at the same time as they do the code.
|
||||
- _Database-stored help entries_ - These are created in-game (using the default `sethelp` command
|
||||
as exemplified in the previous section).
|
||||
- _Auto-generated command help_ - this is literally the doc-strings of
|
||||
the [Command classes](./Commands). The idea is that the command docs are
|
||||
easier to maintain and keep up-to-date if the developer can change them at the
|
||||
same time as they do the code.
|
||||
- _Database-stored help entries_ - These are created in-game (using the
|
||||
default `sethelp` command as exemplified in the previous section).
|
||||
- _File-stored help entries_ - These are created outside the game, as dicts in
|
||||
normal Python modules. They allows developers to write and maintain their help files using
|
||||
a proper text editor.
|
||||
normal Python modules. They allows developers to write and maintain their help
|
||||
files using a proper text editor.
|
||||
|
||||
### The Help Entry
|
||||
|
||||
All help entries (no matter the source) have the following properties:
|
||||
|
||||
- `key` - This is the main topic-name. For Commands, this is literally the command's `key`.
|
||||
- `aliases` - Alternate names for the help entry. This can be useful if the main name is hard to remember.
|
||||
- `help_category` - The general grouping of the entry. This is optional. If not given it will use the
|
||||
default category given by `settings.COMMAND_DEFAULT_HELP_CATEGORY` for Commands and `settings.DEFAULT_HELP_CATEGORY`
|
||||
for file+db help entries.
|
||||
- `locks` - This defines who may read this entry. The locktype checked by the `help` command is `view`. In the
|
||||
case of Commands, it's more common that the `cmd` lock fails - in that case the command is not loaded
|
||||
into the help parser at all.
|
||||
- `tags` - This is not used by default, but could be used to further organize help entries.
|
||||
- `key` - This is the main topic-name. For Commands, this is literally the
|
||||
command's `key`.
|
||||
- `aliases` - Alternate names for the help entry. This can be useful if the main
|
||||
name is hard to remember.
|
||||
- `help_category` - The general grouping of the entry. This is optional. If not
|
||||
given it will use the default category given by
|
||||
`settings.COMMAND_DEFAULT_HELP_CATEGORY` for Commands and
|
||||
`settings.DEFAULT_HELP_CATEGORY` for file+db help entries.
|
||||
- `locks` - Lock string (for commands) or LockHandler (all help entries).
|
||||
This defines who may read this entry. See the next section.
|
||||
- `tags` - This is not used by default, but could be used to further organize
|
||||
help entries.
|
||||
- `text` - The actual help entry text. This will be dedented and stripped of
|
||||
extra space at beginning and end.
|
||||
|
||||
|
||||
A `text` that scrolls off the screen will automatically be paginated by
|
||||
the [EvMore](./EvMore) pager (you can control this with
|
||||
`settings.HELP_MORE_ENABLED=False`). If you use EvMore and want to control
|
||||
exactly where the pager should break the page, mark the break with the control
|
||||
character `\f`.
|
||||
|
||||
|
||||
#### Subtopics
|
||||
|
||||
```versionadded:: 1.0
|
||||
|
|
@ -106,16 +116,15 @@ nested at most to a depth of 5 (which is probably too many levels already). The
|
|||
parser uses fuzzy matching to find the subtopic, so one does not have to type
|
||||
it all out exactly.
|
||||
|
||||
Below is an example of a `text` with sub topics.
|
||||
|
||||
Below is an example of a `text` with sub topics.
|
||||
|
||||
```
|
||||
The theatre is the heart of the city, here you can find ...
|
||||
(This is the main help text, what you get with `help theatre`)
|
||||
|
||||
# subtopics
|
||||
# subtopics
|
||||
|
||||
## lore
|
||||
## lore
|
||||
|
||||
The theatre holds many mysterious things...
|
||||
(`help theatre/lore`)
|
||||
|
|
@ -123,7 +132,7 @@ The theatre holds many mysterious things...
|
|||
### the grand opening
|
||||
|
||||
The grand opening is the name for a mysterious event where ghosts appeared ...
|
||||
(`this is a subsub-topic to lore, accessible as `help theatre/lore/grand` or
|
||||
(`this is a subsub-topic to lore, accessible as `help theatre/lore/grand` or
|
||||
any other partial match).
|
||||
|
||||
### the Phantom
|
||||
|
|
@ -141,7 +150,7 @@ The theatre is a two-story building situated at ...
|
|||
There are many interesting people prowling the halls of the theatre ...
|
||||
(`help theatre/dramatis` or `help theathre/drama` or `help theatre/personae` would work)
|
||||
|
||||
### Primadonna Ada
|
||||
### Primadonna Ada
|
||||
|
||||
Everyone knows the primadonna! She is ...
|
||||
(A subtopic under dramatis personae, accessible as `help theatre/drama/ada` etc)
|
||||
|
|
@ -152,57 +161,64 @@ He always keeps an eye on the door and ...
|
|||
(`help theatre/drama/gate`)
|
||||
|
||||
```
|
||||
|
||||
### Command Auto-help system
|
||||
|
||||
The auto-help system uses the `__doc__` strings of your command classes and
|
||||
formats this to a nice- looking help entry. This makes for a very easy way to
|
||||
keep the help updated - just document your commands well and updating the help
|
||||
file is just a `reload` away.
|
||||
file is just a `reload` away.
|
||||
|
||||
Example (from a module with command definitions):
|
||||
Example (from a module with command definitions):
|
||||
|
||||
```python
|
||||
class CmdMyCmd(Command):
|
||||
"""
|
||||
mycmd - my very own command
|
||||
|
||||
Usage:
|
||||
|
||||
Usage:
|
||||
mycmd[/switches] <args>
|
||||
|
||||
|
||||
Switches:
|
||||
test - test the command
|
||||
run - do something else
|
||||
|
||||
|
||||
This is my own command that does this and that.
|
||||
|
||||
|
||||
"""
|
||||
# [...]
|
||||
|
||||
help_category = "General" # default
|
||||
auto_help = True # default
|
||||
|
||||
locks = "cmd:all();read:all()" # default
|
||||
help_category = "General" # default
|
||||
auto_help = True # default
|
||||
|
||||
# [...]
|
||||
```
|
||||
|
||||
The text at the very top of the command class definition is the class' `__doc__`-string and will be
|
||||
shown to users looking for help. Try to use a consistent format - all default commands are using the
|
||||
structure shown above.
|
||||
The text at the very top of the command class definition is the class'
|
||||
`__doc__`-string and will be shown to users looking for help. Try to use a
|
||||
consistent format - all default commands are using the structure shown above.
|
||||
|
||||
You should also supply the `help_category` class property if you can; this helps to group help
|
||||
entries together for people to more easily find them. See the `help` command in-game to see the
|
||||
default categories. If you don't specify the category, `settings.COMMAND_DEFAULT_HELP_CATEGORY`
|
||||
(default is "General") is used.
|
||||
You can limit access to the help entry by the `view` and/or `read` locks on the
|
||||
Command. See [the section below](#Locking-help-entries) for details.
|
||||
|
||||
If you don't want your command to be picked up by the auto-help system at all (like if you want to
|
||||
write its docs manually using the info in the next section or you use a [cmdset](./Command-Sets) that
|
||||
has its own help functionality) you can explicitly set `auto_help` class property to `False` in your
|
||||
command definition.
|
||||
You should also supply the `help_category` class property if you can; this helps
|
||||
to group help entries together for people to more easily find them. See the
|
||||
`help` command in-game to see the default categories. If you don't specify the
|
||||
category, `settings.COMMAND_DEFAULT_HELP_CATEGORY` (default is "General") is
|
||||
used.
|
||||
|
||||
Alternatively, you can keep the advantages of *auto-help* in commands, but control the display of
|
||||
command helps. You can do so by overriding the command's `get_help(caller, cmdset)` method. By default, this
|
||||
method will return the class docstring. You could modify it to add custom behavior: the text
|
||||
returned by this method will be displayed to the character asking for help in this command.
|
||||
If you don't want your command to be picked up by the auto-help system at all
|
||||
(like if you want to write its docs manually using the info in the next section
|
||||
or you use a [cmdset](./Command-Sets) that has its own help functionality) you
|
||||
can explicitly set `auto_help` class property to `False` in your command
|
||||
definition.
|
||||
|
||||
Alternatively, you can keep the advantages of *auto-help* in commands, but
|
||||
control the display of command helps. You can do so by overriding the command's
|
||||
`get_help(caller, cmdset)` method. By default, this method will return the
|
||||
class docstring. You could modify it to add custom behavior: the text returned
|
||||
by this method will be displayed to the character asking for help in this
|
||||
command.
|
||||
|
||||
### Database-help entries
|
||||
|
||||
|
|
@ -212,41 +228,41 @@ manually, you can do so with `evennia.create_help_entry()`:
|
|||
```python
|
||||
|
||||
from evennia import create_help_entry
|
||||
entry = create_help_entry("emote",
|
||||
"Emoting is important because ...",
|
||||
entry = create_help_entry("emote",
|
||||
"Emoting is important because ...",
|
||||
category="Roleplaying", locks="view:all()")
|
||||
```
|
||||
|
||||
The entity being created is a [evennia.help.models.HelpEntry](api:evennia.help.models.HelpEntry)
|
||||
The entity being created is a [evennia.help.models.HelpEntry](api:evennia.help.models.HelpEntry)
|
||||
object. This is _not_ a [Typeclassed](./Typeclasses) entity and is not meant to
|
||||
be modified to any great degree. It holds the properties listed earlier. The
|
||||
text is stored in a field `entrytext`. It does not provide a `get_help` method
|
||||
like commands, stores and returns the `entrytext` directly.
|
||||
|
||||
You can search for `HelpEntry` objects using `evennia.search_help` but note
|
||||
that this will not return the two other types of help entries.
|
||||
|
||||
You can search for (db-)-`HelpEntry` objects using `evennia.search_help` but note that
|
||||
this will not return the two other types of help entries.
|
||||
|
||||
### File-help entries
|
||||
|
||||
```versionadded:: 1.0
|
||||
```
|
||||
|
||||
File-help entries are created by the game development team outside of the game. The
|
||||
help entries are defined in normal Python modules (`.py` file ending) containing
|
||||
File-help entries are created by the game development team outside of the game. The
|
||||
help entries are defined in normal Python modules (`.py` file ending) containing
|
||||
a `dict` to represent each entry. They require a server `reload` before any changes
|
||||
apply.
|
||||
|
||||
- Evennia will look through all modules given by `settings.FILE_HELP_ENTRY_MODULES`. This
|
||||
should be a list of python-paths for Evennia to import.
|
||||
- If this module contains a top-level variable `HELP_ENTRY_DICTS`, this will be imported
|
||||
and must be a `list` of help-entry dicts.
|
||||
- Evennia will look through all modules given by
|
||||
`settings.FILE_HELP_ENTRY_MODULES`. This should be a list of python-paths for
|
||||
Evennia to import.
|
||||
- If this module contains a top-level variable `HELP_ENTRY_DICTS`, this will be
|
||||
imported and must be a `list` of help-entry dicts.
|
||||
- If no `HELP_ENTRY_DICTS` list is found, _every_ top-level variable in the
|
||||
module that is a `dict` will be read as a help entry. The variable-names will
|
||||
be ignored in this case.
|
||||
|
||||
If you add multiple modules to be read, same-keyed help entries added later in the list
|
||||
will override coming before.
|
||||
If you add multiple modules to be read, same-keyed help entries added later in
|
||||
the list will override coming before.
|
||||
|
||||
Each entry dict must define keys to match that needed by all help entries.
|
||||
Here's an example of a help module:
|
||||
|
|
@ -254,12 +270,13 @@ Here's an example of a help module:
|
|||
```python
|
||||
|
||||
# in a module pointed to by settings.FILE_HELP_ENTRY_MODULES
|
||||
|
||||
|
||||
HELP_ENTRY_DICTS = [
|
||||
{
|
||||
"key": "The Gods", # case-insensitive, can be searched by 'gods' too
|
||||
"aliases": ['pantheon', 'religion']
|
||||
"category": "Lore",
|
||||
"locks": "read:all()", # optional
|
||||
"text": '''
|
||||
The gods formed the world ...
|
||||
|
||||
|
|
@ -281,7 +298,7 @@ HELP_ENTRY_DICTS = [
|
|||
},
|
||||
{
|
||||
"key": "The mortals",
|
||||
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -291,6 +308,47 @@ The help entry text will be dedented and will retain paragraphs. You should try
|
|||
to keep your strings a reasonable width (it will look better). Just reload the
|
||||
server and the file-based help entries will be available to view.
|
||||
|
||||
## Locking help entries
|
||||
|
||||
The default `help` command gather all available commands and help entries
|
||||
together so they can be searched or listed. By setting locks on the command/help
|
||||
entry one can limit who can read help about it.
|
||||
|
||||
- Commands failing the normal `cmd`-lock will be removed before even getting
|
||||
to the help command. In this case the other two lock types below are ignored.
|
||||
- The `view` access type determines if the command/help entry should be visible in
|
||||
the main help index. If not given, it is assumed everyone can view.
|
||||
- The `read` access type determines if the command/help entry can be actually read.
|
||||
If a `read` lock is given and `view` is not, the `read`-lock is assumed to
|
||||
apply to `view`-access as well (so if you can't read the help entry it will
|
||||
also not show up in the index). If `read`-lock is not given, it's assume
|
||||
everyone can read the help entry.
|
||||
|
||||
For Commands you set the help-related locks the same way you would any lock:
|
||||
|
||||
```python
|
||||
class MyCommand(Command):
|
||||
"""
|
||||
<docstring for command>
|
||||
"""
|
||||
key = "mycommand"
|
||||
# everyone can use the command, builders can view it in the help index
|
||||
# but only devs can actually read the help (a weird setup for sure!)
|
||||
locks = "cmd:all();view:perm(Builders);read:perm(Developers)
|
||||
|
||||
```
|
||||
|
||||
Db-help entries and File-Help entries work the same way (except the `cmd`-type
|
||||
lock is not used. A file-help example:
|
||||
|
||||
```python
|
||||
help_entry = {
|
||||
# ...
|
||||
locks = "read:perm(Developer)",
|
||||
# ...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Customizing the look of the help system
|
||||
|
||||
|
|
@ -300,24 +358,23 @@ This is done almost exclusively by overriding the `help` command
|
|||
Since the available commands may vary from moment to moment, `help` is
|
||||
responsible for collating the three sources of help-entries (commands/db/file)
|
||||
together and search through them on the fly. It also does all the formatting of
|
||||
the output.
|
||||
the output.
|
||||
|
||||
To make it easier to tweak the look, the parts of the code that changes the
|
||||
visual presentation has been broken out into separate methods `format_help_entry` and
|
||||
`format_help_index` - override these in your version of `help` to change the display
|
||||
as you please. See the api link above for details.
|
||||
|
||||
visual presentation has been broken out into separate methods
|
||||
`format_help_entry` and `format_help_index` - override these in your version of
|
||||
`help` to change the display as you please. See the api link above for details.
|
||||
|
||||
## Technical notes
|
||||
|
||||
Since it needs to search so different types of data, the help system has to
|
||||
collect all possibilities in memory before searching through the entire set. It
|
||||
uses the [Lunr](https://github.com/yeraydiazdiaz/lunr.py) search engine to
|
||||
search through the main bulk of help entries. Lunr is a mature engine used for
|
||||
search through the main bulk of help entries. Lunr is a mature engine used for
|
||||
web-pages and produces much more sensible results than previous solutions.
|
||||
|
||||
Once the main entry has been found, subtopics are then searched with
|
||||
simple `==`, `startswith` and `in` matching (there are so relatively few of them
|
||||
simple `==`, `startswith` and `in` matching (there are so relatively few of them
|
||||
at that point).
|
||||
|
||||
```versionchanged:: 1.0
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ General Character commands usually available to all characters
|
|||
"""
|
||||
import re
|
||||
from django.conf import settings
|
||||
from evennia.utils import utils, evtable
|
||||
from evennia.utils import utils
|
||||
from evennia.typeclasses.attributes import NickTemplateInvalid
|
||||
|
||||
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
|
@ -400,7 +400,7 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "get"
|
||||
aliases = "grab"
|
||||
locks = "cmd:all()"
|
||||
locks = "cmd:all();view:perm(Developer);read:perm(Developer)"
|
||||
arg_regex = r"\s|$"
|
||||
|
||||
def func(self):
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@ outside the game in modules given by ``settings.FILE_HELP_ENTRY_MODULES``.
|
|||
|
||||
"""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from django.conf import settings
|
||||
from collections import defaultdict
|
||||
from evennia.utils.utils import fill, dedent
|
||||
from evennia.utils.utils import dedent
|
||||
from evennia.help.models import HelpEntry
|
||||
from evennia.utils import create, evmore
|
||||
from evennia.utils.ansi import ANSIString
|
||||
from evennia.help.filehelp import FILE_HELP_ENTRIES
|
||||
from evennia.utils.eveditor import EvEditor
|
||||
from evennia.utils.evmenu import ask_yes_no
|
||||
from evennia.utils.utils import (
|
||||
class_from_module,
|
||||
inherits_from,
|
||||
|
|
@ -259,7 +259,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
return help_index
|
||||
|
||||
def check_show_help(self, cmd_or_topic, caller):
|
||||
def can_read_topic(self, cmd_or_topic, caller):
|
||||
"""
|
||||
Helper method. If this return True, the given help topic
|
||||
be viewable in the help listing. Note that even if this returns False,
|
||||
|
|
@ -273,18 +273,22 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
Returns:
|
||||
bool: If command can be viewed or not.
|
||||
|
||||
Notes:
|
||||
This uses the 'read' lock. If no 'read' lock is defined, the topic is assumed readable
|
||||
by all.
|
||||
|
||||
"""
|
||||
if inherits_from(cmd_or_topic, "evennia.commands.command.Command"):
|
||||
return cmd_or_topic.auto_help and cmd_or_topic.access(caller)
|
||||
return cmd_or_topic.auto_help and cmd_or_topic.access(caller, 'read', default=True)
|
||||
else:
|
||||
return cmd_or_topic.access(caller, 'read', default=True)
|
||||
|
||||
def should_list_topic(self, cmd_or_topic, caller):
|
||||
def can_list_topic(self, cmd_or_topic, caller):
|
||||
"""
|
||||
Should the specified command appear in the help table?
|
||||
|
||||
This method only checks whether a specified command should appear in the table of
|
||||
topics/commands. The command can be used by the caller (see the 'check_show_help' method)
|
||||
topics/commands. The command can be used by the caller (see the 'should_show_help' method)
|
||||
and the command will still be available, for instance, if a character type 'help name of the
|
||||
command'. However, if you return False, the specified command will not appear in the table.
|
||||
This is sometimes useful to "hide" commands in the table, but still access them through the
|
||||
|
|
@ -297,8 +301,123 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
Returns:
|
||||
bool: If command should be listed or not.
|
||||
|
||||
Notes:
|
||||
By default, the 'view' lock will be checked, and if no such lock is defined, the 'read'
|
||||
lock will be used. If neither lock is defined, the help entry is assumed to be
|
||||
accessible to all.
|
||||
|
||||
"""
|
||||
return cmd_or_topic.access(caller, 'view', default=True)
|
||||
has_view = (
|
||||
"view:" in cmd_or_topic.locks
|
||||
if inherits_from(cmd_or_topic, "evennia.commands.command.Command")
|
||||
else cmd_or_topic.locks.get("view")
|
||||
)
|
||||
|
||||
if has_view:
|
||||
return cmd_or_topic.access(caller, 'view', default=True)
|
||||
else:
|
||||
# no explicit 'view' lock - use the 'read' lock
|
||||
return cmd_or_topic.access(caller, 'read', default=True)
|
||||
|
||||
def collect_topics(self, caller, mode='list'):
|
||||
"""
|
||||
Collect help topics from all sources (cmd/db/file).
|
||||
|
||||
Args:
|
||||
caller (Object or Account): The user of the Command.
|
||||
mode (str): One of 'list' or 'query', where the first means we are collecting to view
|
||||
the help index and the second because of wanting to search for a specific help
|
||||
entry/cmd to read. This determines which access should be checked.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple of three dicts containing the different types of help entries
|
||||
in the order cmd-help, db-help, file-help:
|
||||
`({key: cmd,...}, {key: dbentry,...}, {key: fileentry,...}`
|
||||
|
||||
"""
|
||||
# start with cmd-help
|
||||
cmdset = self.cmdset
|
||||
# removing doublets in cmdset, caused by cmdhandler
|
||||
# having to allow doublet commands to manage exits etc.
|
||||
cmdset.make_unique(caller)
|
||||
# retrieve all available commands and database / file-help topics.
|
||||
# also check the 'cmd:' lock here
|
||||
cmd_help_topics = [cmd for cmd in cmdset if cmd and cmd.access(caller, 'cmd')]
|
||||
# get all file-based help entries, checking perms
|
||||
file_help_topics = {
|
||||
topic.key.lower().strip(): topic
|
||||
for topic in FILE_HELP_ENTRIES.all()
|
||||
}
|
||||
# get db-based help entries, checking perms
|
||||
db_help_topics = {
|
||||
topic.key.lower().strip(): topic
|
||||
for topic in HelpEntry.objects.all()
|
||||
}
|
||||
if mode == 'list':
|
||||
# check the view lock for all help entries/commands and determine key
|
||||
cmd_help_topics = {
|
||||
cmd.auto_help_display_key
|
||||
if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
||||
for cmd in cmd_help_topics if self.can_list_topic(cmd, caller)}
|
||||
db_help_topics = {
|
||||
key: entry for key, entry in db_help_topics.items()
|
||||
if self.can_list_topic(entry, caller)
|
||||
}
|
||||
file_help_topics = {
|
||||
key: entry for key, entry in file_help_topics.items()
|
||||
if self.can_list_topic(entry, caller)}
|
||||
else:
|
||||
# query
|
||||
cmd_help_topics = {
|
||||
cmd.auto_help_display_key
|
||||
if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
||||
for cmd in cmd_help_topics if self.can_read_topic(cmd, caller)}
|
||||
db_help_topics = {
|
||||
key: entry for key, entry in db_help_topics.items()
|
||||
if self.can_read_topic(entry, caller)
|
||||
}
|
||||
file_help_topics = {
|
||||
key: entry for key, entry in file_help_topics.items()
|
||||
if self.can_read_topic(entry, caller)}
|
||||
|
||||
return cmd_help_topics, db_help_topics, file_help_topics
|
||||
|
||||
def do_search(self, query, entries, search_fields=None):
|
||||
"""
|
||||
Perform a help-query search, default using Lunr search engine.
|
||||
|
||||
Args:
|
||||
query (str): The help entry to search for.
|
||||
entries (list): All possibilities. A mix of commands, HelpEntries and FileHelpEntries.
|
||||
search_fields (list): A list of dicts defining how Lunr will find the
|
||||
search data on the elements. If not given, will use a default.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple (match, suggestions).
|
||||
|
||||
"""
|
||||
if not search_fields:
|
||||
# lunr search fields/boosts
|
||||
search_fields = [
|
||||
{"field_name": "key", "boost": 10},
|
||||
{"field_name": "aliases", "boost": 9},
|
||||
{"field_name": "category", "boost": 8},
|
||||
{"field_name": "tags", "boost": 1}, # tags are not used by default
|
||||
]
|
||||
match, suggestions = None, None
|
||||
for match_query in (query, f"{query}*"):
|
||||
# We first do an exact word-match followed by a start-by query. The
|
||||
# return of this will either be a HelpCategory, a Command or a
|
||||
# HelpEntry/FileHelpEntry.
|
||||
matches, suggestions = help_search_with_index(
|
||||
match_query, entries,
|
||||
suggestion_maxnum=self.suggestion_maxnum,
|
||||
fields=search_fields
|
||||
)
|
||||
if matches:
|
||||
match = matches[0]
|
||||
break
|
||||
return match, suggestions
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
|
|
@ -331,85 +450,52 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
caller = self.caller
|
||||
query, subtopics, cmdset = self.topic, self.subtopics, self.cmdset
|
||||
|
||||
# removing doublets in cmdset, caused by cmdhandler
|
||||
# having to allow doublet commands to manage exits etc.
|
||||
cmdset.make_unique(caller)
|
||||
|
||||
# retrieve all available commands and database / file-help topics
|
||||
all_cmd_topics = [cmd for cmd in cmdset if self.check_show_help(cmd, caller) if cmd]
|
||||
|
||||
# get all file-based help entries, checking perms
|
||||
file_help_topics = {
|
||||
topic.key.lower().strip(): topic
|
||||
for topic in FILE_HELP_ENTRIES.all()
|
||||
if topic.access(caller)
|
||||
}
|
||||
# get db-based help entries, checking perms
|
||||
db_topics = {
|
||||
topic.key.lower().strip(): topic
|
||||
for topic in HelpEntry.objects.all()
|
||||
if topic.access(caller)
|
||||
}
|
||||
# merge so db topics override file topics with same key
|
||||
all_file_db_topics = list({**file_help_topics, **db_topics}.values())
|
||||
|
||||
# get all categories
|
||||
all_categories = list(set(
|
||||
[HelpCategory(cmd.help_category) for cmd in all_cmd_topics]
|
||||
+ [HelpCategory(topic.help_category) for topic in all_file_db_topics]
|
||||
))
|
||||
|
||||
if not query:
|
||||
# list all available help entries, grouped by category. We want to
|
||||
# build dictionaries {category: [topic, topic, ...], ...}
|
||||
cmd_help_dict = defaultdict(list)
|
||||
file_db_help_dict = defaultdict(list)
|
||||
|
||||
# Filter commands/topics that should be reached by the help
|
||||
# system, but not be displayed in the table, or be displayed differently.
|
||||
for cmd in all_cmd_topics:
|
||||
if self.should_list_topic(cmd, caller):
|
||||
key = (
|
||||
cmd.auto_help_display_key
|
||||
if hasattr(cmd, "auto_help_display_key") else cmd.key
|
||||
)
|
||||
cmd_help_dict[cmd.help_category].append(key)
|
||||
for topic in all_file_db_topics:
|
||||
if self.should_list_topic(topic, caller):
|
||||
file_db_help_dict[topic.help_category].append(topic.key)
|
||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
||||
self.collect_topics(caller, mode='list')
|
||||
|
||||
output = self.format_help_index(cmd_help_dict, file_db_help_dict)
|
||||
# db-topics override file-based ones
|
||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
||||
|
||||
# group by category (cmds are listed separately)
|
||||
cmd_help_by_category = defaultdict(list)
|
||||
file_db_help_by_category = defaultdict(list)
|
||||
for key, cmd in cmd_help_topics.items():
|
||||
cmd_help_by_category[cmd.help_category].append(key)
|
||||
for key, entry in file_db_help_topics.items():
|
||||
file_db_help_by_category[entry.help_category].append(key)
|
||||
|
||||
# generate the index and display
|
||||
output = self.format_help_index(cmd_help_by_category,
|
||||
file_db_help_by_category)
|
||||
self.msg_help(output)
|
||||
|
||||
return
|
||||
|
||||
# We have a query - try to find a specific topic/category using the
|
||||
# Lunr search engine
|
||||
# search for a specific entry. We need to check for 'read' access here before # building the
|
||||
# set of possibilities.
|
||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
||||
self.collect_topics(caller, mode='query')
|
||||
|
||||
# all available help options - will be searched in order
|
||||
entries = all_cmd_topics + all_file_db_topics + all_categories
|
||||
# db-help topics takes priority over file-help
|
||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
||||
|
||||
# commands take priority over the other types
|
||||
all_topics = {**file_db_help_topics, **cmd_help_topics}
|
||||
|
||||
# get all categories
|
||||
all_categories = list(set(
|
||||
HelpCategory(topic.help_category) for topic in all_topics.values()))
|
||||
|
||||
# all available help options - will be searched in order. We also check # the
|
||||
# read-permission here.
|
||||
entries = list(all_topics.values()) + all_categories
|
||||
|
||||
# lunr search fields/boosts
|
||||
search_fields = [
|
||||
{"field_name": "key", "boost": 10},
|
||||
{"field_name": "aliases", "boost": 9},
|
||||
{"field_name": "category", "boost": 8},
|
||||
{"field_name": "tags", "boost": 1}, # tags are not used by default
|
||||
]
|
||||
match, suggestions = None, None
|
||||
|
||||
for match_query in (query, f"{query}*"):
|
||||
# We first do an exact word-match followed by a start-by query. The
|
||||
# return of this will either be a HelpCategory, a Command or a
|
||||
# HelpEntry/FileHelpEntry.
|
||||
matches, suggestions = help_search_with_index(
|
||||
match_query, entries,
|
||||
suggestion_maxnum=self.suggestion_maxnum,
|
||||
fields=search_fields
|
||||
)
|
||||
if matches:
|
||||
match = matches[0]
|
||||
break
|
||||
match, suggestions = self.do_search(query, entries)
|
||||
|
||||
if not match:
|
||||
# no topic matches found. Only give suggestions.
|
||||
|
|
@ -448,24 +534,15 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if isinstance(match, HelpCategory):
|
||||
# no subtopics for categories - these are just lists of topics
|
||||
|
||||
output = self.format_help_index(
|
||||
{
|
||||
match.key: [
|
||||
cmd.key
|
||||
for cmd in all_cmd_topics
|
||||
if match.key.lower() == cmd.help_category
|
||||
]
|
||||
},
|
||||
{
|
||||
match.key: [
|
||||
topic.key
|
||||
for topic in all_file_db_topics
|
||||
if match.key.lower() == topic.help_category
|
||||
]
|
||||
},
|
||||
title_lone_category=True
|
||||
)
|
||||
category = match.key
|
||||
category_lower = category.lower()
|
||||
cmds_in_category = [key for key, cmd in cmd_help_topics.items()
|
||||
if category_lower == cmd.help_category]
|
||||
topics_in_category = [key for key, topic in file_db_help_topics.items()
|
||||
if category_lower == topic.help_category]
|
||||
output = self.format_help_index({category: cmds_in_category},
|
||||
{category: topics_in_category},
|
||||
title_lone_category=True)
|
||||
self.msg_help(output)
|
||||
return
|
||||
|
||||
|
|
@ -565,7 +642,7 @@ def _quithelp(caller):
|
|||
del caller.db._editing_help
|
||||
|
||||
|
||||
class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
||||
class CmdSetHelp(CmdHelp):
|
||||
"""
|
||||
Edit the help database.
|
||||
|
||||
|
|
@ -629,9 +706,15 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
|||
"""
|
||||
|
||||
key = "sethelp"
|
||||
aliases = []
|
||||
switch_options = ("edit", "replace", "append", "extend", "delete")
|
||||
locks = "cmd:perm(Helper)"
|
||||
help_category = "Building"
|
||||
arg_regex = None
|
||||
|
||||
def parse(self):
|
||||
"""We want to use the default parser rather than the CmdHelp.parse"""
|
||||
return COMMAND_DEFAULT_CLASS.parse(self)
|
||||
|
||||
def func(self):
|
||||
"""Implement the function"""
|
||||
|
|
@ -659,18 +742,63 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
|||
old_entry = None
|
||||
|
||||
# check if we have an old entry with the same name
|
||||
try:
|
||||
for querystr in topicstrlist:
|
||||
old_entry = HelpEntry.objects.find_topicmatch(querystr) # also search by alias
|
||||
if old_entry:
|
||||
old_entry = list(old_entry)[0]
|
||||
|
||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
||||
self.collect_topics(self.caller, mode='query')
|
||||
# db-help topics takes priority over file-help
|
||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
||||
# commands take priority over the other types
|
||||
all_topics = {**file_db_help_topics, **cmd_help_topics}
|
||||
# get all categories
|
||||
all_categories = list(set(
|
||||
HelpCategory(topic.help_category) for topic in all_topics.values()))
|
||||
# all available help options - will be searched in order. We also check # the
|
||||
# read-permission here.
|
||||
entries = list(all_topics.values()) + all_categories
|
||||
|
||||
# default setup
|
||||
category = lhslist[1] if nlist > 1 else DEFAULT_HELP_CATEGORY
|
||||
lockstring = ",".join(lhslist[2:]) if nlist > 2 else "read:all()"
|
||||
|
||||
# search for existing entries of this or other types
|
||||
old_entry = None
|
||||
for querystr in topicstrlist:
|
||||
match, _ = self.do_search(querystr, entries)
|
||||
if match:
|
||||
warning = None
|
||||
if isinstance(match, HelpCategory):
|
||||
warning = (f"'{querystr}' matches (or partially matches) the name of "
|
||||
"help-category '{match.key}'. If you continue, your help entry will "
|
||||
"take precedence and the category (or part of its name) *may* not "
|
||||
"be usable for grouping help entries anymore.")
|
||||
elif inherits_from(match, "evennia.commands.command.Command"):
|
||||
warning = (f"'{querystr}' matches (or partially matches) the key/alias of "
|
||||
"Command '{match.key}'. Command-help take precedence over other "
|
||||
"help entries so your help *may* be impossible to reach for those "
|
||||
"with access to that command.")
|
||||
elif inherits_from(match, "evennia.help.filehelp.FileHelpEntry"):
|
||||
warning = (f"'{querystr}' matches (or partially matches) the name/alias of the "
|
||||
"file-based help file '{match.key}'. File-help entries cannot be "
|
||||
"modified from in-game (they are files on-disk). If you continue, "
|
||||
"your help entry *may* shadow the file-based one's name partly or "
|
||||
"completely.")
|
||||
if warning:
|
||||
# show a warning for a clashing help-entry type. Even if user accepts this
|
||||
# we don't break here since we may need to show warnings for other inputs.
|
||||
# We don't count this as an old-entry hit because we can't edit these
|
||||
# types of entries.
|
||||
self.msg(f"|rWarning:\n|r{warning}|n")
|
||||
repl = yield("|wDo you still want to continue? Y/[N]?|n")
|
||||
if repl.lower() not in ('y', 'yes'):
|
||||
self.msg("Aborted.")
|
||||
return
|
||||
else:
|
||||
# a db-based help entry - this is OK
|
||||
old_entry = match
|
||||
category = lhslist[1] if nlist > 1 else old_entry.help_category
|
||||
lockstring = ",".join(lhslist[2:]) if nlist > 2 else old_entry.locks.get()
|
||||
break
|
||||
category = lhslist[1] if nlist > 1 else old_entry.help_category
|
||||
lockstring = ",".join(lhslist[2:]) if nlist > 2 else old_entry.locks.get()
|
||||
except Exception:
|
||||
old_entry = None
|
||||
category = lhslist[1] if nlist > 1 else DEFAULT_HELP_CATEGORY
|
||||
lockstring = ",".join(lhslist[2:]) if nlist > 2 else "view:all()"
|
||||
|
||||
category = category.lower()
|
||||
|
||||
if "edit" in switches:
|
||||
|
|
@ -739,8 +867,8 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
|||
self.msg("Overwrote the old topic '%s'%s." % (topicstr, aliastxt))
|
||||
else:
|
||||
self.msg(
|
||||
"Topic '%s'%s already exists. Use /replace to overwrite "
|
||||
"or /append or /merge to add text to it." % (topicstr, aliastxt)
|
||||
f"Topic '{topicstr}'{aliastxt} already exists. Use /edit to open in editor, or "
|
||||
"/replace, /append and /merge to modify it directly."
|
||||
)
|
||||
else:
|
||||
# no old entry. Create a new one.
|
||||
|
|
@ -748,7 +876,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
|||
topicstr, self.rhs, category=category, locks=lockstring, aliases=aliases
|
||||
)
|
||||
if new_entry:
|
||||
self.msg("Topic '%s'%s was successfully created." % (topicstr, aliastxt))
|
||||
self.msg(f"Topic '{topicstr}'{aliastxt} was successfully created.")
|
||||
if "edit" in switches:
|
||||
# open the line editor to edit the helptext
|
||||
self.caller.db._editing_help = new_entry
|
||||
|
|
@ -763,5 +891,5 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
else:
|
||||
self.msg(
|
||||
"Error when creating topic '%s'%s! Contact an admin." % (topicstr, aliastxt)
|
||||
f"Error when creating topic '{topicstr}'{aliastxt}! Contact an admin."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -431,6 +431,7 @@ class TestHelp(CommandTest):
|
|||
help_module.CmdSetHelp(),
|
||||
"testhelp, General = This is a test",
|
||||
"Topic 'testhelp' was successfully created.",
|
||||
cmdset=CharacterCmdSet()
|
||||
)
|
||||
self.call(help_module.CmdHelp(), "testhelp", "Help for testhelp", cmdset=CharacterCmdSet())
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ forms of help that do not concern commands, like information about the
|
|||
game world, policy info, rules and similar.
|
||||
|
||||
"""
|
||||
from datetime import datetime
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import re
|
|||
|
||||
# these are words that Lunr normally ignores but which we want to find
|
||||
# since we use them (e.g. as command names).
|
||||
# Lunr's default word list is found here:
|
||||
# Lunr's default ignore-word list is found here:
|
||||
# https://github.com/yeraydiazdiaz/lunr.py/blob/master/lunr/stop_word_filter.py
|
||||
_LUNR_STOP_WORD_FILTER_EXCEPTIONS = ("about", "might")
|
||||
_LUNR_STOP_WORD_FILTER_EXCEPTIONS = ("about", "might", "get")
|
||||
|
||||
_LUNR = None
|
||||
_LUNR_EXCEPTION = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue