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"] pyircbot_send default privmsg ["#clonebot", "asdf1234"]
"""
Example Programs Example Programs
---------------- ----------------

View File

@ -251,9 +251,6 @@ class regex(hook):
def cmd_foobar(self, matches, msg): def cmd_foobar(self, matches, msg):
print("Someone's message was exactly "foobar" ({}) in channel {}".format(msg.message, msg.args[0])) 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 :param regexps: expressions to match for
:type keywords: str :type keywords: str
:param allow_private: enable matching in private messages :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.modulebase import ModuleBase, ModuleHook, MissingDependancyException, regex, command
from pyircbot.modules.ModInfo import info
import datetime import datetime
import time import time
import math import math
@ -79,6 +80,7 @@ class Calc(ModuleBase):
seconds = int(remaining - (minutes * 60)) seconds = int(remaining - (minutes * 60))
return "Please wait %s minute(s) and %s second(s)." % (minutes, seconds) 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']) @regex(r'(?:^\.?(?:calc|quote)(?:\s+?(?:([^=]+)(?:\s?(=)\s?(.+)?)?)?)?)', types=['PRIVMSG'])
def cmd_calc(self, message, match): def cmd_calc(self, message, match):
word, changeit, value = match.groups() 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 sqlite3
import time import time
@ -15,7 +17,6 @@ import time
class Seen(ModuleBase): class Seen(ModuleBase):
def __init__(self, bot, moduleName): def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName) ModuleBase.__init__(self, bot, moduleName)
self.hooks = [ModuleHook("PRIVMSG", self.lastSeen)]
# if the database doesnt exist, it will be created # if the database doesnt exist, it will be created
sql = self.getSql() sql = self.getSql()
c = sql.cursor() c = sql.cursor()
@ -27,31 +28,32 @@ class Seen(ModuleBase):
c.execute("CREATE TABLE `seen` (`nick` VARCHAR(32), `date` INTEGER, PRIMARY KEY(`nick`))") c.execute("CREATE TABLE `seen` (`nick` VARCHAR(32), `date` INTEGER, PRIMARY KEY(`nick`))")
self.x = "asdf" 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 # 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)) datest = str(time.time() + (int(self.config["add_hours"]) * 60 * 60))
c.execute("REPLACE INTO `seen` (`nick`, `date`) VALUES (?, ?)", (nick.lower(), datest)) sql = self.getSql()
# self.log.info("Seen: %s on %s" % (nick.lower(), datest)) with closing(sql.cursor()) as c:
sql.commit() # update or add the user's row
if trailing.startswith(".seen"): c.execute("REPLACE INTO `seen` (`nick`, `date`) VALUES (?, ?)", (message.prefix.nick.lower(), datest))
cmdargs = trailing.split(" ") # 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 # query the DB for the user
if len(cmdargs) >= 2: c.execute("SELECT * FROM `seen` WHERE `nick`= ? ", [searchnic])
searchnic = cmdargs[1].lower() rows = c.fetchall()
c.execute("SELECT * FROM `seen` WHERE `nick`= ? ", [searchnic]) if len(rows) == 1:
rows = c.fetchall() self.bot.act_PRIVMSG(message.args[0], "I last saw %s on %s (%s)." %
if len(rows) == 1: (command.args[0], time.strftime("%m/%d/%y at %I:%M %p",
self.bot.act_PRIVMSG(args[0], "I last saw %s on %s (%s)." % time.localtime(rows[0]['date'])), self.config["timezone"]))
(cmdargs[1], time.strftime("%m/%d/%y at %I:%M %p", else:
time.localtime(rows[0]['date'])), self.config["timezone"])) self.bot.act_PRIVMSG(message.args[0], "Sorry, I haven't seen %s!" % command.args[0])
else:
self.bot.act_PRIVMSG(args[0], "Sorry, I haven't seen %s!" % cmdargs[1])
c.close()
def getSql(self): def getSql(self):
# return a SQL reference to the database # return a SQL reference to the database
@ -66,9 +68,3 @@ class Seen(ModuleBase):
for idx, col in enumerate(cursor.description): for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx] d[col[0]] = row[idx]
return d 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) argsStart = len(command)
args = "" args = ""
if argsStart > 0: if argsStart > 0:
args = message[argsStart + 1:] args = message[argsStart + 1:].strip()
if requireArgs and args.strip() == '': if requireArgs and args == '':
return False return False
# Verified! Return the set. # Verified! Return the set.
return ParsedCommand(command, return ParsedCommand(command,
args.split(" "), args.split(" ") if args else [],
args, args,
message) message)