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"]))))
err = batchprocessors.batch_code_exec(codedict,
extra_environ={"caller":caller}, debug=debug) 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,7 +154,13 @@ NORM = r"%cn"
# #
#------------------------------------------------------------ #------------------------------------------------------------
def read_batchcommand_file(pythonpath): class BatchCommandProcessor(object):
"""
This class implements a batch-command processor.
"""
def read_file(self, pythonpath):
""" """
This reads the contents of a batch-command file. This reads the contents of a batch-command file.
Filename is considered to be the name of the batch file Filename is considered to be the name of the batch file
@ -176,13 +175,13 @@ def read_batchcommand_file(pythonpath):
try: try:
fobj = open(abspath) fobj = open(abspath)
except IOError: except IOError:
logger.log_errmsg("Could not open path '%s'." % pythonpath) logger.log_errmsg("Could not open path '%s'." % abspath)
return None return None
lines = fobj.readlines() lines = fobj.readlines()
fobj.close() fobj.close()
return lines return lines
def parse_batchcommand_file(pythonpath): def parse_file(self, pythonpath):
""" """
This parses the lines of a batchfile according to the following This parses the lines of a batchfile according to the following
rules: rules:
@ -213,7 +212,7 @@ def parse_batchcommand_file(pythonpath):
return "empty" return "empty"
#read the indata, if possible. #read the indata, if possible.
lines = read_batchcommand_file(pythonpath) lines = self.read_file(pythonpath)
if not lines: if not lines:
return None return None
@ -252,7 +251,14 @@ def parse_batchcommand_file(pythonpath):
# #
#------------------------------------------------------------ #------------------------------------------------------------
def read_batchcode_file(pythonpath):
class BatchCodeProcessor(object):
"""
This implements a batch-code processor
"""
def read_file(self, pythonpath):
""" """
This reads the contents of batchfile. This reads the contents of batchfile.
Filename is considered to be the name of the batch file Filename is considered to be the name of the batch file
@ -263,24 +269,25 @@ def read_batchcode_file(pythonpath):
pythonpath.startswith('game.')): pythonpath.startswith('game.')):
pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH, pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH,
pythonpath) pythonpath)
abspath = utils.pypath_to_realpath(pythonpath) abspath = utils.pypath_to_realpath(pythonpath, 'py')
try: try:
fobj = open(abspath) fobj = open(abspath)
except IOError: except IOError:
logger.log_errmsg("Could not open path '%s'." % pythonpath) logger.log_errmsg("Could not open path '%s'." % abspath)
return None return None
lines = fobj.readlines() lines = fobj.readlines()
fobj.close() fobj.close()
return lines return lines
def parse_batchcode_file(pythonpath): def parse_file(self, pythonpath):
""" """
This parses the lines of a batchfile according to the following This parses the lines of a batchfile according to the following
rules: rules:
1) Lines starting with #HEADER starts a header block (ends other blocks) 1) Lines starting with #HEADER starts a header block (ends other blocks)
2) Lines starting with #CODE begins a code 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. 3) All lines outside blocks are stripped.
4) All excess whitespace beginning/ending a block is stripped. 4) All excess whitespace beginning/ending a block is stripped.
@ -295,23 +302,30 @@ def parse_batchcode_file(pythonpath):
line = line.strip() line = line.strip()
if line.startswith("#HEADER"): if line.startswith("#HEADER"):
return "header", "" return ("header", "", "")
elif line.startswith("#CODE"): elif line.startswith("#CODE"):
# parse code command # parse code command
line = line.lstrip("#CODE").strip() line = line.lstrip("#CODE").strip()
objs = [] 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: if line:
objs = [obj.strip() for obj in line.split(',')] objs = [obj.strip() for obj in line.split(',')]
return "code", objs return ("codeheader", info, objs)
elif line.startswith("#"): elif line.startswith('#'):
return "comment", "" return ('comment', "", "\n%s" % line)
else: else:
#normal line - return it with a line break. #normal line - return it with a line break.
return None, "\n%s" % line return ('line', "", "\n%s" % line)
# read indata # read indata
lines = read_batchcode_file(pythonpath) lines = self.read_file(pythonpath)
if not lines: if not lines:
return None return None
@ -325,40 +339,44 @@ def parse_batchcode_file(pythonpath):
for line in lines: for line in lines:
# parse line # parse line
mode, line = parse_line(line) mode, info, line = parse_line(line)
# try: # try:
# print "::", in_header, in_code, mode, line.strip() # print "::", in_header, in_code, mode, line.strip()
# except: # except:
# print "::", in_header, in_code, mode, line # print "::", in_header, in_code, mode, line
if mode == 'comment': if mode == 'header':
continue
elif mode == 'header':
in_header = True in_header = True
in_code = False in_code = False
elif mode == 'code': elif mode == 'codeheader':
in_header = False in_header = False
in_code = True in_code = True
# the line is a list of object variable names # the line is a list of object variable names
# (or an empty list) at this point. # (or an empty list) at this point.
codedict = {'objs':line, codedict = {'objs':line,
'info':info,
'code':""} 'code':""}
codes.append(codedict) codes.append(codedict)
elif mode == 'comment' and in_header:
continue
else: else:
# another type of line (empty or code) # another type of line (empty, comment or code)
if in_header: if line and in_header:
header += line header += line
elif in_code: elif line and in_code:
codes[-1]['code'] += line codes[-1]['code'] += line
else: else:
# not in a block (e.g. first in file). Ignore. # not in a block (e.g. first in file). Ignore.
continue continue
# last, we merge the headers with all codes. # last, we merge the headers with all codes.
for codedict in codes: for codedict in codes:
codedict["firstline"] = codedict["code"].strip()[:min(35, len(codedict['code'].strip())-1)] codedict["code"] = "#CODE %s %s\n%s\n\n%s" % (codedict['info'],
codedict["code"] = "%s\n%s" % (header, codedict["code"]) ", ".join(codedict["objs"]),
header.strip(),
codedict["code"].strip())
return codes return codes
def batch_code_exec(codedict, extra_environ=None, debug=False): def code_exec(self, codedict, extra_environ=None, debug=False):
""" """
Execute a single code block, including imports and appending global vars Execute a single code block, including imports and appending global vars
@ -387,7 +405,7 @@ def batch_code_exec(codedict, extra_environ=None, debug=False):
errlist = format_exc().split('\n') errlist = format_exc().split('\n')
if len(errlist) > 4: if len(errlist) > 4:
errlist = errlist[4:] errlist = errlist[4:]
err = "\n".join("<<< %s" % line for line in errlist if line) err = "\n".join(" %s" % line for line in errlist if line)
if debug: if debug:
# try to delete objects again. # try to delete objects again.
try: try:
@ -397,3 +415,6 @@ def batch_code_exec(codedict, extra_environ=None, debug=False):
pass pass
return err return err
return None return None
BATCHCMD = BatchCommandProcessor()
BATCHCODE = BatchCodeProcessor()