import textwrap
from dulwich import errors as dulwich_errors
from errbot import BotPlugin, botcmd
from errbot.utils import git_tag_list
from errbot.version import VERSION
[docs]class Help(BotPlugin):
MSG_HELP_TAIL = (
"Type help <command name> to get more info " "about that specific command."
)
MSG_HELP_UNDEFINED_COMMAND = "That command is not defined."
[docs] def is_git_directory(self, path="."):
try:
tags = git_tag_list(path)
except dulwich_errors.NotGitRepository:
tags = None
except Exception as _:
# we might want to handle other exceptions another way. For now leaving this general
tags = None
return tags.pop(-1) if tags is not None else None
# noinspection PyUnusedLocal
[docs] @botcmd(template="about")
def about(self, msg, args):
"""Return information about this Errbot instance and version"""
git_version = self.is_git_directory()
if git_version:
return dict(version=f"{git_version.decode('utf-8')} GIT CHECKOUT")
else:
return {"version": VERSION}
# noinspection PyUnusedLocal
[docs] @botcmd
def apropos(self, msg, args):
"""Returns a help string listing available options.
Automatically assigned to the "help" command."""
if not args:
return "Usage: " + self._bot.prefix + "apropos search_term"
description = "Available commands:\n"
cls_commands = {}
for name, command in self._bot.all_commands.items():
cls = self._bot.get_plugin_class_from_method(command)
cls = str.__module__ + "." + cls.__name__ # makes the fuul qualified name
commands = cls_commands.get(cls, [])
if (
not self.bot_config.HIDE_RESTRICTED_COMMANDS
or self._bot.check_command_access(msg, name)[0]
):
commands.append((name, command))
cls_commands[cls] = commands
usage = ""
for cls in sorted(cls_commands):
commands = []
for name, command in cls_commands[cls]:
if name == "help":
continue
if command._err_command_hidden:
continue
doc = command.__doc__
if doc is not None and args.lower() not in doc.lower():
continue
name_with_spaces = name.replace("_", " ", 1)
doc = (doc or "(undocumented)").strip().split("\n", 1)[0]
commands.append("\t" + self._bot.prefix + name_with_spaces + ": " + doc)
usage += "\n".join(commands)
usage += "\n\n"
return "".join(filter(None, [description, usage])).strip()
[docs] @botcmd
def help(self, msg, args):
"""Returns a help string listing available options.
Automatically assigned to the "help" command."""
def may_access_command(m, cmd):
m, _, _ = self._bot._process_command_filters(
msg=m, cmd=cmd, args=None, dry_run=True
)
return m is not None
def get_name(named):
return named.__name__.lower()
# Normalize args to lowercase for ease of use
args = args.lower() if args else ""
usage = ""
description = "### All commands\n"
cls_obj_commands = {}
for name, command in self._bot.all_commands.items():
cls = self._bot.get_plugin_class_from_method(command)
obj = command.__self__
_, commands = cls_obj_commands.get(cls, (None, []))
if not self.bot_config.HIDE_RESTRICTED_COMMANDS or may_access_command(
msg, name
):
commands.append((name, command))
cls_obj_commands[cls] = (obj, commands)
# show all
if not args:
for cls in sorted(
cls_obj_commands.keys(), key=lambda c: cls_obj_commands[c][0].name
):
obj, commands = cls_obj_commands[cls]
name = obj.name
# shows class and description
usage += f"\n**{name}**\n\n"
if getattr(cls.__errdoc__, "strip", None):
usage += f"{cls.__errdoc__.strip()}\n\n"
else:
usage += cls.__errdoc__ or "\n\n"
for name, command in sorted(commands):
if command._err_command_hidden:
continue
# show individual commands
usage += self._cmd_help_line(name, command)
usage += "\n\n" # end cls section
elif args:
for cls, (obj, cmds) in cls_obj_commands.items():
if obj.name.lower() == args:
break
else:
cls, obj, cmds = None, None, None
if cls is None:
# Plugin not found.
description = ""
all_commands = dict(self._bot.all_commands)
all_commands.update(
{k.replace("_", " "): v for k, v in all_commands.items()}
)
if args in all_commands:
usage += self._cmd_help_line(args, all_commands[args], True)
else:
usage += self.MSG_HELP_UNDEFINED_COMMAND
else:
# filter out the commands related to this class
description = ""
description += f"\n**{obj.name}**\n\n"
if getattr(cls.__errdoc__, "strip", None):
description += f"{cls.__errdoc__.strip()}\n\n"
else:
description += cls.__errdoc__ or "\n\n"
pairs = []
for name, command in cmds:
if self.bot_config.HIDE_RESTRICTED_COMMANDS:
if command._err_command_hidden:
continue
if not may_access_command(msg, name):
continue
pairs.append((name, command))
pairs = sorted(pairs)
for name, command in pairs:
usage += self._cmd_help_line(name, command)
return "".join(filter(None, [description, usage]))
def _cmd_help_line(self, name, command, show_doc=False):
"""
Returns:
str. a single line indicating the help representation of a command.
"""
cmd_name = name.replace("_", " ")
cmd_doc = textwrap.dedent(self._bot.get_doc(command)).strip()
prefix = (
self._bot.prefix
if getattr(command, "_err_command_prefix_required", True)
else ""
)
name = cmd_name
patt = getattr(command, "_err_command_re_pattern", None)
if patt:
re_help_name = getattr(command, "_err_command_re_name_help", None)
name = re_help_name if re_help_name else patt.pattern
if not show_doc:
cmd_doc = cmd_doc.split("\n")[0]
if len(cmd_doc) > 80:
cmd_doc = f"{cmd_doc[:77]}..."
help_str = f"- **{prefix}{name}** - {cmd_doc}\n"
return help_str