diff --git a/game/gamesrc/commands/examples/example.py b/game/gamesrc/commands/examples/example.py index 250a195035..0be240df64 100644 --- a/game/gamesrc/commands/examples/example.py +++ b/game/gamesrc/commands/examples/example.py @@ -30,10 +30,10 @@ def cmd_example(command): example - example command Usage: - example[/switches] + @testcommand[/switches] switches: - use any string + (can be any string, e.g. /test1 or /tom/sarah/peter) This is the help text for the 'example' command, a command to show how the pluggable command system works. @@ -70,17 +70,34 @@ def cmd_example(command): # A list of switches provided (if any) retval += " Switches: %s\n\r" % command.command_switches # A string with any arguments provided with the command - retval += " Arguments: %s\n\r" % command.command_argument + retval += " Arguments: %s\n\r" % command.command_argument # The function that was looked up via cmdtable.py retval += " Function: %s\n\r" % command.command_function # Extra variables passed with cmdtable.py's add_command(). retval += " Extra vars: %s\n\r" % command.extra_vars + + # Some more info for more advanced commands. + if not command.command_switches and \ + command.command_argument: + retval += "\n Obs: When no switches, also multi-word\n" + retval += " command names are possible. Max allowed\n" + retval += " length is set in game/settings.py.\n" + retval += " So if there exist a matching command in the\n" + retval += " command table, Evennia would also allow\n" + retval += " the following as valid commands (and the\n" + retval += " argument list would shrink accordingly):\n" + multi = "" + for arg in command.command_argument.split(): + multi += " %s" % arg + retval += " %s%s\n" % (command.command_string, multi) + + # send string to player command.source_object.emit_to(retval) # Add the command to the common global command table. Note that # this will auto-create help entries 'example' and # "example_auto_help" for us. -GLOBAL_CMD_TABLE.add_command("example", cmd_example) +GLOBAL_CMD_TABLE.add_command("@testcommand", cmd_example) # # another simple example diff --git a/game/gamesrc/commands/examples/misc_tests.py b/game/gamesrc/commands/examples/misc_tests.py new file mode 100644 index 0000000000..6fef0152b8 --- /dev/null +++ b/game/gamesrc/commands/examples/misc_tests.py @@ -0,0 +1,135 @@ +""" +This module contains various commands for testing some +of Evennia's subsystems. They were used for initial testing +but are also instructive for playing around with to learn +how different systems work. See also state_example.py. + +To make these commands available in-game, add this module +to the CUSTOM_COMMAND_MODULES tuple in game/settings.py +as 'game.gamesrc.commands.examples.misc_tests'. + +None of these commands are auto-added to the help database +(they have no docstrings) in order to help make it clean. +""" +from src.cmdtable import GLOBAL_CMD_TABLE + +#------------------------------------------------------------ +# Tests of the event system +#------------------------------------------------------------ + +def cmd_testevent(command): + # + # This test allows testing the event system + # + # Usage: + # @testevent [pid] + # + # Without argument, this command creates + # a dummy event in the process table. + # Use @ps to see it. Give the equivalent + # pid to remove it again (careful though, + # this command can also remove useful + # events if you give the wrong pid). + # + from src import events + from src import scheduler + + source_object = command.source_object + + if not source_object.is_superuser(): + # To avoid accidental access to process table + source_object.emit_to("This command is superuser only.") + return + + if not command.command_argument: + # No argument given; create a new test event. + event = events.IntervalEvent() + event.description = "Test event created with @testevent." + event.repeats = 3 + event.interval = 5 + pid = scheduler.add_event(event) + string = "Event with pid %s added. " % pid + string += "It repeats %i times and waits " % event.repeats + string += "for %i seconds between each repeat." % event.interval + string += "After all repeats, it will delete itself." + string += "\nUse @ps to see it and give this " + string += "command with the pid as argument to delete it." + source_object.emit_to(string) + else: + # An argument given; assume this is a pid. + try: + pid = int(command.command_argument) + except: + source_object.emit_to("Not a valid argument. You must give a number.") + return + if pid < 3: + string = "This low pid might belong to a system process, \n" + string += "so as a safety measure you cannot delete it using \n" + string += "this test command. Use @delevent instead." + source_object.emit_to(string) + return + pid = command.command_argument + scheduler.del_event(pid) + string = "Event with pid %s removed (if it existed)." % pid + string += " Confirm this worked using @ps." + source_object.emit_to(string) +GLOBAL_CMD_TABLE.add_command("@testevent", cmd_testevent, + auto_help_override=False) + + +#------------------------------------------------------------ +# Test of Cache system +#------------------------------------------------------------ + +def cmd_testcache(command): + # + # Tests the cache system by writing to it + # back and forth several times. + # + # Usage: + # @testcache [get] + # + # Use without 'get' to store test data in + # caches and with 'get' to read them back + # and make sure they all saved as they + # should. You might also want to + # try shut down the server between + # calls to make sure the persistent + # cache does survive the shutdown. + + from src.cache import cache + from src import gametime + + source_object = command.source_object + switches = command.command_switches + + s1 = "Value: Cache: OK" + s2 = "Value: PCache 1 (set using property assignment): OK" + s3 = "Value: PCache 2 (set using function call): OK" + if switches and "get" in switches: + # Reading from cache + source_object.emit_to("Reading from cache ...") + cache.load_pcache() + cache_vol = source_object.cache.testcache + source_object.emit_to("< volatile cache:\n %s" % cache_vol) + cache_perm = source_object.pcache.testcache_perm + source_object.emit_to("< persistent cache 1/2:\n %s" % cache_perm) + cache_perm2 = cache.get_pcache("permtest2") + source_object.emit_to("< persistent cache 2/2:\n %s" % cache_perm2) + else: + # Saving to cache + source_object.emit_to("Save to cache ...") + source_object.cache.testcache = s1 + # using two different ways to set pcache + source_object.pcache.testcache_perm = s2 + cache.set_pcache("permtest2", s3) + + source_object.emit_to("> volatile cache:\n %s" % s1) + source_object.emit_to("> persistent cache 1/2:\n %s" % s2) + source_object.emit_to("> persistent cache 2/2:\n %s" % s3) + cache.save_pcache() + string = "Caches saved. Use /get as a switch to read them back." + source_object.emit_to(string) + source_object.emit_to("Running Gametime: %i" % gametime.time()) +GLOBAL_CMD_TABLE.add_command("@testcache", cmd_testcache, + auto_help_override=False) diff --git a/game/gamesrc/commands/examples/state_example.py b/game/gamesrc/commands/examples/state_example.py index adb8cb0412..ec95dea76c 100644 --- a/game/gamesrc/commands/examples/state_example.py +++ b/game/gamesrc/commands/examples/state_example.py @@ -15,7 +15,7 @@ files are recognized. Next enter the mud and give the command -> entermenu +> @testmenu Note that the help entries related to this little menu are not part of the normal help database, they are stored with the state and only @@ -25,10 +25,12 @@ in action. To further test the state system, try the command -> enterstate +> @teststate This takes arguments between 1-6 to set up various states with varying access to different global commands. + +See also misc_tests.py for other tests. """ # This is the normal command table, accessible by default @@ -54,7 +56,7 @@ STATENAME = 'menu' def cmd_entermenu(command): """ entermenu - enter the example menu - + Usage: entermenu @@ -144,7 +146,8 @@ def print_menu(source_obj, choice=None): source_obj.emit_to(string) # Add the 'entry' command to the normal command table -GLOBAL_CMD_TABLE.add_command("entermenu", cmd_entermenu) +GLOBAL_CMD_TABLE.add_command("@testmenu", cmd_entermenu, + auto_help_override=False) # create the state. We make sure the player can exit it at # any time by @exit. @@ -182,11 +185,11 @@ TSTATE6 = 'noglobal_allow_exits_obj_cmds' # def cmd_test_state(command): """ - enterstate - testing the state system + @teststate - testing the state system - Usage: enterstate [1 - 6] + Usage: @teststate [1 - 6] - Give arguments 1..6 to enter different game states. Use @exit to + Give arguments 1-6 to enter different game states. Use @exit to get out of the state at any time. 1: A very limited state; only contains the 'test' state command. @@ -202,13 +205,13 @@ def cmd_test_state(command): both traverse exits and use object-based cmds. Ideas for in-game use: - 1: Try out the 'entermenu' command for an example of this state. + 1: Try out the '@testmenu' command for an example of this state. 2: Could be used in order to stop someone from moving despite exits being open (tied up? In combat?) 3: someone incapacitated or blinded might get only limited commands available 4: in e.g. a combat state, things like crafting should not be - possible + possible. 5: Pretty much default operation, just removing some global commands. Maybe limiting the use of magical weapons in a room or similar. 6: A state of panic - You can move, but not take in your surroundings. @@ -219,7 +222,7 @@ def cmd_test_state(command): args = command.command_argument # check for missing arguments if not args: - source_object.emit_to("Usage: enterstate [1 - 6]") + source_object.emit_to("Usage: @teststate [1 - 6]") return # build up a return string string = "\n Entering state ... \nThis state includes the" @@ -325,6 +328,6 @@ GLOBAL_STATE_TABLE.add_command(TSTATE5, 'test', cmd_instate_cmd) GLOBAL_STATE_TABLE.add_command(TSTATE6, 'test', cmd_instate_cmd) #create the entry function for testing all states -GLOBAL_CMD_TABLE.add_command('enterstate', cmd_test_state) +GLOBAL_CMD_TABLE.add_command('@teststate', cmd_test_state) diff --git a/src/cmdhandler.py b/src/cmdhandler.py index fca74374a7..3e13e2d4f8 100755 --- a/src/cmdhandler.py +++ b/src/cmdhandler.py @@ -91,7 +91,6 @@ class Command(object): # create a list with at least two entries. raw = "%s " % self.raw_input cmd_words = raw.split(' ') - try: if '/' in cmd_words[0]: # if we have switches we directly go for the first command form. @@ -489,8 +488,7 @@ def handle(command, ignore_state=False): command_table_lookup(command, state_cmd_table) else: # Not in a state. Normal operation. - state = None #make sure, in case the object had a malformed statename. - + state = None # make sure, in case the object had a malformed statename. # Check if the user is using a channel command. match_channel(command) # See if the user is trying to traverse an exit. diff --git a/src/cmdtable.py b/src/cmdtable.py index d6461a7947..24d09dec73 100644 --- a/src/cmdtable.py +++ b/src/cmdtable.py @@ -43,14 +43,13 @@ class CommandTable(object): the help entry. If not given, 'General' is assumed. priv_help_tuple (tuple) String tuple of permissions required to view this help entry. If nothing is given, priv_tuple is used. - auto_help_override (bool): Override the value in settings.AUTO_HELP_ENABLED with the + auto_help_override (bool/None): Override the value in settings.AUTO_HELP_ENABLED with the value given. Use None to not override. This can be useful when developing a new routine and has made manual changes to help entries of other commands in the database (and so do not want to use global - auto-help). It is also used by e.g. the state system - to selectively deactive auto-help. - + auto-help). + Note: the auto_help system also supports limited markup. You can divide your __doc__ with markers of any combinations of the forms [[Title]] diff --git a/src/commands/batchprocess.py b/src/commands/batchprocess.py index 34c7fde859..b7aa806a52 100644 --- a/src/commands/batchprocess.py +++ b/src/commands/batchprocess.py @@ -59,7 +59,7 @@ from src.statetable import GLOBAL_STATE_TABLE STATENAME="_interactive batch processor" cwhite = r"%cn%ch%cw" -cred = r"%cn%ch%cw" +cred = r"%cn%ch%cr" cgreen = r"%cn%ci%cg" cyellow = r"%cn%ch%cy" cnorm = r"%cn" @@ -172,7 +172,6 @@ def cmd_batchprocess(command): Interactive mode allows the user more control over the processing of the file. """ - #global CMDSTACKS,STACKPTRS,FILENAMES source_object = command.source_object @@ -183,7 +182,7 @@ def cmd_batchprocess(command): args = command.command_argument if not args: - source_object.emit_to("Usage: @batchprocess[/interactive] ") + source_object.emit_to("Usage: @batchprocess[/interactive] ") return filename = args.strip() @@ -196,23 +195,19 @@ def cmd_batchprocess(command): return switches = command.command_switches if switches and switches[0] in ['inter','interactive']: - # allow more control over how batch file is executed + # Allow more control over how batch file is executed + + if source_object.has_flag("ADMIN_NOSTATE"): + source_object.unset_flag("ADMIN_NOSTATE") + string = cred + "\nOBS: Flag ADMIN_NOSTATE unset in order to " + string += "run Interactive mode. Don't forget to re-set " + string += "it (if you need it) after you're done." + source_object.emit_to(string) - if not source_object.set_state(STATENAME): - # if we failed it is likely because we have - # ADMIN_NOSTATE set. - source_object.unset_flag("ADMIN_NOSTATE") - if not source_object.set_state(STATENAME): - source_object.emit_to("Error in entering the interactive state.") - source_object.set_flag("ADMIN_NOSTATE") - return - else: - string = "OBS: Flag ADMIN_NOSTATE unset in order to " - string += "run Interactive mode. Don't forget to re-set " - string += "it (if you need it) after you're done." - source_object.emit_to(string) - - # store work data in cache + # Set interactive state directly + source_object.cache.state = STATENAME + + # Store work data in cache source_object.cache.batch_cmdstack = commands source_object.cache.batch_stackptr = 0 source_object.cache.batch_filename = filename @@ -234,13 +229,8 @@ def cmd_batchprocess(command): GLOBAL_CMD_TABLE.add_command("@batchprocess", cmd_batchprocess, priv_tuple=("genperms.process_control",), help_category="Building") -#interactive state commands -def printfooter(): - "prints a nice footer" - #s = "%s\n== nn/bb/jj == pp/ss == ll == rr/rrr == cc/qq == (hh for help) ==" % cgreen - s = "" - return s +# The Interactive batch processor state def show_curr(source_object,showall=False): "Show the current command." @@ -264,7 +254,6 @@ def show_curr(source_object,showall=False): cnorm) if showall: s += "\n%s" % command - s += printfooter() source_object.emit_to(s) def process_commands(source_object, steps=0): @@ -309,7 +298,7 @@ def exit_state(source_object): # since clear_state() is protected against exiting the interactive mode # (to avoid accidental drop-outs by rooms clearing a player's state), # we have to clear the state directly here. - source_object.state = None + source_object.cache.state = None def cmd_state_ll(command): """ @@ -327,7 +316,6 @@ def cmd_state_pp(command): Process the currently shown command definition. """ process_commands(command.source_object) - command.source_object.emit_to(printfooter()) def cmd_state_rr(command): """ diff --git a/src/commands/general.py b/src/commands/general.py index a93cf4e452..c4733f890a 100644 --- a/src/commands/general.py +++ b/src/commands/general.py @@ -18,7 +18,7 @@ def cmd_password(command): @password - set your password Usage: - @paassword = + @password = Changes your password. Make sure to pick a safe one. """ @@ -666,12 +666,17 @@ def cmd_help(command): help - view help database Usage: - help + help[/switches] + + Switch: + apropos - show a list of all topics loosely matching the search criterion + (you can also use the commands 'apropos' or 'suggest' for this). Examples: help index help topic help 345 - + help/apropos del + Shows the available help on . Use without to get the help index. If more than one topic match your query, you will get a list of topics to choose between. You can also supply a help entry number @@ -680,6 +685,7 @@ def cmd_help(command): source_object = command.source_object topicstr = command.command_argument + switches = command.command_switches if not command.command_argument: #display topic index if just help command is given @@ -689,7 +695,7 @@ def cmd_help(command): #check valid query source_object.emit_to("Your search query must be at least two letters long.") return - + # speciel help index names. These entries are dynamically # created upon request. if topicstr in ['topic','topics']: @@ -706,6 +712,21 @@ def cmd_help(command): source_object.emit_to(text) return + if switches and 'apropos' in switches: + # run a loose apropos match + topics = HelpEntry.objects.find_apropos(source_object, topicstr) + if topics: + if len(topics) > 50: + string = "Topics containing string '%s' (first 50):\n" % topicstr + topics = topics[:49] + else: + string = "Topics containing string '%s':\n" % topicstr + string += ", ".join(topic.get_topicname() for topic in topics) + else: + string = "No matches found for %s." % topicstr + source_object.emit_to(string) + return + # not a special help index entry. Do a search for the help entry. topics = HelpEntry.objects.find_topicmatch(source_object, topicstr) @@ -765,51 +786,22 @@ def cmd_help(command): source_object.emit_to(string) GLOBAL_CMD_TABLE.add_command("help", cmd_help) +def cmd_apropos(command): + """ + apropos - show rough help matches -## def cmd_testevent(command): -## from src import events -## from src import scheduler -## source_object = command.source_object - -## if not command.command_argument: -## #event = events.IntervalEvent() -## event = events.IntervalEvent() -## event.repeats = 3 -## event.interval = 5 -## pid = scheduler.add_event(event) -## source_object.emit_to("event with pid %s added." % pid) -## else: -## pid = command.command_argument -## scheduler.del_event(pid) -## source_object.emit_to("event with pid %s removed (if it existed)." % pid) -## GLOBAL_CMD_TABLE.add_command("testevent", cmd_testevent) - -## def cmd_testcache(command): -## from src.cache import cache -## from src import scheduler -## from src import events -## from src import gametime -## source_object = command.source_object -## switches = command.command_switches -## s1 = "Temp_cache_val_OK" -## s2 = "Perm_cache_val_OK" -## s3 = "Perm_cache_val2_OK" -## if switches and "get" in switches: -## cache.load_pcache() -## cache_vol = source_object.cache.testcache -## source_object.emit_to("< volatile cache: %s" % cache_vol) -## cache_perm = source_object.pcache.testcache_perm -## source_object.emit_to("< cache_perm1: %s" % cache_perm) -## cache_perm2 = cache.get_pcache("permtest2") -## source_object.emit_to("< cache_perm2: %s" % cache_perm2) -## else: -## source_object.cache.testcache = s1 -## source_object.pcache.testcache_perm = s2 -## cache.set_pcache("permtest2", s3) -## source_object.emit_to("> volatile cache: %s" % s1) -## source_object.emit_to("> cache_perm1: %s" % s2) -## source_object.emit_to("> cache_perm2: %s" % s3) -## cache.save_pcache() -## source_object.emit_to("Caches saved.") -## source_object.emit_to("Time: %i" % gametime.time()) -## GLOBAL_CMD_TABLE.add_command("testcache", cmd_testcache) + Usage: + apropos + or + suggest + + This presents a list of topics very loosely matching your + search text. Use this command when you are searching for + help on a certain concept but don't know any exact + command names. You can also use the normal help command + with the /apropos switch to get the same functionality. + """ + arg = command.command_argument + command.source_object.execute_cmd("help/apropos %s" % arg) +GLOBAL_CMD_TABLE.add_command("apropos", cmd_apropos) +GLOBAL_CMD_TABLE.add_command("suggest", cmd_apropos) diff --git a/src/commands/privileged.py b/src/commands/privileged.py index f97474495f..414ed498c6 100644 --- a/src/commands/privileged.py +++ b/src/commands/privileged.py @@ -18,6 +18,7 @@ from src import cache from src import scheduler + def cmd_reload(command): """ @reload - reload game subsystems @@ -771,13 +772,18 @@ def cmd_delevent(command): @delevent - remove events manually Usage: - @delevent + @delevent[/switch] + + Switch: + force - by default, certain default low-pid system events are protected + from accidental deletion. This switch overrides this + protection. Removes an event with the given pid (process ID) from the event scheduler. To see all active events and their pids, use the @ps command. """ source_object = command.source_object - + switches = command.command_switches if not command.command_argument: source_object.emit_to("Usage: @delevent ") return @@ -786,6 +792,19 @@ def cmd_delevent(command): except ValueError: source_object.emit_to("You must supply a valid pid number.") return + + if pid < 3 and "force" not in switches: + # low-pid protection + string = "Warning:\n" + string += " Pid %i is a low-pid system event. It is usually not\n" % pid + string +=" something you want to delete since crucial\n" + string += " engine functions depend on it. If you were not\n" + string += " mistaken and know what you are doing, give this\n" + string += " command again with the /force switch to override\n" + string += " this protection." + source_object.emit_to(string) + return + event = scheduler.get_event(pid) if event: desc = event.description diff --git a/src/helpsys/managers.py b/src/helpsys/managers.py index c31fb0007c..144a6c9642 100644 --- a/src/helpsys/managers.py +++ b/src/helpsys/managers.py @@ -19,37 +19,24 @@ class HelpEntryManager(models.Manager): # check permissions t_query = [topic for topic in t_query if topic.can_view(pobject)] return t_query + + def find_apropos(self, pobject, topicstr): + """ + Do a very loose search, returning all help entries containing + the search criterion in their titles. + """ + topics = self.filter(topicname__icontains=topicstr) + return [topic for topic in topics if topic.can_view(pobject)] def find_topicsuggestions(self, pobject, topicstr): """ Do a fuzzy match, preferably within the category of the current topic. """ - basetopic = self.filter(topicname__iexact=topicstr) - if not basetopic: - # this topic does not exist; just reply partial - # matches to the string - topics = self.filter(topicname__icontains=topicstr) - return [topic for topic in topics if topic.can_view(pobject)] - - # we know that the topic exists, try to find similar ones within - # its category. - basetopic = basetopic[0] - category = basetopic.category - topics = [] - - #remove the @ - crop = topicstr.lstrip('@') - - # first we filter for matches with the full name - topics = self.filter(category__iexact=category).filter(topicname__icontains=crop) - if len(crop) > 4: - # next search with a cropped version of the command. - ttemp = self.filter(category__iexact=category).filter(topicname__icontains=crop[:4]) - ttemp = [topic for topic in ttemp if topic not in topics] - topics = list(topics) + list(ttemp) + topics = self.find_apropos(pobject, topicstr) # we need to clean away the given help entry. - return [topic for topic in topics if topic.topicname.lower() != topicstr.lower()] + return [topic for topic in topics + if topic.topicname.lower() != topicstr.lower()] def find_topics_with_category(self, pobject, category): """ @@ -57,3 +44,4 @@ class HelpEntryManager(models.Manager): """ t_query = self.filter(category__iexact=category) return [topic for topic in t_query if topic.can_view(pobject)] + diff --git a/src/objects/managers/object.py b/src/objects/managers/object.py index 91047a22d4..76259329b6 100644 --- a/src/objects/managers/object.py +++ b/src/objects/managers/object.py @@ -441,6 +441,12 @@ class ObjectManager(models.Manager): new_object.owner = None new_object.zone = None else: + if owner == None: + # if owner is None for a non-player object we are probably + # creating an object from a script. In this case we set + # the owner to be the superuser. + owner = self.get_object_from_dbref("#1") + new_object.owner = owner if new_object.get_owner().get_zone(): new_object.zone = new_object.get_owner().get_zone() diff --git a/src/objects/models.py b/src/objects/models.py index 58fde3b3fb..5f4dd078db 100755 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -62,7 +62,7 @@ class Attribute(models.Model): """ attr_value = self.attr_value if self.attr_ispickled: - attr_value = pickle.loads(str(attr_value)) + attr_value = pickle.loads(str(attr_value)) return attr_value def set_value(self, new_value): @@ -82,7 +82,7 @@ class Attribute(models.Model): self.attr_value = new_value self.attr_ispickled = ispickled self.save() - + def get_object(self): """ Returns the object that the attribute resides on. @@ -315,7 +315,7 @@ class Object(models.Model): no matter what) """ # The Command object has all of the methods for parsing and preparing - # for searching and execution. Send it to the handler once populated. + # for searching and execution. Send it to the handler once populated. cmdhandler.handle(cmdhandler.Command(self, command_str, session=session), ignore_state=ignore_state) @@ -1267,14 +1267,15 @@ class Object(models.Model): # we never enter other states if we are already in # the interactive batch processor. - nostate = nostate or self.get_state() == "_interactive batch processor" + nostate = nostate or \ + self.get_state() == "_interactive batch processor" if nostate: return False + # switch the state self.cache.state = state_name return True - - + def clear_state(self): """ Set to no state (return to normal operation) @@ -1284,7 +1285,7 @@ class Object(models.Model): (batch processor clears the state directly instead) """ if not self.state == "_interactive batch processor": - self.state = None + self.cache.state = None def purge_object(self): "Completely clears all aspects of the object." diff --git a/src/statetable.py b/src/statetable.py index 953adac93e..c4191269ae 100644 --- a/src/statetable.py +++ b/src/statetable.py @@ -354,10 +354,21 @@ def cmd_state_help(command): help - view help database Usage: - help + help[/switches] + + Switch: + apropos - show a list of all matches containing the search criterion + (you can also use the command 'apropos' for this). - Shows the available help on . Use without a topic - to get the index. + Examples: help index + help topic + help 345 + help/apropos del + + Shows the available help on . Use without to get the help + index. If more than one topic match your query, you will get a + list of topics to choose between. You can also supply a help entry number + directly if you know it. """ source_object = command.source_object @@ -385,7 +396,7 @@ def cmd_state_help(command): if helptext: source_object.emit_to("\n%s" % helptext) else: - source_object.execute_cmd("help %s" % topicstr, ignore_state=True) + source_object.execute_cmd("%s" % command.raw_input, ignore_state=True) #import this instance into your modules GLOBAL_STATE_TABLE = StateTable()