Added the apropos command for broader help searching (uses icontains).

The suggestions: footer used in help gave too narrow results, now using apropos-style search instead.
Bug fix of state-help command to make it accept switches.
Added several new example commands and cleaned up old ones to be more user-friendly.
Added protection in @delevent to make it harder to delete system events.
Some small bug fixes and other cleanup.
This commit is contained in:
Griatch 2009-12-20 12:39:08 +00:00
parent c7cbc4854e
commit 81bec61d7d
12 changed files with 295 additions and 138 deletions

View file

@ -30,10 +30,10 @@ def cmd_example(command):
example - example command
Usage:
example[/switches] <text>
@testcommand[/switches] <text>
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

View file

@ -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)

View file

@ -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)

View file

@ -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.

View file

@ -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]]

View file

@ -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] <filename with full path>")
source_object.emit_to("Usage: @batchprocess[/interactive] <path/to/file>")
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):
"""

View file

@ -18,7 +18,7 @@ def cmd_password(command):
@password - set your password
Usage:
@paassword <old password> = <new password>
@password <old password> = <new 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 <topic>
help[/switches] <topic>
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 <topic>. Use without <topic> 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 <text>
or
suggest <text>
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)

View file

@ -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 <pid>
@delevent[/switch] <pid>
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 <pid>")
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

View file

@ -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)]

View file

@ -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()

View file

@ -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."

View file

@ -354,10 +354,21 @@ def cmd_state_help(command):
help - view help database
Usage:
help <topic>
help[/switches] <topic>
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 <topic>. Use without a topic
to get the index.
Examples: help index
help topic
help 345
help/apropos del
Shows the available help on <topic>. Use without <topic> 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()