mirror of
https://github.com/evennia/evennia.git
synced 2026-03-19 14:26:30 +01:00
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:
parent
e114c33d8a
commit
4d8fc05157
6 changed files with 373 additions and 316 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue