Cleanup of the help command, making it a lot easier to read. Also added cos-suggest algorithm to make better help entry suggestions.

This commit is contained in:
Griatch 2012-04-22 19:45:45 +02:00
parent 0c292b5ff2
commit 181abb84a8

View file

@ -11,13 +11,13 @@ from src.utils.utils import fill, dedent
from src.commands.command import Command from src.commands.command import Command
from src.help.models import HelpEntry from src.help.models import HelpEntry
from src.utils import create from src.utils import create
from src.utils.utils import string_suggestions
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdHelp", "CmdSetHelp") __all__ = ("CmdHelp", "CmdSetHelp")
LIST_ARGS = ("list", "all")
SEP = "{C" + "-"*78 + "{n" SEP = "{C" + "-"*78 + "{n"
def format_help_entry(title, help_text, aliases=None, suggested=None): def format_help_entry(title, help_text, aliases=None, suggested=None):
@ -26,7 +26,7 @@ def format_help_entry(title, help_text, aliases=None, suggested=None):
""" """
string = SEP + "\n" string = SEP + "\n"
if title: if title:
string += "{CHelp topic for {w%s{n" % (title.capitalize()) string += "{CHelp topic for {w%s{n" % title
if aliases: if aliases:
string += " {C(aliases: {w%s{n{C){n" % (", ".join(aliases)) string += " {C(aliases: {w%s{n{C){n" % (", ".join(aliases))
if help_text: if help_text:
@ -45,12 +45,12 @@ def format_help_list(hdict_cmds, hdict_db):
resectively. resectively.
""" """
string = "" string = ""
if hdict_cmds and hdict_cmds.values(): if hdict_cmds and any(hdict_cmds.values()):
string += "\n" + SEP + "\n {CCommand help entries{n\n" + SEP string += "\n" + SEP + "\n {CCommand help entries{n\n" + SEP
for category in sorted(hdict_cmds.keys()): for category in sorted(hdict_cmds.keys()):
string += "\n {w%s{n:\n" % (str(category).capitalize()) string += "\n {w%s{n:\n" % (str(category).capitalize())
string += "{G" + fill(", ".join(sorted(hdict_cmds[category]))) + "{n" string += "{G" + fill(", ".join(sorted(hdict_cmds[category]))) + "{n"
if hdict_db and hdict_db.values(): if hdict_db and any(hdict_db.values()):
string += "\n\n" + SEP + "\n\r {COther help entries{n\n" + SEP string += "\n\n" + SEP + "\n\r {COther help entries{n\n" + SEP
for category in sorted(hdict_db.keys()): for category in sorted(hdict_db.keys()):
string += "\n\r {w%s{n:\n" % (str(category).capitalize()) string += "\n\r {w%s{n:\n" % (str(category).capitalize())
@ -90,6 +90,9 @@ class CmdHelp(Command):
query, cmdset = self.args, self.cmdset query, cmdset = self.args, self.cmdset
caller = self.caller caller = self.caller
suggestion_cutoff = 0.6
suggestion_maxnum = 5
if not query: if not query:
query = "all" query = "all"
@ -97,79 +100,53 @@ class CmdHelp(Command):
# having to allow doublet commands to manage exits etc. # having to allow doublet commands to manage exits etc.
cmdset.make_unique(caller) cmdset.make_unique(caller)
# Listing all help entries # retrieve all available commands and topics
all_cmds = [cmd for cmd in cmdset if cmd.auto_help and cmd.access(caller)]
all_topics = [topic for topic in HelpEntry.objects.all() if topic.access(caller, 'view', default=True)]
all_categories = list(set([cmd.help_category.lower() for cmd in all_cmds] + [topic.help_category.lower() for topic in all_topics]))
if query in LIST_ARGS: if query in ("list", "all"):
# we want to list all available help entries, grouped by category. # we want to list all available help entries, grouped by category
hdict_cmd = defaultdict(list) hdict_cmd = defaultdict(list)
for cmd in (cmd for cmd in cmdset if cmd.auto_help and not cmd.is_exit hdict_topic = defaultdict(list)
and not cmd.key.startswith('__') and cmd.access(caller)): # create the dictionaries {category:[topic, topic ...]} required by format_help_list
hdict_cmd[cmd.help_category].append(cmd.key) [hdict_cmd[cmd.help_category].append(cmd.key) for cmd in all_cmds]
hdict_db = defaultdict(list) [hdict_topic[topic.help_category].append(topic.key) for topic in all_topics]
for topic in (topic for topic in HelpEntry.objects.get_all_topics() # report back
if topic.access(caller, 'view', default=True)): caller.msg(format_help_list(hdict_cmd, hdict_topic))
hdict_db[topic.help_category].append(topic.key)
help_entry = format_help_list(hdict_cmd, hdict_db)
caller.msg(help_entry)
return return
# Look for a particular help entry # Try to access a particular command
# Cmd auto-help dynamic entries # build vocabulary of suggestions and rate them by string similarity.
cmdmatches = [cmd for cmd in cmdset if query in cmd and cmd.auto_help and cmd.access(caller)] vocabulary = [cmd.key for cmd in all_cmds if cmd] + [topic.key for topic in all_topics] + all_categories
if len(cmdmatches) > 1: [vocabulary.extend(cmd.aliases) for cmd in all_cmds]
# multiple matches. Try to limit it down to exact match suggestions = [sugg for sugg in string_suggestions(query, set(vocabulary), cutoff=suggestion_cutoff, maxnum=suggestion_maxnum)
cmdmatches = [cmd for cmd in cmdmatches if cmd == query] or cmdmatches if sugg != query]
if not suggestions:
suggestions = [sugg for sugg in vocabulary if sugg != query and sugg.startswith(query)]
# Help-database static entries
dbmatches = [topic for topic in
HelpEntry.objects.find_topicmatch(query, exact=False)
if topic.access(caller, 'view', default=True)]
if len(dbmatches) > 1:
# try to get unique match
dbmatches = [topic for topic in HelpEntry.objects.find_topicmatch(query, exact=True)
if topic.access(caller, 'view', default=True)] or dbmatches
# Handle result # try an exact command auto-help match
if (not cmdmatches) and (not dbmatches): match = [cmd for cmd in all_cmds if cmd == query]
# no normal match. Check if this is a category match instead if len(match) == 1:
categ_cmdmatches = [cmd.key for cmd in cmdset if query == cmd.help_category and cmd.access(caller)] caller.msg(format_help_entry(match[0].key, match[0].__doc__, aliases=match[0].aliases, suggested=suggestions))
categ_dbmatches = [topic.key for topic in HelpEntry.objects.find_topics_with_category(query) return
if topic.access(caller, 'view', default=True)]
cmddict = None
dbdict = None
if categ_cmdmatches: # try a database help entry match
cmddict = {query:categ_cmdmatches} match = list(HelpEntry.objects.find_topicmatch(query, exact=True))
if categ_dbmatches: if len(match) == 1:
dbdict = {query:categ_dbmatches} caller.msg(format_help_entry(match[0].key, match[0].entrytext, suggested=suggestions))
if cmddict or dbdict: return
help_entry = format_help_list(cmddict, dbdict)
else:
help_entry = "No help entry found for '%s'" % self.original_args
elif len(cmdmatches) == 1: # try to see if a category name was entered
# we matched against a unique command name or alias. Show its help entry. if query in all_categories:
suggested = [] caller.msg(format_help_list({query:[cmd.key for cmd in all_cmds if cmd.help_category==query]},
if dbmatches: {query:[topic.key for topic in all_topics if topic.help_category==query]}))
suggested = [entry.key for entry in dbmatches] return
cmd = cmdmatches[0]
help_entry = format_help_entry(cmd.key, cmd.__doc__,
aliases=cmd.aliases,
suggested=suggested)
elif len(dbmatches) == 1:
# matched against a database entry
entry = dbmatches[0]
help_entry = format_help_entry(entry.key, entry.entrytext)
else:
# multiple matches of either type
cmdalts = [cmd.key for cmd in cmdmatches]
dbalts = [entry.key for entry in dbmatches]
helptext = "Multiple help entries match your search ..."
help_entry = format_help_entry("", helptext, None, cmdalts + dbalts)
# send result to user # no exact matches found. Just give suggestions.
caller.msg(help_entry) caller.msg(format_help_entry("", "No help entry found for '%s'" % query, None, suggested=suggestions))
class CmdSetHelp(MuxCommand): class CmdSetHelp(MuxCommand):
""" """
@ -189,8 +166,13 @@ class CmdSetHelp(MuxCommand):
Examples: Examples:
@sethelp/add throw = This throws something at ... @sethelp/add throw = This throws something at ...
@sethelp/append pickpocketing,Thievery,is_thief, is_staff) = This steals ... @sethelp/append pickpocketing,Thievery = This steals ...
@sethelp/append pickpocketing, ,is_thief, is_staff) = This steals ... @sethelp/append pickpocketing, ,attr(is_thief) = This steals ...
This command manipulates the help database. A help entry can be created,
appended/merged to and deleted. If you don't assign a category, the "General"
category will be used. If no lockstring is specified, default is to let everyone read
the help file.
""" """
key = "@help" key = "@help"
@ -204,81 +186,74 @@ class CmdSetHelp(MuxCommand):
caller = self.caller caller = self.caller
switches = self.switches switches = self.switches
lhslist = self.lhslist lhslist = self.lhslist
rhs = self.rhs
if not self.args: if not self.args:
caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,locks,..] = <text>]") caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,locks,..] = <text>")
return return
topicstr = "" topicstr = ""
category = "" category = "General"
lockstring = "" lockstring = "view:all()"
try: try:
topicstr = lhslist[0] topicstr = lhslist[0]
category = lhslist[1] category = lhslist[1]
lockstring = ",".join(lhslist[2:]) lockstring = ",".join(lhslist[2:])
except Exception: except Exception:
pass pass
if not topicstr: if not topicstr:
caller.msg("You have to define a topic!") caller.msg("You have to define a topic!")
return return
string = "" # check if we have an old entry with the same name
#print topicstr, category, lockstring try:
old_entry = HelpEntry.objects.get(db_key__iexact=topicstr)
if switches and switches[0] in ('append', 'app','merge'): except Exception:
# add text to the end of a help topic
# find the topic to append to
old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr)
if not old_entry:
string = "Could not find topic '%s'. You must give an exact name." % topicstr
else:
old_entry = old_entry[0]
entrytext = old_entry.entrytext
if switches[0] == 'merge':
old_entry.entrytext = "%s %s" % (entrytext, self.rhs)
string = "Added the new text right after the old one (merge)."
else:
old_entry.entrytext = "%s\n\n%s" % (entrytext, self.rhs)
string = "Added the new text as a new paragraph after the old one (append)"
old_entry.save()
elif switches and switches[0] in ('delete','del'):
#delete a help entry
old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr)
if not old_entry:
string = "Could not find topic '%s'." % topicstr
else:
old_entry[0].delete()
string = "Deleted the help entry '%s'." % topicstr
else:
# add a new help entry.
force_create = ('for' in switches) or ('force' in switches)
old_entry = None old_entry = None
try:
old_entry = HelpEntry.objects.get(key=topicstr) if 'append' in switches or "merge" in switches:
except Exception: # merge/append operations
pass if not old_entry:
if old_entry: caller.msg("Could not find topic '%s'. You must give an exact name." % topicstr)
if force_create: return
old_entry.key = topicstr if not self.rhs:
old_entry.entrytext = self.rhs caller.msg("You must supply text to append/merge.")
old_entry.help_category = category return
old_entry.locks.clear() if 'merge' in switches:
old_entry.locks.add(lockstring) old_entry.entrytext += " " + self.rhs
old_entry.save()
string = "Overwrote the old topic '%s' with a new one." % topicstr
else:
string = "Topic '%s' already exists. Use /force to overwrite it." % topicstr
else: else:
# no old entry. Create a new one. old_entry.entrytext += "\n\n%s" % self.rhs
new_entry = create.create_help_entry(topicstr, caller.msg("Entry updated:\n%s" % old_entry.entrytext)
rhs, category, lockstring) return
if 'delete' in switches or 'del' in switches:
# delete the help entry
if not old_entry:
caller.msg("Could not find topic '%s'" % topicstr)
return
old_entry.delete()
caller.msg("Deleted help entry '%s'." % topicstr)
return
if new_entry: # at this point it means we want to add a new help entry.
string = "Topic '%s' was successfully created." % topicstr if not self.rhs:
else: caller.msg("You must supply a help text to add.")
string = "Error when creating topic '%s'! Maybe it already exists?" % topicstr return
if old_entry:
# give feedback if 'for' in switches or 'force' in switches:
caller.msg(string) # overwrite old entry
old_entry.key = topicstr
old_entry.entrytext = self.rhs
old_entry.help_category = category
old_entry.locks.clear()
old_entry.locks.add(lockstring)
old_entry.save()
caller.msg("Overwrote the old topic '%s' with a new one." % topicstr)
else:
caller.msg("Topic '%s' already exists. Use /force to overwrite or /append or /merge to add text to it." % topicstr)
else:
# no old entry. Create a new one.
new_entry = create.create_help_entry(topicstr,
self.rhs, category, lockstring)
if new_entry:
caller.msg("Topic '%s' was successfully created." % topicstr)
else:
caller.msg("Error when creating topic '%s'! Maybe it already exists?" % topicstr)