Functioning help subcategories
This commit is contained in:
parent
062aba2926
commit
67908c5af0
4 changed files with 348 additions and 74 deletions
|
|
@ -4,6 +4,7 @@ are best written by those that write the commands - the admins. So
|
||||||
command-help is all auto-loaded and searched from the current command
|
command-help is all auto-loaded and searched from the current command
|
||||||
set. The normal, database-tied help system is used for collaborative
|
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
|
import re
|
||||||
|
|
@ -19,7 +20,7 @@ from evennia.utils.utils import (
|
||||||
string_suggestions,
|
string_suggestions,
|
||||||
class_from_module,
|
class_from_module,
|
||||||
inherits_from,
|
inherits_from,
|
||||||
format_grid,
|
format_grid, pad
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
@ -27,10 +28,12 @@ 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(
|
_RE_HELP_SUBTOPICS_START = re.compile(
|
||||||
r"^\s*?#\s*?help[- ]subtopics\s*?$", re.I + re.M)
|
r"^\s*?#\s*?subtopics\s*?$", re.I + re.M)
|
||||||
_RE_HELP_SUBTOPIC_SPLIT = re.compile(r"^\s*?(\#{2,6}\s*?\w+?)$", re.M)
|
_RE_HELP_SUBTOPIC_SPLIT = re.compile(r"^\s*?(\#{2,6}\s*?\w+?[a-z0-9 \-\?!,\.]*?)$", re.M + re.I)
|
||||||
_RE_HELP_SUBTOPIC_PARSE = re.compile(
|
_RE_HELP_SUBTOPIC_PARSE = re.compile(
|
||||||
r"^(?P<nesting>\#{2,6})\s*?(?P<name>.*?)$", re.I + re.M)
|
r"^(?P<nesting>\#{2,6})\s*?(?P<name>.*?)$", re.I + re.M)
|
||||||
|
MAX_SUBTOPIC_NESTING = 5
|
||||||
|
|
||||||
|
|
||||||
# limit symbol import for API
|
# limit symbol import for API
|
||||||
__all__ = ("CmdHelp", "CmdSetHelp")
|
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||||
|
|
@ -123,7 +126,7 @@ def parse_entry_for_subcategories(entry):
|
||||||
'''
|
'''
|
||||||
Main topic text
|
Main topic text
|
||||||
|
|
||||||
# HELP-SUBCATEGORIES
|
# SUBTOPICS
|
||||||
|
|
||||||
## foo
|
## foo
|
||||||
|
|
||||||
|
|
@ -176,10 +179,9 @@ def parse_entry_for_subcategories(entry):
|
||||||
"""
|
"""
|
||||||
topic, *subtopics = _RE_HELP_SUBTOPICS_START.split(entry, maxsplit=1)
|
topic, *subtopics = _RE_HELP_SUBTOPICS_START.split(entry, maxsplit=1)
|
||||||
structure = {None: topic.strip()}
|
structure = {None: topic.strip()}
|
||||||
subtopics_index = []
|
|
||||||
|
|
||||||
if subtopics:
|
if subtopics:
|
||||||
subctopics = subtopics[0]
|
subtopics = subtopics[0]
|
||||||
else:
|
else:
|
||||||
return structure
|
return structure
|
||||||
|
|
||||||
|
|
@ -187,14 +189,20 @@ def parse_entry_for_subcategories(entry):
|
||||||
current_nesting = 0
|
current_nesting = 0
|
||||||
subtopic = None
|
subtopic = None
|
||||||
|
|
||||||
for part in _RE_HELP_SUBTOPIC_SPLIT.split(subtopics):
|
# from evennia import set_trace;set_trace()
|
||||||
subtopic_match = _RE_HELP_SUBTOPIC_PARSE.match(part)
|
for part in _RE_HELP_SUBTOPIC_SPLIT.split(subtopics.strip()):
|
||||||
|
|
||||||
|
subtopic_match = _RE_HELP_SUBTOPIC_PARSE.match(part.strip())
|
||||||
if subtopic_match:
|
if subtopic_match:
|
||||||
# a new sub(-sub..) category starts.
|
# a new sub(-sub..) category starts.
|
||||||
mdict = subtopic_match.groupdict()
|
mdict = subtopic_match.groupdict()
|
||||||
subtopic = mdict['name'].strip()
|
subtopic = mdict['name'].lower().strip()
|
||||||
subtopic_index.append(subtopic)
|
|
||||||
new_nesting = len(mdict['nesting']) - 1
|
new_nesting = len(mdict['nesting']) - 1
|
||||||
|
|
||||||
|
if new_nesting > MAX_SUBTOPIC_NESTING:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Can have max {MAX_SUBTOPIC_NESTING} levels of nested help subtopics.")
|
||||||
|
|
||||||
nestdiff = new_nesting - current_nesting
|
nestdiff = new_nesting - current_nesting
|
||||||
if nestdiff < 0:
|
if nestdiff < 0:
|
||||||
# jumping back up in nesting
|
# jumping back up in nesting
|
||||||
|
|
@ -203,22 +211,28 @@ def parse_entry_for_subcategories(entry):
|
||||||
keypath.pop()
|
keypath.pop()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
elif nestdiff == 0:
|
||||||
|
# don't add a deeper nesting but replace the current
|
||||||
|
try:
|
||||||
|
keypath.pop()
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
keypath.append(subtopic)
|
keypath.append(subtopic)
|
||||||
current_nesting = new_nesting
|
current_nesting = new_nesting
|
||||||
else:
|
else:
|
||||||
# an entry belonging to a subtopic - find the nested location
|
# an entry belonging to a subtopic - find the nested location
|
||||||
dct = structure
|
dct = structure
|
||||||
if not keypath and subtopic is not None:
|
if not keypath and subtopic is not None:
|
||||||
structure[subtopic] = part.strip()
|
structure[subtopic] = dedent(part.strip())
|
||||||
else:
|
else:
|
||||||
for key in keypath:
|
for key in keypath:
|
||||||
if key in dct:
|
if key in dct:
|
||||||
dct = dct[key]
|
dct = dct[key]
|
||||||
else:
|
else:
|
||||||
dct[key] = {
|
dct[key] = {
|
||||||
None: part.strip()
|
None: dedent(part.strip())
|
||||||
}
|
}
|
||||||
return structure, subtopic_index
|
return structure
|
||||||
|
|
||||||
|
|
||||||
class CmdHelp(COMMAND_DEFAULT_CLASS):
|
class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -250,6 +264,11 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
# 'HELP_MORE = False' in your settings/conf/settings.py
|
# 'HELP_MORE = False' in your settings/conf/settings.py
|
||||||
help_more = HELP_MORE
|
help_more = HELP_MORE
|
||||||
|
|
||||||
|
# colors for the help index
|
||||||
|
index_type_separator_clr = "|w"
|
||||||
|
index_category_clr = "|W"
|
||||||
|
index_topic_clr = "|G"
|
||||||
|
|
||||||
# suggestion cutoff, between 0 and 1 (1 => perfect match)
|
# suggestion cutoff, between 0 and 1 (1 => perfect match)
|
||||||
suggestion_cutoff = 0.6
|
suggestion_cutoff = 0.6
|
||||||
|
|
||||||
|
|
@ -295,32 +314,41 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
help_text (str): Text of the help entry.
|
help_text (str): Text of the help entry.
|
||||||
aliases (list): List of help-aliases (displayed in header).
|
aliases (list): List of help-aliases (displayed in header).
|
||||||
suggested (list): Strings suggested reading (based on title).
|
suggested (list): Strings suggested reading (based on title).
|
||||||
subtopics (list): A list of strings - the subcategories to this entry.
|
subtopics (list): A list of strings - the subcategories available
|
||||||
|
for this entry.
|
||||||
|
|
||||||
Returns the formatted string, ready to be sent.
|
Returns the formatted string, ready to be sent.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
start = f"{_SEP}\n"
|
start = f"{_SEP}\n"
|
||||||
|
|
||||||
title = f"|CHelp for |w{topic}|n" if topic else ""
|
title = f"|CHelp for |w{topic}|n" if topic else ""
|
||||||
aliases = (
|
|
||||||
" |C(aliases: {}|C)|n".format("|C,|n ".join(f"|w{ali}|n" for ali in aliases))
|
if aliases:
|
||||||
if aliases else ""
|
aliases = (
|
||||||
)
|
" |C(aliases: {}|C)|n".format("|C,|n ".join(f"|w{ali}|n" for ali in aliases))
|
||||||
help_text = (
|
|
||||||
f"\n{dedent(help_text.rstrip())}" if help_text else ""
|
|
||||||
)
|
|
||||||
subtopics = (
|
|
||||||
"\nSubtopics (read with |whelp {} / subtopic): {}".format(
|
|
||||||
topic, "|C,|n ".join(f"|w{subtop}|n" for subtop in subtopics)
|
|
||||||
if subtopics else ""
|
|
||||||
)
|
)
|
||||||
)
|
else:
|
||||||
suggested = (
|
aliases = ''
|
||||||
"\n\n|CSuggested:|n {}".format(
|
|
||||||
fill("|C,|n ".join(f"|w{sug}|n" for sug in suggested))
|
help_text = f"\n\n{dedent(' ' + help_text.strip())}\n" if help_text else ""
|
||||||
|
|
||||||
|
if subtopics:
|
||||||
|
subtopics = (
|
||||||
|
"\n|CSubtopics:|n\n {}".format(
|
||||||
|
"\n ".join(f"|w{topic}/{subtop}|n" for subtop in subtopics))
|
||||||
)
|
)
|
||||||
if suggested else ""
|
else:
|
||||||
)
|
subtopics = ''
|
||||||
|
|
||||||
|
if suggested:
|
||||||
|
suggested = (
|
||||||
|
"\n\n|CSuggested other topics:|n\n{}".format(
|
||||||
|
fill("|C,|n ".join(f"|w{sug}|n" for sug in suggested), indent=2))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
suggested = ''
|
||||||
|
|
||||||
end = f"\n{_SEP}"
|
end = f"\n{_SEP}"
|
||||||
|
|
||||||
return "".join((start, title, aliases, help_text, subtopics, suggested, end))
|
return "".join((start, title, aliases, help_text, subtopics, suggested, end))
|
||||||
|
|
@ -345,27 +373,61 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
custom display of the list of commands and topics.
|
custom display of the list of commands and topics.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
category_clr = "|w"
|
def _group_by_category(help_dict):
|
||||||
topic_clr = "|G"
|
grid = []
|
||||||
|
verbatim_elements = []
|
||||||
|
|
||||||
|
if len(help_dict) == 1:
|
||||||
|
# don't list categories if there is only one
|
||||||
|
for category in help_dict:
|
||||||
|
entries = sorted(set(help_dict.get(category, [])))
|
||||||
|
grid.extend(entries)
|
||||||
|
else:
|
||||||
|
# list the categories
|
||||||
|
for category in sorted(set(list(help_dict.keys()))):
|
||||||
|
category_str = f"-- {category.title()} "
|
||||||
|
grid.append(
|
||||||
|
ANSIString(
|
||||||
|
self.index_category_clr + category_str
|
||||||
|
+ "-" * (width - len(category_str))
|
||||||
|
+ self.index_topic_clr
|
||||||
|
)
|
||||||
|
)
|
||||||
|
verbatim_elements.append(len(grid) - 1)
|
||||||
|
|
||||||
|
entries = sorted(set(help_dict.get(category, [])))
|
||||||
|
grid.extend(entries)
|
||||||
|
|
||||||
|
return grid, verbatim_elements
|
||||||
|
|
||||||
|
help_index = ""
|
||||||
width = self.client_width()
|
width = self.client_width()
|
||||||
grid = []
|
grid = []
|
||||||
verbatim_elements = []
|
verbatim_elements = []
|
||||||
for category in sorted(set(list(cmd_help_dict.keys()) + list(db_help_dict.keys()))):
|
|
||||||
|
|
||||||
category_str = f"-- {category.title()} "
|
|
||||||
grid.append(
|
|
||||||
ANSIString(
|
|
||||||
category_clr + category_str + "-" * (width - len(category_str)) + topic_clr
|
|
||||||
)
|
|
||||||
)
|
|
||||||
verbatim_elements.append(len(grid) - 1)
|
|
||||||
|
|
||||||
entries = sorted(set(cmd_help_dict.get(category, []) + db_help_dict.get(category, [])))
|
|
||||||
grid.extend(entries)
|
|
||||||
|
|
||||||
|
# get the command-help entries by-category
|
||||||
|
sep1 = (self.index_type_separator_clr
|
||||||
|
+ pad("Commands", width=width, fillchar='-')
|
||||||
|
+ self.index_topic_clr)
|
||||||
|
grid, verbatim_elements = _group_by_category(cmd_help_dict)
|
||||||
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
||||||
gridrows = ANSIString("\n").join(gridrows)
|
cmd_grid = ANSIString("\n").join(gridrows) if gridrows else ""
|
||||||
return gridrows
|
|
||||||
|
# get db-based help entries by-category
|
||||||
|
sep2 = (self.index_type_separator_clr
|
||||||
|
+ pad("Game & World", width=width, fillchar='-')
|
||||||
|
+ self.index_topic_clr)
|
||||||
|
grid, verbatim_elements = _group_by_category(db_help_dict)
|
||||||
|
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
||||||
|
db_grid = ANSIString("\n").join(gridrows) if gridrows else ""
|
||||||
|
|
||||||
|
# only show the main separators if there are actually both cmd and db-based help
|
||||||
|
if cmd_grid and db_grid:
|
||||||
|
help_index = f"{sep1}\n{cmd_grid}\n{sep2}\n{db_grid}"
|
||||||
|
else:
|
||||||
|
help_index = f"{cmd_grid}{db_grid}"
|
||||||
|
|
||||||
|
return help_index
|
||||||
|
|
||||||
def check_show_help(self, cmd, caller):
|
def check_show_help(self, cmd, caller):
|
||||||
"""
|
"""
|
||||||
|
|
@ -518,7 +580,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
{
|
{
|
||||||
match.key: [
|
match.key: [
|
||||||
topic.key
|
topic.key
|
||||||
for topic in all_topics
|
for topic in all_db_topics
|
||||||
if match.key.lower() == topic.help_category
|
if match.key.lower() == topic.help_category
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -530,7 +592,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
# a command match
|
# a command match
|
||||||
topic = match.key
|
topic = match.key
|
||||||
help_text = match.get_help(caller, cmdset)
|
help_text = match.get_help(caller, cmdset)
|
||||||
aliases = match.aliases,
|
aliases = match.aliases
|
||||||
suggested=suggestions[1:]
|
suggested=suggestions[1:]
|
||||||
else:
|
else:
|
||||||
# a database match
|
# a database match
|
||||||
|
|
@ -550,22 +612,44 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
if subtopics:
|
if subtopics:
|
||||||
# if we asked for subtopics, parse the found topic_text to see if any match.
|
# if we asked for subtopics, parse the found topic_text to see if any match.
|
||||||
# the subtopics is a list describing the path through the subtopic_map.
|
# the subtopics is a list describing the path through the subtopic_map.
|
||||||
|
|
||||||
for subtopic_query in subtopics:
|
for subtopic_query in subtopics:
|
||||||
subtopic_query_lower = subtopic_query.lower()
|
|
||||||
checked_topic = topic + f" / {subtopic_query.lower().capitalize()}"
|
if subtopic_query not in subtopic_map:
|
||||||
|
# exact match failed. Try startswith-match
|
||||||
|
fuzzy_match = False
|
||||||
|
for key in subtopic_map:
|
||||||
|
if key and key.startswith(subtopic_query):
|
||||||
|
subtopic_query = key
|
||||||
|
fuzzy_match = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not fuzzy_match:
|
||||||
|
# startswith failed - try an 'in' match
|
||||||
|
for key in subtopic_map:
|
||||||
|
if key and subtopic_query in key:
|
||||||
|
subtopic_query = key
|
||||||
|
fuzzy_match = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not fuzzy_match:
|
||||||
|
# no match found - give up
|
||||||
|
checked_topic = topic + f"/{subtopic_query}"
|
||||||
|
output = self.format_help_entry(
|
||||||
|
topic=topic,
|
||||||
|
help_text=f"No help entry found for '{checked_topic}'",
|
||||||
|
subtopics=subtopic_index
|
||||||
|
)
|
||||||
|
self.msg_help(output)
|
||||||
|
return
|
||||||
|
|
||||||
|
# if we get here we have an exact or fuzzy match
|
||||||
|
|
||||||
|
subtopic_map = subtopic_map.pop(subtopic_query)
|
||||||
subtopic_index = [subtopic for subtopic in subtopic_map if subtopic is not None]
|
subtopic_index = [subtopic for subtopic in subtopic_map if subtopic is not None]
|
||||||
if subtopic_query_lower() in subtopic_index:
|
# keep stepping down into the tree, append path to show position
|
||||||
# keep stepping down into the tree
|
topic = topic + f"/{subtopic_query}"
|
||||||
subtopic_map = subtopic_map.pop(subtopic_query)
|
|
||||||
topic = checked_topic
|
|
||||||
else:
|
|
||||||
output = self.format_help_entry(
|
|
||||||
topic=topic,
|
|
||||||
help_text=f"No help entry found for '{checked_topic}'",
|
|
||||||
subtopics=subtopic_index
|
|
||||||
)
|
|
||||||
self.msg_help(output)
|
|
||||||
return
|
|
||||||
# we reached the bottom of the topic tree
|
# we reached the bottom of the topic tree
|
||||||
help_text = subtopic_map[None]
|
help_text = subtopic_map[None]
|
||||||
|
|
||||||
|
|
@ -614,15 +698,51 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
||||||
delete - remove help topic.
|
delete - remove help topic.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
sethelp throw = This throws something at ...
|
sethelp lore = In the beginning was ...
|
||||||
sethelp/append pickpocketing,Thievery = This steals ...
|
sethelp/append pickpocketing,Thievery = This steals ...
|
||||||
sethelp/replace pickpocketing, ,attr(is_thief) = This steals ...
|
sethelp/replace pickpocketing, ,attr(is_thief) = This steals ...
|
||||||
sethelp/edit thievery
|
sethelp/edit thievery
|
||||||
|
|
||||||
This command manipulates the help database. A help entry can be created,
|
If not assigning a category, the "General" category will be used. If no
|
||||||
appended/merged to and deleted. If you don't assign a category, the
|
lockstring is specified, everyone will be able to read the help entry.
|
||||||
"General" category will be used. If no lockstring is specified, default
|
Sub-topics are embedded in the help text.
|
||||||
is to let everyone read the help file.
|
|
||||||
|
Note that this cannot modify command-help entries - these are modified
|
||||||
|
in-code, outside the game.
|
||||||
|
|
||||||
|
# SUBTOPICS
|
||||||
|
|
||||||
|
## Adding subtopics
|
||||||
|
|
||||||
|
Subtopics helps to break up a long help entry into sub-sections. Users can
|
||||||
|
access subtopics with |whelp topic/subtopic/...|n Subtopics are created and
|
||||||
|
stored together with the main topic.
|
||||||
|
|
||||||
|
To start adding subtopics, add the text '# SUBTOPICS' on a new line at the
|
||||||
|
end of your help text. After this you can now add any number of subtopics,
|
||||||
|
each starting with '## <subtopic-name>' on a line, followed by the
|
||||||
|
help-text of that subtopic.
|
||||||
|
Use '### <subsub-name>' to add a sub-subtopic and so on. Max depth is 5. A
|
||||||
|
subtopic's title is case-insensitive and can consist of multiple words -
|
||||||
|
the user will be able to enter a partial match to access it.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
| Main help text for <topic>
|
||||||
|
|
|
||||||
|
| # SUBTOPICS
|
||||||
|
|
|
||||||
|
| ## about
|
||||||
|
|
|
||||||
|
| Text for the '<topic>/about' subtopic'
|
||||||
|
|
|
||||||
|
| ### more about-info
|
||||||
|
|
|
||||||
|
| Text for the '<topic>/about/more about-info sub-subtopic
|
||||||
|
|
|
||||||
|
| ## extra
|
||||||
|
|
|
||||||
|
| Text for the '<topic>/extra' subtopic
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import types
|
||||||
import datetime
|
import datetime
|
||||||
from anything import Anything
|
from anything import Anything
|
||||||
|
|
||||||
|
from parameterized import parameterized
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from unittest.mock import patch, Mock, MagicMock
|
from unittest.mock import patch, Mock, MagicMock
|
||||||
|
|
||||||
|
|
@ -442,7 +443,7 @@ class TestHelp(CommandTest):
|
||||||
entry = """
|
entry = """
|
||||||
Main topic text
|
Main topic text
|
||||||
|
|
||||||
# help-subtopics
|
# subtopics
|
||||||
|
|
||||||
## foo
|
## foo
|
||||||
|
|
||||||
|
|
@ -484,9 +485,155 @@ class TestHelp(CommandTest):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actual_result = help_module.CmdHelp.parse_entry_for_subcategories(entry)
|
actual_result = help_module.parse_entry_for_subcategories(entry)
|
||||||
self.assertEqual(expected, actual_result)
|
self.assertEqual(expected, actual_result)
|
||||||
|
|
||||||
|
def test_parse_single_entry(self):
|
||||||
|
"""
|
||||||
|
Test parsing single subcategory
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
entry = """
|
||||||
|
Main topic text
|
||||||
|
|
||||||
|
# SUBTOPICS
|
||||||
|
|
||||||
|
## creating extra stuff
|
||||||
|
|
||||||
|
Help on creating extra stuff.
|
||||||
|
|
||||||
|
"""
|
||||||
|
expected = {
|
||||||
|
None: "Main topic text",
|
||||||
|
"creating extra stuff": {
|
||||||
|
None: "Help on creating extra stuff."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual_result = help_module.parse_entry_for_subcategories(entry)
|
||||||
|
self.assertEqual(expected, actual_result)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
("test", # main help entry
|
||||||
|
"Help for test\n\n"
|
||||||
|
"Main help text\n\n"
|
||||||
|
"Subtopics:\n"
|
||||||
|
" test/creating extra stuff\n"
|
||||||
|
" test/something else\n"
|
||||||
|
" test/more"
|
||||||
|
),
|
||||||
|
("test/creating extra stuff", # subtopic, full match
|
||||||
|
"Help for test/creating extra stuff\n\n"
|
||||||
|
"Help on creating extra stuff.\n\n"
|
||||||
|
"Subtopics:\n"
|
||||||
|
" test/creating extra stuff/subsubtopic\n"
|
||||||
|
),
|
||||||
|
("test/creating", # startswith-match
|
||||||
|
"Help for test/creating extra stuff\n\n"
|
||||||
|
"Help on creating extra stuff.\n\n"
|
||||||
|
"Subtopics:\n"
|
||||||
|
" test/creating extra stuff/subsubtopic\n"
|
||||||
|
),
|
||||||
|
("test/extra", # partial match
|
||||||
|
"Help for test/creating extra stuff\n\n"
|
||||||
|
"Help on creating extra stuff.\n\n"
|
||||||
|
"Subtopics:\n"
|
||||||
|
" test/creating extra stuff/subsubtopic\n"
|
||||||
|
),
|
||||||
|
("test/extra/subsubtopic", # partial subsub-match
|
||||||
|
"Help for test/creating extra stuff/subsubtopic\n\n"
|
||||||
|
"A subsubtopic text"
|
||||||
|
),
|
||||||
|
("test/creating extra/subsub", # partial subsub-match
|
||||||
|
"Help for test/creating extra stuff/subsubtopic\n\n"
|
||||||
|
"A subsubtopic text"
|
||||||
|
),
|
||||||
|
("test/Something else", # case
|
||||||
|
"Help for test/something else\n\n"
|
||||||
|
"Something else"
|
||||||
|
),
|
||||||
|
("test/More", # case
|
||||||
|
"Help for test/more\n\n"
|
||||||
|
"Another text\n\n"
|
||||||
|
"Subtopics:\n"
|
||||||
|
" test/more/second-more"
|
||||||
|
),
|
||||||
|
("test/More/Second-more",
|
||||||
|
"Help for test/more/second-more\n\n"
|
||||||
|
"The Second More text.\n\n"
|
||||||
|
"Subtopics:\n"
|
||||||
|
" test/more/second-more/more again\n\n"
|
||||||
|
" test/more/second-more/third more"
|
||||||
|
),
|
||||||
|
("test/More/-more", # partial match
|
||||||
|
"Help for test/more/second-more\n\n"
|
||||||
|
"The Second More text.\n\n"
|
||||||
|
"Subtopics:\n"
|
||||||
|
" test/more/second-more/more again\n"
|
||||||
|
" test/more/second-more/third more"
|
||||||
|
),
|
||||||
|
("test/more/second/more again",
|
||||||
|
"Help for test/more/second-more/more again\n"
|
||||||
|
"Even more text.\n"
|
||||||
|
),
|
||||||
|
("test/more/second/third",
|
||||||
|
"Help for test/more/second-more/third more\n\n"
|
||||||
|
"Third more text\n"
|
||||||
|
),
|
||||||
|
])
|
||||||
|
def test_subtopic_fetch(self, helparg, expected):
|
||||||
|
"""
|
||||||
|
Check retrieval of subtopics.
|
||||||
|
|
||||||
|
"""
|
||||||
|
class TestCmd(Command):
|
||||||
|
"""
|
||||||
|
Main help text
|
||||||
|
|
||||||
|
# SUBTOPICS
|
||||||
|
|
||||||
|
## creating extra stuff
|
||||||
|
|
||||||
|
Help on creating extra stuff.
|
||||||
|
|
||||||
|
### subsubtopic
|
||||||
|
|
||||||
|
A subsubtopic text
|
||||||
|
|
||||||
|
## Something else
|
||||||
|
|
||||||
|
Something else
|
||||||
|
|
||||||
|
## More
|
||||||
|
|
||||||
|
Another text
|
||||||
|
|
||||||
|
### Second-More
|
||||||
|
|
||||||
|
The Second More text.
|
||||||
|
|
||||||
|
#### More again
|
||||||
|
|
||||||
|
Even more text.
|
||||||
|
|
||||||
|
#### Third more
|
||||||
|
|
||||||
|
Third more text
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = "test"
|
||||||
|
|
||||||
|
class TestCmdSet(CmdSet):
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
self.add(TestCmd())
|
||||||
|
self.add(help_module.CmdHelp())
|
||||||
|
|
||||||
|
self.call(help_module.CmdHelp(),
|
||||||
|
helparg,
|
||||||
|
expected,
|
||||||
|
cmdset=TestCmdSet())
|
||||||
|
|
||||||
|
|
||||||
class TestSystem(CommandTest):
|
class TestSystem(CommandTest):
|
||||||
def test_py(self):
|
def test_py(self):
|
||||||
|
|
|
||||||
|
|
@ -1476,7 +1476,7 @@ class NickHandler(AttributeHandler):
|
||||||
then must mark numbered arguments as a named regex-groupd named `argN`.
|
then must mark numbered arguments as a named regex-groupd named `argN`.
|
||||||
For example, `(?P<arg1>.+?)` will match the behavior of using `$1`
|
For example, `(?P<arg1>.+?)` will match the behavior of using `$1`
|
||||||
in the shell pattern.
|
in the shell pattern.
|
||||||
kwargs (any, optional): These are passed on to `AttributeHandler.get`.
|
**kwargs (any, optional): These are passed on to `AttributeHandler.get`.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
For most cases, the shell-pattern is much shorter and easier. The
|
For most cases, the shell-pattern is much shorter and easier. The
|
||||||
|
|
|
||||||
|
|
@ -168,16 +168,18 @@ def crop(text, width=None, suffix="[...]"):
|
||||||
return to_str(text)
|
return to_str(text)
|
||||||
|
|
||||||
|
|
||||||
def dedent(text, baseline_index=None):
|
def dedent(text, baseline_index=None, indent=None):
|
||||||
"""
|
"""
|
||||||
Safely clean all whitespace at the left of a paragraph.
|
Safely clean all whitespace at the left of a paragraph.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text (str): The text to dedent.
|
text (str): The text to dedent.
|
||||||
baseline_index (int or None, optional): Which row to use as a 'base'
|
baseline_index (int, optional): Which row to use as a 'base'
|
||||||
for the indentation. Lines will be dedented to this level but
|
for the indentation. Lines will be dedented to this level but
|
||||||
no further. If None, indent so as to completely deindent the
|
no further. If None, indent so as to completely deindent the
|
||||||
least indented text.
|
least indented text.
|
||||||
|
indent (int, optional): If given, force all lines to this indent.
|
||||||
|
This bypasses `baseline_index`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
text (str): Dedented string.
|
text (str): Dedented string.
|
||||||
|
|
@ -190,7 +192,12 @@ def dedent(text, baseline_index=None):
|
||||||
"""
|
"""
|
||||||
if not text:
|
if not text:
|
||||||
return ""
|
return ""
|
||||||
if baseline_index is None:
|
if indent is not None:
|
||||||
|
lines = text.split("\n")
|
||||||
|
ind = " " * indent
|
||||||
|
indline = "\n" + ind
|
||||||
|
return ind + indline.join(line.strip() for line in lines)
|
||||||
|
elif baseline_index is None:
|
||||||
return textwrap.dedent(text)
|
return textwrap.dedent(text)
|
||||||
else:
|
else:
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue