Skip to content

Instantly share code, notes, and snippets.

@andrewthetechie
Created January 29, 2018 17:43
Show Gist options
  • Save andrewthetechie/2347161a4ebf2449db5131315276d721 to your computer and use it in GitHub Desktop.
Save andrewthetechie/2347161a4ebf2449db5131315276d721 to your computer and use it in GitHub Desktop.
A modification of errbot to allow custom help text
import re
import shlex
import inspect
from functools import wraps
from typing import Callable, Any
from errbot import BotPlugin
from errbot import botcmd
from errbot import _tag_botcmd
from errbot import ArgumentParser
from errbot import ArgumentParseError
from errbot import HelpRequested
from errbot.backends.base import Message
# Some clients automatically convert consecutive dashes into a fancy
# hyphen, which breaks long-form arguments. Undo this conversion to
# provide a better user experience.
# Same happens with quotations marks, which are required for parsing
# complex strings in arguments
# Map of characters to sanitized equivalents
ARG_BOTCMD_CHARACTER_REPLACEMENTS = {'': '--', '“': '"', '”': '"'}
# modification of https://github.com/errbotio/errbot/blob/master/errbot/__init__.py#L277
def arg_botcmd_customhelp(*args,
hidden: bool = None,
name: str = None,
admin_only: bool = False,
historize: bool = True,
template: str = None,
flow_only: bool = False,
unpack_args: bool = True,
**kwargs) -> Callable[[BotPlugin, Message, Any], Any]:
"""
Decorator for argparse-based bot command functions that want to implement their own help text
https://docs.python.org/3/library/argparse.html
This decorator creates an argparse.ArgumentParser and uses it to parse the commands arguments.
This decorator can be used multiple times to specify multiple arguments.
Any valid argparse.add_argument() parameters can be passed into the decorator.
Each time this decorator is used it adds a new argparse argument to the command.
:param hidden: Prevents the command from being shown by the built-in help command when `True`.
:param name: The name to give to the command. Defaults to name of the function itself.
:param admin_only: Only allow the command to be executed by admins when `True`.
:param historize: Store the command in the history list (`!history`). This is enabled
by default.
:param template: The template to use when using markdown output
:param flow_only: Flag this command to be available only when it is part of a flow.
If True and hidden is None, it will switch hidden to True.
:param unpack_args: Should the argparser arguments be "unpacked" and passed on the the bot
command individually? If this is True (the default) you must define all arguments in the
function separately. If this is False you must define a single argument `args` (or
whichever name you prefer) to receive the result of `ArgumentParser.parse_args()`.
This decorator should be applied to methods of :class:`~errbot.botplugin.BotPlugin`
classes to turn them into commands that can be given to the bot. The methods will be called
with the original msg and the argparse parsed arguments. These methods are
expected to have a signature like the following (assuming `unpack_args=True`)::
@arg_botcmd('value', type=str)
@arg_botcmd('--repeat-count', dest='repeat', type=int, default=2)
def repeat_the_value(self, msg, value=None, repeat=None):
return value * repeat
The given `msg` will be the full message object that was received, which includes data
like sender, receiver, the plain-text and html body (if applicable), etc.
`value` will hold the value passed in place of the `value` argument and
`repeat` will hold the value passed in place of the `--repeat-count` argument.
If you don't like this automatic *"unpacking"* of the arguments,
you can use `unpack_args=False` like this::
@arg_botcmd('value', type=str)
@arg_botcmd('--repeat-count', dest='repeat', type=int, default=2, unpack_args=False)
def repeat_the_value(self, msg, args):
return arg.value * args.repeat
.. note::
The `unpack_args=False` only needs to be specified once, on the bottom `@args_botcmd`
statement.
"""
def decorator(func):
if not hasattr(func, '_err_command'):
err_command_parser = ArgumentParser(
prog=name or func.__name__,
description=func.__doc__,
add_help=False
)
@wraps(func)
def wrapper(self, msg, args):
# Attempt to sanitize arguments of bad characters
try:
sanitizer_re = re.compile('|'.join(re.escape(ii) for ii in ARG_BOTCMD_CHARACTER_REPLACEMENTS))
args = sanitizer_re.sub(lambda mm: ARG_BOTCMD_CHARACTER_REPLACEMENTS[mm.group()], args)
args = shlex.split(args)
parsed_args = err_command_parser.parse_args(args)
except ArgumentParseError as e:
yield "I'm sorry, I couldn't parse the arguments; %s" % e
yield err_command_parser.format_usage()
return
except HelpRequested:
yield err_command_parser.format_help()
return
except ValueError as ve:
yield "I'm sorry, I couldn't parse this command; %s" % ve
yield err_command_parser.format_help()
return
if unpack_args:
func_args = []
func_kwargs = vars(parsed_args)
else:
func_args = [parsed_args]
func_kwargs = {}
if inspect.isgeneratorfunction(func):
for reply in func(self, msg, *func_args, **func_kwargs):
yield reply
else:
yield func(self, msg, *func_args, **func_kwargs)
_tag_botcmd(wrapper,
_re=False,
_arg=True,
hidden=hidden,
name=name or wrapper.__name__,
admin_only=admin_only,
historize=historize,
template=template,
flow_only=flow_only,
command_parser=err_command_parser)
else:
# the function has already been wrapped
# alias it so we can update it's arguments below
wrapper = func
wrapper._err_command_parser.add_argument(*args, **kwargs)
wrapper.__doc__ = wrapper._err_command_parser.format_help()
fmt = wrapper._err_command_parser.format_usage()
wrapper._err_command_syntax = fmt[len('usage: ') + len(wrapper._err_command_parser.prog) + 1:-1]
return wrapper
return decorator
"""Bot Cmds"""
@botcmd
@arg_botcmd_customhelp('args', type=str, nargs="*", help="Args")
@arg_botcmd_customhelp('--help', '-h', dest='help_flag', action='store_true', help='Will print help text')
def test(self, msg, args, help_flag=False):
"""
Notifies support of the state of a node
Args:
msg (errbot.backends.base.Message): Message object passed along
args(list): Args
help_flag (bool): Prints help message to the user
"""
help_text = """
To use:
`./test {args} [--help]`
Put your nice pretty help text in here to tell your user whats up
"""
if help_flag:
# lets send a help message to the user in a thread
self.send(msg.to,
text=help_text,
in_reply_to=msg)
return
# do other stuff now
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment