Start add help subtopics

This commit is contained in:
Griatch 2021-04-25 21:39:46 +02:00
parent a10a297c55
commit e301a1410f
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/unban[/quiet] channelname[, channelname, ...] = subscribername
channel/who channelname 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" 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. creation of other help topics such as RP help or game-world aides.
""" """
import re
from django.conf import settings from django.conf import settings
from collections import defaultdict from collections import defaultdict
from evennia.utils.utils import fill, dedent 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 HELP_MORE = settings.HELP_MORE
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES 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 # limit symbol import for API
__all__ = ("CmdHelp", "CmdSetHelp") __all__ = ("CmdHelp", "CmdSetHelp")
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
@ -152,6 +159,122 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
self.msg(text=(text, {"type": "help"})) 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 @staticmethod
def format_help_entry(title, help_text, aliases=None, suggested=None): 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.original_args = self.args.strip()
self.args = self.args.strip().lower() self.args = self.args.strip().lower()
def func(self): def func(self):
""" """
Run the dynamic help entry creator. 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.commands.default.cmdset_character import CharacterCmdSet
from evennia.utils.test_resources import EvenniaTest from evennia.utils.test_resources import EvenniaTest
from evennia.commands.default import ( from evennia.commands.default import (
help, help as help_module,
general, general,
system, system,
admin, admin,
@ -407,6 +407,9 @@ class TestGeneral(CommandTest):
class TestHelp(CommandTest): class TestHelp(CommandTest):
maxDiff = None
def setUp(self): def setUp(self):
super().setUp() super().setUp()
# we need to set up a logger here since lunr takes over the logger otherwise # 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) logging.disable(level=logging.ERROR)
def test_help(self): def test_help(self):
self.call(help.CmdHelp(), "", "Admin", cmdset=CharacterCmdSet()) self.call(help_module.CmdHelp(), "", "Admin", cmdset=CharacterCmdSet())
def test_set_help(self): def test_set_help(self):
self.call( self.call(
help.CmdSetHelp(), help_module.CmdSetHelp(),
"testhelp, General = This is a test", "testhelp, General = This is a test",
"Topic 'testhelp' was successfully created.", "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): class TestSystem(CommandTest):