Multiple fixes and cleanups - command parser excludes inaccessible commands already at parse level now. Fixed the functionality of a few of the lock functions to be more intuitive. Added functionality to the examine command to better show the commands available to an object.

This commit is contained in:
Griatch 2011-06-26 14:35:02 +00:00
parent 334c0b1d08
commit 95d672763b
17 changed files with 207 additions and 165 deletions

View file

@ -31,16 +31,14 @@ command line. The process is as follows:
by the parser. The result is a list of command matches tied to their respective match object.
9) If we found no matches, branch to system command CMD_NOMATCH --> Finished
10) If we were unable to weed out multiple matches, branch CMD_MULTIMATCH --> Finished
11) If we have a single match, we now check user permissions.
not permissions: branch to system command CMD_NOPERM --> Finished
12) We analyze the matched command to determine if it is a channel-type command, that is
11) We analyze the matched command to determine if it is a channel-type command, that is
a command auto-created to represent a valid comm channel. If so, we see if CMD_CHANNEL is
custom-defined in the merged cmdset, or we launch the auto-created command
direclty --> Finished
13 We next check if this is an exit-type command, that is, a command auto-created to represent
12 We next check if this is an exit-type command, that is, a command auto-created to represent
an exit from this room. If so we check for custom CMD_EXIT in cmdset or launch
the auto-generated command directly --> Finished
14) At this point we have found a normal command. We assign useful variables to it, that
13) At this point we have found a normal command. We assign useful variables to it, that
will be available to the command coder at run-time.
When launching the command (normal, or system command both), two hook functions are called
@ -68,7 +66,6 @@ COMMAND_PARSER = utils.mod_import(*settings.COMMAND_PARSER.rsplit('.', 1))
CMD_NOINPUT = "__noinput_command"
CMD_NOMATCH = "__nomatch_command"
CMD_MULTIMATCH = "__multimatch_command"
CMD_NOPERM = "__noperm_command"
CMD_CHANNEL = "__send_to_channel"
class NoCmdSets(Exception):
@ -176,21 +173,9 @@ def cmdhandler(caller, raw_string, unloggedin=False, testing=False):
raise ExecSystemCommand(syscmd, sysarg)
# Parse the input string and match to available cmdset.
matches = COMMAND_PARSER(raw_string, cmdset)
#string ="Command candidates"
#for cand in cmd_candidates:
# string += "\n %s || %s" % (cand.cmdname, cand.args)
#caller.msg(string)
# Try to produce a unique match between the merged
# cmdset and the candidates.
# if unloggedin:
# matches = match_command(cmd_candidates, cmdset)
# else:
# matches = match_command(cmd_candidates, cmdset, caller)
#print "matches: ", matches
# This also checks for permissions, so all commands in match
# are commands the caller is allowed to call.
matches = COMMAND_PARSER(raw_string, cmdset, caller)
# Deal with matches
if not matches:
@ -216,15 +201,6 @@ def cmdhandler(caller, raw_string, unloggedin=False, testing=False):
match = matches[0]
cmdname, args, cmd = match[0], match[1], match[2]
# Check so we have permission to use this command.
if not cmd.access(caller):
cmd = cmdset.get(CMD_NOPERM)
if cmd:
sysarg = raw_string
else:
sysarg = "Huh? (type 'help' for help)"
raise ExecSystemCommand(cmd, sysarg)
# Check if this is a Channel match.
if hasattr(cmd, 'is_channel') and cmd.is_channel:
# even if a user-defined syscmd is not defined, the

View file

@ -5,7 +5,7 @@ replacing cmdparser function. The replacement parser must
return a CommandCandidates object.
"""
def cmdparser(raw_string, cmdset, match_index=None):
def cmdparser(raw_string, cmdset, caller, match_index=None):
"""
This function is called by the cmdhandler once it has
gathered all valid cmdsets for the calling player. raw_string
@ -61,6 +61,9 @@ def cmdparser(raw_string, cmdset, match_index=None):
# feed result back to parser iteratively
return cmdparser(new_raw_string, cmdset, match_index=mindex)
# only select command matches we are actually allowed to call.
matches = [match for match in matches if match[2].access(caller, 'cmd')]
if len(matches) > 1:
# see if it helps to analyze the match with preserved case.
matches = [match for match in matches if raw_string.startswith(match[0])]
@ -82,7 +85,7 @@ def cmdparser(raw_string, cmdset, match_index=None):
if len(matches) > 1 and match_index != None and 0 <= match_index < len(matches):
# We couldn't separate match by quality, but we have an index argument to
# tell us which match to use.
matches = [matches[match_index]]
matches = [matches[match_index]]
# no matter what we have at this point, we have to return it.
return matches

View file

@ -180,7 +180,8 @@ class CmdSet(object):
no_exits = False
no_objs = False
no_channels = False
permanent = False
def __init__(self, cmdsetobj=None, key=None):
"""
Creates a new CmdSet instance.
@ -272,6 +273,10 @@ class CmdSet(object):
if thiscmd == cmd:
return thiscmd
def count(self):
"Return number of commands in set"
return len(self.commands)
def get_system_cmds(self):
"""
Return system commands in the cmdset, defined as
@ -323,7 +328,7 @@ class CmdSet(object):
"""
Show all commands in cmdset when printing it.
"""
return ", ".join([str(cmd) for cmd in self.commands])
return ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o:o.key)])
def __iter__(self):
"""

View file

@ -64,7 +64,7 @@ example, you can have a 'On a boat' set, onto which you then tack on
the 'Fishing' set. Fishing from a boat? No problem!
"""
import traceback
from src.utils import logger
from src.utils import logger, utils
from src.commands.cmdset import CmdSet
from src.server.models import ServerConfig
@ -163,7 +163,7 @@ class CmdSetHandler(object):
"Display current commands"
string = ""
merged = False
mergelist = []
if len(self.cmdset_stack) > 1:
# We have more than one cmdset in stack; list them all
num = 0
@ -176,19 +176,19 @@ class CmdSetHandler(object):
string += "\n %i: <%s (%s, prio %i)>: %s" % \
(snum, cmdset.key, mergetype,
cmdset.priority, cmdset)
mergelist.append(str(snum))
string += "\n"
merged = True
# Display the currently active cmdset, limited by self.obj's permissions
mergetype = self.mergetype_stack[-1]
if mergetype != self.current.mergetype:
merged_on = self.cmdset_stack[-2].key
mergetype = "custom %s on %s" % (mergetype, merged_on)
if merged:
string += " <Merged (%s, prio %i)>: %s" % (mergetype, self.current.priority, self.current)
if mergelist:
string += " <Merged %s (%s, prio %i)>: %s" % ("+".join(mergelist), mergetype, self.current.priority, self.current)
else:
string += " <%s (%s, prio %i)>: %s" % (self.current.key, mergetype, self.current.priority,
", ".join(cmd.key for cmd in self.current if cmd.access(self.obj, "cmd")))
", ".join(cmd.key for cmd in sorted(self.current, key=lambda o:o.key)))
return string.strip()
def update(self, init_mode=False):
@ -259,6 +259,8 @@ class CmdSetHandler(object):
that has to be documented.
"""
if callable(cmdset):
if not utils.inherits_from(cmdset, CmdSet):
raise Exception("Only CmdSets can be added to the cmdsethandler!")
cmdset = cmdset(self.obj)
elif isinstance(cmdset, basestring):
# this is (maybe) a python path. Try to import from cache.
@ -286,6 +288,8 @@ class CmdSetHandler(object):
See also the notes for self.add(), which applies here too.
"""
if callable(cmdset):
if not utils.inherits_from(cmdset, CmdSet):
raise Exception("Only CmdSets can be added to the cmdsethandler!")
cmdset = cmdset(self.obj)
elif isinstance(cmdset, basestring):
# this is (maybe) a python path. Try to import from cache.

View file

@ -31,6 +31,9 @@ class CommandMeta(type):
temp = []
if hasattr(mcs, 'permissions'):
mcs.locks = mcs.permissions
if not hasattr(mcs, 'locks'):
# default if one forgets to define completely
mcs.locks = "cmd:all()"
for lockstring in mcs.locks.split(';'):
if lockstring and not ':' in lockstring:
lockstring = "cmd:%s" % lockstring

View file

@ -218,8 +218,8 @@ class CmdBatchCommands(MuxCommand):
if not commands:
string = "'%s' not found.\nYou have to supply the python path "
string += "of the file relative to \nyour batch-file directory (%s)."
caller.msg(string % (python_path, settings.BASE_BATCHPROCESS_PATH))
string += "of the file relative to \none of your batch-file directories (%s)."
caller.msg(string % (python_path, ", ".join(settings.BASE_BATCHPROCESS_PATHS)))
return
switches = self.switches
@ -404,9 +404,10 @@ class CmdStateRR(MuxCommand):
def func(self):
caller = self.caller
if caller.ndb.batch_batchmode == "batch_code":
BATCHCODE.parse_file(caller.ndb.batch_pythonpath)
new_data = BATCHCODE.parse_file(caller.ndb.batch_pythonpath)
else:
BATCHCMD.parse_file(caller.ndb.batch_pythonpath)
new_data = BATCHCMD.parse_file(caller.ndb.batch_pythonpath)
caller.ndb.batch_stack = new_data
caller.msg(format_code("File reloaded. Staying on same command."))
show_curr(caller)

View file

@ -350,6 +350,7 @@ class CmdCreate(ObjManipCommand):
switch:
drop - automatically drop the new object into your current location (this is not echoed)
this also sets the new object's home to the current location rather than to you.
Creates one or more new objects. If typeclass is given, the object
is created as a child of this typeclass. The typeclass script is
@ -405,6 +406,7 @@ class CmdCreate(ObjManipCommand):
obj.db.desc = "You see nothing special."
if 'drop' in self.switches:
if caller.location:
obj.home = caller.location
obj.move_to(caller.location, quiet=True)
caller.msg(string)
@ -482,7 +484,7 @@ class CmdDesc(MuxCommand):
return
desc = self.rhs
else:
obj = caller
obj = caller.location
desc = self.args
# storing the description
obj.db.desc = desc
@ -494,14 +496,17 @@ class CmdDestroy(MuxCommand):
@destroy - remove objects from the game
Usage:
@destroy[/<switches>] obj [,obj2, obj3, ...]
@delete ''
@destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]
switches:
override - The @destroy command will usually avoid accidentally destroying
player objects. This switch overrides this safety.
examples:
@destroy house, roof, door, 44-78
@destroy 5-10, flower, 45
Destroys one or many objects.
Destroys one or many objects. If dbrefs are used, a range to delete can be
given, e.g. 4-10. Also the end points will be deleted.
"""
key = "@destroy"
@ -515,22 +520,22 @@ class CmdDestroy(MuxCommand):
caller = self.caller
if not self.args or not self.lhslist:
caller.msg("Usage: @destroy[/switches] obj [,obj2, obj3, ...]")
return
caller.msg("Usage: @destroy[/switches] [obj, obj2, obj3, [dbref-dbref],...]")
return ""
string = ""
for objname in self.lhslist:
def delobj(objname):
# helper function for deleting a single object
string = ""
obj = caller.search(objname)
if not obj:
self.caller.msg(" (Objects to destroy must either be local or specified with a unique dbref.)")
return
return ""
objname = obj.name
if not obj.access(caller, 'delete'):
string += "\nYou don't have permission to delete %s." % objname
continue
return "\nYou don't have permission to delete %s." % objname
if obj.player and not 'override' in self.switches:
string += "\nObject %s is controlled by an active player. Use /override to delete anyway." % objname
continue
return "\nObject %s is controlled by an active player. Use /override to delete anyway." % objname
had_exits = hasattr(obj, "exits") and obj.exits
had_objs = hasattr(obj, "contents") and any(obj for obj in obj.contents
if not (hasattr(obj, "exits") and obj not in obj.exits))
@ -544,9 +549,21 @@ class CmdDestroy(MuxCommand):
string += " Exits to and from %s were destroyed as well." % objname
if had_objs:
string += " Objects inside %s were moved to their homes." % objname
return string
string = ""
for objname in self.lhslist:
if '-' in objname:
# might be a range of dbrefs
dmin, dmax = [utils.dbref(part) for part in objname.split('-', 1)]
if dmin and dmax:
for dbref in range(int(dmin),int(dmax+1)):
string += delobj(str(dbref))
else:
string += delobj(objname)
if string:
caller.msg(string.strip())
class CmdDig(ObjManipCommand):
"""
@ -619,7 +636,7 @@ class CmdDig(ObjManipCommand):
to_exit = self.rhs_objs[0]
if not to_exit["name"]:
exit_to_string = \
"\nYou didn't give a name for the exit to the new room."
"\nNo exit created to new room."
elif not location:
exit_to_string = \
"\nYou cannot create an exit from a None-location."
@ -646,7 +663,7 @@ class CmdDig(ObjManipCommand):
back_exit = self.rhs_objs[1]
if not back_exit["name"]:
exit_back_string = \
"\nYou didn't give a name for the exit back here."
"\nNo back exit created."
elif not location:
exit_back_string = \
"\nYou cannot create an exit back to a None-location."
@ -1506,7 +1523,8 @@ class CmdExamine(ObjManipCommand):
"destination":"\n{wDestination{n: %s",
"perms":"\n{wPermissions{n: %s",
"locks":"\n{wLocks{n:",
"cmdset":"\n{wCurrent Cmdset (including permission checks){n:\n %s",
"cmdset":"\n{wCurrent Cmdset(s){n:\n %s",
"cmdset_avail":"\n{wActual commands available to %s (incl. lock-checks, external cmds etc){n:\n %s",
"scripts":"\n{wScripts{n:\n %s",
"exits":"\n{wExits{n: ",
"characters":"\n{wCharacters{n: ",
@ -1520,7 +1538,8 @@ class CmdExamine(ObjManipCommand):
"destination":"\nDestination: %s",
"perms":"\nPermissions: %s",
"locks":"\nLocks:",
"cmdset":"\nCurrent Cmdset (including permission checks):\n %s",
"cmdset":"\nCurrent Cmdset(s):\n %s",
"cmdset_avail":"\nActual commands available to %s (incl. lock-checks, external cmds, etc):\n %s",
"scripts":"\nScripts:\n %s",
"exits":"\nExits: ",
"characters":"\nCharacters: ",
@ -1556,8 +1575,18 @@ class CmdExamine(ObjManipCommand):
string += headers["locks"] + utils.fill("; ".join([lock for lock in locks.split(';')]), indent=6)
if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "Empty"):
cmdsetstr = "\n".join([utils.fill(cmdset, indent=2) for cmdset in str(obj.cmdset).split("\n")])
# list the current cmdsets
cmdsetstr = "\n".join([utils.fill(cmdset, indent=2) for cmdset in str(obj.cmdset).split("\n")])
string += headers["cmdset"] % cmdsetstr
# list the actually available commands
from src.commands.cmdhandler import get_and_merge_cmdsets
avail_cmdset = get_and_merge_cmdsets(obj)
avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")])
cmdsetstr = utils.fill(", ".join(avail_cmdset), indent=2)
string += headers["cmdset_avail"] % (obj.key, cmdsetstr)
if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
string += headers["scripts"] % obj.scripts
# add the attributes

View file

@ -229,11 +229,11 @@ class CmdInventory(MuxCommand):
# format item list into nice collumns
cols = [[],[]]
for item in items:
cols[0].append(item.name)
desc = utils.crop(item.db.desc)
cols[0].append(item.name)
desc = item.db.desc
if not desc:
desc = ""
cols[1].append(desc)
cols[1].append(utils.crop(str(desc)))
# auto-format the columns to make them evenly wide
ftable = utils.format_table(cols)
string = "You are carrying:"

View file

@ -26,7 +26,6 @@ from src.utils import create
from src.commands.cmdhandler import CMD_NOINPUT
from src.commands.cmdhandler import CMD_NOMATCH
from src.commands.cmdhandler import CMD_MULTIMATCH
from src.commands.cmdhandler import CMD_NOPERM
from src.commands.cmdhandler import CMD_CHANNEL
from src.commands.default.muxcommand import MuxCommand
@ -124,26 +123,8 @@ class SystemMultimatch(MuxCommand):
"""
string = self.format_multimatches(self.caller, self.matches)
self.caller.msg(string)
class SystemNoPerm(MuxCommand):
"""
This is called when the user does not have the
correct permissions to use a particular command.
"""
key = CMD_NOPERM
locks = "cmd:all()"
def func(self):
"""
This receives the original raw
input string (the one whose command failed to validate)
as argument.
"""
self.caller.msg("You are not allowed to do that.")
# Command called when the comman given at the command line
# Command called when the command given at the command line
# was identified as a channel name, like there existing a
# channel named 'ooc' and the user wrote
# > ooc Hello!

View file

@ -124,6 +124,7 @@ class CmdScripts(MuxCommand):
Switches:
stop - stops an existing script
kill - kills a script - without running its cleanup hooks
validate - run a validation on the script(s)
If no switches are given, this command just views all active
@ -212,15 +213,19 @@ class CmdScripts(MuxCommand):
caller.msg(string)
return
if self.switches and self.switches[0] in ('stop', 'del', 'delete'):
if self.switches and self.switches[0] in ('stop', 'del', 'delete', 'kill'):
# we want to delete something
if not scripts:
string = "No scripts/objects matching '%s'. " % args
string += "Be more specific."
elif len(scripts) == 1:
# we have a unique match!
string = "Stopping script '%s'." % scripts[0].key
scripts[0].stop()
if 'kill' in self.switches:
string = "Killing script '%s'" % scripts[0].key
scripts[0].stop(kill=True)
else:
string = "Stopping script '%s'." % scripts[0].key
scripts[0].stop()
ScriptDB.objects.validate() #just to be sure all is synced
else:
# multiple matches.