Cleaned up the batch processors (both cmd- and code processor) and homogenized their interfaces and APIs. Also test-ran the example codes and fixed some bugs.

This commit is contained in:
Griatch 2010-09-02 11:39:01 +00:00
parent e114c33d8a
commit 4d8fc05157
6 changed files with 373 additions and 316 deletions

View file

@ -23,23 +23,46 @@ Example batch-code file: game/gamesrc/commands/examples/example_batch_code.py
""" """
from traceback import format_exc from traceback import format_exc
from django.conf import settings from django.conf import settings
from src.utils import batchprocessors from src.utils.batchprocessors import BATCHCMD, BATCHCODE
from game.gamesrc.commands.default.muxcommand import MuxCommand from game.gamesrc.commands.default.muxcommand import MuxCommand
from src.commands.cmdset import CmdSet from src.commands.cmdset import CmdSet
HEADER_WIDTH = 70
#global defines for storage #global defines for storage
CWHITE = r"%cn%ch%cw"
CRED = r"%cn%ch%cr"
CGREEN = r"%cn%ci%cg"
CYELLOW = r"%cn%ch%cy"
CNORM = r"%cn"
#------------------------------------------------------------ #------------------------------------------------------------
# Helper functions # Helper functions
#------------------------------------------------------------ #------------------------------------------------------------
def format_header(caller, entry):
"""
Formats a header
"""
width = HEADER_WIDTH - 10
entry = entry.strip()
header = entry[:min(width, min(len(entry), entry.find('\n')))]
if len(entry) > width:
header = "%s[...]" % header
ptr = caller.ndb.batch_stackptr + 1
stacklen = len(caller.ndb.batch_stack)
header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header)
# add extra space to the side for padding.
header = "%s%s" % (header, " "*(width-len(header)))
header = header.replace('\n', '\\n')
return header
def format_code(entry):
"""
Formats the viewing of code and errors
"""
code = ""
for line in entry.split('\n'):
code += "\n{G>>>{n %s" % line
return code.strip()
def batch_cmd_exec(caller): def batch_cmd_exec(caller):
""" """
Helper function for executing a single batch-command entry Helper function for executing a single batch-command entry
@ -47,19 +70,14 @@ def batch_cmd_exec(caller):
ptr = caller.ndb.batch_stackptr ptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack stack = caller.ndb.batch_stack
command = stack[ptr] command = stack[ptr]
cmdname = command[:command.find(" ")] caller.msg(format_header(caller, command))
caller.msg("%s %02i/%02i: %s %s%s" % (CGREEN, ptr+1,
len(stack),
cmdname,
CGREEN, " "*(50-len(cmdname))))
try: try:
caller.execute_cmd(command) caller.execute_cmd(command)
except Exception: except Exception:
caller.msg(format_exc()) caller.msg(format_code(format_exc()))
return False return False
return True return True
def batch_code_exec(caller): def batch_code_exec(caller):
""" """
Helper function for executing a single batch-code entry Helper function for executing a single batch-code entry
@ -67,20 +85,16 @@ def batch_code_exec(caller):
ptr = caller.ndb.batch_stackptr ptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack stack = caller.ndb.batch_stack
debug = caller.ndb.batch_debug debug = caller.ndb.batch_debug
codedict = stack[ptr] codedict = stack[ptr]
caller.msg("%s %02i/%02i: %s %s%s" % (CGREEN, ptr + 1,
len(stack), caller.msg(format_header(caller, codedict['code']))
codedict["firstline"], err = BATCHCODE.code_exec(codedict,
CGREEN, " "*(50-len(codedict["firstline"])))) extra_environ={"caller":caller}, debug=debug)
err = batchprocessors.batch_code_exec(codedict,
extra_environ={"caller":caller}, debug=debug)
if err: if err:
caller.msg(err) caller.msg(format_code(err))
return False return False
return True return True
def step_pointer(caller, step=1): def step_pointer(caller, step=1):
""" """
Step in stack, returning the item located. Step in stack, returning the item located.
@ -93,13 +107,15 @@ def step_pointer(caller, step=1):
stack = caller.ndb.batch_stack stack = caller.ndb.batch_stack
nstack = len(stack) nstack = len(stack)
if ptr + step <= 0: if ptr + step <= 0:
caller.msg("Beginning of batch file.") caller.msg("{RBeginning of batch file.")
if ptr + step >= nstack: if ptr + step >= nstack:
caller.msg("End of batch file.") caller.msg("{REnd of batch file.")
caller.ndb.batch_stackptr = max(0, min(nstack-1, ptr + step)) caller.ndb.batch_stackptr = max(0, min(nstack-1, ptr + step))
def show_curr(caller, showall=False): def show_curr(caller, showall=False):
"Show the current position in stack." """
Show the current position in stack
"""
stackptr = caller.ndb.batch_stackptr stackptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack stack = caller.ndb.batch_stack
@ -107,25 +123,39 @@ def show_curr(caller, showall=False):
caller.ndb.batch_stackptr = len(stack) - 1 caller.ndb.batch_stackptr = len(stack) - 1
show_curr(caller, showall) show_curr(caller, showall)
return return
entry = stack[stackptr] entry = stack[stackptr]
if type(entry) == dict: if type(entry) == dict:
# we first try the batch-code syntax # this is a batch-code entry
firstline = entry['code'][:min(35, len(entry['code'])-1)] string = format_header(caller, entry['code'])
codeall = entry['code'] codeall = entry['code'].strip()
else: else:
# we try the batch-cmd syntax instead # this is a batch-cmd entry
firstline = entry[:min(35, len(entry)-1)] string = format_header(caller, entry)
codeall = entry codeall = entry.strip()
string = "%s %02i/%02i: %s %s %s %s%s" % (CGREEN, string += "{G(hh for help)"
stackptr+1, len(stack),
firstline, CGREEN,
"(hh for help)",
" "*(35-len(firstline.strip())),
CNORM)
if showall: if showall:
string += "\n%s" % codeall for line in codeall.split('\n'):
string += "\n{n>>> %s" % line
caller.msg(string) caller.msg(string)
def purge_processor(caller):
"""
This purges all effects running
on the caller.
"""
try:
del caller.ndb.batch_stack
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
except:
pass
# clear everything but the default cmdset.
caller.cmdset.delete(BatchSafeCmdSet)
caller.cmdset.clear()
caller.scripts.validate() # this will purge interactive mode
#------------------------------------------------------------ #------------------------------------------------------------
# main access commands # main access commands
@ -164,7 +194,7 @@ class CmdBatchCommands(MuxCommand):
#parse indata file #parse indata file
commands = batchprocessors.parse_batchcommand_file(python_path) commands = BATCHCMD.parse_file(python_path)
if not commands: if not commands:
string = "'%s' not found.\nYou have to supply the python path " string = "'%s' not found.\nYou have to supply the python path "
string += "of the file relative to \nyour batch-file directory (%s)." string += "of the file relative to \nyour batch-file directory (%s)."
@ -202,7 +232,8 @@ class CmdBatchCommands(MuxCommand):
del caller.ndb.batch_pythonpath del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode del caller.ndb.batch_batchmode
string = " Batchfile '%s' applied." % python_path string = " Batchfile '%s' applied." % python_path
caller.msg("%s%s%s" % (CGREEN, string, " "*(60-len(string)))) caller.msg("{G%s" % string)
purge_processor(caller)
class CmdBatchCode(MuxCommand): class CmdBatchCode(MuxCommand):
""" """
@ -240,7 +271,7 @@ class CmdBatchCode(MuxCommand):
python_path = self.args python_path = self.args
#parse indata file #parse indata file
codes = batchprocessors.parse_batchcode_file(python_path) codes = BATCHCODE.parse_file(python_path)
if not codes: if not codes:
string = "'%s' not found.\nYou have to supply the python path " string = "'%s' not found.\nYou have to supply the python path "
string += "of the file relative to \nyour batch-file directory (%s)." string += "of the file relative to \nyour batch-file directory (%s)."
@ -283,7 +314,8 @@ class CmdBatchCode(MuxCommand):
del caller.ndb.batch_pythonpath del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode del caller.ndb.batch_batchmode
string = " Batchfile '%s' applied." % python_path string = " Batchfile '%s' applied." % python_path
caller.msg("%s%s%s" % (CGREEN, string, " "*(60-len(string)))) caller.msg("{G%s" % string)
purge_processor(caller)
#------------------------------------------------------------ #------------------------------------------------------------
# State-commands for the interactive batch processor modes # State-commands for the interactive batch processor modes
@ -294,23 +326,17 @@ class CmdStateAbort(MuxCommand):
""" """
@abort @abort
Exits back the default cmdset, regardless of what state This is a safety feature. It force-ejects us out of the processor and to
we are currently in. the default cmdset, regardless of what current cmdset the processor might
have put us in (e.g. when testing buggy scripts etc).
""" """
key = "@abort" key = "@abort"
help_category = "BatchProcess" help_category = "BatchProcess"
def func(self): def func(self):
"Exit back to default." "Exit back to default."
caller = self.caller purge_processor(self.caller)
del caller.ndb.batch_stack self.caller.msg("Exited processor and reset out active cmdset back to the default one.")
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
# clear everything but the default cmdset.
caller.cmdset.delete(BatchSafeCmdSet)
caller.cmdset.clear()
caller.msg("Exit: Cleared back to default state.")
class CmdStateLL(MuxCommand): class CmdStateLL(MuxCommand):
""" """
@ -358,10 +384,10 @@ class CmdStateRR(MuxCommand):
def func(self): def func(self):
caller = self.caller caller = self.caller
if caller.ndb.batch_batchmode == "batch_code": if caller.ndb.batch_batchmode == "batch_code":
batchprocessors.read_batchcommand_file(caller.ndb.batch_pythonpath) BATCHCODE.read_file(caller.ndb.batch_pythonpath)
else: else:
batchprocessors.read_batchcode_file(caller.ndb.batch_pythonpath) BATHCMD.read_file(caller.ndb.batch_pythonpath)
caller.msg("\nFile reloaded. Staying on same command.\n") caller.msg(format_code("File reloaded. Staying on same command."))
show_curr(caller) show_curr(caller)
class CmdStateRRR(MuxCommand): class CmdStateRRR(MuxCommand):
@ -377,11 +403,11 @@ class CmdStateRRR(MuxCommand):
def func(self): def func(self):
caller = self.caller caller = self.caller
if caller.ndb.batch_batchmode == "batch_code": if caller.ndb.batch_batchmode == "batch_code":
batchprocessors.read_batchcommand_file(caller.ndb.batch_pythonpath) BATCHCODE.read_file(caller.ndb.batch_pythonpath)
else: else:
batchprocessors.read_batchcode_file(caller.ndb.batch_pythonpath) BATCHCMD.read_file(caller.ndb.batch_pythonpath)
caller.ndb.batch_stackptr = 0 caller.ndb.batch_stackptr = 0
caller.msg("\nFile reloaded. Restarting from top.\n") caller.msg(format_code("File reloaded. Restarting from top."))
show_curr(caller) show_curr(caller)
class CmdStateNN(MuxCommand): class CmdStateNN(MuxCommand):
@ -545,7 +571,7 @@ class CmdStateCC(MuxCommand):
del caller.ndb.batch_stackptr del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode del caller.ndb.batch_batchmode
caller.msg("Finished processing batch file.") caller.msg(format_code("Finished processing batch file."))
class CmdStateJJ(MuxCommand): class CmdStateJJ(MuxCommand):
""" """
@ -562,7 +588,7 @@ class CmdStateJJ(MuxCommand):
if arg and arg.isdigit(): if arg and arg.isdigit():
number = int(self.args)-1 number = int(self.args)-1
else: else:
caller.msg("You must give a number index.") caller.msg(format_code("You must give a number index."))
return return
ptr = caller.ndb.batch_stackptr ptr = caller.ndb.batch_stackptr
step = number - ptr step = number - ptr
@ -584,7 +610,7 @@ class CmdStateJL(MuxCommand):
if arg and arg.isdigit(): if arg and arg.isdigit():
number = int(self.args)-1 number = int(self.args)-1
else: else:
caller.msg("You must give a number index.") caller.msg(format_code("You must give a number index."))
return return
ptr = caller.ndb.batch_stackptr ptr = caller.ndb.batch_stackptr
step = number - ptr step = number - ptr
@ -601,26 +627,19 @@ class CmdStateQQ(MuxCommand):
help_category = "BatchProcess" help_category = "BatchProcess"
def func(self): def func(self):
caller = self.caller purge_processor(self.caller)
del caller.ndb.batch_stack self.caller.msg("Aborted interactive batch mode.")
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
caller.cmdset.delete(BatchSafeCmdSet)
caller.cmdset.delete(BatchInteractiveCmdSet)
caller.scripts.validate() # this will clear interactive mode.
caller.msg("Aborted interactive batch mode.")
class CmdStateHH(MuxCommand): class CmdStateHH(MuxCommand):
"Help command" "Help command"
key = "help" key = "hh"
aliases = "hh"
help_category = "BatchProcess" help_category = "BatchProcess"
def func(self): def func(self):
string = """ string = """
Interactive batch processing commands: Interactive batch processing commands:
nn [steps] - next command (no processing) nn [steps] - next command (no processing)
nl [steps] - next & look nl [steps] - next & look
bb [steps] - back to previous command (no processing) bb [steps] - back to previous command (no processing)
@ -637,6 +656,11 @@ class CmdStateHH(MuxCommand):
cc - continue processing to end, then quit. cc - continue processing to end, then quit.
qq - quit (abort all remaining commands) qq - quit (abort all remaining commands)
@abort - this is a safety command that always is available
regardless of what cmdsets gets added to us during
batch-command processing. It immediately shuts down
the processor and returns us to the default cmdset.
""" """
self.caller.msg(string) self.caller.msg(string)

View file

@ -603,13 +603,13 @@ class CmdCreate(ObjManipCommand):
obj = create.create_object(typeclass, name, caller, obj = create.create_object(typeclass, name, caller,
home=caller, aliases=aliases) home=caller, aliases=aliases)
if not obj: if not obj:
string += "\nError when creating object." string = "Error when creating object."
continue continue
if aliases: if aliases:
string += "\nYou create a new %s: %s (aliases: %s)." string = "You create a new %s: %s (aliases: %s)."
string = string % (obj.typeclass, obj.name, ", ".join(aliases)) string = string % (obj.typeclass, obj.name, ", ".join(aliases))
else: else:
string += "\nYou create a new %s: %s." string = "You create a new %s: %s."
string = string % (obj.typeclass, obj.name) string = string % (obj.typeclass, obj.name)
if 'drop' in self.switches: if 'drop' in self.switches:
if caller.location: if caller.location:
@ -1192,18 +1192,19 @@ class CmdDestroy(MuxCommand):
continue continue
objname = obj.name objname = obj.name
if obj.player and not 'override' in self.switches: if obj.player and not 'override' in self.switches:
string += "\n\rObject %s is a player object. Use /override to delete anyway." % objname string = "Object %s is a player object. Use /override to delete anyway." % objname
continue continue
if not has_perm(caller, obj, 'create'): if not has_perm(caller, obj, 'create'):
string += "\n\rYou don't have permission to delete %s." % objname string = "You don't have permission to delete %s." % objname
continue continue
# do the deletion # do the deletion
okay = obj.delete() okay = obj.delete()
if not okay: if not okay:
string += "\n\rERROR: %s NOT deleted, probably because at_obj_delete() returned False." % objname string = "ERROR: %s NOT deleted, probably because at_obj_delete() returned False." % objname
else: else:
string += "\n\r%s was deleted." % objname string = "%s was deleted." % objname
caller.msg(string.strip('\n')) if string:
caller.msg(string.strip())
#NOT VALID IN NEW SYSTEM! #NOT VALID IN NEW SYSTEM!

View file

@ -17,37 +17,43 @@
# automatically be made available for each block. Observe # automatically be made available for each block. Observe
# that changes to these variables made in one block is not # that changes to these variables made in one block is not
# preserved between blocks!) # preserved between blocks!)
# #CODE [objname, objname, ...] - This designates a code block that will be executed like a # #CODE (infotext) [objname, objname, ...] - This designates a code block that will be executed like a
# stand-alone piece of code together with any #HEADER # stand-alone piece of code together with any #HEADER
# defined. <objname>s mark the (variable-)names of objects created in the code, # defined.
# infotext is a describing text about what goes in in this block. It will be
# shown by the batchprocessing command.
# <objname>s mark the (variable-)names of objects created in the code,
# and which may be auto-deleted by the processor if desired (such as when # and which may be auto-deleted by the processor if desired (such as when
# debugging the script). E.g., if the code contains the command # debugging the script). E.g., if the code contains the command
# myobj = create.create_object(...), you could put 'myobj' in the #CODE header # myobj = create.create_object(...), you could put 'myobj' in the #CODE header
# regardless of what the created object is actually called in-game. # regardless of what the created object is actually called in-game.
# The following variables are automatically made available for the script: # The following variable is automatically made available for the script:
# caller - the object executing the script # caller - the object executing the script
# #
#
#HEADER #HEADER
# everything in this block will be imported to all CODE blocks when # everything in this block will be appended to the beginning of
# they are executed. # all other #CODE blocks when they are executed.
from src.utils import create, search from src.utils import create, search
from game.gamesrc.typeclasses.examples import red_button from game.gamesrc.objects.examples import red_button
from game.gamesrc.typeclasses import basetypes from game.gamesrc.objects import baseobjects
#CODE limbo = search.objects(caller, 'Limbo', global_search=True)[0]
#CODE (create red button)
# This is the first code block. Within each block, python # This is the first code block. Within each block, python
# code works as normal. # code works as normal. Note how we make use if imports and
# 'limbo' defined in the #HEADER block. This block's header
# offers no information about red_button variable, so it
# won't be able to be deleted in debug mode.
# get the limbo room.
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
caller.msg(limbo)
# create a red button in limbo # create a red button in limbo
red_button = create.create_object(red_button.RedButton, key="Red button", red_button = create.create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"]) location=limbo, aliases=["button"])
@ -55,17 +61,22 @@ red_button = create.create_object(red_button.RedButton, key="Red button",
# we take a look at what we created # we take a look at what we created
caller.msg("A %s was created." % red_button.key) caller.msg("A %s was created." % red_button.key)
#CODE table, chair
#CODE (create table and chair) table, chair
# this code block has 'table' and 'chair' set as deletable # this code block has 'table' and 'chair' set as deletable
# objects. This means that when the batchcode processor runs in # objects. This means that when the batchcode processor runs in
# testing mode, objects created in these variables will be deleted # testing mode, objects created in these variables will be deleted
# again (so as to avoid duplicate objects when testing the script). # again (so as to avoid duplicate objects when testing the script many
# times).
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
caller.msg(limbo.key) caller.msg(limbo.key)
table = create.create_object(basetypes.Object, key="Table", location=limbo)
chair = create.create_object(basetypes.Object, key="Chair", location=limbo) # the python variables we assign to must match the ones given in the
# header for the system to be able to delete them afterwards during a
# debugging run.
table = create.create_object(baseobjects.Object, key="Table", location=limbo)
chair = create.create_object(baseobjects.Object, key="Chair", location=limbo)
string = "A %s and %s were created. If debug was active, they were deleted again." string = "A %s and %s were created. If debug was active, they were deleted again."
caller.msg(string % (table, chair)) caller.msg(string % (table, chair))

View file

@ -504,6 +504,7 @@ class ObjectDB(TypedObject):
""" """
# This is an important function that must always work. # This is an important function that must always work.
# we use a different __getattribute__ to avoid recursive loops. # we use a different __getattribute__ to avoid recursive loops.
if from_obj: if from_obj:
try: try:
from_obj.at_msg_send(message, self) from_obj.at_msg_send(message, self)

View file

@ -27,7 +27,7 @@ def handle_search_errors(emit_to_obj, ostring, results, global_search=False):
dbrefs instead of only numbers) dbrefs instead of only numbers)
""" """
if not results: if not results:
emit_to_obj.emit_to("Could not find '%s'." % ostring) emit_to_obj.msg("Could not find '%s'." % ostring)
return None return None
if len(results) > 1: if len(results) > 1:
# we have more than one match. We will display a # we have more than one match. We will display a
@ -48,7 +48,7 @@ def handle_search_errors(emit_to_obj, ostring, results, global_search=False):
dbreftext = "(#%i)" % result.id dbreftext = "(#%i)" % result.id
string += "\n %i-%s%s%s" % (num+1, result.name, string += "\n %i-%s%s%s" % (num+1, result.name,
dbreftext, invtext) dbreftext, invtext)
emit_to_obj.emit_to(string) emit_to_obj.msg(string.strip())
return None return None
else: else:
return results[0] return results[0]
@ -101,4 +101,3 @@ def object_multimatch_parser(ostring):
return (None, ostring) return (None, ostring)
except IndexError: except IndexError:
return (None, ostring) return (None, ostring)

View file

@ -147,13 +147,6 @@ from game import settings as settings_module
from django.core.management import setup_environ from django.core.management import setup_environ
from traceback import format_exc from traceback import format_exc
# colours
WHITE = r"%cn%ch%cw"
RED = r"%cn%ch%cr"
GREEN = r"%cn%ci%cg"
YELLOW = r"%cn%ch%cy"
NORM = r"%cn"
#------------------------------------------------------------ #------------------------------------------------------------
# #
@ -161,90 +154,96 @@ NORM = r"%cn"
# #
#------------------------------------------------------------ #------------------------------------------------------------
def read_batchcommand_file(pythonpath): class BatchCommandProcessor(object):
""" """
This reads the contents of a batch-command file. This class implements a batch-command processor.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
"""
if pythonpath and not (pythonpath.startswith('src.') or
pythonpath.startswith('game.')):
pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH,
pythonpath)
abspath = utils.pypath_to_realpath(pythonpath, 'ev')
try:
fobj = open(abspath)
except IOError:
logger.log_errmsg("Could not open path '%s'." % pythonpath)
return None
lines = fobj.readlines()
fobj.close()
return lines
def parse_batchcommand_file(pythonpath):
"""
This parses the lines of a batchfile according to the following
rules:
1) # at the beginning of a line marks the end of the command before it.
It is also a comment and any number of # can exist on subsequent
lines (but not inside comments).
2) Commands are placed alone at the beginning of a line and their
arguments are considered to be everything following (on any
number of lines) until the next comment line beginning with #.
3) Newlines are ignored in command definitions
4) A completely empty line in a command line definition is condered
a newline (so two empty lines is a paragraph).
5) Excess spaces and indents inside arguments are stripped.
""" """
#helper function def read_file(self, pythonpath):
def identify_line(line):
""" """
Identifies the line type (comment, commanddef or empty) This reads the contents of a batch-command file.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
""" """
if pythonpath and not (pythonpath.startswith('src.') or
pythonpath.startswith('game.')):
pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH,
pythonpath)
abspath = utils.pypath_to_realpath(pythonpath, 'ev')
try: try:
if line.strip()[0] == '#': fobj = open(abspath)
return "comment" except IOError:
else: logger.log_errmsg("Could not open path '%s'." % abspath)
return "commanddef" return None
except IndexError: lines = fobj.readlines()
return "empty" fobj.close()
return lines
#read the indata, if possible. def parse_file(self, pythonpath):
lines = read_batchcommand_file(pythonpath) """
if not lines: This parses the lines of a batchfile according to the following
return None rules:
1) # at the beginning of a line marks the end of the command before it.
It is also a comment and any number of # can exist on subsequent
lines (but not inside comments).
2) Commands are placed alone at the beginning of a line and their
arguments are considered to be everything following (on any
number of lines) until the next comment line beginning with #.
3) Newlines are ignored in command definitions
4) A completely empty line in a command line definition is condered
a newline (so two empty lines is a paragraph).
5) Excess spaces and indents inside arguments are stripped.
commands = [] """
curr_cmd = ""
#purge all superfluous whitespace and newlines from lines #helper function
reg1 = re.compile(r"\s+") def identify_line(line):
lines = [reg1.sub(" ", l) for l in lines] """
Identifies the line type (comment, commanddef or empty)
"""
try:
if line.strip()[0] == '#':
return "comment"
else:
return "commanddef"
except IndexError:
return "empty"
#parse all command definitions into a list. #read the indata, if possible.
for line in lines: lines = self.read_file(pythonpath)
typ = identify_line(line) if not lines:
if typ == "commanddef": return None
curr_cmd += line
elif typ == "empty" and curr_cmd:
curr_cmd += "\r\n"
else: #comment
if curr_cmd:
commands.append(curr_cmd.strip())
curr_cmd = ""
if curr_cmd:
commands.append(curr_cmd.strip())
#second round to clean up now merged line edges etc. commands = []
reg2 = re.compile(r"[ \t\f\v]+") curr_cmd = ""
commands = [reg2.sub(" ", c) for c in commands]
#remove eventual newline at the end of commands #purge all superfluous whitespace and newlines from lines
commands = [c.strip('\r\n') for c in commands] reg1 = re.compile(r"\s+")
return commands lines = [reg1.sub(" ", l) for l in lines]
#parse all command definitions into a list.
for line in lines:
typ = identify_line(line)
if typ == "commanddef":
curr_cmd += line
elif typ == "empty" and curr_cmd:
curr_cmd += "\r\n"
else: #comment
if curr_cmd:
commands.append(curr_cmd.strip())
curr_cmd = ""
if curr_cmd:
commands.append(curr_cmd.strip())
#second round to clean up now merged line edges etc.
reg2 = re.compile(r"[ \t\f\v]+")
commands = [reg2.sub(" ", c) for c in commands]
#remove eventual newline at the end of commands
commands = [c.strip('\r\n') for c in commands]
return commands
#------------------------------------------------------------ #------------------------------------------------------------
# #
@ -252,148 +251,170 @@ def parse_batchcommand_file(pythonpath):
# #
#------------------------------------------------------------ #------------------------------------------------------------
def read_batchcode_file(pythonpath):
class BatchCodeProcessor(object):
""" """
This reads the contents of batchfile. This implements a batch-code processor
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
"""
if pythonpath and not (pythonpath.startswith('src.') or
pythonpath.startswith('game.')):
pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH,
pythonpath)
abspath = utils.pypath_to_realpath(pythonpath)
try:
fobj = open(abspath)
except IOError:
logger.log_errmsg("Could not open path '%s'." % pythonpath)
return None
lines = fobj.readlines()
fobj.close()
return lines
def parse_batchcode_file(pythonpath):
"""
This parses the lines of a batchfile according to the following
rules:
1) Lines starting with #HEADER starts a header block (ends other blocks)
2) Lines starting with #CODE begins a code block (ends other blocks)
3) All lines outside blocks are stripped.
4) All excess whitespace beginning/ending a block is stripped.
""" """
# helper function def read_file(self, pythonpath):
def parse_line(line):
""" """
Identifies the line type: block command, comment, empty or normal code. This reads the contents of batchfile.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
"""
if pythonpath and not (pythonpath.startswith('src.') or
pythonpath.startswith('game.')):
pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH,
pythonpath)
abspath = utils.pypath_to_realpath(pythonpath, 'py')
try:
fobj = open(abspath)
except IOError:
logger.log_errmsg("Could not open path '%s'." % abspath)
return None
lines = fobj.readlines()
fobj.close()
return lines
def parse_file(self, pythonpath):
"""
This parses the lines of a batchfile according to the following
rules:
1) Lines starting with #HEADER starts a header block (ends other blocks)
2) Lines starting with #CODE begins a code block (ends other blocks)
3) #CODE headers may be of the following form: #CODE (info) objname, objname2, ...
3) All lines outside blocks are stripped.
4) All excess whitespace beginning/ending a block is stripped.
""" """
line = line.strip()
if line.startswith("#HEADER"): # helper function
return "header", "" def parse_line(line):
elif line.startswith("#CODE"): """
# parse code command Identifies the line type: block command, comment, empty or normal code.
line = line.lstrip("#CODE").strip()
objs = []
if line:
objs = [obj.strip() for obj in line.split(',')]
return "code", objs
elif line.startswith("#"):
return "comment", ""
else:
#normal line - return it with a line break.
return None, "\n%s" % line
# read indata """
line = line.strip()
lines = read_batchcode_file(pythonpath) if line.startswith("#HEADER"):
if not lines: return ("header", "", "")
return None elif line.startswith("#CODE"):
# parse code command
# parse file into blocks line = line.lstrip("#CODE").strip()
objs = []
header = "" info = ""
codes = [] if line and '(' in line and ')' in line:
# a code description
in_header = False lp = line.find('(')
in_code = False rp = line.find(')')
info = line[lp:rp+1]
for line in lines: line = line[rp+1:]
# parse line if line:
mode, line = parse_line(line) objs = [obj.strip() for obj in line.split(',')]
# try: return ("codeheader", info, objs)
# print "::", in_header, in_code, mode, line.strip() elif line.startswith('#'):
# except: return ('comment', "", "\n%s" % line)
# print "::", in_header, in_code, mode, line
if mode == 'comment':
continue
elif mode == 'header':
in_header = True
in_code = False
elif mode == 'code':
in_header = False
in_code = True
# the line is a list of object variable names
# (or an empty list) at this point.
codedict = {'objs':line,
'code':""}
codes.append(codedict)
else:
# another type of line (empty or code)
if in_header:
header += line
elif in_code:
codes[-1]['code'] += line
else: else:
# not in a block (e.g. first in file). Ignore. #normal line - return it with a line break.
return ('line', "", "\n%s" % line)
# read indata
lines = self.read_file(pythonpath)
if not lines:
return None
# parse file into blocks
header = ""
codes = []
in_header = False
in_code = False
for line in lines:
# parse line
mode, info, line = parse_line(line)
# try:
# print "::", in_header, in_code, mode, line.strip()
# except:
# print "::", in_header, in_code, mode, line
if mode == 'header':
in_header = True
in_code = False
elif mode == 'codeheader':
in_header = False
in_code = True
# the line is a list of object variable names
# (or an empty list) at this point.
codedict = {'objs':line,
'info':info,
'code':""}
codes.append(codedict)
elif mode == 'comment' and in_header:
continue continue
# last, we merge the headers with all codes. else:
for codedict in codes: # another type of line (empty, comment or code)
codedict["firstline"] = codedict["code"].strip()[:min(35, len(codedict['code'].strip())-1)] if line and in_header:
codedict["code"] = "%s\n%s" % (header, codedict["code"]) header += line
return codes elif line and in_code:
codes[-1]['code'] += line
else:
# not in a block (e.g. first in file). Ignore.
continue
def batch_code_exec(codedict, extra_environ=None, debug=False): # last, we merge the headers with all codes.
""" for codedict in codes:
Execute a single code block, including imports and appending global vars codedict["code"] = "#CODE %s %s\n%s\n\n%s" % (codedict['info'],
", ".join(codedict["objs"]),
header.strip(),
codedict["code"].strip())
return codes
extra_environ - dict with environment variables def code_exec(self, codedict, extra_environ=None, debug=False):
""" """
Execute a single code block, including imports and appending global vars
# define the execution environment extra_environ - dict with environment variables
environ = "setup_environ(settings_module)" """
environdict = {"setup_environ":setup_environ,
"settings_module":settings_module}
if extra_environ:
for key, value in extra_environ.items():
environdict[key] = value
# merge all into one block # define the execution environment
code = "%s\n%s" % (environ, codedict['code']) environ = "setup_environ(settings_module)"
if debug: environdict = {"setup_environ":setup_environ,
# try to delete marked objects "settings_module":settings_module}
for obj in codedict['objs']: if extra_environ:
code += "\ntry: %s.delete()\nexcept: pass" % obj for key, value in extra_environ.items():
environdict[key] = value
# execute the block # merge all into one block
try: code = "%s\n%s" % (environ, codedict['code'])
exec(code, environdict)
except Exception:
errlist = format_exc().split('\n')
if len(errlist) > 4:
errlist = errlist[4:]
err = "\n".join("<<< %s" % line for line in errlist if line)
if debug: if debug:
# try to delete objects again. # try to delete marked objects
try: for obj in codedict['objs']:
for obj in codedict['objs']: code += "\ntry: %s.delete()\nexcept: pass" % obj
eval("%s.delete()" % obj, environdict)
except Exception: # execute the block
pass try:
return err exec(code, environdict)
return None except Exception:
errlist = format_exc().split('\n')
if len(errlist) > 4:
errlist = errlist[4:]
err = "\n".join(" %s" % line for line in errlist if line)
if debug:
# try to delete objects again.
try:
for obj in codedict['objs']:
eval("%s.delete()" % obj, environdict)
except Exception:
pass
return err
return None
BATCHCMD = BatchCommandProcessor()
BATCHCODE = BatchCodeProcessor()