Start add help subtopics

This commit is contained in:
Griatch 2021-04-25 21:39:46 +02:00
parent 3236421143
commit 7043634e5b
3 changed files with 192 additions and 5 deletions

View file

@ -76,7 +76,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
channel/unban[/quiet] channelname[, channelname, ...] = subscribername
channel/who channelname
This handles all operations on channels.
# help-subcategories
## channel/list
This handles all operations on channels. Note that the default operation is to
assign a nick/alias for sending to a channel. This would mean you can send
using 'foo Hello world' instead of using 'channel foo = Hello world'. Note that
aliases set when creating the channel are made available as aliases to subscribers
automatically.
"""
key = "channel"

View file

@ -6,6 +6,7 @@ set. The normal, database-tied help system is used for collaborative
creation of other help topics such as RP help or game-world aides.
"""
import re
from django.conf import settings
from collections import defaultdict
from evennia.utils.utils import fill, dedent
@ -25,6 +26,12 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
HELP_MORE = settings.HELP_MORE
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
_RE_HELP_SUBTOPICS_START = re.compile(
r"^\s*?#\s*?help[- ]subtopics\s*?$", re.I + re.M)
_RE_HELP_SUBTOPIC_SPLIT = re.compile(r"^\s*?(\#{2,6}\s*?\w+?)$", re.M)
_RE_HELP_SUBTOPIC_PARSE = re.compile(
r"^(?P<nesting>\#{2,6})\s*?(?P<name>.*?)$", re.I + re.M)
# limit symbol import for API
__all__ = ("CmdHelp", "CmdSetHelp")
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
@ -152,6 +159,122 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
self.msg(text=(text, {"type": "help"}))
@staticmethod
def parse_entry_for_subcategories(entry):
"""
Parse a command docstring for special sub-category blocks:
Args:
entry (str): A help entry to parse
Returns:
dict: A mapping that splits the entry into subcategories. This
will always hold a key `None` for the main help entry and
zero or more keys holding the subcategories. Each is itself
a dict with a key `None` for the main text of that subcategory
followed by any sub-sub-categories down to a max-depth of 5.
Example:
::
'''
Main topic text
# help-subcategories
## foo
A subcategory of the main entry, accessible as `help topic foo`
(or using /, like `help topic/foo`)
## bar
Another subcategory, accessed as `help topic bar`
(or `help topic/bar`)
### moo
A subcategory of bar, accessed as `help bar moo`
(or `help bar/moo`)
#### dum
A subcategory of moo, accessed `help bar moo dum`
(or `help bar/moo/dum`)
'''
This will result in this returned entry structure:
::
{
None: "Main topic text":
"foo": {
None: "main topic/foo text"
},
"bar": {
None: "Main topic/bar text",
"moo": {
None: "topic/bar/moo text"
"dum": {
None: "topic/bar/moo/dum text"
}
}
}
}
Apart from making
sub-categories at the bottom of the entry.
This will be applied both to command docstrings and database-based help
entries.
"""
topic, *subcategories = _RE_HELP_SUBTOPICS_START.split(entry, maxsplit=1)
structure = {None: topic.strip()}
if subcategories:
subcategories = subcategories[0]
else:
return structure
keypath = []
current_nesting = 0
subtopic = None
for part in _RE_HELP_SUBTOPIC_SPLIT.split(subcategories):
subtopic_match = _RE_HELP_SUBTOPIC_PARSE.match(part)
if subtopic_match:
# a new sub(-sub..) category starts.
mdict = subtopic_match.groupdict()
subtopic = mdict['name'].strip()
new_nesting = len(mdict['nesting']) - 1
nestdiff = new_nesting - current_nesting
if nestdiff < 0:
# jumping back up in nesting
for _ in range(abs(nestdiff) + 1):
try:
keypath.pop()
except IndexError:
pass
keypath.append(subtopic)
current_nesting = new_nesting
else:
# an entry belonging to a subtopic - find the nested location
dct = structure
if not keypath and subtopic is not None:
structure[subtopic] = part.strip()
else:
for key in keypath:
if key in dct:
dct = dct[key]
else:
dct[key] = {
None: part.strip()
}
return structure
@staticmethod
def format_help_entry(title, help_text, aliases=None, suggested=None):
"""
@ -260,6 +383,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
self.original_args = self.args.strip()
self.args = self.args.strip().lower()
def func(self):
"""
Run the dynamic help entry creator.

View file

@ -23,7 +23,7 @@ from evennia import DefaultRoom, DefaultExit, ObjectDB
from evennia.commands.default.cmdset_character import CharacterCmdSet
from evennia.utils.test_resources import EvenniaTest
from evennia.commands.default import (
help,
help as help_module,
general,
system,
admin,
@ -407,6 +407,9 @@ class TestGeneral(CommandTest):
class TestHelp(CommandTest):
maxDiff = None
def setUp(self):
super().setUp()
# we need to set up a logger here since lunr takes over the logger otherwise
@ -421,15 +424,68 @@ class TestHelp(CommandTest):
logging.disable(level=logging.ERROR)
def test_help(self):
self.call(help.CmdHelp(), "", "Admin", cmdset=CharacterCmdSet())
self.call(help_module.CmdHelp(), "", "Admin", cmdset=CharacterCmdSet())
def test_set_help(self):
self.call(
help.CmdSetHelp(),
help_module.CmdSetHelp(),
"testhelp, General = This is a test",
"Topic 'testhelp' was successfully created.",
)
self.call(help.CmdHelp(), "testhelp", "Help for testhelp", cmdset=CharacterCmdSet())
self.call(help_module.CmdHelp(), "testhelp", "Help for testhelp", cmdset=CharacterCmdSet())
def test_parse_entry(self):
"""
Test for subcategories
"""
entry = """
Main topic text
# help-subtopics
## foo
Foo sub-category
### moo
Foo/Moo subsub-category
#### dum
Foo/Moo/Dum subsubsub-category
## bar
Bar subcategory
### moo
Bar/Moo subcategory
"""
expected = {
None: "Main topic text",
"foo": {
None: "Foo sub-category",
"moo": {
None: "Foo/Moo subsub-category",
"dum": {
None: "Foo/Moo/Dum subsubsub-category",
}
},
},
"bar": {
None: "Bar subcategory",
"moo": {
None: "Bar/Moo subcategory"
}
}
}
actual_result = help_module.CmdHelp.parse_entry_for_subcategories(entry)
self.assertEqual(expected, actual_result)
class TestSystem(CommandTest):