Prettify help text layouts

This commit is contained in:
dave 2018-02-10 19:52:32 -08:00
parent 4bdce886ab
commit a77bde4288
20 changed files with 83 additions and 66 deletions

View File

@ -11,15 +11,17 @@ import and use a decorator from this module. For example:
# ...
@info("help [command] show the manual for all or [commands]", cmds=["help"])
@info("help [command]", "show the manual for all or [commands]", cmds=["help"])
@command("help")
def cmd_help(self, msg, cmd):
# ...
The `info` decorator takes a mandatory string parameter describing the command. The second, optional, list parameter
`cmds` is a list of short names thart are aliases for the function that aide in help lookup. In all cases, the cases,
commands will be prefixed with the default command prefix (`from pyircbot.modulebase.command.prefix`).
The `info` decorator takes mandatory string parameters describing the command. The first is an argspec; simply the name
of the command with parameters marked using `<` and `>`. Optional parameters should be encased in `[`square brackets`]`.
The second parameter is a short text description of the command. The third, optional, list parameter `cmds` is a list of
short names thart are aliases for the function that aide in help lookup. In all cases, the cases, commands will be
prefixed with the default command prefix (`from pyircbot.modulebase.command.prefix`).
Class Reference

View File

@ -32,7 +32,7 @@ class ASCII(ModuleBase):
self.running_asciis = defaultdict(lambda: None)
self.killed_channels = defaultdict(lambda: False)
@info("listascii list available asciis", cmds=["listascii"])
@info("listascii", "list available asciis", cmds=["listascii"])
@command("listascii")
def cmd_listascii(self, msg, cmd):
"""
@ -48,7 +48,7 @@ class ASCII(ModuleBase):
self.bot.act_PRIVMSG(msg.args[0], "...and {} more".format(len(fnames) - self.config.get("list_max")))
return
@info("ascii <name> print an ascii", cmds=["ascii"])
@info("ascii <name>", "print an ascii", cmds=["ascii"])
@command("ascii", require_args=True)
def cmd_ascii(self, msg, cmd):
if self.channel_busy(msg.args[0]):
@ -68,7 +68,7 @@ class ASCII(ModuleBase):
except FileNotFoundError:
return
@info("stopascii stop the currently scrolling ascii", cmds=["stopascii"])
@info("stopascii", "stop the currently scrolling ascii", cmds=["stopascii"])
@command("stopascii")
def cmd_stopascii(self, msg, cmd):
"""
@ -119,7 +119,7 @@ class ASCII(ModuleBase):
del self.running_asciis[channel]
del self.killed_channels[channel]
@info("asciiedit <args> customize an ascii with input", cmds=["asciiedit"])
@info("asciiedit <args>", "customize an ascii with input", cmds=["asciiedit"])
@command("asciiedit", require_args=True)
def cmd_asciiedit(self, msg, cmd):
ascii_name = cmd.args.pop(0)

View File

@ -20,7 +20,7 @@ class BitcoinPrice(ModuleBase):
self.cache = None
self.cacheAge = 0
@info("btc retrieve the current price of bitcoin", cmds=["btc"])
@info("btc", "retrieve the current price of bitcoin", cmds=["btc"])
@command("btc", "bitcoin")
def btc(self, msg, cmd):
replyTo = msg.prefix.nick if "#" not in msg.args[0] else msg.args[0]

View File

@ -80,7 +80,7 @@ class Calc(ModuleBase):
seconds = int(remaining - (minutes * 60))
return "Please wait %s minute(s) and %s second(s)." % (minutes, seconds)
@info("quote [key[ =[ value]]] set or update facts", cmds=["quote"])
@info("quote [key[ =[ value]]]", "set or update facts", cmds=["quote"])
@regex(r'(?:^\.?(?:calc|quote)(?:\s+?(?:([^=]+)(?:\s?(=)\s?(.+)?)?)?)?)', types=['PRIVMSG'])
def cmd_calc(self, message, match):
word, changeit, value = match.groups()
@ -136,7 +136,7 @@ class Calc(ModuleBase):
randCalc["definition"], randCalc["by"]))
self.updateTimeSince(channel, "calc")
@info("match <value> search for facts by key", cmds=["match"])
@info("match <value>", "search for facts by key", cmds=["match"])
@command("match", require_args=True)
def cmd_match(self, msg, cmd):
if self.config["delayMatch"] > 0 and self.timeSince(msg.args[0], "match") < self.config["delayMatch"]:

View File

@ -21,7 +21,7 @@ class CryptoWallet(ModuleBase):
def getMods(self):
return (self.bot.getBestModuleForService("attributes"), self.bot.getBestModuleForService("bitcoinrpc"))
@info("setaddr <currency> <address> set withdraw address", cmds=["setaddr"])
@info("setaddr <currency> <address>", "set withdraw address", cmds=["setaddr"])
@command("setaddr", require_args=2, allow_private=True)
def handle_setaddr(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
@ -46,7 +46,7 @@ class CryptoWallet(ModuleBase):
self.bot.act_PRIVMSG(msg.args[0], ".setaddr: Your address has been saved as: {}. Please verify that this is "
"correct or your coins could be lost.".format(cmd.args[1]))
@info("getbal <currency> retrieve your balance ", cmds=["getbal"])
@info("getbal <currency>", "retrieve your balance ", cmds=["getbal"])
@command("getbal", require_args=1, allow_private=True)
def handle_getbal(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
@ -72,7 +72,7 @@ class CryptoWallet(ModuleBase):
self.bot.act_PRIVMSG(msg.args[0],
"{}: your balance is: {} {}".format(msg.prefix.nick, amount, cmd.args[0].upper()))
@info("withdraw <currency> <amount> send coins to your withdraw address", cmds=["withdraw"])
@info("withdraw <currency> <amount>", "send coins to your withdraw address", cmds=["withdraw"])
@command("withdraw", require_args=2, allow_private=True)
def handle_withdraw(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
@ -132,7 +132,7 @@ class CryptoWallet(ModuleBase):
self.bot.act_PRIVMSG(msg.args[0], "{}: .withdraw: Transaction create failed. Maybe the transaction was too "
"large for the network? Try a smaller increment.".format(msg.prefix.nick))
@info("send <currency> <amount> <nick_or_address> send coins elsewhere", cmds=["send"])
@info("send <currency> <amount> <nick_or_address>", "send coins elsewhere", cmds=["send"])
@command("send", require_args=3, allow_private=True)
def handle_send(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
@ -217,7 +217,7 @@ class CryptoWallet(ModuleBase):
self.bot.act_PRIVMSG(msg.args[0], "{}: uh-oh, something went wrong doing that."
.format(msg.prefix.nick))
@info("getaddr <currency> get deposit address", cmds=["getaddr"])
@info("getaddr <currency>", "get deposit address", cmds=["getaddr"])
@command("getaddr", require_args=1, allow_private=True)
def handle_getaddr(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
@ -237,7 +237,7 @@ class CryptoWallet(ModuleBase):
self.bot.act_PRIVMSG(msg.args[0], "{}: your {} deposit address is: {}"
.format(msg.prefix.nick, cmd.args[0].upper(), walletaddr))
@info("curinfo list supported coins", cmds=["curinfo"])
@info("curinfo", "list supported coins", cmds=["curinfo"])
@command("curinfo", allow_private=True)
def handle_curinfo(self, msg, cmd):
attr, rpc = self.getMods()

View File

@ -29,7 +29,7 @@ class DuckHunt(ModuleBase):
self.startHunt()
@info("huntscore show your duckhunt score", cmds=["huntscore"])
@info("huntscore", "show your duckhunt score", cmds=["huntscore"])
@command("huntscore", allow_private=True)
def hunt(self, msg, cmd):
scores = self.loadScores()
@ -61,7 +61,7 @@ class DuckHunt(ModuleBase):
(prime, runts, shots, misses))
# self.bot.act_PRIVMSG(fromWho, "More info & highscores: http://duckhunt.xmopx.net/")
@info("shoot shoot active targets", cmds=["shoot"])
@info("shoot", "shoot active targets", cmds=["shoot"])
@command("shoot")
def cmd_shoot(self, msg, args):
if self.isDuckOut:

View File

@ -33,7 +33,7 @@ class Inventory(ModuleBase):
) ;""")
c.close()
@info("have <item> give the bot an item", cmds=["have"])
@info("have <item>", "give the bot an item", cmds=["have"])
@command("have")
def checkInv(self, msg, cmd):
if len(cmd.args) < 1:
@ -57,7 +57,7 @@ class Inventory(ModuleBase):
self.bot.act_PRIVMSG(msg.args[0], self.config["recv_msg"] %
{"item": newItem, "adjective": "these " if newItem[-1:] == "s" else "this "})
@info("inventory show the bot's inventory", cmds=["inventory", "inv"])
@info("inventory", "show the bot's inventory", cmds=["inventory", "inv"])
@command("inventory", "inv")
def cmd_inv(self, msg, cmd):
inv = self.getinventory()

View File

@ -15,7 +15,7 @@ BASE_URL = "http://lmgtfy.com/?q="
class LMGTFY(ModuleBase):
@info("lmgtfy <term> display a condescending internet query", cmds=["lmgtfy"])
@info("lmgtfy <term>", "display a condescending internet query", cmds=["lmgtfy"])
@command("lmgtfy", require_args=True)
def handleMessage(self, msg, cmd):
link = self.createLink(cmd.args_str)

View File

@ -29,49 +29,70 @@ class info(object):
command has the alias "rtfm"
:type cmds: list
"""
def __init__(self, docstring, cmds=None):
def __init__(self, cmdspec, docstring, cmds=None):
self.cmdspec = cmdspec
self.docstring = docstring
self.commands = cmds or []
self.aliases = cmds or []
def __call__(self, func):
if hasattr(func, "irchelp"):
func.irchelp.append(self.docstring)
func.irchelp.append(self)
else:
setattr(func, "irchelp", [self.docstring])
if hasattr(func, "irchelpc"):
func.irchelpc.append(self.commands)
else:
setattr(func, "irchelpc", [self.commands])
setattr(func, "irchelp", [self])
return func
class ModInfo(ModuleBase):
@info("help [command] show the manual for all or [commands]", cmds=["help"])
@info("help [command]", "show the manual for all or [commands]", cmds=["help"])
@command("help")
def cmd_help(self, msg, cmd):
"""
Get help on a command
"""
if cmd.args:
for modname, module, helptext, helpcommands in self.iter_modules():
if cmd.args[0] in ["{}{}".format(command.prefix, i) for i in helpcommands]:
self.bot.act_PRIVMSG(msg.args[0], "RTFM: {}: {}".format(cmd.args[0], helptext))
for modname, module, cmdspec, docstring, aliases in self.iter_modules():
if cmd.args[0] in ["{}{}".format(command.prefix, i) for i in aliases]:
self.bot.act_PRIVMSG(msg.args[0], "RTFM: {}: ({}{}) {}"
.format(cmd.args[0], command.prefix, cmdspec, docstring))
else:
for modname, module, helptext, helpcommands in self.iter_modules():
self.bot.act_PRIVMSG(msg.args[0], "{}: {}{}".format(modname, command.prefix, helptext))
rows = []
for modname, module, cmdspec, docstring, aliases in self.iter_modules():
rows.append((modname, command.prefix + cmdspec, docstring))
rows.sort(key=lambda item: item[0] + item[1])
self.send_columnized(msg.args[0], rows)
@info("helpindex show a short list of all commands", cmds=["helpindex"])
def send_columnized(self, channel, rows):
if not rows:
return
widths = []
# Find how many col widths we must calculate
for _ in rows[0]:
widths.append(0)
# Find widest value per col
for row in rows:
for col, value in enumerate(row):
print(col, value)
vlen = len(value)
if vlen > widths[col]:
widths[col] = vlen
# Print each row
for row in rows:
message = ""
for colid, col in enumerate(row):
message += str(col)
message += (" " * (widths[colid] - len(col) + 1))
self.bot.act_PRIVMSG(channel, message)
@info("helpindex", "show a short list of all commands", cmds=["helpindex"])
@command("helpindex")
def cmd_helpindex(self, msg, cmd):
"""
Short index of commands
"""
commands = []
for modname, module, helptext, helpcommands in self.iter_modules():
commands += ["{}{}".format(command.prefix, i) for i in helpcommands]
for modname, module, cmdspec, docstring, aliases in self.iter_modules():
commands += ["{}{}".format(command.prefix, i) for i in aliases]
self.bot.act_PRIVMSG(msg.args[0], "{}: commands: {}".format(msg.prefix.nick, ", ".join(commands)))
@ -80,12 +101,12 @@ class ModInfo(ModuleBase):
Iterator that cycles through module methods that are tagged with help information. The iterator yields tuples
of:
(module_name, module_object, helptext, command_list)
(module_name, module_object, command_spec, command_help, command_list)
"""
for modname, module in self.bot.moduleInstances.items():
for attr_name in dir(module):
attr = getattr(module, attr_name)
if callable(attr) and hasattr(attr, "irchelp"):
for cmdhelp, cmdaliases in zip(getattr(attr, "irchelp"), getattr(attr, "irchelpc")):
yield (modname, module, cmdhelp, cmdaliases, )
for cmdinfo in attr.irchelp:
yield (modname, module, cmdinfo.cmdspec, cmdinfo.docstring, cmdinfo.aliases)
raise StopIteration()

View File

@ -20,7 +20,7 @@ class NFLLive(ModuleBase):
self.cache = None
self.cacheAge = 0
@info("nfl show nfl schedule & score", cmds=["nfl"])
@info("nfl", "show nfl schedule & score", cmds=["nfl"])
@command("nfl")
def nflitup(self, message, cmd):
games = self.getNflGamesCached()

View File

@ -36,9 +36,9 @@ class NickUser(ModuleBase):
else:
self.handlePm(msg.prefix, msg.trailing)
@info("setpass [<oldpass>] <password> set or change password", cmds=["setpass"])
@info("login <password> authenticate with the bot", cmds=["login"])
@info("logout log out of the bot", cmds=["logout"])
@info("setpass [<oldpass>] <password>", "set or change password", cmds=["setpass"])
@info("login <password>", "authenticate with the bot", cmds=["login"])
@info("logout", "log out of the bot", cmds=["logout"])
def handlePm(self, prefix, trailing):
cmd = messageHasCommand(".setpass", trailing)
if cmd:

View File

@ -33,7 +33,7 @@ class RandQuote(ModuleBase):
) ;""")
c.close()
@info("randquote print a random quote", cmds=["randquote", "randomquote", "rq"])
@info("randquote", "print a random quote", cmds=["randquote", "randomquote", "rq"])
@command("randquote", "randomquote", "rq")
def fetchquotes(self, msg, cmd):
c = self.db.query("SELECT * FROM `chat` ORDER BY RANDOM() LIMIT 1;")

View File

@ -106,7 +106,7 @@ class Remind(ModuleBase):
def ondisable(self):
self.disabled = True
@info("remind <time> have the bot remind you", cmds=["remind", "at"])
@info("remind <time>", "have the bot remind you", cmds=["remind", "at"])
@command("remind", "at", allow_private=True)
def remindat(self, msg, cmd):
regex = re.compile(r'(\d+):(\d+)(?::(\d+))?([^\s\d]+)? (.*)')
@ -198,7 +198,7 @@ class Remind(ModuleBase):
else:
return zonestr
@info("after <duration> <reminder> have the bot remind after", cmds=["after", "in"])
@info("after <duration> <reminder>", "have the bot remind after", cmds=["after", "in"])
@command("after", "in", allow_private=True)
def remindin(self, msg, cmd):
replyTo = msg.args[0]

View File

@ -134,7 +134,7 @@ class SMS(ModuleBase):
"""
cherrypy.engine.exit()
@info("text-<name> text somebody on the VIP list", cmds=["text"])
@info("text-<name>", "text somebody on the VIP list", cmds=["text"])
@regex(r'(?:^\.text\-([a-zA-Z0-9]+)(?:\s+(.+))?)', types=['PRIVMSG'])
def cmd_text(self, msg, match):
"""

View File

@ -38,7 +38,7 @@ class Seen(ModuleBase):
# self.log.info("Seen: %s on %s" % (nick.lower(), datest))
sql.commit()
@info("seen <nick> print last time user was seen", cmds=["seen"])
@info("seen <nick>", "print last time user was seen", cmds=["seen"])
@command("seen", require_args=True)
def lastSeen(self, message, command):
sql = self.getSql()

View File

@ -61,7 +61,7 @@ class Tell(ModuleBase):
# Delete
self.db.query("DELETE FROM `tells` WHERE `id`=?", (tell["id"],))
@info("tell <person> <message> relay a message when the target is online", cmds=["tell"])
@info("tell <person> <message>", "relay a message when the target is online", cmds=["tell"])
@command("tell", allow_private=True)
def tellcmds(self, msg, cmd):
if len(cmd.args) < 2:

View File

@ -14,7 +14,7 @@ from pyircbot.modules.ModInfo import info
class Urban(ModuleBase):
@info("urban <term> lookup an urban dictionary definition", cmds=["urban", "u"])
@info("urban <term>", "lookup an urban dictionary definition", cmds=["urban", "u"])
@command("urban", "u")
def urban(self, msg, cmd):
definitions = requests.get("http://www.urbandictionary.com/iphone/search/define",

View File

@ -32,7 +32,7 @@ class Weather(ModuleBase):
self.log.error("Weather: An 'attributes' service is required")
return
@info("weather [location] display the forecast", cmds=["weather", "w"])
@info("weather [location]", "display the forecast", cmds=["weather", "w"])
@command("weather", "w")
def cmd_weather(self, msg, cmd):
hasUnit = self.attr.get(msg.prefix.nick, "weather-unit")
@ -50,7 +50,7 @@ class Weather(ModuleBase):
self.bot.act_PRIVMSG(msg.args[0], "%s: %s" % (msg.prefix.nick, self.getWeather(weatherZip, hasUnit)))
@info("setloc <location> set your home location for weather lookups", cmds=["setloc"])
@info("setloc <location>", "set your home location for weather lookups", cmds=["setloc"])
@command("setloc", allow_private=True)
def cmd_setloc(self, msg, cmd):
# if not cmd.args:
@ -79,7 +79,7 @@ class Weather(ModuleBase):
if self.attr.get(msg.prefix.nick, "weather-zip") is None:
self.bot.act_PRIVMSG(reply_to, "Tip: choose C or F with .wunit <C/F>")
@info("wunit <c|f> set preferred weather unit", cmds=["wunit"])
@info("wunit <c|f>", "set preferred weather unit", cmds=["wunit"])
@command("wunit", allow_private=True)
def cmd_wunit(self, msg, cmd):
if cmd.args[0].lower() not in ['c', 'f']:

View File

@ -34,7 +34,7 @@ class Youtube(ModuleBase):
) # http://stackoverflow.com/a/16742742
return ISO_8601_period_rx.match(stamp).groupdict()
@info("yt search for youtube videos", cmds=["yt", "youtube"])
@info("yt", "search for youtube videos", cmds=["yt", "youtube"])
@command("yt", "youtube")
def youtube(self, msg, cmd):
j = get("https://www.googleapis.com/youtube/v3/search",

View File

@ -10,14 +10,8 @@ from tests.lib import * # NOQA - fixtures
@pytest.fixture
def nickbot(fakebot):
"""
Provide a bot loaded with the Calc module. Clear the database.
Provide a bot loaded with the Nickuser module. Clear the database.
"""
fakebot.botconfig["module_configs"]["Calc"] = {
"allowDelete": True,
"delaySubmit": 0,
"delayCalc": 0,
"delayCalcSpecific": 0,
"delayMatch": 0}
fakebot.loadmodule("SQLite")
with closing(fakebot.moduleInstances["SQLite"].opendb("attributes.db")) as db:
for table in ["attribute", "items", "values"]: