mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Fix bugs, correct one-line format_grid function
This commit is contained in:
parent
770fac275d
commit
055bbcfee3
7 changed files with 162 additions and 113 deletions
|
|
@ -51,14 +51,13 @@ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
|||
|
||||
class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
Talk on and manage in-game channels.
|
||||
Use and manage in-game channels.
|
||||
|
||||
Usage:
|
||||
channel
|
||||
channel channelname <msg>
|
||||
channel channel name [= <msg>]
|
||||
channel/list
|
||||
channel/all
|
||||
channel channel name = <msg>
|
||||
channel (show all subscription)
|
||||
channel/all (show available channels)
|
||||
channel/alias channelname = alias[;alias...]
|
||||
channel/unalias alias
|
||||
channel/who channelname
|
||||
|
|
@ -73,10 +72,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
channel/desc channelname = description
|
||||
channel/lock channelname = lockstring
|
||||
channel/unlock channelname = lockstring
|
||||
channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]
|
||||
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/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]
|
||||
|
||||
# subtopics
|
||||
|
||||
|
|
@ -85,8 +84,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
Usage: channel channelname msg
|
||||
channel channel name = msg (with space in channel name)
|
||||
|
||||
This sends a message to the channel. Note that you will rarely use this command
|
||||
like this; instead you can use the alias
|
||||
This sends a message to the channel. Note that you will rarely use this
|
||||
command like this; instead you can use the alias
|
||||
|
||||
channelname <msg>
|
||||
channelalias <msg>
|
||||
|
|
@ -117,8 +116,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
wguild Hello
|
||||
warchannel Hello
|
||||
|
||||
Note that this will not work if the alias has a space in it. So the 'warrior guild'
|
||||
alias must be used with the `channel` command:
|
||||
Note that this will not work if the alias has a space in it. So the
|
||||
'warrior guild' alias must be used with the `channel` command:
|
||||
|
||||
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
|
||||
index number, you will step that many lines back before viewing those 20 lines.
|
||||
|
||||
For example:
|
||||
|
||||
channel/history public = 35
|
||||
|
|
@ -182,14 +182,39 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
listen - who may listen or join the channel.
|
||||
send - who may send messages to the channel
|
||||
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
|
||||
to but only builders can talk on, use this:
|
||||
Common lockfuncs are all() and perm(). To make a channel everyone can
|
||||
listen to but only builders can talk on, use this:
|
||||
|
||||
listen:all()
|
||||
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"
|
||||
aliases = ["chan", "channels"]
|
||||
|
|
@ -505,7 +530,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
new_chan = create.create_channel(
|
||||
name, aliases=aliases, desc=description, locks=lockstring, typeclass=typeclass)
|
||||
new_chan.connect(caller)
|
||||
self.sub_to_channel(new_chan)
|
||||
return new_chan, ""
|
||||
|
||||
def destroy_channel(self, channel, message=None):
|
||||
|
|
@ -831,12 +856,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
table = self.display_all_channels(subscribed, available)
|
||||
|
||||
self.msg(
|
||||
"\n|wAvailable channels|n (use /list to "
|
||||
f"only show subscriptions)\n{table}")
|
||||
"\n|wAvailable channels|n (use no argument to "
|
||||
f"only show your subscriptions)\n{table}")
|
||||
return
|
||||
|
||||
if not channel_names:
|
||||
# (empty or /list) show only subscribed channels
|
||||
# empty arg show only subscribed channels
|
||||
subscribed, _ = self.list_channels()
|
||||
table = self.display_subbed_channels(subscribed)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ DEFAULT_HELP_CATEGORY = settings.DEFAULT_HELP_CATEGORY
|
|||
|
||||
# limit symbol import for API
|
||||
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||
_SEP = "|C" + "-" * _DEFAULT_WIDTH + "|n"
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -59,16 +57,16 @@ class HelpCategory:
|
|||
|
||||
class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
View help or a list of topics
|
||||
Get help.
|
||||
|
||||
Usage:
|
||||
help
|
||||
help <topic, command or category>
|
||||
help <topic> / <subtopic>
|
||||
help <topic> / <subtopic> / <subsubtopic> ...
|
||||
help <topic>/<subtopic>
|
||||
help <topic>/<subtopic>/<subsubtopic> ...
|
||||
|
||||
Use the help command alone to see an index of all help topics, organized by
|
||||
category. Some long topics may offer additional sub-topics.
|
||||
Use the 'help' command alone to see an index of all help topics, organized
|
||||
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"}))
|
||||
|
||||
@staticmethod
|
||||
def format_help_entry(topic="", help_text="", aliases=None, suggested=None,
|
||||
def format_help_entry(self, topic="", help_text="", aliases=None, suggested=None,
|
||||
subtopics=None):
|
||||
"""
|
||||
This visually formats the help entry.
|
||||
|
|
@ -142,7 +139,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
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"
|
||||
|
||||
|
|
@ -156,26 +154,27 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
help_text = "\n\n" + dedent(help_text.strip('\n')) + "\n" if help_text else ""
|
||||
|
||||
if subtopics:
|
||||
subtopics = [f"|w{topic}/{subtop}|n" for subtop in subtopics]
|
||||
subtopics = (
|
||||
"\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:
|
||||
subtopics = ''
|
||||
|
||||
if suggested:
|
||||
suggested = [f"|w{sug}|n" for sug in suggested]
|
||||
suggested = (
|
||||
"\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:
|
||||
suggested = ''
|
||||
|
||||
end = f"\n{_SEP}"
|
||||
end = f"\n{separator}"
|
||||
|
||||
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):
|
||||
"""
|
||||
Output a category-ordered g for displaying the main help, grouped by
|
||||
|
|
|
|||
|
|
@ -439,8 +439,8 @@ class TestHelp(CommandTest):
|
|||
"Help for test\n\n"
|
||||
"Main help text\n\n"
|
||||
"Subtopics:\n"
|
||||
" test/creating extra stuff\n"
|
||||
" test/something else\n"
|
||||
" test/creating extra stuff"
|
||||
" test/something else"
|
||||
" test/more"
|
||||
),
|
||||
("test/creating extra stuff", # subtopic, full match
|
||||
|
|
@ -483,14 +483,14 @@ class TestHelp(CommandTest):
|
|||
"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/more again"
|
||||
" 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/more again"
|
||||
" test/more/second-more/third more"
|
||||
),
|
||||
("test/more/second/more again",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
* Talking NPC (Griatch 2011) - A talking NPC object that offers a
|
||||
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
|
||||
branching EvMenu with selection options sourced from a single
|
||||
multi-line string.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
structure = {None: topic.strip()}
|
||||
structure = {None: topic.strip('\n')}
|
||||
|
||||
if subtopics:
|
||||
subtopics = subtopics[0]
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ class TestFormatGrid(TestCase):
|
|||
"""Grid with small variations"""
|
||||
elements = self._generate_elements(3, 1, 30)
|
||||
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))
|
||||
|
||||
def test_disparate_grid(self):
|
||||
|
|
@ -356,7 +356,7 @@ class TestFormatGrid(TestCase):
|
|||
"lock",
|
||||
)
|
||||
rows = utils.format_grid(elements, width=78)
|
||||
self.assertEqual(len(rows), 2)
|
||||
self.assertEqual(len(rows), 3)
|
||||
for element in elements:
|
||||
self.assertTrue(element in "\n".join(rows), f"element {element} is missing.")
|
||||
|
||||
|
|
|
|||
|
|
@ -1846,7 +1846,7 @@ def percentile(iterable, percent, key=lambda x: x):
|
|||
return d0 + d1
|
||||
|
||||
|
||||
def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
|
||||
def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
|
||||
"""
|
||||
This helper function makes a 'grid' output, where it distributes the given
|
||||
string-elements as evenly as possible to fill out the given width.
|
||||
|
|
@ -1869,6 +1869,97 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
|
|||
like this to make it easier to insert decorations between rows, such
|
||||
as horizontal bars.
|
||||
"""
|
||||
def _minimal_rows(elements):
|
||||
"""
|
||||
Minimalistic distribution with minimal spacing, good for single-line
|
||||
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.
|
||||
"""
|
||||
|
||||
wls = [len(elem) for elem in elements]
|
||||
wls_percentile = [wl for iw, wl in enumerate(wls) if iw not in verbatim_elements]
|
||||
|
||||
if wls_percentile:
|
||||
# get the nth percentile as a good representation of average width
|
||||
averlen = int(percentile(sorted(wls_percentile), 0.9)) + 2 # include extra space
|
||||
aver_per_row = width // averlen + 1
|
||||
else:
|
||||
# no adjustable rows, just keep all as-is
|
||||
aver_per_row = 1
|
||||
|
||||
if aver_per_row == 1:
|
||||
# one line per row, output directly since this is trivial
|
||||
# we use rstrip here to remove extra spaces added by sep
|
||||
return [
|
||||
crop(element.rstrip(), width) + " " * max(0, width - len(element.rstrip()))
|
||||
for iel, element in enumerate(elements)
|
||||
]
|
||||
|
||||
indices = [averlen * ind for ind in range(aver_per_row - 1)]
|
||||
|
||||
rows = []
|
||||
ic = 0
|
||||
row = ""
|
||||
for ie, element in enumerate(elements):
|
||||
|
||||
wl = wls[ie]
|
||||
lrow = len(row)
|
||||
debug = row.replace(" ", ".")
|
||||
|
||||
if lrow + wl > width:
|
||||
# this slot extends outside grid, move to next line
|
||||
row += " " * (width - lrow)
|
||||
rows.append(row)
|
||||
if wl >= width:
|
||||
# remove sep if this fills the entire line
|
||||
element = element.rstrip()
|
||||
row = crop(element, width)
|
||||
ic = 0
|
||||
elif ic >= aver_per_row - 1:
|
||||
# no more slots available on this line
|
||||
row += " " * max(0, (width - lrow))
|
||||
rows.append(row)
|
||||
row = crop(element, width)
|
||||
ic = 0
|
||||
else:
|
||||
try:
|
||||
while lrow > max(0, indices[ic]):
|
||||
# slot too wide, extend into adjacent slot
|
||||
ic += 1
|
||||
row += " " * max(0, indices[ic] - lrow)
|
||||
except IndexError:
|
||||
# we extended past edge of grid, crop or move to next line
|
||||
if ic == 0:
|
||||
row = crop(element, width)
|
||||
else:
|
||||
row += " " * max(0, width - lrow)
|
||||
rows.append(row)
|
||||
ic = 0
|
||||
else:
|
||||
# add a new slot
|
||||
row += element + " " * max(0, averlen - wl)
|
||||
ic += 1
|
||||
|
||||
if ie >= nelements - 1:
|
||||
# last element, make sure to store
|
||||
row += " " * max(0, width - len(row))
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
if not elements:
|
||||
return []
|
||||
if not verbatim_elements:
|
||||
|
|
@ -1877,76 +1968,15 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
|
|||
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_percentile = [wl for iw, wl in enumerate(wls) if iw not in verbatim_elements]
|
||||
|
||||
if wls_percentile:
|
||||
# get the nth percentile as a good representation of average width
|
||||
averlen = int(percentile(sorted(wls_percentile), 0.9)) + 2 # include extra space
|
||||
aver_per_row = width // averlen + 1
|
||||
if sum(len(element) for element in elements) <= width:
|
||||
# grid fits in one line
|
||||
return _minimal_rows(elements)
|
||||
else:
|
||||
# no adjustable rows, just keep all as-is
|
||||
aver_per_row = 1
|
||||
# full multi-line grid
|
||||
return _weighted_rows(elements)
|
||||
|
||||
if aver_per_row == 1:
|
||||
# one line per row, output directly since this is trivial
|
||||
# we use rstrip here to remove extra spaces added by sep
|
||||
return [
|
||||
crop(element.rstrip(), width) + " " * max(0, width - len(element.rstrip()))
|
||||
for iel, element in enumerate(elements)
|
||||
]
|
||||
|
||||
indices = [averlen * ind for ind in range(aver_per_row - 1)]
|
||||
|
||||
rows = []
|
||||
ic = 0
|
||||
row = ""
|
||||
for ie, element in enumerate(elements):
|
||||
|
||||
wl = wls[ie]
|
||||
lrow = len(row)
|
||||
debug = row.replace(" ", ".")
|
||||
|
||||
if lrow + wl > width:
|
||||
# this slot extends outside grid, move to next line
|
||||
row += " " * (width - lrow)
|
||||
rows.append(row)
|
||||
if wl >= width:
|
||||
# remove sep if this fills the entire line
|
||||
element = element.rstrip()
|
||||
row = crop(element, width)
|
||||
ic = 0
|
||||
elif ic >= aver_per_row - 1:
|
||||
# no more slots available on this line
|
||||
row += " " * max(0, (width - lrow))
|
||||
rows.append(row)
|
||||
row = crop(element, width)
|
||||
ic = 0
|
||||
else:
|
||||
try:
|
||||
while lrow > max(0, indices[ic]):
|
||||
# slot too wide, extend into adjacent slot
|
||||
ic += 1
|
||||
row += " " * max(0, indices[ic] - lrow)
|
||||
except IndexError:
|
||||
# we extended past edge of grid, crop or move to next line
|
||||
if ic == 0:
|
||||
row = crop(element, width)
|
||||
else:
|
||||
row += " " * max(0, width - lrow)
|
||||
rows.append(row)
|
||||
ic = 0
|
||||
else:
|
||||
# add a new slot
|
||||
row += element + " " * max(0, averlen - wl)
|
||||
ic += 1
|
||||
|
||||
if ie >= nelements - 1:
|
||||
# last element, make sure to store
|
||||
row += " " * max(0, width - len(row))
|
||||
rows.append(row)
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
def get_evennia_pids():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue