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 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 src.commands.cmdset import CmdSet
HEADER_WIDTH = 70
#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
#------------------------------------------------------------
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):
"""
Helper function for executing a single batch-command entry
@ -47,19 +70,14 @@ def batch_cmd_exec(caller):
ptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack
command = stack[ptr]
cmdname = command[:command.find(" ")]
caller.msg("%s %02i/%02i: %s %s%s" % (CGREEN, ptr+1,
len(stack),
cmdname,
CGREEN, " "*(50-len(cmdname))))
caller.msg(format_header(caller, command))
try:
caller.execute_cmd(command)
except Exception:
caller.msg(format_exc())
caller.msg(format_code(format_exc()))
return False
return True
def batch_code_exec(caller):
"""
Helper function for executing a single batch-code entry
@ -67,20 +85,16 @@ def batch_code_exec(caller):
ptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack
debug = caller.ndb.batch_debug
codedict = stack[ptr]
caller.msg("%s %02i/%02i: %s %s%s" % (CGREEN, ptr + 1,
len(stack),
codedict["firstline"],
CGREEN, " "*(50-len(codedict["firstline"]))))
err = batchprocessors.batch_code_exec(codedict,
extra_environ={"caller":caller}, debug=debug)
caller.msg(format_header(caller, codedict['code']))
err = BATCHCODE.code_exec(codedict,
extra_environ={"caller":caller}, debug=debug)
if err:
caller.msg(err)
caller.msg(format_code(err))
return False
return True
def step_pointer(caller, step=1):
"""
Step in stack, returning the item located.
@ -93,13 +107,15 @@ def step_pointer(caller, step=1):
stack = caller.ndb.batch_stack
nstack = len(stack)
if ptr + step <= 0:
caller.msg("Beginning of batch file.")
caller.msg("{RBeginning of batch file.")
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))
def show_curr(caller, showall=False):
"Show the current position in stack."
"""
Show the current position in stack
"""
stackptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack
@ -107,25 +123,39 @@ def show_curr(caller, showall=False):
caller.ndb.batch_stackptr = len(stack) - 1
show_curr(caller, showall)
return
entry = stack[stackptr]
if type(entry) == dict:
# we first try the batch-code syntax
firstline = entry['code'][:min(35, len(entry['code'])-1)]
codeall = entry['code']
# this is a batch-code entry
string = format_header(caller, entry['code'])
codeall = entry['code'].strip()
else:
# we try the batch-cmd syntax instead
firstline = entry[:min(35, len(entry)-1)]
codeall = entry
string = "%s %02i/%02i: %s %s %s %s%s" % (CGREEN,
stackptr+1, len(stack),
firstline, CGREEN,
"(hh for help)",
" "*(35-len(firstline.strip())),
CNORM)
# this is a batch-cmd entry
string = format_header(caller, entry)
codeall = entry.strip()
string += "{G(hh for help)"
if showall:
string += "\n%s" % codeall
for line in codeall.split('\n'):
string += "\n{n>>> %s" % line
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
@ -164,7 +194,7 @@ class CmdBatchCommands(MuxCommand):
#parse indata file
commands = batchprocessors.parse_batchcommand_file(python_path)
commands = BATCHCMD.parse_file(python_path)
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)."
@ -202,7 +232,8 @@ class CmdBatchCommands(MuxCommand):
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
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):
"""
@ -240,7 +271,7 @@ class CmdBatchCode(MuxCommand):
python_path = self.args
#parse indata file
codes = batchprocessors.parse_batchcode_file(python_path)
codes = BATCHCODE.parse_file(python_path)
if not codes:
string = "'%s' not found.\nYou have to supply the python path "
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_batchmode
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
@ -294,23 +326,17 @@ class CmdStateAbort(MuxCommand):
"""
@abort
Exits back the default cmdset, regardless of what state
we are currently in.
This is a safety feature. It force-ejects us out of the processor and to
the default cmdset, regardless of what current cmdset the processor might
have put us in (e.g. when testing buggy scripts etc).
"""
key = "@abort"
help_category = "BatchProcess"
def func(self):
"Exit back to default."
caller = self.caller
del caller.ndb.batch_stack
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.")
purge_processor(self.caller)
self.caller.msg("Exited processor and reset out active cmdset back to the default one.")
class CmdStateLL(MuxCommand):
"""
@ -358,10 +384,10 @@ class CmdStateRR(MuxCommand):
def func(self):
caller = self.caller
if caller.ndb.batch_batchmode == "batch_code":
batchprocessors.read_batchcommand_file(caller.ndb.batch_pythonpath)
BATCHCODE.read_file(caller.ndb.batch_pythonpath)
else:
batchprocessors.read_batchcode_file(caller.ndb.batch_pythonpath)
caller.msg("\nFile reloaded. Staying on same command.\n")
BATHCMD.read_file(caller.ndb.batch_pythonpath)
caller.msg(format_code("File reloaded. Staying on same command."))
show_curr(caller)
class CmdStateRRR(MuxCommand):
@ -377,11 +403,11 @@ class CmdStateRRR(MuxCommand):
def func(self):
caller = self.caller
if caller.ndb.batch_batchmode == "batch_code":
batchprocessors.read_batchcommand_file(caller.ndb.batch_pythonpath)
BATCHCODE.read_file(caller.ndb.batch_pythonpath)
else:
batchprocessors.read_batchcode_file(caller.ndb.batch_pythonpath)
BATCHCMD.read_file(caller.ndb.batch_pythonpath)
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)
class CmdStateNN(MuxCommand):
@ -545,7 +571,7 @@ class CmdStateCC(MuxCommand):
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
caller.msg("Finished processing batch file.")
caller.msg(format_code("Finished processing batch file."))
class CmdStateJJ(MuxCommand):
"""
@ -562,7 +588,7 @@ class CmdStateJJ(MuxCommand):
if arg and arg.isdigit():
number = int(self.args)-1
else:
caller.msg("You must give a number index.")
caller.msg(format_code("You must give a number index."))
return
ptr = caller.ndb.batch_stackptr
step = number - ptr
@ -584,7 +610,7 @@ class CmdStateJL(MuxCommand):
if arg and arg.isdigit():
number = int(self.args)-1
else:
caller.msg("You must give a number index.")
caller.msg(format_code("You must give a number index."))
return
ptr = caller.ndb.batch_stackptr
step = number - ptr
@ -601,26 +627,19 @@ class CmdStateQQ(MuxCommand):
help_category = "BatchProcess"
def func(self):
caller = self.caller
del caller.ndb.batch_stack
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.")
purge_processor(self.caller)
self.caller.msg("Aborted interactive batch mode.")
class CmdStateHH(MuxCommand):
"Help command"
key = "help"
aliases = "hh"
key = "hh"
help_category = "BatchProcess"
def func(self):
string = """
Interactive batch processing commands:
nn [steps] - next command (no processing)
nl [steps] - next & look
bb [steps] - back to previous command (no processing)
@ -637,6 +656,11 @@ class CmdStateHH(MuxCommand):
cc - continue processing to end, then quit.
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)

View file

@ -603,13 +603,13 @@ class CmdCreate(ObjManipCommand):
obj = create.create_object(typeclass, name, caller,
home=caller, aliases=aliases)
if not obj:
string += "\nError when creating object."
string = "Error when creating object."
continue
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))
else:
string += "\nYou create a new %s: %s."
string = "You create a new %s: %s."
string = string % (obj.typeclass, obj.name)
if 'drop' in self.switches:
if caller.location:
@ -1189,21 +1189,22 @@ class CmdDestroy(MuxCommand):
for objname in self.lhslist:
obj = caller.search(objname)
if not obj:
continue
continue
objname = obj.name
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
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
# do the deletion
okay = obj.delete()
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:
string += "\n\r%s was deleted." % objname
caller.msg(string.strip('\n'))
string = "%s was deleted." % objname
if string:
caller.msg(string.strip())
#NOT VALID IN NEW SYSTEM!

View file

@ -17,37 +17,43 @@
# automatically be made available for each block. Observe
# that changes to these variables made in one block is not
# 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
# 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
# debugging the script). E.g., if the code contains the command
# myobj = create.create_object(...), you could put 'myobj' in the #CODE header
# 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
#
#
#HEADER
# everything in this block will be imported to all CODE blocks when
# they are executed.
# everything in this block will be appended to the beginning of
# all other #CODE blocks when they are executed.
from src.utils import create, search
from game.gamesrc.typeclasses.examples import red_button
from game.gamesrc.typeclasses import basetypes
from game.gamesrc.objects.examples import red_button
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
# 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
red_button = create.create_object(red_button.RedButton, key="Red 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
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
# objects. This means that when the batchcode processor runs in
# 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)
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."
caller.msg(string % (table, chair))

View file

@ -504,6 +504,7 @@ class ObjectDB(TypedObject):
"""
# This is an important function that must always work.
# we use a different __getattribute__ to avoid recursive loops.
if from_obj:
try:
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)
"""
if not results:
emit_to_obj.emit_to("Could not find '%s'." % ostring)
emit_to_obj.msg("Could not find '%s'." % ostring)
return None
if len(results) > 1:
# we have more than one match. We will display a
@ -47,8 +47,8 @@ def handle_search_errors(emit_to_obj, ostring, results, global_search=False):
if show_dbref:
dbreftext = "(#%i)" % result.id
string += "\n %i-%s%s%s" % (num+1, result.name,
dbreftext, invtext)
emit_to_obj.emit_to(string)
dbreftext, invtext)
emit_to_obj.msg(string.strip())
return None
else:
return results[0]
@ -101,4 +101,3 @@ def object_multimatch_parser(ostring):
return (None, ostring)
except IndexError:
return (None, ostring)

View file

@ -147,13 +147,6 @@ from game import settings as settings_module
from django.core.management import setup_environ
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.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
This class implements a batch-command processor.
"""
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 identify_line(line):
"""
Identifies the line type (comment, commanddef or empty)
def read_file(self, pythonpath):
"""
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:
if line.strip()[0] == '#':
return "comment"
else:
return "commanddef"
except IndexError:
return "empty"
fobj = open(abspath)
except IOError:
logger.log_errmsg("Could not open path '%s'." % abspath)
return None
lines = fobj.readlines()
fobj.close()
return lines
#read the indata, if possible.
lines = read_batchcommand_file(pythonpath)
if not lines:
return None
def parse_file(self, 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.
commands = []
curr_cmd = ""
"""
#purge all superfluous whitespace and newlines from lines
reg1 = re.compile(r"\s+")
lines = [reg1.sub(" ", l) for l in lines]
#helper function
def identify_line(line):
"""
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.
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())
#read the indata, if possible.
lines = self.read_file(pythonpath)
if not lines:
return None
#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]
commands = []
curr_cmd = ""
#remove eventual newline at the end of commands
commands = [c.strip('\r\n') for c in commands]
return commands
#purge all superfluous whitespace and newlines from lines
reg1 = re.compile(r"\s+")
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.
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.
This implements a batch-code processor
"""
# helper function
def parse_line(line):
def read_file(self, pythonpath):
"""
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
"""
line = line.strip()
if line.startswith("#HEADER"):
return "header", ""
elif line.startswith("#CODE"):
# parse code command
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
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
lines = read_batchcode_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, line = parse_line(line)
# try:
# print "::", in_header, in_code, mode, line.strip()
# except:
# 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
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.
"""
# helper function
def parse_line(line):
"""
Identifies the line type: block command, comment, empty or normal code.
"""
line = line.strip()
if line.startswith("#HEADER"):
return ("header", "", "")
elif line.startswith("#CODE"):
# parse code command
line = line.lstrip("#CODE").strip()
objs = []
info = ""
if line and '(' in line and ')' in line:
# a code description
lp = line.find('(')
rp = line.find(')')
info = line[lp:rp+1]
line = line[rp+1:]
if line:
objs = [obj.strip() for obj in line.split(',')]
return ("codeheader", info, objs)
elif line.startswith('#'):
return ('comment', "", "\n%s" % line)
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
# last, we merge the headers with all codes.
for codedict in codes:
codedict["firstline"] = codedict["code"].strip()[:min(35, len(codedict['code'].strip())-1)]
codedict["code"] = "%s\n%s" % (header, codedict["code"])
return codes
else:
# another type of line (empty, comment or code)
if line and in_header:
header += line
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):
"""
Execute a single code block, including imports and appending global vars
# last, we merge the headers with all codes.
for codedict in codes:
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
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
extra_environ - dict with environment variables
"""
# merge all into one block
code = "%s\n%s" % (environ, codedict['code'])
if debug:
# try to delete marked objects
for obj in codedict['objs']:
code += "\ntry: %s.delete()\nexcept: pass" % obj
# execute the block
try:
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)
# define the execution environment
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
code = "%s\n%s" % (environ, codedict['code'])
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
# try to delete marked objects
for obj in codedict['objs']:
code += "\ntry: %s.delete()\nexcept: pass" % obj
# execute the block
try:
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:
# 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()