Refactored batch processors, addressing points in #489.
This commit is contained in:
parent
792b3c9282
commit
0dc62a5fc9
4 changed files with 111 additions and 206 deletions
|
|
@ -52,34 +52,34 @@ from ev import Object
|
||||||
limbo = search_object('Limbo')[0]
|
limbo = search_object('Limbo')[0]
|
||||||
|
|
||||||
|
|
||||||
#CODE (create red button)
|
##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. Note how we make use if imports and
|
## code works as normal. Note how we make use if imports and
|
||||||
# 'limbo' defined in the #HEADER block. This block's header
|
## 'limbo' defined in the #HEADER block. This block's header
|
||||||
# offers no information about red_button variable, so it
|
## offers no information about red_button variable, so it
|
||||||
# won't be able to be deleted in debug mode.
|
## won't be able to be deleted in debug mode.
|
||||||
|
#
|
||||||
# create a red button in limbo
|
## create a red button in limbo
|
||||||
red_button = create_object(red_button.RedButton, key="Red button",
|
#red_button = create_object(red_button.RedButton, key="Red button",
|
||||||
location=limbo, aliases=["button"])
|
# location=limbo, aliases=["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 (create table and chair) 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 many
|
## again (so as to avoid duplicate objects when testing the script many
|
||||||
# times).
|
## times).
|
||||||
|
#
|
||||||
# the python variables we assign to must match the ones given in the
|
## 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
|
## header for the system to be able to delete them afterwards during a
|
||||||
# debugging run.
|
## debugging run.
|
||||||
table = create_object(Object, key="Table", location=limbo)
|
#table = create_object(Object, key="Table", location=limbo)
|
||||||
chair = create_object(Object, key="Chair", location=limbo)
|
#chair = create_object(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))
|
||||||
|
|
|
||||||
|
|
@ -134,10 +134,10 @@ 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]
|
code = stack[ptr]
|
||||||
|
|
||||||
caller.msg(format_header(caller, codedict['code']))
|
caller.msg(format_header(caller, code))
|
||||||
err = BATCHCODE.code_exec(codedict,
|
err = BATCHCODE.code_exec(code,
|
||||||
extra_environ={"caller": caller}, debug=debug)
|
extra_environ={"caller": caller}, debug=debug)
|
||||||
if err:
|
if err:
|
||||||
caller.msg(format_code(err))
|
caller.msg(format_code(err))
|
||||||
|
|
@ -177,14 +177,8 @@ def show_curr(caller, showall=False):
|
||||||
|
|
||||||
entry = stack[stackptr]
|
entry = stack[stackptr]
|
||||||
|
|
||||||
if type(entry) == dict:
|
string = format_header(caller, entry)
|
||||||
# this is a batch-code entry
|
codeall = entry.strip()
|
||||||
string = format_header(caller, entry['code'])
|
|
||||||
codeall = entry['code'].strip()
|
|
||||||
else:
|
|
||||||
# this is a batch-cmd entry
|
|
||||||
string = format_header(caller, entry)
|
|
||||||
codeall = entry.strip()
|
|
||||||
string += "{G(hh for help)"
|
string += "{G(hh for help)"
|
||||||
if showall:
|
if showall:
|
||||||
for line in codeall.split('\n'):
|
for line in codeall.split('\n'):
|
||||||
|
|
@ -349,10 +343,11 @@ class CmdBatchCode(MuxCommand):
|
||||||
caller.msg("Usage: @batchcode[/interactive/debug] <path.to.file>")
|
caller.msg("Usage: @batchcode[/interactive/debug] <path.to.file>")
|
||||||
return
|
return
|
||||||
python_path = self.args
|
python_path = self.args
|
||||||
|
debug = 'debug' in self.switches
|
||||||
|
|
||||||
#parse indata file
|
#parse indata file
|
||||||
try:
|
try:
|
||||||
codes = BATCHCODE.parse_file(python_path)
|
codes = BATCHCODE.parse_file(python_path, debug=debug)
|
||||||
except UnicodeDecodeError, err:
|
except UnicodeDecodeError, err:
|
||||||
lnum = err.linenum
|
lnum = err.linenum
|
||||||
caller.msg(_UTF8_ERROR % (python_path, lnum))
|
caller.msg(_UTF8_ERROR % (python_path, lnum))
|
||||||
|
|
@ -365,10 +360,6 @@ class CmdBatchCode(MuxCommand):
|
||||||
return
|
return
|
||||||
switches = self.switches
|
switches = self.switches
|
||||||
|
|
||||||
debug = False
|
|
||||||
if 'debug' in switches:
|
|
||||||
debug = True
|
|
||||||
|
|
||||||
# Store work data in cache
|
# Store work data in cache
|
||||||
caller.ndb.batch_stack = codes
|
caller.ndb.batch_stack = codes
|
||||||
caller.ndb.batch_stackptr = 0
|
caller.ndb.batch_stackptr = 0
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
the disconnect method
|
the disconnect method
|
||||||
"""
|
"""
|
||||||
self.sessionhandler.disconnect(self)
|
self.sessionhandler.disconnect(self)
|
||||||
self.transport.close()
|
self.transport.loseConnection()
|
||||||
|
|
||||||
def dataReceived(self, data):
|
def dataReceived(self, data):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -201,52 +201,31 @@ def read_batchfile(pythonpath, file_ending='.py'):
|
||||||
abspaths.append(utils.pypath_to_realpath("%s.%s" % (basepath, pythonpath), file_ending))
|
abspaths.append(utils.pypath_to_realpath("%s.%s" % (basepath, pythonpath), file_ending))
|
||||||
else:
|
else:
|
||||||
abspaths = [utils.pypath_to_realpath(pythonpath, file_ending)]
|
abspaths = [utils.pypath_to_realpath(pythonpath, file_ending)]
|
||||||
fobj, lines, err = None, [], None
|
text, fobj = None, None
|
||||||
for file_encoding in ENCODINGS:
|
fileerr, decoderr = [], []
|
||||||
# try different encodings, in order
|
for abspath in abspaths:
|
||||||
load_errors = []
|
# try different paths, until we get a match
|
||||||
for abspath in abspaths:
|
# we read the file directly into unicode.
|
||||||
# try different paths, until we get a match
|
for file_encoding in ENCODINGS:
|
||||||
|
# try different encodings, in order
|
||||||
try:
|
try:
|
||||||
# we read the file directly into unicode.
|
|
||||||
fobj = codecs.open(abspath, 'r', encoding=file_encoding)
|
fobj = codecs.open(abspath, 'r', encoding=file_encoding)
|
||||||
except IOError:
|
text = fobj.read()
|
||||||
load_errors.append("Could not open batchfile '%s'." % abspath)
|
except IOError, e:
|
||||||
|
# could not find the file
|
||||||
|
fileerr.append(str(e))
|
||||||
|
break
|
||||||
|
except (ValueError, UnicodeDecodeError), e:
|
||||||
|
# this means an encoding error; try another encoding
|
||||||
|
decoderr.append(str(e))
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
if not fobj:
|
if not fobj:
|
||||||
continue
|
raise IOError("\n".join(fileerr))
|
||||||
|
if not text:
|
||||||
|
raise UnicodeDecodeError("\n".join(decoderr))
|
||||||
|
|
||||||
load_errors = []
|
return text
|
||||||
err = None
|
|
||||||
# We have successfully found and opened the file. Now actually
|
|
||||||
# try to decode it using the given protocol.
|
|
||||||
try:
|
|
||||||
lines = fobj.readlines()
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
# give the line of failure
|
|
||||||
fobj.seek(0)
|
|
||||||
try:
|
|
||||||
lnum = 0
|
|
||||||
for lnum, line in enumerate(fobj):
|
|
||||||
pass
|
|
||||||
except UnicodeDecodeError, err:
|
|
||||||
# lnum starts from 0, so we add +1 line,
|
|
||||||
# besides the faulty line is never read
|
|
||||||
# so we add another 1 (thus +2) to get
|
|
||||||
# the actual line number seen in an editor.
|
|
||||||
err.linenum = lnum + 2
|
|
||||||
fobj.close()
|
|
||||||
# possibly try another encoding
|
|
||||||
continue
|
|
||||||
# if we get here, the encoding worked. Stop iteration.
|
|
||||||
break
|
|
||||||
if load_errors:
|
|
||||||
logger.log_errmsg("\n".join(load_errors))
|
|
||||||
if err:
|
|
||||||
return err
|
|
||||||
else:
|
|
||||||
return lines
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
@ -293,6 +272,7 @@ class BatchCommandProcessor(object):
|
||||||
#remove eventual newline at the end of commands
|
#remove eventual newline at the end of commands
|
||||||
commands = [c.strip('\r\n') for c in commands]
|
commands = [c.strip('\r\n') for c in commands]
|
||||||
commands = [c for c in commands if c]
|
commands = [c for c in commands if c]
|
||||||
|
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -319,7 +299,7 @@ class BatchCodeProcessor(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def parse_file(self, pythonpath):
|
def parse_file(self, pythonpath, debug=False):
|
||||||
"""
|
"""
|
||||||
This parses the lines of a batchfile according to the following
|
This parses the lines of a batchfile according to the following
|
||||||
rules:
|
rules:
|
||||||
|
|
@ -334,127 +314,73 @@ class BatchCodeProcessor(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# helper function
|
text = "".join(read_batchfile(pythonpath, file_ending='.py'))
|
||||||
def parse_line(line):
|
|
||||||
"""
|
|
||||||
Identifies the line type:
|
|
||||||
block command, comment, empty or normal code.
|
|
||||||
"""
|
|
||||||
parseline = line.strip()
|
|
||||||
|
|
||||||
if parseline.startswith("#HEADER"):
|
def clean_block(text):
|
||||||
return ("header", "", "")
|
text = re.sub(r"^\#.*?$|^\s*$", "", text, flags=re.MULTILINE)
|
||||||
if parseline.startswith("#INSERT"):
|
return "\n".join([line for line in text.split("\n") if line])
|
||||||
filename = line.lstrip("#INSERT").strip()
|
|
||||||
if filename:
|
|
||||||
return ('insert', "", filename)
|
|
||||||
else:
|
|
||||||
return ('comment', "", "{r#INSERT <None>{n")
|
|
||||||
elif parseline.startswith("#CODE"):
|
|
||||||
# parse code command
|
|
||||||
line = line.lstrip("#CODE").strip()
|
|
||||||
info = CODE_INFO_HEADER.findall(line) or ""
|
|
||||||
if info:
|
|
||||||
info = info[0]
|
|
||||||
line = line.replace(info, "")
|
|
||||||
objs = [o.strip() for o in line.split(",") if o.strip()]
|
|
||||||
return ("codeheader", info, objs)
|
|
||||||
elif parseline.startswith('#'):
|
|
||||||
return ('comment', "", "%s" % line)
|
|
||||||
else:
|
|
||||||
#normal line - return it with a line break.
|
|
||||||
return ('line', "", "%s" % line)
|
|
||||||
|
|
||||||
# read indata
|
def replace_insert(match):
|
||||||
|
"Map replace entries"
|
||||||
|
return "\#\n".join(self.parse_file(match.group()))
|
||||||
|
|
||||||
lines = read_batchfile(pythonpath, file_ending='.py')
|
text = re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
|
||||||
if not lines:
|
blocks = re.split(r"(^\#CODE.*?$|^\#HEADER)$", text, flags=re.MULTILINE)
|
||||||
return None
|
headers = []
|
||||||
|
codes = [] # list of tuples (code, info, objtuple)
|
||||||
|
if blocks:
|
||||||
|
if blocks[0]:
|
||||||
|
# the first block is either empty or an unmarked code block
|
||||||
|
code = clean_block(blocks.pop(0))
|
||||||
|
if code:
|
||||||
|
codes.append((code, ""))
|
||||||
|
iblock = 0
|
||||||
|
for block in blocks[::2]:
|
||||||
|
# loop over every second component; these are the #CODE/#HEADERs
|
||||||
|
if block.startswith("#HEADER"):
|
||||||
|
headers.append(clean_block(blocks[iblock + 1]))
|
||||||
|
elif block.startswith("#CODE"):
|
||||||
|
match = re.search(r"\(.*?\)", block)
|
||||||
|
info = match.group() if match else ""
|
||||||
|
objs = []
|
||||||
|
if debug:
|
||||||
|
# insert auto-delete lines into code
|
||||||
|
objs = block[match.end():].split(",")
|
||||||
|
objs = ["# added by Evennia's debug mode\n%s.delete()" % obj.strip() for obj in objs if obj]
|
||||||
|
# build the code block
|
||||||
|
code = "\n".join([clean_block(blocks[iblock + 1])] + objs)
|
||||||
|
if code:
|
||||||
|
codes.append((code, info))
|
||||||
|
iblock += 2
|
||||||
|
|
||||||
# parse file into blocks
|
# join the headers together to one header
|
||||||
|
headers = "\n".join(headers)
|
||||||
header = ""
|
if codes:
|
||||||
codes = []
|
# add the headers at the top of each non-empty block
|
||||||
|
codes = ["%s\n%s\n%s" % ("#CODE %s: " % tup[1], headers, tup[0]) for tup in codes if tup[0]]
|
||||||
in_header = False
|
else:
|
||||||
in_code = False
|
codes = ["#CODE: \n" + headers]
|
||||||
|
|
||||||
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 == 'insert':
|
|
||||||
# recursive load of inserted code files - note that we
|
|
||||||
# are not checking for cyclic imports!
|
|
||||||
in_header = False
|
|
||||||
in_code = False
|
|
||||||
inserted_codes = self.parse_file(line) or [{'objs': "", 'info': line, 'code': ""}]
|
|
||||||
for codedict in inserted_codes:
|
|
||||||
codedict["inserted"] = True
|
|
||||||
codes.extend(inserted_codes)
|
|
||||||
elif 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
|
|
||||||
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
|
|
||||||
|
|
||||||
# last, we merge the headers with all codes.
|
|
||||||
for codedict in codes:
|
|
||||||
#print "codedict:", codedict
|
|
||||||
if codedict and "inserted" in codedict:
|
|
||||||
# we don't need to merge code+header in this case
|
|
||||||
# since that was already added in the recursion. We
|
|
||||||
# just check for errors.
|
|
||||||
if not codedict['code']:
|
|
||||||
codedict['code'] = "{r#INSERT ERROR: %s{n" % codedict['info']
|
|
||||||
else:
|
|
||||||
objs = ", ".join(codedict["objs"])
|
|
||||||
if objs:
|
|
||||||
objs = "[%s]" % objs
|
|
||||||
codedict["code"] = "#CODE %s %s \n%s\n\n%s" % (codedict['info'],
|
|
||||||
objs,
|
|
||||||
header.strip(),
|
|
||||||
codedict["code"].strip())
|
|
||||||
return codes
|
return codes
|
||||||
|
|
||||||
def code_exec(self, codedict, extra_environ=None, debug=False):
|
|
||||||
|
def code_exec(self, code, 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
|
||||||
|
|
||||||
extra_environ - dict with environment variables
|
extra_environ - dict with environment variables
|
||||||
"""
|
"""
|
||||||
# define the execution environment
|
# define the execution environment
|
||||||
environ = "settings_module.configure()"
|
|
||||||
environdict = {"settings_module": settings}
|
environdict = {"settings_module": settings}
|
||||||
|
environ = "settings_module.configure()"
|
||||||
if extra_environ:
|
if extra_environ:
|
||||||
for key, value in extra_environ.items():
|
for key, value in extra_environ.items():
|
||||||
environdict[key] = value
|
environdict[key] = value
|
||||||
|
|
||||||
# merge all into one block
|
# initializing the django settings at the top of code
|
||||||
code = "# auto-added by Evennia\ntry:%s\nexcept RuntimeError:pass\nfinally:del settings_module\n%s" % (environ, codedict['code'])
|
code = "# auto-added by Evennia\n" \
|
||||||
if debug:
|
"try: %s\n" \
|
||||||
# try to delete marked objects
|
"except RuntimeError: pass\n" \
|
||||||
for obj in codedict['objs']:
|
"finally: del settings_module\n%s" % (environ, code)
|
||||||
code += "\ntry: %s.delete()\nexcept: pass" % obj
|
|
||||||
|
|
||||||
# execute the block
|
# execute the block
|
||||||
try:
|
try:
|
||||||
|
|
@ -475,18 +401,6 @@ class BatchCodeProcessor(object):
|
||||||
err += "\n%02i: %s" % (iline + 1, line)
|
err += "\n%02i: %s" % (iline + 1, line)
|
||||||
|
|
||||||
err += "\n".join(traceback.format_exception(etype, value, tb))
|
err += "\n".join(traceback.format_exception(etype, value, tb))
|
||||||
#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 err
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue