Fix bugs, correct one-line format_grid function

This commit is contained in:
Griatch 2021-05-12 09:32:08 +02:00
parent 770fac275d
commit 055bbcfee3
7 changed files with 162 additions and 113 deletions

View file

@ -51,14 +51,13 @@ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
class CmdChannel(COMMAND_DEFAULT_CLASS): class CmdChannel(COMMAND_DEFAULT_CLASS):
""" """
Talk on and manage in-game channels. Use and manage in-game channels.
Usage: Usage:
channel
channel channelname <msg> channel channelname <msg>
channel channel name [= <msg>] channel channel name = <msg>
channel/list channel (show all subscription)
channel/all channel/all (show available channels)
channel/alias channelname = alias[;alias...] channel/alias channelname = alias[;alias...]
channel/unalias alias channel/unalias alias
channel/who channelname channel/who channelname
@ -73,10 +72,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
channel/desc channelname = description channel/desc channelname = description
channel/lock channelname = lockstring channel/lock channelname = lockstring
channel/unlock channelname = lockstring channel/unlock channelname = lockstring
channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]
channel/ban channelname (list bans) channel/ban channelname (list bans)
channe/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason] channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: logmessage]
channel/unban[/quiet] channelname[, channelname, ...] = subscribername channel/unban[/quiet] channelname[, channelname, ...] = subscribername
channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]
# subtopics # subtopics
@ -85,8 +84,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
Usage: channel channelname msg Usage: channel channelname msg
channel channel name = msg (with space in channel name) channel channel name = msg (with space in channel name)
This sends a message to the channel. Note that you will rarely use this command This sends a message to the channel. Note that you will rarely use this
like this; instead you can use the alias command like this; instead you can use the alias
channelname <msg> channelname <msg>
channelalias <msg> channelalias <msg>
@ -117,8 +116,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
wguild Hello wguild Hello
warchannel Hello warchannel Hello
Note that this will not work if the alias has a space in it. So the 'warrior guild' Note that this will not work if the alias has a space in it. So the
alias must be used with the `channel` command: 'warrior guild' alias must be used with the `channel` command:
channel warrior guild = Hello channel warrior guild = Hello
@ -138,6 +137,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
This will display the last |c20|n lines of channel history. By supplying an This will display the last |c20|n lines of channel history. By supplying an
index number, you will step that many lines back before viewing those 20 lines. index number, you will step that many lines back before viewing those 20 lines.
For example: For example:
channel/history public = 35 channel/history public = 35
@ -184,12 +184,37 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
control - who controls the channel. This is usually the one creating control - who controls the channel. This is usually the one creating
the channel. the channel.
Common lockfuncs are all() and perm(). To make a channel everyone can listen Common lockfuncs are all() and perm(). To make a channel everyone can
to but only builders can talk on, use this: listen to but only builders can talk on, use this:
listen:all() listen:all()
send: perm(Builders) send: perm(Builders)
## boot and ban
Usage: channel/ban channelname (list bans)
channel/ban channelname[, channelname, ...] = subscribername [: logmessage]
channel/unban channelname[, channelname, ...] = subscribername
channel/unban channelname
channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]
Booting will kick a named subscriber from a channel temporarily. It will
also remove all their aliases. They are still able to re-connect unless
they are also banned. The 'reason' given will be echoed to the user and
channel.
Banning will block a given subscriber's ability to connect to a channel. It
will not automatically boot them. The 'logmessage' will be stored on the
channel and shown when you list your bans (so you can remember why they
were banned).
So to permanently get rid of a user, the way to do so is to first ban them
and then boot them.
Example:
ban mychannel1,mychannel2 = EvilUser : Was banned for spamming.
boot mychannel1,mychannel2 = EvilUser : No more spamming!
""" """
key = "channel" key = "channel"
aliases = ["chan", "channels"] aliases = ["chan", "channels"]
@ -505,7 +530,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
new_chan = create.create_channel( new_chan = create.create_channel(
name, aliases=aliases, desc=description, locks=lockstring, typeclass=typeclass) name, aliases=aliases, desc=description, locks=lockstring, typeclass=typeclass)
new_chan.connect(caller) self.sub_to_channel(new_chan)
return new_chan, "" return new_chan, ""
def destroy_channel(self, channel, message=None): def destroy_channel(self, channel, message=None):
@ -831,12 +856,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
table = self.display_all_channels(subscribed, available) table = self.display_all_channels(subscribed, available)
self.msg( self.msg(
"\n|wAvailable channels|n (use /list to " "\n|wAvailable channels|n (use no argument to "
f"only show subscriptions)\n{table}") f"only show your subscriptions)\n{table}")
return return
if not channel_names: if not channel_names:
# (empty or /list) show only subscribed channels # empty arg show only subscribed channels
subscribed, _ = self.list_channels() subscribed, _ = self.list_channels()
table = self.display_subbed_channels(subscribed) table = self.display_subbed_channels(subscribed)

View file

@ -31,8 +31,6 @@ DEFAULT_HELP_CATEGORY = settings.DEFAULT_HELP_CATEGORY
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdHelp", "CmdSetHelp") __all__ = ("CmdHelp", "CmdSetHelp")
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
_SEP = "|C" + "-" * _DEFAULT_WIDTH + "|n"
@dataclass @dataclass
@ -59,7 +57,7 @@ class HelpCategory:
class CmdHelp(COMMAND_DEFAULT_CLASS): class CmdHelp(COMMAND_DEFAULT_CLASS):
""" """
View help or a list of topics Get help.
Usage: Usage:
help help
@ -67,8 +65,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
help <topic>/<subtopic> help <topic>/<subtopic>
help <topic>/<subtopic>/<subsubtopic> ... help <topic>/<subtopic>/<subsubtopic> ...
Use the help command alone to see an index of all help topics, organized by Use the 'help' command alone to see an index of all help topics, organized
category. Some long topics may offer additional sub-topics. by category.eSome big topics may offer additional sub-topics.
""" """
@ -123,8 +121,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
self.msg(text=(text, {"type": "help"})) self.msg(text=(text, {"type": "help"}))
@staticmethod def format_help_entry(self, topic="", help_text="", aliases=None, suggested=None,
def format_help_entry(topic="", help_text="", aliases=None, suggested=None,
subtopics=None): subtopics=None):
""" """
This visually formats the help entry. This visually formats the help entry.
@ -142,7 +139,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
Returns the formatted string, ready to be sent. Returns the formatted string, ready to be sent.
""" """
start = f"{_SEP}\n" separator = "|C" + "-" * self.client_width() + "|n"
start = f"{separator}\n"
title = f"|CHelp for |w{topic}|n" if topic else "|rNo help found|n" title = f"|CHelp for |w{topic}|n" if topic else "|rNo help found|n"
@ -156,26 +154,27 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
help_text = "\n\n" + dedent(help_text.strip('\n')) + "\n" if help_text else "" help_text = "\n\n" + dedent(help_text.strip('\n')) + "\n" if help_text else ""
if subtopics: if subtopics:
subtopics = [f"|w{topic}/{subtop}|n" for subtop in subtopics]
subtopics = ( subtopics = (
"\n|CSubtopics:|n\n {}".format( "\n|CSubtopics:|n\n {}".format(
"\n ".join(f"|w{topic}/{subtop}|n" for subtop in subtopics)) "\n ".join(format_grid(subtopics, width=self.client_width())))
) )
else: else:
subtopics = '' subtopics = ''
if suggested: if suggested:
suggested = [f"|w{sug}|n" for sug in suggested]
suggested = ( suggested = (
"\n|CSuggestions:|n\n{}".format( "\n|CSuggestions:|n\n{}".format(
fill("|C,|n ".join(f"|w{sug}|n" for sug in suggested), indent=2)) "\n ".join(format_grid(suggested, width=self.client_width())))
) )
else: else:
suggested = '' suggested = ''
end = f"\n{_SEP}" end = f"\n{separator}"
return "".join((start, title, aliases, help_text, subtopics, suggested, end)) return "".join((start, title, aliases, help_text, subtopics, suggested, end))
def format_help_index(self, cmd_help_dict=None, db_help_dict=None, title_lone_category=False): def format_help_index(self, cmd_help_dict=None, db_help_dict=None, title_lone_category=False):
""" """
Output a category-ordered g for displaying the main help, grouped by Output a category-ordered g for displaying the main help, grouped by

View file

@ -439,8 +439,8 @@ class TestHelp(CommandTest):
"Help for test\n\n" "Help for test\n\n"
"Main help text\n\n" "Main help text\n\n"
"Subtopics:\n" "Subtopics:\n"
" test/creating extra stuff\n" " test/creating extra stuff"
" test/something else\n" " test/something else"
" test/more" " test/more"
), ),
("test/creating extra stuff", # subtopic, full match ("test/creating extra stuff", # subtopic, full match
@ -483,14 +483,14 @@ class TestHelp(CommandTest):
"Help for test/more/second-more\n\n" "Help for test/more/second-more\n\n"
"The Second More text.\n\n" "The Second More text.\n\n"
"Subtopics:\n" "Subtopics:\n"
" test/more/second-more/more again\n" " test/more/second-more/more again"
" test/more/second-more/third more" " test/more/second-more/third more"
), ),
("test/More/-more", # partial match ("test/More/-more", # partial match
"Help for test/more/second-more\n\n" "Help for test/more/second-more\n\n"
"The Second More text.\n\n" "The Second More text.\n\n"
"Subtopics:\n" "Subtopics:\n"
" test/more/second-more/more again\n" " test/more/second-more/more again"
" test/more/second-more/third more" " test/more/second-more/third more"
), ),
("test/more/second/more again", ("test/more/second/more again",

View file

@ -59,6 +59,8 @@ things you want from here into your game folder and change them there.
time to pass depending on if you are walking/running etc. time to pass depending on if you are walking/running etc.
* Talking NPC (Griatch 2011) - A talking NPC object that offers a * Talking NPC (Griatch 2011) - A talking NPC object that offers a
menu-driven conversation tree. menu-driven conversation tree.
* Traits (Whitenoise, Griatch 2021) - Properties for handling and tracking
changing RPG skill and stat values.
* Tree Select (FlutterSprite 2017) - A simple system for creating a * Tree Select (FlutterSprite 2017) - A simple system for creating a
branching EvMenu with selection options sourced from a single branching EvMenu with selection options sourced from a single
multi-line string. multi-line string.

View file

@ -173,16 +173,9 @@ def parse_entry_for_subcategories(entry):
} }
} }
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, *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('\n')}
if subtopics: if subtopics:
subtopics = subtopics[0] subtopics = subtopics[0]

View file

@ -321,7 +321,7 @@ class TestFormatGrid(TestCase):
"""Grid with small variations""" """Grid with small variations"""
elements = self._generate_elements(3, 1, 30) elements = self._generate_elements(3, 1, 30)
rows = utils.format_grid(elements, width=78) rows = utils.format_grid(elements, width=78)
self.assertEqual(len(rows), 3) self.assertEqual(len(rows), 4)
self.assertTrue(all(len(row) == 78 for row in rows)) self.assertTrue(all(len(row) == 78 for row in rows))
def test_disparate_grid(self): def test_disparate_grid(self):
@ -356,7 +356,7 @@ class TestFormatGrid(TestCase):
"lock", "lock",
) )
rows = utils.format_grid(elements, width=78) rows = utils.format_grid(elements, width=78)
self.assertEqual(len(rows), 2) self.assertEqual(len(rows), 3)
for element in elements: for element in elements:
self.assertTrue(element in "\n".join(rows), f"element {element} is missing.") self.assertTrue(element in "\n".join(rows), f"element {element} is missing.")

View file

@ -1869,14 +1869,27 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
like this to make it easier to insert decorations between rows, such like this to make it easier to insert decorations between rows, such
as horizontal bars. as horizontal bars.
""" """
if not elements: def _minimal_rows(elements):
return [] """
if not verbatim_elements: Minimalistic distribution with minimal spacing, good for single-line
verbatim_elements = [] grids but will look messy over many lines.
"""
rows = [""]
for element in elements:
rowlen = len(rows[-1])
elen = len(element)
if rowlen + elen <= width:
rows[-1] += element
else:
rows.append(element)
return rows
def _weighted_rows(elements):
"""
Dynamic-space, good for making even columns in a multi-line grid but
will look strange for a single line.
"""
nelements = len(elements)
# add sep to all but the very last element
elements = [elements[ie] + sep for ie in range(nelements - 1)] + [elements[-1]]
wls = [len(elem) for elem in elements] wls = [len(elem) for elem in elements]
wls_percentile = [wl for iw, wl in enumerate(wls) if iw not in verbatim_elements] wls_percentile = [wl for iw, wl in enumerate(wls) if iw not in verbatim_elements]
@ -1945,9 +1958,26 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
# last element, make sure to store # last element, make sure to store
row += " " * max(0, width - len(row)) row += " " * max(0, width - len(row))
rows.append(row) rows.append(row)
return rows return rows
if not elements:
return []
if not verbatim_elements:
verbatim_elements = []
nelements = len(elements)
# add sep to all but the very last element
elements = [elements[ie] + sep for ie in range(nelements - 1)] + [elements[-1]]
if sum(len(element) for element in elements) <= width:
# grid fits in one line
return _minimal_rows(elements)
else:
# full multi-line grid
return _weighted_rows(elements)
def get_evennia_pids(): def get_evennia_pids():
""" """