From 8b99b8a13080ded074e26950da5c6c107917ae50 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 21 Mar 2012 01:53:33 +0100 Subject: [PATCH] Added #INSERT parameter for batch-code processor. This should resolve the issue of wanting to combine several batch files. Also improved traceback feedback from the batch-code processor. --- game/gamesrc/world/examples/batch_code.py | 5 +- src/utils/batchprocessors.py | 83 +++++++++++++++++------ 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/game/gamesrc/world/examples/batch_code.py b/game/gamesrc/world/examples/batch_code.py index 79c65248e6..09ef65d8bf 100644 --- a/game/gamesrc/world/examples/batch_code.py +++ b/game/gamesrc/world/examples/batch_code.py @@ -27,6 +27,10 @@ # 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. +# #INSERT filename - this includes another code batch file. The named file will be loaded and +# run at this point. Note that code from the inserted file will NOT share #HEADERs +# with the importing file, but will only use the headers in the importing file. +# make sure to not create a cyclic import here! # The following variable is automatically made available for the script: @@ -61,7 +65,6 @@ 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 (create table and chair) table, chair # this code block has 'table' and 'chair' set as deletable diff --git a/src/utils/batchprocessors.py b/src/utils/batchprocessors.py index 88388eeebb..15f33a707d 100644 --- a/src/utils/batchprocessors.py +++ b/src/utils/batchprocessors.py @@ -139,6 +139,7 @@ script = create.create_script() import re import codecs +import traceback, sys from traceback import format_exc from django.conf import settings from django.core.management import setup_environ @@ -302,6 +303,13 @@ class BatchCommandProcessor(object): # #------------------------------------------------------------ +def tb_filename(tb): + "Helper to get filename from traceback" + return tb.tb_frame.f_code.co_filename +def tb_iter(tb): + while tb is not None: + yield tb + tb = tb.tb_next class BatchCodeProcessor(object): """ @@ -316,7 +324,8 @@ class BatchCodeProcessor(object): 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) #CODE headers may be of the following form: #CODE (info) objname, objname2, ... + 4) Lines starting with #INSERT are on form #INSERT filename. 3) All lines outside blocks are stripped. 4) All excess whitespace beginning/ending a block is stripped. @@ -332,6 +341,12 @@ class BatchCodeProcessor(object): if parseline.startswith("#HEADER"): return ("header", "", "") + if parseline.startswith("#INSERT"): + filename = line.lstrip("#INSERT").strip() + if filename: + return ('insert', "", filename) + else: + return ('comment', "", "{r#INSERT {n") elif parseline.startswith("#CODE"): # parse code command line = line.lstrip("#CODE").strip() @@ -368,7 +383,16 @@ class BatchCodeProcessor(object): # print "::", in_header, in_code, mode, line.strip() # except: # print "::", in_header, in_code, mode, line - if mode == 'header': + 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': @@ -376,9 +400,7 @@ class BatchCodeProcessor(object): 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':""} + codedict = {'objs':line, 'info':info, 'code':""} codes.append(codedict) elif mode == 'comment' and in_header: continue @@ -394,13 +416,21 @@ class BatchCodeProcessor(object): # last, we merge the headers with all codes. for codedict in codes: - 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()) + #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 def code_exec(self, codedict, extra_environ=None, debug=False): @@ -409,7 +439,6 @@ class BatchCodeProcessor(object): extra_environ - dict with environment variables """ - # define the execution environment environ = "setup_environ(settings_module)" environdict = {"setup_environ":setup_environ, @@ -419,7 +448,7 @@ class BatchCodeProcessor(object): environdict[key] = value # merge all into one block - code = "%s\n%s" % (environ, codedict['code']) + code = "%s # auto-added by Evennia\n%s" % (environ, codedict['code']) if debug: # try to delete marked objects for obj in codedict['objs']: @@ -428,11 +457,27 @@ class BatchCodeProcessor(object): # execute the block try: exec(code, environdict) - except Exception, e: - errlist = format_exc().split('\n') - if len(errlist) > 4: - errlist = errlist[4:] - err = "\n".join(" %s" % line for line in errlist if line) + except Exception: + etype, value, tb = sys.exc_info() + + fname = tb_filename(tb) + for tb in tb_iter(tb): + if fname != tb_filename(tb): + break + lineno = tb.tb_lineno - 1 + err = "" + for iline, line in enumerate(code.split("\n")): + if iline == lineno: + err += "\n{w%02i{n: %s" % (iline + 1, line) + elif lineno - 5 < iline < lineno + 5: + err += "\n%02i: %s" % (iline + 1, line) + + 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: