Implement command help system

This commit is contained in:
dave 2017-11-22 22:09:25 -08:00
parent 5c8f6b02fd
commit 2f88dc28c6
7 changed files with 143 additions and 37 deletions

View File

@ -0,0 +1,31 @@
:mod:`ModInfo` --- Module command help system
=============================================
Implements global `help` and `helpindex` commands that print help information about all available modules. Modules must
import and use a decorator from this module. For example:
.. code-block:: python
from pyircbot.modules.ModInfo import info
# ...
@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`).
Class Reference
---------------
.. automodule:: pyircbot.modules.ModInfo
:members:
:undoc-members:
:show-inheritance:

View File

@ -93,7 +93,6 @@ Client sending a message that the bot will relay
pyircbot_send default privmsg ["#clonebot", "asdf1234"]
"""
Example Programs
----------------

View File

@ -251,9 +251,6 @@ class regex(hook):
def cmd_foobar(self, matches, msg):
print("Someone's message was exactly "foobar" ({}) in channel {}".format(msg.message, msg.args[0]))
This stores a list of IRC actions each function is tagged for in method.__tag_regexes. This attribute is scanned
during module init and appropriate hooks are set up.
:param regexps: expressions to match for
:type keywords: str
:param allow_private: enable matching in private messages

View File

@ -1,5 +1,6 @@
from pyircbot.modulebase import ModuleBase, ModuleHook, MissingDependancyException, regex, command
from pyircbot.modules.ModInfo import info
import datetime
import time
import math
@ -79,6 +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"])
@regex(r'(?:^\.?(?:calc|quote)(?:\s+?(?:([^=]+)(?:\s?(=)\s?(.+)?)?)?)?)', types=['PRIVMSG'])
def cmd_calc(self, message, match):
word, changeit, value = match.groups()

View File

@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""
.. module::ModInfo
:synopsis: Provides manpage-like info for commands
"""
from pyircbot.modulebase import ModuleBase, command
class info(object):
"""
Decorator for tagging module methods with help text
.. code-block:: python
from pyircbot.modules.ModInfo import info
...
@info("help [command] show the manual for all or [commands]", cmds=["help", "rtfm"])
@command("help")
def cmd_help(self, msg, cmd):
...
:param docstring: command help formatted as above
:type docstring: str
:param cmds: enable command names or aliases this function implements, as a list of strings. E.g. if the "help"
command has the alias "rtfm"
:type cmds: list
"""
def __init__(self, docstring, cmds=None):
self.docstring = docstring
self.commands = cmds or []
def __call__(self, func):
setattr(func, "irchelp", self.docstring)
setattr(func, "irchelpc", self.commands)
return func
class ModInfo(ModuleBase):
@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))
else:
for modname, module, helptext, helpcommands in self.iter_modules():
self.bot.act_PRIVMSG(msg.args[0], "{}: {}{}".format(modname, command.prefix, helptext))
@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]
self.bot.act_PRIVMSG(msg.args[0], "{}: commands: {}".format(msg.prefix.nick, ", ".join(commands)))
def iter_modules(self):
"""
Iterator that cycles through module methods that are tagged with help information. The iterator yields tuples
of:
(module_name, module_object, helptext, 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"):
yield (modname, module, getattr(attr, "irchelp"), getattr(attr, "irchelpc"), )
raise StopIteration()

View File

@ -7,7 +7,9 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modules.ModInfo import info
from pyircbot.modulebase import ModuleBase, command, hook
from contextlib import closing
import sqlite3
import time
@ -15,7 +17,6 @@ import time
class Seen(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = [ModuleHook("PRIVMSG", self.lastSeen)]
# if the database doesnt exist, it will be created
sql = self.getSql()
c = sql.cursor()
@ -27,31 +28,32 @@ class Seen(ModuleBase):
c.execute("CREATE TABLE `seen` (`nick` VARCHAR(32), `date` INTEGER, PRIMARY KEY(`nick`))")
self.x = "asdf"
def lastSeen(self, args, prefix, trailing):
@hook("PRIVMSG")
def recordSeen(self, message, command):
# using a message to update last seen, also, the .seen query
prefixObj = self.bot.decodePrefix(prefix)
nick = prefixObj.nick
sql = self.getSql()
c = sql.cursor()
# update or add the user's row
datest = str(time.time() + (int(self.config["add_hours"]) * 60 * 60))
c.execute("REPLACE INTO `seen` (`nick`, `date`) VALUES (?, ?)", (nick.lower(), datest))
# self.log.info("Seen: %s on %s" % (nick.lower(), datest))
sql.commit()
if trailing.startswith(".seen"):
cmdargs = trailing.split(" ")
sql = self.getSql()
with closing(sql.cursor()) as c:
# update or add the user's row
c.execute("REPLACE INTO `seen` (`nick`, `date`) VALUES (?, ?)", (message.prefix.nick.lower(), datest))
# self.log.info("Seen: %s on %s" % (nick.lower(), datest))
sql.commit()
@info("seen <nick> print last time user was seen", cmds=["seen"])
@command("seen", require_args=True)
def lastSeen(self, message, command):
sql = self.getSql()
searchnic = command.args[0].lower()
with closing(sql.cursor()) as c:
# query the DB for the user
if len(cmdargs) >= 2:
searchnic = cmdargs[1].lower()
c.execute("SELECT * FROM `seen` WHERE `nick`= ? ", [searchnic])
rows = c.fetchall()
if len(rows) == 1:
self.bot.act_PRIVMSG(args[0], "I last saw %s on %s (%s)." %
(cmdargs[1], time.strftime("%m/%d/%y at %I:%M %p",
time.localtime(rows[0]['date'])), self.config["timezone"]))
else:
self.bot.act_PRIVMSG(args[0], "Sorry, I haven't seen %s!" % cmdargs[1])
c.close()
c.execute("SELECT * FROM `seen` WHERE `nick`= ? ", [searchnic])
rows = c.fetchall()
if len(rows) == 1:
self.bot.act_PRIVMSG(message.args[0], "I last saw %s on %s (%s)." %
(command.args[0], time.strftime("%m/%d/%y at %I:%M %p",
time.localtime(rows[0]['date'])), self.config["timezone"]))
else:
self.bot.act_PRIVMSG(message.args[0], "Sorry, I haven't seen %s!" % command.args[0])
def getSql(self):
# return a SQL reference to the database
@ -66,9 +68,3 @@ class Seen(ModuleBase):
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def test(self, arg):
print("TEST: %s" % arg)
print("self.x = %s" % self.x)
return arg

View File

@ -369,14 +369,14 @@ class PyIRCBot(object):
argsStart = len(command)
args = ""
if argsStart > 0:
args = message[argsStart + 1:]
args = message[argsStart + 1:].strip()
if requireArgs and args.strip() == '':
if requireArgs and args == '':
return False
# Verified! Return the set.
return ParsedCommand(command,
args.split(" "),
args.split(" ") if args else [],
args,
message)