Created
January 29, 2018 17:43
-
-
Save andrewthetechie/2347161a4ebf2449db5131315276d721 to your computer and use it in GitHub Desktop.
A modification of errbot to allow custom help text
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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