summaryrefslogtreecommitdiffstats
path: root/py-bin/lib/em.py
diff options
context:
space:
mode:
Diffstat (limited to 'py-bin/lib/em.py')
-rwxr-xr-xpy-bin/lib/em.py3288
1 files changed, 3288 insertions, 0 deletions
diff --git a/py-bin/lib/em.py b/py-bin/lib/em.py
new file mode 100755
index 0000000..6079316
--- /dev/null
+++ b/py-bin/lib/em.py
@@ -0,0 +1,3288 @@
+#!/usr/local/bin/python
+#
+# $Id: //projects/empy/em.py#146 $ $Date: 2003/10/27 $
+
+"""
+A system for processing Python as markup embedded in text.
+"""
+
+
+__program__ = 'empy'
+__version__ = '3.3'
+__url__ = 'http://www.alcyone.com/software/empy/'
+__author__ = 'Erik Max Francis <max@alcyone.com>'
+__copyright__ = 'Copyright (C) 2002-2003 Erik Max Francis'
+__license__ = 'LGPL'
+
+
+import copy
+import getopt
+import os
+import re
+import string
+import sys
+import types
+
+try:
+ # The equivalent of import cStringIO as StringIO.
+ import cStringIO
+ StringIO = cStringIO
+ del cStringIO
+except ImportError:
+ import StringIO
+
+# For backward compatibility, we can't assume these are defined.
+False, True = 0, 1
+
+# Some basic defaults.
+FAILURE_CODE = 1
+DEFAULT_PREFIX = '@'
+DEFAULT_PSEUDOMODULE_NAME = 'empy'
+DEFAULT_SCRIPT_NAME = '?'
+SIGNIFICATOR_RE_SUFFIX = r"%(\S+)\s*(.*)\s*$"
+SIGNIFICATOR_RE_STRING = DEFAULT_PREFIX + SIGNIFICATOR_RE_SUFFIX
+BANGPATH = '#!'
+DEFAULT_CHUNK_SIZE = 8192
+DEFAULT_ERRORS = 'strict'
+
+# Character information.
+IDENTIFIER_FIRST_CHARS = '_abcdefghijklmnopqrstuvwxyz' \
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+IDENTIFIER_CHARS = IDENTIFIER_FIRST_CHARS + '0123456789.'
+ENDING_CHARS = {'(': ')', '[': ']', '{': '}'}
+
+# Environment variable names.
+OPTIONS_ENV = 'EMPY_OPTIONS'
+PREFIX_ENV = 'EMPY_PREFIX'
+PSEUDO_ENV = 'EMPY_PSEUDO'
+FLATTEN_ENV = 'EMPY_FLATTEN'
+RAW_ENV = 'EMPY_RAW_ERRORS'
+INTERACTIVE_ENV = 'EMPY_INTERACTIVE'
+BUFFERED_ENV = 'EMPY_BUFFERED_OUTPUT'
+NO_OVERRIDE_ENV = 'EMPY_NO_OVERRIDE'
+UNICODE_ENV = 'EMPY_UNICODE'
+INPUT_ENCODING_ENV = 'EMPY_UNICODE_INPUT_ENCODING'
+OUTPUT_ENCODING_ENV = 'EMPY_UNICODE_OUTPUT_ENCODING'
+INPUT_ERRORS_ENV = 'EMPY_UNICODE_INPUT_ERRORS'
+OUTPUT_ERRORS_ENV = 'EMPY_UNICODE_OUTPUT_ERRORS'
+
+# Interpreter options.
+BANGPATH_OPT = 'processBangpaths' # process bangpaths as comments?
+BUFFERED_OPT = 'bufferedOutput' # fully buffered output?
+RAW_OPT = 'rawErrors' # raw errors?
+EXIT_OPT = 'exitOnError' # exit on error?
+FLATTEN_OPT = 'flatten' # flatten pseudomodule namespace?
+OVERRIDE_OPT = 'override' # override sys.stdout with proxy?
+CALLBACK_OPT = 'noCallbackError' # is no custom callback an error?
+
+# Usage info.
+OPTION_INFO = [
+("-V --version", "Print version and exit"),
+("-h --help", "Print usage and exit"),
+("-H --extended-help", "Print extended usage and exit"),
+("-k --suppress-errors", "Do not exit on errors; go interactive"),
+("-p --prefix=<char>", "Change prefix to something other than @"),
+(" --no-prefix", "Do not do any markup processing at all"),
+("-m --module=<name>", "Change the internal pseudomodule name"),
+("-f --flatten", "Flatten the members of pseudmodule to start"),
+("-r --raw-errors", "Show raw Python errors"),
+("-i --interactive", "Go into interactive mode after processing"),
+("-n --no-override-stdout", "Do not override sys.stdout with proxy"),
+("-o --output=<filename>", "Specify file for output as write"),
+("-a --append=<filename>", "Specify file for output as append"),
+("-b --buffered-output", "Fully buffer output including open"),
+(" --binary", "Treat the file as a binary"),
+(" --chunk-size=<chunk>", "Use this chunk size for reading binaries"),
+("-P --preprocess=<filename>", "Interpret EmPy file before main processing"),
+("-I --import=<modules>", "Import Python modules before processing"),
+("-D --define=<definition>", "Execute Python assignment statement"),
+("-E --execute=<statement>", "Execute Python statement before processing"),
+("-F --execute-file=<filename>", "Execute Python file before processing"),
+(" --pause-at-end", "Prompt at the ending of processing"),
+(" --relative-path", "Add path of EmPy script to sys.path"),
+(" --no-callback-error", "Custom markup without callback is error"),
+(" --no-bangpath-processing", "Suppress bangpaths as comments"),
+("-u --unicode", "Enable Unicode subsystem (Python 2+ only)"),
+(" --unicode-encoding=<e>", "Set both input and output encodings"),
+(" --unicode-input-encoding=<e>", "Set input encoding"),
+(" --unicode-output-encoding=<e>", "Set output encoding"),
+(" --unicode-errors=<E>", "Set both input and output error handler"),
+(" --unicode-input-errors=<E>", "Set input error handler"),
+(" --unicode-output-errors=<E>", "Set output error handler"),
+]
+
+USAGE_NOTES = """\
+Notes: Whitespace immediately inside parentheses of @(...) are
+ignored. Whitespace immediately inside braces of @{...} are ignored,
+unless ... spans multiple lines. Use @{ ... }@ to suppress newline
+following expansion. Simple expressions ignore trailing dots; `@x.'
+means `@(x).'. A #! at the start of a file is treated as a @#
+comment."""
+
+MARKUP_INFO = [
+("@# ... NL", "Comment; remove everything up to newline"),
+("@? NAME NL", "Set the current context name"),
+("@! INTEGER NL", "Set the current context line number"),
+("@ WHITESPACE", "Remove following whitespace; line continuation"),
+("@\\ ESCAPE_CODE", "A C-style escape sequence"),
+("@@", "Literal @; @ is escaped (duplicated prefix)"),
+("@), @], @}", "Literal close parenthesis, bracket, brace"),
+("@ STRING_LITERAL", "Replace with string literal contents"),
+("@( EXPRESSION )", "Evaluate expression and substitute with str"),
+("@( TEST [? THEN [! ELSE]] )", "If test is true, evaluate then, otherwise else"),
+("@( TRY $ CATCH )", "Expand try expression, or catch if it raises"),
+("@ SIMPLE_EXPRESSION", "Evaluate simple expression and substitute;\n"
+ "e.g., @x, @x.y, @f(a, b), @l[i], etc."),
+("@` EXPRESSION `", "Evaluate expression and substitute with repr"),
+("@: EXPRESSION : [DUMMY] :", "Evaluates to @:...:expansion:"),
+("@{ STATEMENTS }", "Statements are executed for side effects"),
+("@[ CONTROL ]", "Control markups: if E; elif E; for N in E;\n"
+ "while E; try; except E, N; finally; continue;\n"
+ "break; end X"),
+("@%% KEY WHITESPACE VALUE NL", "Significator form of __KEY__ = VALUE"),
+("@< CONTENTS >", "Custom markup; meaning provided by user"),
+]
+
+ESCAPE_INFO = [
+("@\\0", "NUL, null"),
+("@\\a", "BEL, bell"),
+("@\\b", "BS, backspace"),
+("@\\dDDD", "three-digit decimal code DDD"),
+("@\\e", "ESC, escape"),
+("@\\f", "FF, form feed"),
+("@\\h", "DEL, delete"),
+("@\\n", "LF, linefeed, newline"),
+("@\\N{NAME}", "Unicode character named NAME"),
+("@\\oOOO", "three-digit octal code OOO"),
+("@\\qQQQQ", "four-digit quaternary code QQQQ"),
+("@\\r", "CR, carriage return"),
+("@\\s", "SP, space"),
+("@\\t", "HT, horizontal tab"),
+("@\\uHHHH", "16-bit hexadecimal Unicode HHHH"),
+("@\\UHHHHHHHH", "32-bit hexadecimal Unicode HHHHHHHH"),
+("@\\v", "VT, vertical tab"),
+("@\\xHH", "two-digit hexadecimal code HH"),
+("@\\z", "EOT, end of transmission"),
+]
+
+PSEUDOMODULE_INFO = [
+("VERSION", "String representing EmPy version"),
+("SIGNIFICATOR_RE_STRING", "Regular expression matching significators"),
+("SIGNIFICATOR_RE_SUFFIX", "The above stub, lacking the prefix"),
+("interpreter", "Currently-executing interpreter instance"),
+("argv", "The EmPy script name and command line arguments"),
+("args", "The command line arguments only"),
+("identify()", "Identify top context as name, line"),
+("setContextName(name)", "Set the name of the current context"),
+("setContextLine(line)", "Set the line number of the current context"),
+("atExit(callable)", "Invoke no-argument function at shutdown"),
+("getGlobals()", "Retrieve this interpreter's globals"),
+("setGlobals(dict)", "Set this interpreter's globals"),
+("updateGlobals(dict)", "Merge dictionary into interpreter's globals"),
+("clearGlobals()", "Start globals over anew"),
+("saveGlobals([deep])", "Save a copy of the globals"),
+("restoreGlobals([pop])", "Restore the most recently saved globals"),
+("defined(name, [loc])", "Find if the name is defined"),
+("evaluate(expression, [loc])", "Evaluate the expression"),
+("serialize(expression, [loc])", "Evaluate and serialize the expression"),
+("execute(statements, [loc])", "Execute the statements"),
+("single(source, [loc])", "Execute the 'single' object"),
+("atomic(name, value, [loc])", "Perform an atomic assignment"),
+("assign(name, value, [loc])", "Perform an arbitrary assignment"),
+("significate(key, [value])", "Significate the given key, value pair"),
+("include(file, [loc])", "Include filename or file-like object"),
+("expand(string, [loc])", "Explicitly expand string and return"),
+("string(data, [name], [loc])", "Process string-like object"),
+("quote(string)", "Quote prefixes in provided string and return"),
+("flatten([keys])", "Flatten module contents into globals namespace"),
+("getPrefix()", "Get current prefix"),
+("setPrefix(char)", "Set new prefix"),
+("stopDiverting()", "Stop diverting; data sent directly to output"),
+("createDiversion(name)", "Create a diversion but do not divert to it"),
+("retrieveDiversion(name)", "Retrieve the actual named diversion object"),
+("startDiversion(name)", "Start diverting to given diversion"),
+("playDiversion(name)", "Recall diversion and then eliminate it"),
+("replayDiversion(name)", "Recall diversion but retain it"),
+("purgeDiversion(name)", "Erase diversion"),
+("playAllDiversions()", "Stop diverting and play all diversions in order"),
+("replayAllDiversions()", "Stop diverting and replay all diversions"),
+("purgeAllDiversions()", "Stop diverting and purge all diversions"),
+("getFilter()", "Get current filter"),
+("resetFilter()", "Reset filter; no filtering"),
+("nullFilter()", "Install null filter"),
+("setFilter(shortcut)", "Install new filter or filter chain"),
+("attachFilter(shortcut)", "Attach single filter to end of current chain"),
+("areHooksEnabled()", "Return whether or not hooks are enabled"),
+("enableHooks()", "Enable hooks (default)"),
+("disableHooks()", "Disable hook invocation"),
+("getHooks()", "Get all the hooks"),
+("clearHooks()", "Clear all hooks"),
+("addHook(hook, [i])", "Register the hook (optionally insert)"),
+("removeHook(hook)", "Remove an already-registered hook from name"),
+("invokeHook(name_, ...)", "Manually invoke hook"),
+("getCallback()", "Get interpreter callback"),
+("registerCallback(callback)", "Register callback with interpreter"),
+("deregisterCallback()", "Deregister callback from interpreter"),
+("invokeCallback(contents)", "Invoke the callback directly"),
+("Interpreter", "The interpreter class"),
+]
+
+ENVIRONMENT_INFO = [
+(OPTIONS_ENV, "Specified options will be included"),
+(PREFIX_ENV, "Specify the default prefix: -p <value>"),
+(PSEUDO_ENV, "Specify name of pseudomodule: -m <value>"),
+(FLATTEN_ENV, "Flatten empy pseudomodule if defined: -f"),
+(RAW_ENV, "Show raw errors if defined: -r"),
+(INTERACTIVE_ENV, "Enter interactive mode if defined: -i"),
+(BUFFERED_ENV, "Fully buffered output if defined: -b"),
+(NO_OVERRIDE_ENV, "Do not override sys.stdout if defined: -n"),
+(UNICODE_ENV, "Enable Unicode subsystem: -n"),
+(INPUT_ENCODING_ENV, "Unicode input encoding"),
+(OUTPUT_ENCODING_ENV, "Unicode output encoding"),
+(INPUT_ERRORS_ENV, "Unicode input error handler"),
+(OUTPUT_ERRORS_ENV, "Unicode output error handler"),
+]
+
+class Error(Exception):
+ """The base class for all EmPy errors."""
+ pass
+
+EmpyError = EmPyError = Error # DEPRECATED
+
+class DiversionError(Error):
+ """An error related to diversions."""
+ pass
+
+class FilterError(Error):
+ """An error related to filters."""
+ pass
+
+class StackUnderflowError(Error):
+ """A stack underflow."""
+ pass
+
+class SubsystemError(Error):
+ """An error associated with the Unicode subsystem."""
+ pass
+
+class FlowError(Error):
+ """An exception related to control flow."""
+ pass
+
+class ContinueFlow(FlowError):
+ """A continue control flow."""
+ pass
+
+class BreakFlow(FlowError):
+ """A break control flow."""
+ pass
+
+class ParseError(Error):
+ """A parse error occurred."""
+ pass
+
+class TransientParseError(ParseError):
+ """A parse error occurred which may be resolved by feeding more data.
+ Such an error reaching the toplevel is an unexpected EOF error."""
+ pass
+
+
+class MetaError(Exception):
+
+ """A wrapper around a real Python exception for including a copy of
+ the context."""
+
+ def __init__(self, contexts, exc):
+ Exception.__init__(self, exc)
+ self.contexts = contexts
+ self.exc = exc
+
+ def __str__(self):
+ backtrace = map(lambda x: str(x), self.contexts)
+ return "%s: %s (%s)" % (self.exc.__class__, self.exc, \
+ (string.join(backtrace, ', ')))
+
+
+class Subsystem:
+
+ """The subsystem class defers file creation so that it can create
+ Unicode-wrapped files if desired (and possible)."""
+
+ def __init__(self):
+ self.useUnicode = False
+ self.inputEncoding = None
+ self.outputEncoding = None
+ self.errors = None
+
+ def initialize(self, inputEncoding=None, outputEncoding=None, \
+ inputErrors=None, outputErrors=None):
+ self.useUnicode = True
+ try:
+ unicode
+ import codecs
+ except (NameError, ImportError):
+ raise SubsystemError, "Unicode subsystem unavailable"
+ defaultEncoding = sys.getdefaultencoding()
+ if inputEncoding is None:
+ inputEncoding = defaultEncoding
+ self.inputEncoding = inputEncoding
+ if outputEncoding is None:
+ outputEncoding = defaultEncoding
+ self.outputEncoding = outputEncoding
+ if inputErrors is None:
+ inputErrors = DEFAULT_ERRORS
+ self.inputErrors = inputErrors
+ if outputErrors is None:
+ outputErrors = DEFAULT_ERRORS
+ self.outputErrors = outputErrors
+
+ def assertUnicode(self):
+ if not self.useUnicode:
+ raise SubsystemError, "Unicode subsystem unavailable"
+
+ def open(self, name, mode=None):
+ if self.useUnicode:
+ return self.unicodeOpen(name, mode)
+ else:
+ return self.defaultOpen(name, mode)
+
+ def defaultOpen(self, name, mode=None):
+ if mode is None:
+ mode = 'r'
+ return open(name, mode)
+
+ def unicodeOpen(self, name, mode=None):
+ import codecs
+ if mode is None:
+ mode = 'rb'
+ if mode.find('w') >= 0 or mode.find('a') >= 0:
+ encoding = self.outputEncoding
+ errors = self.outputErrors
+ else:
+ encoding = self.inputEncoding
+ errors = self.inputErrors
+ return codecs.open(name, mode, encoding, errors)
+
+theSubsystem = Subsystem()
+
+
+class Stack:
+
+ """A simple stack that behaves as a sequence (with 0 being the top
+ of the stack, not the bottom)."""
+
+ def __init__(self, seq=None):
+ if seq is None:
+ seq = []
+ self.data = seq
+
+ def top(self):
+ """Access the top element on the stack."""
+ try:
+ return self.data[-1]
+ except IndexError:
+ raise StackUnderflowError, "stack is empty for top"
+
+ def pop(self):
+ """Pop the top element off the stack and return it."""
+ try:
+ return self.data.pop()
+ except IndexError:
+ raise StackUnderflowError, "stack is empty for pop"
+
+ def push(self, object):
+ """Push an element onto the top of the stack."""
+ self.data.append(object)
+
+ def filter(self, function):
+ """Filter the elements of the stack through the function."""
+ self.data = filter(function, self.data)
+
+ def purge(self):
+ """Purge the stack."""
+ self.data = []
+
+ def clone(self):
+ """Create a duplicate of this stack."""
+ return self.__class__(self.data[:])
+
+ def __nonzero__(self): return len(self.data) != 0
+ def __len__(self): return len(self.data)
+ def __getitem__(self, index): return self.data[-(index + 1)]
+
+ def __repr__(self):
+ return '<%s instance at 0x%x [%s]>' % \
+ (self.__class__, id(self), \
+ string.join(map(repr, self.data), ', '))
+
+
+class AbstractFile:
+
+ """An abstracted file that, when buffered, will totally buffer the
+ file, including even the file open."""
+
+ def __init__(self, filename, mode='w', buffered=False):
+ # The calls below might throw, so start off by marking this
+ # file as "done." This way destruction of a not-completely-
+ # initialized AbstractFile will generate no further errors.
+ self.done = True
+ self.filename = filename
+ self.mode = mode
+ self.buffered = buffered
+ if buffered:
+ self.bufferFile = StringIO.StringIO()
+ else:
+ self.bufferFile = theSubsystem.open(filename, mode)
+ # Okay, we got this far, so the AbstractFile is initialized.
+ # Flag it as "not done."
+ self.done = False
+
+ def __del__(self):
+ self.close()
+
+ def write(self, data):
+ self.bufferFile.write(data)
+
+ def writelines(self, data):
+ self.bufferFile.writelines(data)
+
+ def flush(self):
+ self.bufferFile.flush()
+
+ def close(self):
+ if not self.done:
+ self.commit()
+ self.done = True
+
+ def commit(self):
+ if self.buffered:
+ file = theSubsystem.open(self.filename, self.mode)
+ file.write(self.bufferFile.getvalue())
+ file.close()
+ else:
+ self.bufferFile.close()
+
+ def abort(self):
+ if self.buffered:
+ self.bufferFile = None
+ else:
+ self.bufferFile.close()
+ self.bufferFile = None
+ self.done = True
+
+
+class Diversion:
+
+ """The representation of an active diversion. Diversions act as
+ (writable) file objects, and then can be recalled either as pure
+ strings or (readable) file objects."""
+
+ def __init__(self):
+ self.file = StringIO.StringIO()
+
+ # These methods define the writable file-like interface for the
+ # diversion.
+
+ def write(self, data):
+ self.file.write(data)
+
+ def writelines(self, lines):
+ for line in lines:
+ self.write(line)
+
+ def flush(self):
+ self.file.flush()
+
+ def close(self):
+ self.file.close()
+
+ # These methods are specific to diversions.
+
+ def asString(self):
+ """Return the diversion as a string."""
+ return self.file.getvalue()
+
+ def asFile(self):
+ """Return the diversion as a file."""
+ return StringIO.StringIO(self.file.getvalue())
+
+
+class Stream:
+
+ """A wrapper around an (output) file object which supports
+ diversions and filtering."""
+
+ def __init__(self, file):
+ self.file = file
+ self.currentDiversion = None
+ self.diversions = {}
+ self.filter = file
+ self.done = False
+
+ def write(self, data):
+ if self.currentDiversion is None:
+ self.filter.write(data)
+ else:
+ self.diversions[self.currentDiversion].write(data)
+
+ def writelines(self, lines):
+ for line in lines:
+ self.write(line)
+
+ def flush(self):
+ self.filter.flush()
+
+ def close(self):
+ if not self.done:
+ self.undivertAll(True)
+ self.filter.close()
+ self.done = True
+
+ def shortcut(self, shortcut):
+ """Take a filter shortcut and translate it into a filter, returning
+ it. Sequences don't count here; these should be detected
+ independently."""
+ if shortcut == 0:
+ return NullFilter()
+ elif type(shortcut) is types.FunctionType or \
+ type(shortcut) is types.BuiltinFunctionType or \
+ type(shortcut) is types.BuiltinMethodType or \
+ type(shortcut) is types.LambdaType:
+ return FunctionFilter(shortcut)
+ elif type(shortcut) is types.StringType:
+ return StringFilter(filter)
+ elif type(shortcut) is types.DictType:
+ raise NotImplementedError, "mapping filters not yet supported"
+ else:
+ # Presume it's a plain old filter.
+ return shortcut
+
+ def last(self):
+ """Find the last filter in the current filter chain, or None if
+ there are no filters installed."""
+ if self.filter is None:
+ return None
+ thisFilter, lastFilter = self.filter, None
+ while thisFilter is not None and thisFilter is not self.file:
+ lastFilter = thisFilter
+ thisFilter = thisFilter.next()
+ return lastFilter
+
+ def install(self, shortcut=None):
+ """Install a new filter; None means no filter. Handle all the
+ special shortcuts for filters here."""
+ # Before starting, execute a flush.
+ self.filter.flush()
+ if shortcut is None or shortcut == [] or shortcut == ():
+ # Shortcuts for "no filter."
+ self.filter = self.file
+ else:
+ if type(shortcut) in (types.ListType, types.TupleType):
+ shortcuts = list(shortcut)
+ else:
+ shortcuts = [shortcut]
+ # Run through the shortcut filter names, replacing them with
+ # full-fledged instances of Filter.
+ filters = []
+ for shortcut in shortcuts:
+ filters.append(self.shortcut(shortcut))
+ if len(filters) > 1:
+ # If there's more than one filter provided, chain them
+ # together.
+ lastFilter = None
+ for filter in filters:
+ if lastFilter is not None:
+ lastFilter.attach(filter)
+ lastFilter = filter
+ lastFilter.attach(self.file)
+ self.filter = filters[0]
+ else:
+ # If there's only one filter, assume that it's alone or it's
+ # part of a chain that has already been manually chained;
+ # just find the end.
+ filter = filters[0]
+ lastFilter = filter.last()
+ lastFilter.attach(self.file)
+ self.filter = filter
+
+ def attach(self, shortcut):
+ """Attached a solitary filter (no sequences allowed here) at the
+ end of the current filter chain."""
+ lastFilter = self.last()
+ if lastFilter is None:
+ # Just install it from scratch if there is no active filter.
+ self.install(shortcut)
+ else:
+ # Attach the last filter to this one, and this one to the file.
+ filter = self.shortcut(shortcut)
+ lastFilter.attach(filter)
+ filter.attach(self.file)
+
+ def revert(self):
+ """Reset any current diversions."""
+ self.currentDiversion = None
+
+ def create(self, name):
+ """Create a diversion if one does not already exist, but do not
+ divert to it yet."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ if not self.diversions.has_key(name):
+ self.diversions[name] = Diversion()
+
+ def retrieve(self, name):
+ """Retrieve the given diversion."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ if self.diversions.has_key(name):
+ return self.diversions[name]
+ else:
+ raise DiversionError, "nonexistent diversion: %s" % name
+
+ def divert(self, name):
+ """Start diverting."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ self.create(name)
+ self.currentDiversion = name
+
+ def undivert(self, name, purgeAfterwards=False):
+ """Undivert a particular diversion."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ if self.diversions.has_key(name):
+ diversion = self.diversions[name]
+ self.filter.write(diversion.asString())
+ if purgeAfterwards:
+ self.purge(name)
+ else:
+ raise DiversionError, "nonexistent diversion: %s" % name
+
+ def purge(self, name):
+ """Purge the specified diversion."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ if self.diversions.has_key(name):
+ del self.diversions[name]
+ if self.currentDiversion == name:
+ self.currentDiversion = None
+
+ def undivertAll(self, purgeAfterwards=True):
+ """Undivert all pending diversions."""
+ if self.diversions:
+ self.revert() # revert before undiverting!
+ names = self.diversions.keys()
+ names.sort()
+ for name in names:
+ self.undivert(name)
+ if purgeAfterwards:
+ self.purge(name)
+
+ def purgeAll(self):
+ """Eliminate all existing diversions."""
+ if self.diversions:
+ self.diversions = {}
+ self.currentDiversion = None
+
+
+class NullFile:
+
+ """A simple class that supports all the file-like object methods
+ but simply does nothing at all."""
+
+ def __init__(self): pass
+ def write(self, data): pass
+ def writelines(self, lines): pass
+ def flush(self): pass
+ def close(self): pass
+
+
+class UncloseableFile:
+
+ """A simple class which wraps around a delegate file-like object
+ and lets everything through except close calls."""
+
+ def __init__(self, delegate):
+ self.delegate = delegate
+
+ def write(self, data):
+ self.delegate.write(data)
+
+ def writelines(self, lines):
+ self.delegate.writelines(data)
+
+ def flush(self):
+ self.delegate.flush()
+
+ def close(self):
+ """Eat this one."""
+ pass
+
+
+class ProxyFile:
+
+ """The proxy file object that is intended to take the place of
+ sys.stdout. The proxy can manage a stack of file objects it is
+ writing to, and an underlying raw file object."""
+
+ def __init__(self, bottom):
+ self.stack = Stack()
+ self.bottom = bottom
+
+ def current(self):
+ """Get the current stream to write to."""
+ if self.stack:
+ return self.stack[-1][1]
+ else:
+ return self.bottom
+
+ def push(self, interpreter):
+ self.stack.push((interpreter, interpreter.stream()))
+
+ def pop(self, interpreter):
+ result = self.stack.pop()
+ assert interpreter is result[0]
+
+ def clear(self, interpreter):
+ self.stack.filter(lambda x, i=interpreter: x[0] is not i)
+
+ def write(self, data):
+ self.current().write(data)
+
+ def writelines(self, lines):
+ self.current().writelines(lines)
+
+ def flush(self):
+ self.current().flush()
+
+ def close(self):
+ """Close the current file. If the current file is the bottom, then
+ close it and dispose of it."""
+ current = self.current()
+ if current is self.bottom:
+ self.bottom = None
+ current.close()
+
+ def _testProxy(self): pass
+
+
+class Filter:
+
+ """An abstract filter."""
+
+ def __init__(self):
+ if self.__class__ is Filter:
+ raise NotImplementedError
+ self.sink = None
+
+ def next(self):
+ """Return the next filter/file-like object in the sequence, or None."""
+ return self.sink
+
+ def write(self, data):
+ """The standard write method; this must be overridden in subclasses."""
+ raise NotImplementedError
+
+ def writelines(self, lines):
+ """Standard writelines wrapper."""
+ for line in lines:
+ self.write(line)
+
+ def _flush(self):
+ """The _flush method should always flush the sink and should not
+ be overridden."""
+ self.sink.flush()
+
+ def flush(self):
+ """The flush method can be overridden."""
+ self._flush()
+
+ def close(self):
+ """Close the filter. Do an explicit flush first, then close the
+ sink."""
+ self.flush()
+ self.sink.close()
+
+ def attach(self, filter):
+ """Attach a filter to this one."""
+ if self.sink is not None:
+ # If it's already attached, detach it first.
+ self.detach()
+ self.sink = filter
+
+ def detach(self):
+ """Detach a filter from its sink."""
+ self.flush()
+ self._flush() # do a guaranteed flush to just to be safe
+ self.sink = None
+
+ def last(self):
+ """Find the last filter in this chain."""
+ this, last = self, self
+ while this is not None:
+ last = this
+ this = this.next()
+ return last
+
+class NullFilter(Filter):
+
+ """A filter that never sends any output to its sink."""
+
+ def write(self, data): pass
+
+class FunctionFilter(Filter):
+
+ """A filter that works simply by pumping its input through a
+ function which maps strings into strings."""
+
+ def __init__(self, function):
+ Filter.__init__(self)
+ self.function = function
+
+ def write(self, data):
+ self.sink.write(self.function(data))
+
+class StringFilter(Filter):
+
+ """A filter that takes a translation string (256 characters) and
+ filters any incoming data through it."""
+
+ def __init__(self, table):
+ if not (type(table) == types.StringType and len(table) == 256):
+ raise FilterError, "table must be 256-character string"
+ Filter.__init__(self)
+ self.table = table
+
+ def write(self, data):
+ self.sink.write(string.translate(data, self.table))
+
+class BufferedFilter(Filter):
+
+ """A buffered filter is one that doesn't modify the source data
+ sent to the sink, but instead holds it for a time. The standard
+ variety only sends the data along when it receives a flush
+ command."""
+
+ def __init__(self):
+ Filter.__init__(self)
+ self.buffer = ''
+
+ def write(self, data):
+ self.buffer = self.buffer + data
+
+ def flush(self):
+ if self.buffer:
+ self.sink.write(self.buffer)
+ self._flush()
+
+class SizeBufferedFilter(BufferedFilter):
+
+ """A size-buffered filter only in fixed size chunks (excepting the
+ final chunk)."""
+
+ def __init__(self, bufferSize):
+ BufferedFilter.__init__(self)
+ self.bufferSize = bufferSize
+
+ def write(self, data):
+ BufferedFilter.write(self, data)
+ while len(self.buffer) > self.bufferSize:
+ chunk, self.buffer = \
+ self.buffer[:self.bufferSize], self.buffer[self.bufferSize:]
+ self.sink.write(chunk)
+
+class LineBufferedFilter(BufferedFilter):
+
+ """A line-buffered filter only lets data through when it sees
+ whole lines."""
+
+ def __init__(self):
+ BufferedFilter.__init__(self)
+
+ def write(self, data):
+ BufferedFilter.write(self, data)
+ chunks = string.split(self.buffer, '\n')
+ for chunk in chunks[:-1]:
+ self.sink.write(chunk + '\n')
+ self.buffer = chunks[-1]
+
+class MaximallyBufferedFilter(BufferedFilter):
+
+ """A maximally-buffered filter only lets its data through on the final
+ close. It ignores flushes."""
+
+ def __init__(self):
+ BufferedFilter.__init__(self)
+
+ def flush(self): pass
+
+ def close(self):
+ if self.buffer:
+ BufferedFilter.flush(self)
+ self.sink.close()
+
+
+class Context:
+
+ """An interpreter context, which encapsulates a name, an input
+ file object, and a parser object."""
+
+ DEFAULT_UNIT = 'lines'
+
+ def __init__(self, name, line=0, units=DEFAULT_UNIT):
+ self.name = name
+ self.line = line
+ self.units = units
+ self.pause = False
+
+ def bump(self, quantity=1):
+ if self.pause:
+ self.pause = False
+ else:
+ self.line = self.line + quantity
+
+ def identify(self):
+ return self.name, self.line
+
+ def __str__(self):
+ if self.units == self.DEFAULT_UNIT:
+ return "%s:%s" % (self.name, self.line)
+ else:
+ return "%s:%s[%s]" % (self.name, self.line, self.units)
+
+
+class Hook:
+
+ """The base class for implementing hooks."""
+
+ def __init__(self):
+ self.interpreter = None
+
+ def register(self, interpreter):
+ self.interpreter = interpreter
+
+ def deregister(self, interpreter):
+ if interpreter is not self.interpreter:
+ raise Error, "hook not associated with this interpreter"
+ self.interpreter = None
+
+ def push(self):
+ self.interpreter.push()
+
+ def pop(self):
+ self.interpreter.pop()
+
+ def null(self): pass
+
+ def atStartup(self): pass
+ def atReady(self): pass
+ def atFinalize(self): pass
+ def atShutdown(self): pass
+ def atParse(self, scanner, locals): pass
+ def atToken(self, token): pass
+ def atHandle(self, meta): pass
+ def atInteract(self): pass
+
+ def beforeInclude(self, name, file, locals): pass
+ def afterInclude(self): pass
+
+ def beforeExpand(self, string, locals): pass
+ def afterExpand(self, result): pass
+
+ def beforeFile(self, name, file, locals): pass
+ def afterFile(self): pass
+
+ def beforeBinary(self, name, file, chunkSize, locals): pass
+ def afterBinary(self): pass
+
+ def beforeString(self, name, string, locals): pass
+ def afterString(self): pass
+
+ def beforeQuote(self, string): pass
+ def afterQuote(self, result): pass
+
+ def beforeEscape(self, string, more): pass
+ def afterEscape(self, result): pass
+
+ def beforeControl(self, type, rest, locals): pass
+ def afterControl(self): pass
+
+ def beforeSignificate(self, key, value, locals): pass
+ def afterSignificate(self): pass
+
+ def beforeAtomic(self, name, value, locals): pass
+ def afterAtomic(self): pass
+
+ def beforeMulti(self, name, values, locals): pass
+ def afterMulti(self): pass
+
+ def beforeImport(self, name, locals): pass
+ def afterImport(self): pass
+
+ def beforeClause(self, catch, locals): pass
+ def afterClause(self, exception, variable): pass
+
+ def beforeSerialize(self, expression, locals): pass
+ def afterSerialize(self): pass
+
+ def beforeDefined(self, name, locals): pass
+ def afterDefined(self, result): pass
+
+ def beforeLiteral(self, text): pass
+ def afterLiteral(self): pass
+
+ def beforeEvaluate(self, expression, locals): pass
+ def afterEvaluate(self, result): pass
+
+ def beforeExecute(self, statements, locals): pass
+ def afterExecute(self): pass
+
+ def beforeSingle(self, source, locals): pass
+ def afterSingle(self): pass
+
+class VerboseHook(Hook):
+
+ """A verbose hook that reports all information received by the
+ hook interface. This class dynamically scans the Hook base class
+ to ensure that all hook methods are properly represented."""
+
+ EXEMPT_ATTRIBUTES = ['register', 'deregister', 'push', 'pop']
+
+ def __init__(self, output=sys.stderr):
+ Hook.__init__(self)
+ self.output = output
+ self.indent = 0
+
+ class FakeMethod:
+ """This is a proxy method-like object."""
+ def __init__(self, hook, name):
+ self.hook = hook
+ self.name = name
+
+ def __call__(self, **keywords):
+ self.hook.output.write("%s%s: %s\n" % \
+ (' ' * self.hook.indent, \
+ self.name, repr(keywords)))
+
+ for attribute in dir(Hook):
+ if attribute[:1] != '_' and \
+ attribute not in self.EXEMPT_ATTRIBUTES:
+ self.__dict__[attribute] = FakeMethod(self, attribute)
+
+
+class Token:
+
+ """An element of expansion."""
+
+ def run(self, interpreter, locals):
+ raise NotImplementedError
+
+ def string(self):
+ raise NotImplementedError
+
+ def __str__(self): return self.string()
+
+class NullToken(Token):
+ """A chunk of data not containing markups."""
+ def __init__(self, data):
+ self.data = data
+
+ def run(self, interpreter, locals):
+ interpreter.write(self.data)
+
+ def string(self):
+ return self.data
+
+class ExpansionToken(Token):
+ """A token that involves an expansion."""
+ def __init__(self, prefix, first):
+ self.prefix = prefix
+ self.first = first
+
+ def scan(self, scanner):
+ pass
+
+ def run(self, interpreter, locals):
+ pass
+
+class WhitespaceToken(ExpansionToken):
+ """A whitespace markup."""
+ def string(self):
+ return '%s%s' % (self.prefix, self.first)
+
+class LiteralToken(ExpansionToken):
+ """A literal markup."""
+ def run(self, interpreter, locals):
+ interpreter.write(self.first)
+
+ def string(self):
+ return '%s%s' % (self.prefix, self.first)
+
+class PrefixToken(ExpansionToken):
+ """A prefix markup."""
+ def run(self, interpreter, locals):
+ interpreter.write(interpreter.prefix)
+
+ def string(self):
+ return self.prefix * 2
+
+class CommentToken(ExpansionToken):
+ """A comment markup."""
+ def scan(self, scanner):
+ loc = scanner.find('\n')
+ if loc >= 0:
+ self.comment = scanner.chop(loc, 1)
+ else:
+ raise TransientParseError, "comment expects newline"
+
+ def string(self):
+ return '%s#%s\n' % (self.prefix, self.comment)
+
+class ContextNameToken(ExpansionToken):
+ """A context name change markup."""
+ def scan(self, scanner):
+ loc = scanner.find('\n')
+ if loc >= 0:
+ self.name = string.strip(scanner.chop(loc, 1))
+ else:
+ raise TransientParseError, "context name expects newline"
+
+ def run(self, interpreter, locals):
+ context = interpreter.context()
+ context.name = self.name
+
+class ContextLineToken(ExpansionToken):
+ """A context line change markup."""
+ def scan(self, scanner):
+ loc = scanner.find('\n')
+ if loc >= 0:
+ try:
+ self.line = int(scanner.chop(loc, 1))
+ except ValueError:
+ raise ParseError, "context line requires integer"
+ else:
+ raise TransientParseError, "context line expects newline"
+
+ def run(self, interpreter, locals):
+ context = interpreter.context()
+ context.line = self.line
+ context.pause = True
+
+class EscapeToken(ExpansionToken):
+ """An escape markup."""
+ def scan(self, scanner):
+ try:
+ code = scanner.chop(1)
+ result = None
+ if code in '()[]{}\'\"\\': # literals
+ result = code
+ elif code == '0': # NUL
+ result = '\x00'
+ elif code == 'a': # BEL
+ result = '\x07'
+ elif code == 'b': # BS
+ result = '\x08'
+ elif code == 'd': # decimal code
+ decimalCode = scanner.chop(3)
+ result = chr(string.atoi(decimalCode, 10))
+ elif code == 'e': # ESC
+ result = '\x1b'
+ elif code == 'f': # FF
+ result = '\x0c'
+ elif code == 'h': # DEL
+ result = '\x7f'
+ elif code == 'n': # LF (newline)
+ result = '\x0a'
+ elif code == 'N': # Unicode character name
+ theSubsystem.assertUnicode()
+ import unicodedata
+ if scanner.chop(1) != '{':
+ raise ParseError, r"Unicode name escape should be \N{...}"
+ i = scanner.find('}')
+ name = scanner.chop(i, 1)
+ try:
+ result = unicodedata.lookup(name)
+ except KeyError:
+ raise SubsystemError, \
+ "unknown Unicode character name: %s" % name
+ elif code == 'o': # octal code
+ octalCode = scanner.chop(3)
+ result = chr(string.atoi(octalCode, 8))
+ elif code == 'q': # quaternary code
+ quaternaryCode = scanner.chop(4)
+ result = chr(string.atoi(quaternaryCode, 4))
+ elif code == 'r': # CR
+ result = '\x0d'
+ elif code in 's ': # SP
+ result = ' '
+ elif code == 't': # HT
+ result = '\x09'
+ elif code in 'u': # Unicode 16-bit hex literal
+ theSubsystem.assertUnicode()
+ hexCode = scanner.chop(4)
+ result = unichr(string.atoi(hexCode, 16))
+ elif code in 'U': # Unicode 32-bit hex literal
+ theSubsystem.assertUnicode()
+ hexCode = scanner.chop(8)
+ result = unichr(string.atoi(hexCode, 16))
+ elif code == 'v': # VT
+ result = '\x0b'
+ elif code == 'x': # hexadecimal code
+ hexCode = scanner.chop(2)
+ result = chr(string.atoi(hexCode, 16))
+ elif code == 'z': # EOT
+ result = '\x04'
+ elif code == '^': # control character
+ controlCode = string.upper(scanner.chop(1))
+ if controlCode >= '@' and controlCode <= '`':
+ result = chr(ord(controlCode) - ord('@'))
+ elif controlCode == '?':
+ result = '\x7f'
+ else:
+ raise ParseError, "invalid escape control code"
+ else:
+ raise ParseError, "unrecognized escape code"
+ assert result is not None
+ self.code = result
+ except ValueError:
+ raise ParseError, "invalid numeric escape code"
+
+ def run(self, interpreter, locals):
+ interpreter.write(self.code)
+
+ def string(self):
+ return '%s\\x%02x' % (self.prefix, ord(self.code))
+
+class SignificatorToken(ExpansionToken):
+ """A significator markup."""
+ def scan(self, scanner):
+ loc = scanner.find('\n')
+ if loc >= 0:
+ line = scanner.chop(loc, 1)
+ if not line:
+ raise ParseError, "significator must have nonblank key"
+ if line[0] in ' \t\v\n':
+ raise ParseError, "no whitespace between % and key"
+ # Work around a subtle CPython-Jython difference by stripping
+ # the string before splitting it: 'a '.split(None, 1) has two
+ # elements in Jython 2.1).
+ fields = string.split(string.strip(line), None, 1)
+ if len(fields) == 2 and fields[1] == '':
+ fields.pop()
+ self.key = fields[0]
+ if len(fields) < 2:
+ fields.append(None)
+ self.key, self.valueCode = fields
+ else:
+ raise TransientParseError, "significator expects newline"
+
+ def run(self, interpreter, locals):
+ value = self.valueCode
+ if value is not None:
+ value = interpreter.evaluate(string.strip(value), locals)
+ interpreter.significate(self.key, value)
+
+ def string(self):
+ if self.valueCode is None:
+ return '%s%%%s\n' % (self.prefix, self.key)
+ else:
+ return '%s%%%s %s\n' % (self.prefix, self.key, self.valueCode)
+
+class ExpressionToken(ExpansionToken):
+ """An expression markup."""
+ def scan(self, scanner):
+ z = scanner.complex('(', ')', 0)
+ try:
+ q = scanner.next('$', 0, z, True)
+ except ParseError:
+ q = z
+ try:
+ i = scanner.next('?', 0, q, True)
+ try:
+ j = scanner.next('!', i, q, True)
+ except ParseError:
+ try:
+ j = scanner.next(':', i, q, True) # DEPRECATED
+ except ParseError:
+ j = q
+ except ParseError:
+ i = j = q
+ code = scanner.chop(z, 1)
+ self.testCode = code[:i]
+ self.thenCode = code[i + 1:j]
+ self.elseCode = code[j + 1:q]
+ self.exceptCode = code[q + 1:z]
+
+ def run(self, interpreter, locals):
+ try:
+ result = interpreter.evaluate(self.testCode, locals)
+ if self.thenCode:
+ if result:
+ result = interpreter.evaluate(self.thenCode, locals)
+ else:
+ if self.elseCode:
+ result = interpreter.evaluate(self.elseCode, locals)
+ else:
+ result = None
+ except SyntaxError:
+ # Don't catch syntax errors; let them through.
+ raise
+ except:
+ if self.exceptCode:
+ result = interpreter.evaluate(self.exceptCode, locals)
+ else:
+ raise
+ if result is not None:
+ interpreter.write(str(result))
+
+ def string(self):
+ result = self.testCode
+ if self.thenCode:
+ result = result + '?' + self.thenCode
+ if self.elseCode:
+ result = result + '!' + self.elseCode
+ if self.exceptCode:
+ result = result + '$' + self.exceptCode
+ return '%s(%s)' % (self.prefix, result)
+
+class StringLiteralToken(ExpansionToken):
+ """A string token markup."""
+ def scan(self, scanner):
+ scanner.retreat()
+ assert scanner[0] == self.first
+ i = scanner.quote()
+ self.literal = scanner.chop(i)
+
+ def run(self, interpreter, locals):
+ interpreter.literal(self.literal)
+
+ def string(self):
+ return '%s%s' % (self.prefix, self.literal)
+
+class SimpleExpressionToken(ExpansionToken):
+ """A simple expression markup."""
+ def scan(self, scanner):
+ i = scanner.simple()
+ self.code = self.first + scanner.chop(i)
+
+ def run(self, interpreter, locals):
+ interpreter.serialize(self.code, locals)
+
+ def string(self):
+ return '%s%s' % (self.prefix, self.code)
+
+class ReprToken(ExpansionToken):
+ """A repr markup."""
+ def scan(self, scanner):
+ i = scanner.next('`', 0)
+ self.code = scanner.chop(i, 1)
+
+ def run(self, interpreter, locals):
+ interpreter.write(repr(interpreter.evaluate(self.code, locals)))
+
+ def string(self):
+ return '%s`%s`' % (self.prefix, self.code)
+
+class InPlaceToken(ExpansionToken):
+ """An in-place markup."""
+ def scan(self, scanner):
+ i = scanner.next(':', 0)
+ j = scanner.next(':', i + 1)
+ self.code = scanner.chop(i, j - i + 1)
+
+ def run(self, interpreter, locals):
+ interpreter.write("%s:%s:" % (interpreter.prefix, self.code))
+ try:
+ interpreter.serialize(self.code, locals)
+ finally:
+ interpreter.write(":")
+
+ def string(self):
+ return '%s:%s::' % (self.prefix, self.code)
+
+class StatementToken(ExpansionToken):
+ """A statement markup."""
+ def scan(self, scanner):
+ i = scanner.complex('{', '}', 0)
+ self.code = scanner.chop(i, 1)
+
+ def run(self, interpreter, locals):
+ interpreter.execute(self.code, locals)
+
+ def string(self):
+ return '%s{%s}' % (self.prefix, self.code)
+
+class CustomToken(ExpansionToken):
+ """A custom markup."""
+ def scan(self, scanner):
+ i = scanner.complex('<', '>', 0)
+ self.contents = scanner.chop(i, 1)
+
+ def run(self, interpreter, locals):
+ interpreter.invokeCallback(self.contents)
+
+ def string(self):
+ return '%s<%s>' % (self.prefix, self.contents)
+
+class ControlToken(ExpansionToken):
+
+ """A control token."""
+
+ PRIMARY_TYPES = ['if', 'for', 'while', 'try', 'def']
+ SECONDARY_TYPES = ['elif', 'else', 'except', 'finally']
+ TERTIARY_TYPES = ['continue', 'break']
+ GREEDY_TYPES = ['if', 'elif', 'for', 'while', 'def', 'end']
+ END_TYPES = ['end']
+
+ IN_RE = re.compile(r"\bin\b")
+
+ def scan(self, scanner):
+ scanner.acquire()
+ i = scanner.complex('[', ']', 0)
+ self.contents = scanner.chop(i, 1)
+ fields = string.split(string.strip(self.contents), ' ', 1)
+ if len(fields) > 1:
+ self.type, self.rest = fields
+ else:
+ self.type = fields[0]
+ self.rest = None
+ self.subtokens = []
+ if self.type in self.GREEDY_TYPES and self.rest is None:
+ raise ParseError, "control '%s' needs arguments" % self.type
+ if self.type in self.PRIMARY_TYPES:
+ self.subscan(scanner, self.type)
+ self.kind = 'primary'
+ elif self.type in self.SECONDARY_TYPES:
+ self.kind = 'secondary'
+ elif self.type in self.TERTIARY_TYPES:
+ self.kind = 'tertiary'
+ elif self.type in self.END_TYPES:
+ self.kind = 'end'
+ else:
+ raise ParseError, "unknown control markup: '%s'" % self.type
+ scanner.release()
+
+ def subscan(self, scanner, primary):
+ """Do a subscan for contained tokens."""
+ while True:
+ token = scanner.one()
+ if token is None:
+ raise TransientParseError, \
+ "control '%s' needs more tokens" % primary
+ if isinstance(token, ControlToken) and \
+ token.type in self.END_TYPES:
+ if token.rest != primary:
+ raise ParseError, \
+ "control must end with 'end %s'" % primary
+ break
+ self.subtokens.append(token)
+
+ def build(self, allowed=None):
+ """Process the list of subtokens and divide it into a list of
+ 2-tuples, consisting of the dividing tokens and the list of
+ subtokens that follow them. If allowed is specified, it will
+ represent the list of the only secondary markup types which
+ are allowed."""
+ if allowed is None:
+ allowed = SECONDARY_TYPES
+ result = []
+ latest = []
+ result.append((self, latest))
+ for subtoken in self.subtokens:
+ if isinstance(subtoken, ControlToken) and \
+ subtoken.kind == 'secondary':
+ if subtoken.type not in allowed:
+ raise ParseError, \
+ "control unexpected secondary: '%s'" % subtoken.type
+ latest = []
+ result.append((subtoken, latest))
+ else:
+ latest.append(subtoken)
+ return result
+
+ def run(self, interpreter, locals):
+ interpreter.invoke('beforeControl', type=self.type, rest=self.rest, \
+ locals=locals)
+ if self.type == 'if':
+ info = self.build(['elif', 'else'])
+ elseTokens = None
+ if info[-1][0].type == 'else':
+ elseTokens = info.pop()[1]
+ for secondary, subtokens in info:
+ if secondary.type not in ('if', 'elif'):
+ raise ParseError, \
+ "control 'if' unexpected secondary: '%s'" % secondary.type
+ if interpreter.evaluate(secondary.rest, locals):
+ self.subrun(subtokens, interpreter, locals)
+ break
+ else:
+ if elseTokens:
+ self.subrun(elseTokens, interpreter, locals)
+ elif self.type == 'for':
+ sides = self.IN_RE.split(self.rest, 1)
+ if len(sides) != 2:
+ raise ParseError, "control expected 'for x in seq'"
+ iterator, sequenceCode = sides
+ info = self.build(['else'])
+ elseTokens = None
+ if info[-1][0].type == 'else':
+ elseTokens = info.pop()[1]
+ if len(info) != 1:
+ raise ParseError, "control 'for' expects at most one 'else'"
+ sequence = interpreter.evaluate(sequenceCode, locals)
+ for element in sequence:
+ try:
+ interpreter.assign(iterator, element, locals)
+ self.subrun(info[0][1], interpreter, locals)
+ except ContinueFlow:
+ continue
+ except BreakFlow:
+ break
+ else:
+ if elseTokens:
+ self.subrun(elseTokens, interpreter, locals)
+ elif self.type == 'while':
+ testCode = self.rest
+ info = self.build(['else'])
+ elseTokens = None
+ if info[-1][0].type == 'else':
+ elseTokens = info.pop()[1]
+ if len(info) != 1:
+ raise ParseError, "control 'while' expects at most one 'else'"
+ atLeastOnce = False
+ while True:
+ try:
+ if not interpreter.evaluate(testCode, locals):
+ break
+ atLeastOnce = True
+ self.subrun(info[0][1], interpreter, locals)
+ except ContinueFlow:
+ continue
+ except BreakFlow:
+ break
+ if not atLeastOnce and elseTokens:
+ self.subrun(elseTokens, interpreter, locals)
+ elif self.type == 'try':
+ info = self.build(['except', 'finally'])
+ if len(info) == 1:
+ raise ParseError, "control 'try' needs 'except' or 'finally'"
+ type = info[-1][0].type
+ if type == 'except':
+ for secondary, _tokens in info[1:]:
+ if secondary.type != 'except':
+ raise ParseError, \
+ "control 'try' cannot have 'except' and 'finally'"
+ else:
+ assert type == 'finally'
+ if len(info) != 2:
+ raise ParseError, \
+ "control 'try' can only have one 'finally'"
+ if type == 'except':
+ try:
+ self.subrun(info[0][1], interpreter, locals)
+ except FlowError:
+ raise
+ except Exception, e:
+ for secondary, tokens in info[1:]:
+ exception, variable = interpreter.clause(secondary.rest)
+ if variable is not None:
+ interpreter.assign(variable, e)
+ if isinstance(e, exception):
+ self.subrun(tokens, interpreter, locals)
+ break
+ else:
+ raise
+ else:
+ try:
+ self.subrun(info[0][1], interpreter, locals)
+ finally:
+ self.subrun(info[1][1], interpreter, locals)
+ elif self.type == 'continue':
+ raise ContinueFlow, "control 'continue' without 'for', 'while'"
+ elif self.type == 'break':
+ raise BreakFlow, "control 'break' without 'for', 'while'"
+ elif self.type == 'def':
+ signature = self.rest
+ definition = self.substring()
+ code = 'def %s:\n' \
+ ' r"""%s"""\n' \
+ ' return %s.expand(r"""%s""", locals())\n' % \
+ (signature, definition, interpreter.pseudo, definition)
+ interpreter.execute(code, locals)
+ elif self.type == 'end':
+ raise ParseError, "control 'end' requires primary markup"
+ else:
+ raise ParseError, \
+ "control '%s' cannot be at this level" % self.type
+ interpreter.invoke('afterControl')
+
+ def subrun(self, tokens, interpreter, locals):
+ """Execute a sequence of tokens."""
+ for token in tokens:
+ token.run(interpreter, locals)
+
+ def substring(self):
+ return string.join(map(str, self.subtokens), '')
+
+ def string(self):
+ if self.kind == 'primary':
+ return '%s[%s]%s%s[end %s]' % \
+ (self.prefix, self.contents, self.substring(), \
+ self.prefix, self.type)
+ else:
+ return '%s[%s]' % (self.prefix, self.contents)
+
+
+class Scanner:
+
+ """A scanner holds a buffer for lookahead parsing and has the
+ ability to scan for special symbols and indicators in that
+ buffer."""
+
+ # This is the token mapping table that maps first characters to
+ # token classes.
+ TOKEN_MAP = [
+ (None, PrefixToken),
+ (' \t\v\r\n', WhitespaceToken),
+ (')]}', LiteralToken),
+ ('\\', EscapeToken),
+ ('#', CommentToken),
+ ('?', ContextNameToken),
+ ('!', ContextLineToken),
+ ('%', SignificatorToken),
+ ('(', ExpressionToken),
+ (IDENTIFIER_FIRST_CHARS, SimpleExpressionToken),
+ ('\'\"', StringLiteralToken),
+ ('`', ReprToken),
+ (':', InPlaceToken),
+ ('[', ControlToken),
+ ('{', StatementToken),
+ ('<', CustomToken),
+ ]
+
+ def __init__(self, prefix, data=''):
+ self.prefix = prefix
+ self.pointer = 0
+ self.buffer = data
+ self.lock = 0
+
+ def __nonzero__(self): return self.pointer < len(self.buffer)
+ def __len__(self): return len(self.buffer) - self.pointer
+ def __getitem__(self, index): return self.buffer[self.pointer + index]
+
+ def __getslice__(self, start, stop):
+ if stop > len(self):
+ stop = len(self)
+ return self.buffer[self.pointer + start:self.pointer + stop]
+
+ def advance(self, count=1):
+ """Advance the pointer count characters."""
+ self.pointer = self.pointer + count
+
+ def retreat(self, count=1):
+ self.pointer = self.pointer - count
+ if self.pointer < 0:
+ raise ParseError, "can't retreat back over synced out chars"
+
+ def set(self, data):
+ """Start the scanner digesting a new batch of data; start the pointer
+ over from scratch."""
+ self.pointer = 0
+ self.buffer = data
+
+ def feed(self, data):
+ """Feed some more data to the scanner."""
+ self.buffer = self.buffer + data
+
+ def chop(self, count=None, slop=0):
+ """Chop the first count + slop characters off the front, and return
+ the first count. If count is not specified, then return
+ everything."""
+ if count is None:
+ assert slop == 0
+ count = len(self)
+ if count > len(self):
+ raise TransientParseError, "not enough data to read"
+ result = self[:count]
+ self.advance(count + slop)
+ return result
+
+ def acquire(self):
+ """Lock the scanner so it doesn't destroy data on sync."""
+ self.lock = self.lock + 1
+
+ def release(self):
+ """Unlock the scanner."""
+ self.lock = self.lock - 1
+
+ def sync(self):
+ """Sync up the buffer with the read head."""
+ if self.lock == 0 and self.pointer != 0:
+ self.buffer = self.buffer[self.pointer:]
+ self.pointer = 0
+
+ def unsync(self):
+ """Undo changes; reset the read head."""
+ if self.pointer != 0:
+ self.lock = 0
+ self.pointer = 0
+
+ def rest(self):
+ """Get the remainder of the buffer."""
+ return self[:]
+
+ def read(self, i=0, count=1):
+ """Read count chars starting from i; raise a transient error if
+ there aren't enough characters remaining."""
+ if len(self) < i + count:
+ raise TransientParseError, "need more data to read"
+ else:
+ return self[i:i + count]
+
+ def check(self, i, archetype=None):
+ """Scan for the next single or triple quote, with the specified
+ archetype. Return the found quote or None."""
+ quote = None
+ if self[i] in '\'\"':
+ quote = self[i]
+ if len(self) - i < 3:
+ for j in range(i, len(self)):
+ if self[i] == quote:
+ return quote
+ else:
+ raise TransientParseError, "need to scan for rest of quote"
+ if self[i + 1] == self[i + 2] == quote:
+ quote = quote * 3
+ if quote is not None:
+ if archetype is None:
+ return quote
+ else:
+ if archetype == quote:
+ return quote
+ elif len(archetype) < len(quote) and archetype[0] == quote[0]:
+ return archetype
+ else:
+ return None
+ else:
+ return None
+
+ def find(self, sub, start=0, end=None):
+ """Find the next occurrence of the character, or return -1."""
+ if end is not None:
+ return string.find(self.rest(), sub, start, end)
+ else:
+ return string.find(self.rest(), sub, start)
+
+ def last(self, char, start=0, end=None):
+ """Find the first character that is _not_ the specified character."""
+ if end is None:
+ end = len(self)
+ i = start
+ while i < end:
+ if self[i] != char:
+ return i
+ i = i + 1
+ else:
+ raise TransientParseError, "expecting other than %s" % char
+
+ def next(self, target, start=0, end=None, mandatory=False):
+ """Scan for the next occurrence of one of the characters in
+ the target string; optionally, make the scan mandatory."""
+ if mandatory:
+ assert end is not None
+ quote = None
+ if end is None:
+ end = len(self)
+ i = start
+ while i < end:
+ newQuote = self.check(i, quote)
+ if newQuote:
+ if newQuote == quote:
+ quote = None
+ else:
+ quote = newQuote
+ i = i + len(newQuote)
+ else:
+ c = self[i]
+ if quote:
+ if c == '\\':
+ i = i + 1
+ else:
+ if c in target:
+ return i
+ i = i + 1
+ else:
+ if mandatory:
+ raise ParseError, "expecting %s, not found" % target
+ else:
+ raise TransientParseError, "expecting ending character"
+
+ def quote(self, start=0, end=None, mandatory=False):
+ """Scan for the end of the next quote."""
+ assert self[start] in '\'\"'
+ quote = self.check(start)
+ if end is None:
+ end = len(self)
+ i = start + len(quote)
+ while i < end:
+ newQuote = self.check(i, quote)
+ if newQuote:
+ i = i + len(newQuote)
+ if newQuote == quote:
+ return i
+ else:
+ c = self[i]
+ if c == '\\':
+ i = i + 1
+ i = i + 1
+ else:
+ if mandatory:
+ raise ParseError, "expecting end of string literal"
+ else:
+ raise TransientParseError, "expecting end of string literal"
+
+ def nested(self, enter, exit, start=0, end=None):
+ """Scan from i for an ending sequence, respecting entries and exits
+ only."""
+ depth = 0
+ if end is None:
+ end = len(self)
+ i = start
+ while i < end:
+ c = self[i]
+ if c == enter:
+ depth = depth + 1
+ elif c == exit:
+ depth = depth - 1
+ if depth < 0:
+ return i
+ i = i + 1
+ else:
+ raise TransientParseError, "expecting end of complex expression"
+
+ def complex(self, enter, exit, start=0, end=None, skip=None):
+ """Scan from i for an ending sequence, respecting quotes,
+ entries and exits."""
+ quote = None
+ depth = 0
+ if end is None:
+ end = len(self)
+ last = None
+ i = start
+ while i < end:
+ newQuote = self.check(i, quote)
+ if newQuote:
+ if newQuote == quote:
+ quote = None
+ else:
+ quote = newQuote
+ i = i + len(newQuote)
+ else:
+ c = self[i]
+ if quote:
+ if c == '\\':
+ i = i + 1
+ else:
+ if skip is None or last != skip:
+ if c == enter:
+ depth = depth + 1
+ elif c == exit:
+ depth = depth - 1
+ if depth < 0:
+ return i
+ last = c
+ i = i + 1
+ else:
+ raise TransientParseError, "expecting end of complex expression"
+
+ def word(self, start=0):
+ """Scan from i for a simple word."""
+ length = len(self)
+ i = start
+ while i < length:
+ if not self[i] in IDENTIFIER_CHARS:
+ return i
+ i = i + 1
+ else:
+ raise TransientParseError, "expecting end of word"
+
+ def phrase(self, start=0):
+ """Scan from i for a phrase (e.g., 'word', 'f(a, b, c)', 'a[i]', or
+ combinations like 'x[i](a)'."""
+ # Find the word.
+ i = self.word(start)
+ while i < len(self) and self[i] in '([{':
+ enter = self[i]
+ if enter == '{':
+ raise ParseError, "curly braces can't open simple expressions"
+ exit = ENDING_CHARS[enter]
+ i = self.complex(enter, exit, i + 1) + 1
+ return i
+
+ def simple(self, start=0):
+ """Scan from i for a simple expression, which consists of one
+ more phrases separated by dots."""
+ i = self.phrase(start)
+ length = len(self)
+ while i < length and self[i] == '.':
+ i = self.phrase(i)
+ # Make sure we don't end with a trailing dot.
+ while i > 0 and self[i - 1] == '.':
+ i = i - 1
+ return i
+
+ def one(self):
+ """Parse and return one token, or None if the scanner is empty."""
+ if not self:
+ return None
+ if not self.prefix:
+ loc = -1
+ else:
+ loc = self.find(self.prefix)
+ if loc < 0:
+ # If there's no prefix in the buffer, then set the location to
+ # the end so the whole thing gets processed.
+ loc = len(self)
+ if loc == 0:
+ # If there's a prefix at the beginning of the buffer, process
+ # an expansion.
+ prefix = self.chop(1)
+ assert prefix == self.prefix
+ first = self.chop(1)
+ if first == self.prefix:
+ first = None
+ for firsts, factory in self.TOKEN_MAP:
+ if firsts is None:
+ if first is None:
+ break
+ elif first in firsts:
+ break
+ else:
+ raise ParseError, "unknown markup: %s%s" % (self.prefix, first)
+ token = factory(self.prefix, first)
+ try:
+ token.scan(self)
+ except TransientParseError:
+ # If a transient parse error occurs, reset the buffer pointer
+ # so we can (conceivably) try again later.
+ self.unsync()
+ raise
+ else:
+ # Process everything up to loc as a null token.
+ data = self.chop(loc)
+ token = NullToken(data)
+ self.sync()
+ return token
+
+
+class Interpreter:
+
+ """An interpreter can process chunks of EmPy code."""
+
+ # Constants.
+
+ VERSION = __version__
+ SIGNIFICATOR_RE_SUFFIX = SIGNIFICATOR_RE_SUFFIX
+ SIGNIFICATOR_RE_STRING = None
+
+ # Types.
+
+ Interpreter = None # define this below to prevent a circular reference
+ Hook = Hook # DEPRECATED
+ Filter = Filter # DEPRECATED
+ NullFilter = NullFilter # DEPRECATED
+ FunctionFilter = FunctionFilter # DEPRECATED
+ StringFilter = StringFilter # DEPRECATED
+ BufferedFilter = BufferedFilter # DEPRECATED
+ SizeBufferedFilter = SizeBufferedFilter # DEPRECATED
+ LineBufferedFilter = LineBufferedFilter # DEPRECATED
+ MaximallyBufferedFilter = MaximallyBufferedFilter # DEPRECATED
+
+ # Tables.
+
+ ESCAPE_CODES = {0x00: '0', 0x07: 'a', 0x08: 'b', 0x1b: 'e', 0x0c: 'f', \
+ 0x7f: 'h', 0x0a: 'n', 0x0d: 'r', 0x09: 't', 0x0b: 'v', \
+ 0x04: 'z'}
+
+ ASSIGN_TOKEN_RE = re.compile(r"[_a-zA-Z][_a-zA-Z0-9]*|\(|\)|,")
+
+ DEFAULT_OPTIONS = {BANGPATH_OPT: True,
+ BUFFERED_OPT: False,
+ RAW_OPT: False,
+ EXIT_OPT: True,
+ FLATTEN_OPT: False,
+ OVERRIDE_OPT: True,
+ CALLBACK_OPT: False}
+
+ _wasProxyInstalled = False # was a proxy installed?
+
+ # Construction, initialization, destruction.
+
+ def __init__(self, output=None, argv=None, prefix=DEFAULT_PREFIX, \
+ pseudo=None, options=None, globals=None, hooks=None):
+ self.interpreter = self # DEPRECATED
+ # Set up the stream.
+ if output is None:
+ output = UncloseableFile(sys.__stdout__)
+ self.output = output
+ self.prefix = prefix
+ if pseudo is None:
+ pseudo = DEFAULT_PSEUDOMODULE_NAME
+ self.pseudo = pseudo
+ if argv is None:
+ argv = [DEFAULT_SCRIPT_NAME]
+ self.argv = argv
+ self.args = argv[1:]
+ if options is None:
+ options = {}
+ self.options = options
+ # Initialize any hooks.
+ self.hooksEnabled = None # special sentinel meaning "false until added"
+ self.hooks = []
+ if hooks is None:
+ hooks = []
+ for hook in hooks:
+ self.register(hook)
+ # Initialize callback.
+ self.callback = None
+ # Finalizers.
+ self.finals = []
+ # The interpreter stacks.
+ self.contexts = Stack()
+ self.streams = Stack()
+ # Now set up the globals.
+ self.globals = globals
+ self.fix()
+ self.history = Stack()
+ # Install a proxy stdout if one hasn't been already.
+ self.installProxy()
+ # Finally, reset the state of all the stacks.
+ self.reset()
+ # Okay, now flatten the namespaces if that option has been set.
+ if self.options.get(FLATTEN_OPT, False):
+ self.flatten()
+ # Set up old pseudomodule attributes.
+ if prefix is None:
+ self.SIGNIFICATOR_RE_STRING = None
+ else:
+ self.SIGNIFICATOR_RE_STRING = prefix + self.SIGNIFICATOR_RE_SUFFIX
+ self.Interpreter = self.__class__
+ # Done. Now declare that we've started up.
+ self.invoke('atStartup')
+
+ def __del__(self):
+ self.shutdown()
+
+ def __repr__(self):
+ return '<%s pseudomodule/interpreter at 0x%x>' % \
+ (self.pseudo, id(self))
+
+ def ready(self):
+ """Declare the interpreter ready for normal operations."""
+ self.invoke('atReady')
+
+ def fix(self):
+ """Reset the globals, stamping in the pseudomodule."""
+ if self.globals is None:
+ self.globals = {}
+ # Make sure that there is no collision between two interpreters'
+ # globals.
+ if self.globals.has_key(self.pseudo):
+ if self.globals[self.pseudo] is not self:
+ raise Error, "interpreter globals collision"
+ self.globals[self.pseudo] = self
+
+ def unfix(self):
+ """Remove the pseudomodule (if present) from the globals."""
+ UNWANTED_KEYS = [self.pseudo, '__builtins__']
+ for unwantedKey in UNWANTED_KEYS:
+ if self.globals.has_key(unwantedKey):
+ del self.globals[unwantedKey]
+
+ def update(self, other):
+ """Update the current globals dictionary with another dictionary."""
+ self.globals.update(other)
+ self.fix()
+
+ def clear(self):
+ """Clear out the globals dictionary with a brand new one."""
+ self.globals = {}
+ self.fix()
+
+ def save(self, deep=True):
+ if deep:
+ copyMethod = copy.deepcopy
+ else:
+ copyMethod = copy.copy
+ """Save a copy of the current globals on the history stack."""
+ self.unfix()
+ self.history.push(copyMethod(self.globals))
+ self.fix()
+
+ def restore(self, destructive=True):
+ """Restore the topmost historic globals."""
+ if destructive:
+ fetchMethod = self.history.pop
+ else:
+ fetchMethod = self.history.top
+ self.unfix()
+ self.globals = fetchMethod()
+ self.fix()
+
+ def shutdown(self):
+ """Declare this interpreting session over; close the stream file
+ object. This method is idempotent."""
+ if self.streams is not None:
+ try:
+ self.finalize()
+ self.invoke('atShutdown')
+ while self.streams:
+ stream = self.streams.pop()
+ stream.close()
+ finally:
+ self.streams = None
+
+ def ok(self):
+ """Is the interpreter still active?"""
+ return self.streams is not None
+
+ # Writeable file-like methods.
+
+ def write(self, data):
+ self.stream().write(data)
+
+ def writelines(self, stuff):
+ self.stream().writelines(stuff)
+
+ def flush(self):
+ self.stream().flush()
+
+ def close(self):
+ self.shutdown()
+
+ # Stack-related activity.
+
+ def context(self):
+ return self.contexts.top()
+
+ def stream(self):
+ return self.streams.top()
+
+ def reset(self):
+ self.contexts.purge()
+ self.streams.purge()
+ self.streams.push(Stream(self.output))
+ if self.options.get(OVERRIDE_OPT, True):
+ sys.stdout.clear(self)
+
+ def push(self):
+ if self.options.get(OVERRIDE_OPT, True):
+ sys.stdout.push(self)
+
+ def pop(self):
+ if self.options.get(OVERRIDE_OPT, True):
+ sys.stdout.pop(self)
+
+ # Higher-level operations.
+
+ def include(self, fileOrFilename, locals=None):
+ """Do an include pass on a file or filename."""
+ if type(fileOrFilename) is types.StringType:
+ # Either it's a string representing a filename ...
+ filename = fileOrFilename
+ name = filename
+ file = theSubsystem.open(filename, 'r')
+ else:
+ # ... or a file object.
+ file = fileOrFilename
+ name = "<%s>" % str(file.__class__)
+ self.invoke('beforeInclude', name=name, file=file, locals=locals)
+ self.file(file, name, locals)
+ self.invoke('afterInclude')
+
+ def expand(self, data, locals=None):
+ """Do an explicit expansion on a subordinate stream."""
+ outFile = StringIO.StringIO()
+ stream = Stream(outFile)
+ self.invoke('beforeExpand', string=data, locals=locals)
+ self.streams.push(stream)
+ try:
+ self.string(data, '<expand>', locals)
+ stream.flush()
+ expansion = outFile.getvalue()
+ self.invoke('afterExpand', result=expansion)
+ return expansion
+ finally:
+ self.streams.pop()
+
+ def quote(self, data):
+ """Quote the given string so that if it were expanded it would
+ evaluate to the original."""
+ self.invoke('beforeQuote', string=data)
+ scanner = Scanner(self.prefix, data)
+ result = []
+ i = 0
+ try:
+ j = scanner.next(self.prefix, i)
+ result.append(data[i:j])
+ result.append(self.prefix * 2)
+ i = j + 1
+ except TransientParseError:
+ pass
+ result.append(data[i:])
+ result = string.join(result, '')
+ self.invoke('afterQuote', result=result)
+ return result
+
+ def escape(self, data, more=''):
+ """Escape a string so that nonprintable characters are replaced
+ with compatible EmPy expansions."""
+ self.invoke('beforeEscape', string=data, more=more)
+ result = []
+ for char in data:
+ if char < ' ' or char > '~':
+ charOrd = ord(char)
+ if Interpreter.ESCAPE_CODES.has_key(charOrd):
+ result.append(self.prefix + '\\' + \
+ Interpreter.ESCAPE_CODES[charOrd])
+ else:
+ result.append(self.prefix + '\\x%02x' % charOrd)
+ elif char in more:
+ result.append(self.prefix + '\\' + char)
+ else:
+ result.append(char)
+ result = string.join(result, '')
+ self.invoke('afterEscape', result=result)
+ return result
+
+ # Processing.
+
+ def wrap(self, callable, args):
+ """Wrap around an application of a callable and handle errors.
+ Return whether no error occurred."""
+ try:
+ apply(callable, args)
+ self.reset()
+ return True
+ except KeyboardInterrupt, e:
+ # Handle keyboard interrupts specially: we should always exit
+ # from these.
+ self.fail(e, True)
+ except Exception, e:
+ # A standard exception (other than a keyboard interrupt).
+ self.fail(e)
+ except:
+ # If we get here, then either it's an exception not derived from
+ # Exception or it's a string exception, so get the error type
+ # from the sys module.
+ e = sys.exc_type
+ self.fail(e)
+ # An error occurred if we leak through to here, so do cleanup.
+ self.reset()
+ return False
+
+ def interact(self):
+ """Perform interaction."""
+ self.invoke('atInteract')
+ done = False
+ while not done:
+ result = self.wrap(self.file, (sys.stdin, '<interact>'))
+ if self.options.get(EXIT_OPT, True):
+ done = True
+ else:
+ if result:
+ done = True
+ else:
+ self.reset()
+
+ def fail(self, error, fatal=False):
+ """Handle an actual error that occurred."""
+ if self.options.get(BUFFERED_OPT, False):
+ try:
+ self.output.abort()
+ except AttributeError:
+ # If the output file object doesn't have an abort method,
+ # something got mismatched, but it's too late to do
+ # anything about it now anyway, so just ignore it.
+ pass
+ meta = self.meta(error)
+ self.handle(meta)
+ if self.options.get(RAW_OPT, False):
+ raise
+ if fatal or self.options.get(EXIT_OPT, True):
+ sys.exit(FAILURE_CODE)
+
+ def file(self, file, name='<file>', locals=None):
+ """Parse the entire contents of a file-like object, line by line."""
+ context = Context(name)
+ self.contexts.push(context)
+ self.invoke('beforeFile', name=name, file=file, locals=locals)
+ scanner = Scanner(self.prefix)
+ first = True
+ done = False
+ while not done:
+ self.context().bump()
+ line = file.readline()
+ if first:
+ if self.options.get(BANGPATH_OPT, True) and self.prefix:
+ # Replace a bangpath at the beginning of the first line
+ # with an EmPy comment.
+ if string.find(line, BANGPATH) == 0:
+ line = self.prefix + '#' + line[2:]
+ first = False
+ if line:
+ scanner.feed(line)
+ else:
+ done = True
+ self.safe(scanner, done, locals)
+ self.invoke('afterFile')
+ self.contexts.pop()
+
+ def binary(self, file, name='<binary>', chunkSize=0, locals=None):
+ """Parse the entire contents of a file-like object, in chunks."""
+ if chunkSize <= 0:
+ chunkSize = DEFAULT_CHUNK_SIZE
+ context = Context(name, units='bytes')
+ self.contexts.push(context)
+ self.invoke('beforeBinary', name=name, file=file, \
+ chunkSize=chunkSize, locals=locals)
+ scanner = Scanner(self.prefix)
+ done = False
+ while not done:
+ chunk = file.read(chunkSize)
+ if chunk:
+ scanner.feed(chunk)
+ else:
+ done = True
+ self.safe(scanner, done, locals)
+ self.context().bump(len(chunk))
+ self.invoke('afterBinary')
+ self.contexts.pop()
+
+ def string(self, data, name='<string>', locals=None):
+ """Parse a string."""
+ context = Context(name)
+ self.contexts.push(context)
+ self.invoke('beforeString', name=name, string=data, locals=locals)
+ context.bump()
+ scanner = Scanner(self.prefix, data)
+ self.safe(scanner, True, locals)
+ self.invoke('afterString')
+ self.contexts.pop()
+
+ def safe(self, scanner, final=False, locals=None):
+ """Do a protected parse. Catch transient parse errors; if
+ final is true, then make a final pass with a terminator,
+ otherwise ignore the transient parse error (more data is
+ pending)."""
+ try:
+ self.parse(scanner, locals)
+ except TransientParseError:
+ if final:
+ # If the buffer doesn't end with a newline, try tacking on
+ # a dummy terminator.
+ buffer = scanner.rest()
+ if buffer and buffer[-1] != '\n':
+ scanner.feed(self.prefix + '\n')
+ # A TransientParseError thrown from here is a real parse
+ # error.
+ self.parse(scanner, locals)
+
+ def parse(self, scanner, locals=None):
+ """Parse and run as much from this scanner as possible."""
+ self.invoke('atParse', scanner=scanner, locals=locals)
+ while True:
+ token = scanner.one()
+ if token is None:
+ break
+ self.invoke('atToken', token=token)
+ token.run(self, locals)
+
+ # Medium-level evaluation and execution.
+
+ def tokenize(self, name):
+ """Take an lvalue string and return a name or a (possibly recursive)
+ list of names."""
+ result = []
+ stack = [result]
+ for garbage in self.ASSIGN_TOKEN_RE.split(name):
+ garbage = string.strip(garbage)
+ if garbage:
+ raise ParseError, "unexpected assignment token: '%s'" % garbage
+ tokens = self.ASSIGN_TOKEN_RE.findall(name)
+ # While processing, put a None token at the start of any list in which
+ # commas actually appear.
+ for token in tokens:
+ if token == '(':
+ stack.append([])
+ elif token == ')':
+ top = stack.pop()
+ if len(top) == 1:
+ top = top[0] # no None token means that it's not a 1-tuple
+ elif top[0] is None:
+ del top[0] # remove the None token for real tuples
+ stack[-1].append(top)
+ elif token == ',':
+ if len(stack[-1]) == 1:
+ stack[-1].insert(0, None)
+ else:
+ stack[-1].append(token)
+ # If it's a 1-tuple at the top level, turn it into a real subsequence.
+ if result and result[0] is None:
+ result = [result[1:]]
+ if len(result) == 1:
+ return result[0]
+ else:
+ return result
+
+ def significate(self, key, value=None, locals=None):
+ """Declare a significator."""
+ self.invoke('beforeSignificate', key=key, value=value, locals=locals)
+ name = '__%s__' % key
+ self.atomic(name, value, locals)
+ self.invoke('afterSignificate')
+
+ def atomic(self, name, value, locals=None):
+ """Do an atomic assignment."""
+ self.invoke('beforeAtomic', name=name, value=value, locals=locals)
+ if locals is None:
+ self.globals[name] = value
+ else:
+ locals[name] = value
+ self.invoke('afterAtomic')
+
+ def multi(self, names, values, locals=None):
+ """Do a (potentially recursive) assignment."""
+ self.invoke('beforeMulti', names=names, values=values, locals=locals)
+ # No zip in 1.5, so we have to do it manually.
+ i = 0
+ try:
+ values = tuple(values)
+ except TypeError:
+ raise TypeError, "unpack non-sequence"
+ if len(names) != len(values):
+ raise ValueError, "unpack tuple of wrong size"
+ for i in range(len(names)):
+ name = names[i]
+ if type(name) is types.StringType:
+ self.atomic(name, values[i], locals)
+ else:
+ self.multi(name, values[i], locals)
+ self.invoke('afterMulti')
+
+ def assign(self, name, value, locals=None):
+ """Do a potentially complex (including tuple unpacking) assignment."""
+ left = self.tokenize(name)
+ # The return value of tokenize can either be a string or a list of
+ # (lists of) strings.
+ if type(left) is types.StringType:
+ self.atomic(left, value, locals)
+ else:
+ self.multi(left, value, locals)
+
+ def import_(self, name, locals=None):
+ """Do an import."""
+ self.invoke('beforeImport', name=name, locals=locals)
+ self.execute('import %s' % name, locals)
+ self.invoke('afterImport')
+
+ def clause(self, catch, locals=None):
+ """Given the string representation of an except clause, turn it into
+ a 2-tuple consisting of the class name, and either a variable name
+ or None."""
+ self.invoke('beforeClause', catch=catch, locals=locals)
+ if catch is None:
+ exceptionCode, variable = None, None
+ elif string.find(catch, ',') >= 0:
+ exceptionCode, variable = string.split(string.strip(catch), ',', 1)
+ variable = string.strip(variable)
+ else:
+ exceptionCode, variable = string.strip(catch), None
+ if not exceptionCode:
+ exception = Exception
+ else:
+ exception = self.evaluate(exceptionCode, locals)
+ self.invoke('afterClause', exception=exception, variable=variable)
+ return exception, variable
+
+ def serialize(self, expression, locals=None):
+ """Do an expansion, involving evaluating an expression, then
+ converting it to a string and writing that string to the
+ output if the evaluation is not None."""
+ self.invoke('beforeSerialize', expression=expression, locals=locals)
+ result = self.evaluate(expression, locals)
+ if result is not None:
+ self.write(str(result))
+ self.invoke('afterSerialize')
+
+ def defined(self, name, locals=None):
+ """Return a Boolean indicating whether or not the name is
+ defined either in the locals or the globals."""
+ self.invoke('beforeDefined', name=name, local=local)
+ if locals is not None:
+ if locals.has_key(name):
+ result = True
+ else:
+ result = False
+ elif self.globals.has_key(name):
+ result = True
+ else:
+ result = False
+ self.invoke('afterDefined', result=result)
+
+ def literal(self, text):
+ """Process a string literal."""
+ self.invoke('beforeLiteral', text=text)
+ self.serialize(text)
+ self.invoke('afterLiteral')
+
+ # Low-level evaluation and execution.
+
+ def evaluate(self, expression, locals=None):
+ """Evaluate an expression."""
+ if expression in ('1', 'True'): return True
+ if expression in ('0', 'False'): return False
+ self.push()
+ try:
+ self.invoke('beforeEvaluate', \
+ expression=expression, locals=locals)
+ if locals is not None:
+ result = eval(expression, self.globals, locals)
+ else:
+ result = eval(expression, self.globals)
+ self.invoke('afterEvaluate', result=result)
+ return result
+ finally:
+ self.pop()
+
+ def execute(self, statements, locals=None):
+ """Execute a statement."""
+ # If there are any carriage returns (as opposed to linefeeds/newlines)
+ # in the statements code, then remove them. Even on DOS/Windows
+ # platforms,
+ if string.find(statements, '\r') >= 0:
+ statements = string.replace(statements, '\r', '')
+ # If there are no newlines in the statements code, then strip any
+ # leading or trailing whitespace.
+ if string.find(statements, '\n') < 0:
+ statements = string.strip(statements)
+ self.push()
+ try:
+ self.invoke('beforeExecute', \
+ statements=statements, locals=locals)
+ if locals is not None:
+ exec statements in self.globals, locals
+ else:
+ exec statements in self.globals
+ self.invoke('afterExecute')
+ finally:
+ self.pop()
+
+ def single(self, source, locals=None):
+ """Execute an expression or statement, just as if it were
+ entered into the Python interactive interpreter."""
+ self.push()
+ try:
+ self.invoke('beforeSingle', \
+ source=source, locals=locals)
+ code = compile(source, '<single>', 'single')
+ if locals is not None:
+ exec code in self.globals, locals
+ else:
+ exec code in self.globals
+ self.invoke('afterSingle')
+ finally:
+ self.pop()
+
+ # Hooks.
+
+ def register(self, hook, prepend=False):
+ """Register the provided hook."""
+ hook.register(self)
+ if self.hooksEnabled is None:
+ # A special optimization so that hooks can be effectively
+ # disabled until one is added or they are explicitly turned on.
+ self.hooksEnabled = True
+ if prepend:
+ self.hooks.insert(0, hook)
+ else:
+ self.hooks.append(hook)
+
+ def deregister(self, hook):
+ """Remove an already registered hook."""
+ hook.deregister(self)
+ self.hooks.remove(hook)
+
+ def invoke(self, _name, **keywords):
+ """Invoke the hook(s) associated with the hook name, should they
+ exist."""
+ if self.hooksEnabled:
+ for hook in self.hooks:
+ hook.push()
+ try:
+ method = getattr(hook, _name)
+ apply(method, (), keywords)
+ finally:
+ hook.pop()
+
+ def finalize(self):
+ """Execute any remaining final routines."""
+ self.push()
+ self.invoke('atFinalize')
+ try:
+ # Pop them off one at a time so they get executed in reverse
+ # order and we remove them as they're executed in case something
+ # bad happens.
+ while self.finals:
+ final = self.finals.pop()
+ final()
+ finally:
+ self.pop()
+
+ # Error handling.
+
+ def meta(self, exc=None):
+ """Construct a MetaError for the interpreter's current state."""
+ return MetaError(self.contexts.clone(), exc)
+
+ def handle(self, meta):
+ """Handle a MetaError."""
+ first = True
+ self.invoke('atHandle', meta=meta)
+ for context in meta.contexts:
+ if first:
+ if meta.exc is not None:
+ desc = "error: %s: %s" % (meta.exc.__class__, meta.exc)
+ else:
+ desc = "error"
+ else:
+ desc = "from this context"
+ first = False
+ sys.stderr.write('%s: %s\n' % (context, desc))
+
+ def installProxy(self):
+ """Install a proxy if necessary."""
+ # Unfortunately, there's no surefire way to make sure that installing
+ # a sys.stdout proxy is idempotent, what with different interpreters
+ # running from different modules. The best we can do here is to try
+ # manipulating the proxy's test function ...
+ try:
+ sys.stdout._testProxy()
+ except AttributeError:
+ # ... if the current stdout object doesn't have one, then check
+ # to see if we think _this_ particularly Interpreter class has
+ # installed it before ...
+ if Interpreter._wasProxyInstalled:
+ # ... and if so, we have a proxy problem.
+ raise Error, "interpreter stdout proxy lost"
+ else:
+ # Otherwise, install the proxy and set the flag.
+ sys.stdout = ProxyFile(sys.stdout)
+ Interpreter._wasProxyInstalled = True
+
+ #
+ # Pseudomodule routines.
+ #
+
+ # Identification.
+
+ def identify(self):
+ """Identify the topmost context with a 2-tuple of the name and
+ line number."""
+ return self.context().identify()
+
+ def atExit(self, callable):
+ """Register a function to be called at exit."""
+ self.finals.append(callable)
+
+ # Context manipulation.
+
+ def pushContext(self, name='<unnamed>', line=0):
+ """Create a new context and push it."""
+ self.contexts.push(Context(name, line))
+
+ def popContext(self):
+ """Pop the top context."""
+ self.contexts.pop()
+
+ def setContextName(self, name):
+ """Set the name of the topmost context."""
+ context = self.context()
+ context.name = name
+
+ def setContextLine(self, line):
+ """Set the name of the topmost context."""
+ context = self.context()
+ context.line = line
+
+ setName = setContextName # DEPRECATED
+ setLine = setContextLine # DEPRECATED
+
+ # Globals manipulation.
+
+ def getGlobals(self):
+ """Retrieve the globals."""
+ return self.globals
+
+ def setGlobals(self, globals):
+ """Set the globals to the specified dictionary."""
+ self.globals = globals
+ self.fix()
+
+ def updateGlobals(self, otherGlobals):
+ """Merge another mapping object into this interpreter's globals."""
+ self.update(otherGlobals)
+
+ def clearGlobals(self):
+ """Clear out the globals with a brand new dictionary."""
+ self.clear()
+
+ def saveGlobals(self, deep=True):
+ """Save a copy of the globals off onto the history stack."""
+ self.save(deep)
+
+ def restoreGlobals(self, destructive=True):
+ """Restore the most recently saved copy of the globals."""
+ self.restore(destructive)
+
+ # Hook support.
+
+ def areHooksEnabled(self):
+ """Return whether or not hooks are presently enabled."""
+ if self.hooksEnabled is None:
+ return True
+ else:
+ return self.hooksEnabled
+
+ def enableHooks(self):
+ """Enable hooks."""
+ self.hooksEnabled = True
+
+ def disableHooks(self):
+ """Disable hooks."""
+ self.hooksEnabled = False
+
+ def getHooks(self):
+ """Get the current hooks."""
+ return self.hooks[:]
+
+ def clearHooks(self):
+ """Clear all hooks."""
+ self.hooks = []
+
+ def addHook(self, hook, prepend=False):
+ """Add a new hook; optionally insert it rather than appending it."""
+ self.register(hook, prepend)
+
+ def removeHook(self, hook):
+ """Remove a preexisting hook."""
+ self.deregister(hook)
+
+ def invokeHook(self, _name, **keywords):
+ """Manually invoke a hook."""
+ apply(self.invoke, (_name,), keywords)
+
+ # Callbacks.
+
+ def getCallback(self):
+ """Get the callback registered with this interpreter, or None."""
+ return self.callback
+
+ def registerCallback(self, callback):
+ """Register a custom markup callback with this interpreter."""
+ self.callback = callback
+
+ def deregisterCallback(self):
+ """Remove any previously registered callback with this interpreter."""
+ self.callback = None
+
+ def invokeCallback(self, contents):
+ """Invoke the callback."""
+ if self.callback is None:
+ if self.options.get(CALLBACK_OPT, False):
+ raise Error, "custom markup invoked with no defined callback"
+ else:
+ self.callback(contents)
+
+ # Pseudomodule manipulation.
+
+ def flatten(self, keys=None):
+ """Flatten the contents of the pseudo-module into the globals
+ namespace."""
+ if keys is None:
+ keys = self.__dict__.keys() + self.__class__.__dict__.keys()
+ dict = {}
+ for key in keys:
+ # The pseudomodule is really a class instance, so we need to
+ # fumble use getattr instead of simply fumbling through the
+ # instance's __dict__.
+ dict[key] = getattr(self, key)
+ # Stomp everything into the globals namespace.
+ self.globals.update(dict)
+
+ # Prefix.
+
+ def getPrefix(self):
+ """Get the current prefix."""
+ return self.prefix
+
+ def setPrefix(self, prefix):
+ """Set the prefix."""
+ self.prefix = prefix
+
+ # Diversions.
+
+ def stopDiverting(self):
+ """Stop any diverting."""
+ self.stream().revert()
+
+ def createDiversion(self, name):
+ """Create a diversion (but do not divert to it) if it does not
+ already exist."""
+ self.stream().create(name)
+
+ def retrieveDiversion(self, name):
+ """Retrieve the diversion object associated with the name."""
+ return self.stream().retrieve(name)
+
+ def startDiversion(self, name):
+ """Start diverting to the given diversion name."""
+ self.stream().divert(name)
+
+ def playDiversion(self, name):
+ """Play the given diversion and then purge it."""
+ self.stream().undivert(name, True)
+
+ def replayDiversion(self, name):
+ """Replay the diversion without purging it."""
+ self.stream().undivert(name, False)
+
+ def purgeDiversion(self, name):
+ """Eliminate the given diversion."""
+ self.stream().purge(name)
+
+ def playAllDiversions(self):
+ """Play all existing diversions and then purge them."""
+ self.stream().undivertAll(True)
+
+ def replayAllDiversions(self):
+ """Replay all existing diversions without purging them."""
+ self.stream().undivertAll(False)
+
+ def purgeAllDiversions(self):
+ """Purge all existing diversions."""
+ self.stream().purgeAll()
+
+ def getCurrentDiversion(self):
+ """Get the name of the current diversion."""
+ return self.stream().currentDiversion
+
+ def getAllDiversions(self):
+ """Get the names of all existing diversions."""
+ names = self.stream().diversions.keys()
+ names.sort()
+ return names
+
+ # Filter.
+
+ def resetFilter(self):
+ """Reset the filter so that it does no filtering."""
+ self.stream().install(None)
+
+ def nullFilter(self):
+ """Install a filter that will consume all text."""
+ self.stream().install(0)
+
+ def getFilter(self):
+ """Get the current filter."""
+ filter = self.stream().filter
+ if filter is self.stream().file:
+ return None
+ else:
+ return filter
+
+ def setFilter(self, shortcut):
+ """Set the filter."""
+ self.stream().install(shortcut)
+
+ def attachFilter(self, shortcut):
+ """Attach a single filter to the end of the current filter chain."""
+ self.stream().attach(shortcut)
+
+
+class Document:
+
+ """A representation of an individual EmPy document, as used by a
+ processor."""
+
+ def __init__(self, ID, filename):
+ self.ID = ID
+ self.filename = filename
+ self.significators = {}
+
+
+class Processor:
+
+ """An entity which is capable of processing a hierarchy of EmPy
+ files and building a dictionary of document objects associated
+ with them describing their significator contents."""
+
+ DEFAULT_EMPY_EXTENSIONS = ('.em',)
+ SIGNIFICATOR_RE = re.compile(SIGNIFICATOR_RE_STRING)
+
+ def __init__(self, factory=Document):
+ self.factory = factory
+ self.documents = {}
+
+ def identifier(self, pathname, filename): return filename
+
+ def clear(self):
+ self.documents = {}
+
+ def scan(self, basename, extensions=DEFAULT_EMPY_EXTENSIONS):
+ if type(extensions) is types.StringType:
+ extensions = (extensions,)
+ def _noCriteria(x):
+ return True
+ def _extensionsCriteria(pathname, extensions=extensions):
+ if extensions:
+ for extension in extensions:
+ if pathname[-len(extension):] == extension:
+ return True
+ return False
+ else:
+ return True
+ self.directory(basename, _noCriteria, _extensionsCriteria, None)
+ self.postprocess()
+
+ def postprocess(self):
+ pass
+
+ def directory(self, basename, dirCriteria, fileCriteria, depth=None):
+ if depth is not None:
+ if depth <= 0:
+ return
+ else:
+ depth = depth - 1
+ filenames = os.listdir(basename)
+ for filename in filenames:
+ pathname = os.path.join(basename, filename)
+ if os.path.isdir(pathname):
+ if dirCriteria(pathname):
+ self.directory(pathname, dirCriteria, fileCriteria, depth)
+ elif os.path.isfile(pathname):
+ if fileCriteria(pathname):
+ documentID = self.identifier(pathname, filename)
+ document = self.factory(documentID, pathname)
+ self.file(document, open(pathname))
+ self.documents[documentID] = document
+
+ def file(self, document, file):
+ while True:
+ line = file.readline()
+ if not line:
+ break
+ self.line(document, line)
+
+ def line(self, document, line):
+ match = self.SIGNIFICATOR_RE.search(line)
+ if match:
+ key, valueS = match.groups()
+ valueS = string.strip(valueS)
+ if valueS:
+ value = eval(valueS)
+ else:
+ value = None
+ document.significators[key] = value
+
+
+def expand(_data, _globals=None, \
+ _argv=None, _prefix=DEFAULT_PREFIX, _pseudo=None, _options=None, \
+ **_locals):
+ """Do an atomic expansion of the given source data, creating and
+ shutting down an interpreter dedicated to the task. The sys.stdout
+ object is saved off and then replaced before this function
+ returns."""
+ if len(_locals) == 0:
+ # If there were no keyword arguments specified, don't use a locals
+ # dictionary at all.
+ _locals = None
+ output = NullFile()
+ interpreter = Interpreter(output, argv=_argv, prefix=_prefix, \
+ pseudo=_pseudo, options=_options, \
+ globals=_globals)
+ if interpreter.options.get(OVERRIDE_OPT, True):
+ oldStdout = sys.stdout
+ try:
+ result = interpreter.expand(_data, _locals)
+ finally:
+ interpreter.shutdown()
+ if _globals is not None:
+ interpreter.unfix() # remove pseudomodule to prevent clashes
+ if interpreter.options.get(OVERRIDE_OPT, True):
+ sys.stdout = oldStdout
+ return result
+
+def environment(name, default=None):
+ """Get data from the current environment. If the default is True
+ or False, then presume that we're only interested in the existence
+ or non-existence of the environment variable."""
+ if os.environ.has_key(name):
+ # Do the True/False test by value for future compatibility.
+ if default == False or default == True:
+ return True
+ else:
+ return os.environ[name]
+ else:
+ return default
+
+def info(table):
+ DEFAULT_LEFT = 28
+ maxLeft = 0
+ maxRight = 0
+ for left, right in table:
+ if len(left) > maxLeft:
+ maxLeft = len(left)
+ if len(right) > maxRight:
+ maxRight = len(right)
+ FORMAT = ' %%-%ds %%s\n' % max(maxLeft, DEFAULT_LEFT)
+ for left, right in table:
+ if right.find('\n') >= 0:
+ for right in right.split('\n'):
+ sys.stderr.write(FORMAT % (left, right))
+ left = ''
+ else:
+ sys.stderr.write(FORMAT % (left, right))
+
+def usage(verbose=True):
+ """Print usage information."""
+ programName = sys.argv[0]
+ def warn(line=''):
+ sys.stderr.write("%s\n" % line)
+ warn("""\
+Usage: %s [options] [<filename, or '-' for stdin> [<argument>...]]
+Welcome to EmPy version %s.""" % (programName, __version__))
+ warn()
+ warn("Valid options:")
+ info(OPTION_INFO)
+ if verbose:
+ warn()
+ warn("The following markups are supported:")
+ info(MARKUP_INFO)
+ warn()
+ warn("Valid escape sequences are:")
+ info(ESCAPE_INFO)
+ warn()
+ warn("The %s pseudomodule contains the following attributes:" % \
+ DEFAULT_PSEUDOMODULE_NAME)
+ info(PSEUDOMODULE_INFO)
+ warn()
+ warn("The following environment variables are recognized:")
+ info(ENVIRONMENT_INFO)
+ warn()
+ warn(USAGE_NOTES)
+ else:
+ warn()
+ warn("Type %s -H for more extensive help." % programName)
+
+def invoke(args):
+ """Run a standalone instance of an EmPy interpeter."""
+ # Initialize the options.
+ _output = None
+ _options = {BUFFERED_OPT: environment(BUFFERED_ENV, False),
+ RAW_OPT: environment(RAW_ENV, False),
+ EXIT_OPT: True,
+ FLATTEN_OPT: environment(FLATTEN_ENV, False),
+ OVERRIDE_OPT: not environment(NO_OVERRIDE_ENV, False),
+ CALLBACK_OPT: False}
+ _preprocessing = []
+ _prefix = environment(PREFIX_ENV, DEFAULT_PREFIX)
+ _pseudo = environment(PSEUDO_ENV, None)
+ _interactive = environment(INTERACTIVE_ENV, False)
+ _extraArguments = environment(OPTIONS_ENV)
+ _binary = -1 # negative for not, 0 for default size, positive for size
+ _unicode = environment(UNICODE_ENV, False)
+ _unicodeInputEncoding = environment(INPUT_ENCODING_ENV, None)
+ _unicodeOutputEncoding = environment(OUTPUT_ENCODING_ENV, None)
+ _unicodeInputErrors = environment(INPUT_ERRORS_ENV, None)
+ _unicodeOutputErrors = environment(OUTPUT_ERRORS_ENV, None)
+ _hooks = []
+ _pauseAtEnd = False
+ _relativePath = False
+ if _extraArguments is not None:
+ _extraArguments = string.split(_extraArguments)
+ args = _extraArguments + args
+ # Parse the arguments.
+ pairs, remainder = getopt.getopt(args, 'VhHvkp:m:frino:a:buBP:I:D:E:F:', ['version', 'help', 'extended-help', 'verbose', 'null-hook', 'suppress-errors', 'prefix=', 'no-prefix', 'module=', 'flatten', 'raw-errors', 'interactive', 'no-override-stdout', 'binary', 'chunk-size=', 'output=' 'append=', 'preprocess=', 'import=', 'define=', 'execute=', 'execute-file=', 'buffered-output', 'pause-at-end', 'relative-path', 'no-callback-error', 'no-bangpath-processing', 'unicode', 'unicode-encoding=', 'unicode-input-encoding=', 'unicode-output-encoding=', 'unicode-errors=', 'unicode-input-errors=', 'unicode-output-errors='])
+ for option, argument in pairs:
+ if option in ('-V', '--version'):
+ sys.stderr.write("%s version %s\n" % (__program__, __version__))
+ return
+ elif option in ('-h', '--help'):
+ usage(False)
+ return
+ elif option in ('-H', '--extended-help'):
+ usage(True)
+ return
+ elif option in ('-v', '--verbose'):
+ _hooks.append(VerboseHook())
+ elif option in ('--null-hook',):
+ _hooks.append(Hook())
+ elif option in ('-k', '--suppress-errors'):
+ _options[EXIT_OPT] = False
+ _interactive = True # suppress errors implies interactive mode
+ elif option in ('-m', '--module'):
+ _pseudo = argument
+ elif option in ('-f', '--flatten'):
+ _options[FLATTEN_OPT] = True
+ elif option in ('-p', '--prefix'):
+ _prefix = argument
+ elif option in ('--no-prefix',):
+ _prefix = None
+ elif option in ('-r', '--raw-errors'):
+ _options[RAW_OPT] = True
+ elif option in ('-i', '--interactive'):
+ _interactive = True
+ elif option in ('-n', '--no-override-stdout'):
+ _options[OVERRIDE_OPT] = False
+ elif option in ('-o', '--output'):
+ _output = argument, 'w', _options[BUFFERED_OPT]
+ elif option in ('-a', '--append'):
+ _output = argument, 'a', _options[BUFFERED_OPT]
+ elif option in ('-b', '--buffered-output'):
+ _options[BUFFERED_OPT] = True
+ elif option in ('-B',): # DEPRECATED
+ _options[BUFFERED_OPT] = True
+ elif option in ('--binary',):
+ _binary = 0
+ elif option in ('--chunk-size',):
+ _binary = int(argument)
+ elif option in ('-P', '--preprocess'):
+ _preprocessing.append(('pre', argument))
+ elif option in ('-I', '--import'):
+ for module in string.split(argument, ','):
+ module = string.strip(module)
+ _preprocessing.append(('import', module))
+ elif option in ('-D', '--define'):
+ _preprocessing.append(('define', argument))
+ elif option in ('-E', '--execute'):
+ _preprocessing.append(('exec', argument))
+ elif option in ('-F', '--execute-file'):
+ _preprocessing.append(('file', argument))
+ elif option in ('-u', '--unicode'):
+ _unicode = True
+ elif option in ('--pause-at-end',):
+ _pauseAtEnd = True
+ elif option in ('--relative-path',):
+ _relativePath = True
+ elif option in ('--no-callback-error',):
+ _options[CALLBACK_OPT] = True
+ elif option in ('--no-bangpath-processing',):
+ _options[BANGPATH_OPT] = False
+ elif option in ('--unicode-encoding',):
+ _unicodeInputEncoding = _unicodeOutputEncoding = argument
+ elif option in ('--unicode-input-encoding',):
+ _unicodeInputEncoding = argument
+ elif option in ('--unicode-output-encoding',):
+ _unicodeOutputEncoding = argument
+ elif option in ('--unicode-errors',):
+ _unicodeInputErrors = _unicodeOutputErrors = argument
+ elif option in ('--unicode-input-errors',):
+ _unicodeInputErrors = argument
+ elif option in ('--unicode-output-errors',):
+ _unicodeOutputErrors = argument
+ # Set up the Unicode subsystem if required.
+ if _unicode or \
+ _unicodeInputEncoding or _unicodeOutputEncoding or \
+ _unicodeInputErrors or _unicodeOutputErrors:
+ theSubsystem.initialize(_unicodeInputEncoding, \
+ _unicodeOutputEncoding, \
+ _unicodeInputErrors, _unicodeOutputErrors)
+ # Now initialize the output file if something has already been selected.
+ if _output is not None:
+ _output = apply(AbstractFile, _output)
+ # Set up the main filename and the argument.
+ if not remainder:
+ remainder.append('-')
+ filename, arguments = remainder[0], remainder[1:]
+ # Set up the interpreter.
+ if _options[BUFFERED_OPT] and _output is None:
+ raise ValueError, "-b only makes sense with -o or -a arguments"
+ if _prefix == 'None':
+ _prefix = None
+ if _prefix and type(_prefix) is types.StringType and len(_prefix) != 1:
+ raise Error, "prefix must be single-character string"
+ interpreter = Interpreter(output=_output, \
+ argv=remainder, \
+ prefix=_prefix, \
+ pseudo=_pseudo, \
+ options=_options, \
+ hooks=_hooks)
+ try:
+ # Execute command-line statements.
+ i = 0
+ for which, thing in _preprocessing:
+ if which == 'pre':
+ command = interpreter.file
+ target = theSubsystem.open(thing, 'r')
+ name = thing
+ elif which == 'define':
+ command = interpreter.string
+ if string.find(thing, '=') >= 0:
+ target = '%s{%s}' % (_prefix, thing)
+ else:
+ target = '%s{%s = None}' % (_prefix, thing)
+ name = '<define:%d>' % i
+ elif which == 'exec':
+ command = interpreter.string
+ target = '%s{%s}' % (_prefix, thing)
+ name = '<exec:%d>' % i
+ elif which == 'file':
+ command = interpreter.string
+ name = '<file:%d (%s)>' % (i, thing)
+ target = '%s{execfile("""%s""")}' % (_prefix, thing)
+ elif which == 'import':
+ command = interpreter.string
+ name = '<import:%d>' % i
+ target = '%s{import %s}' % (_prefix, thing)
+ else:
+ assert 0
+ interpreter.wrap(command, (target, name))
+ i = i + 1
+ # Now process the primary file.
+ interpreter.ready()
+ if filename == '-':
+ if not _interactive:
+ name = '<stdin>'
+ path = ''
+ file = sys.stdin
+ else:
+ name, file = None, None
+ else:
+ name = filename
+ file = theSubsystem.open(filename, 'r')
+ path = os.path.split(filename)[0]
+ if _relativePath:
+ sys.path.insert(0, path)
+ if file is not None:
+ if _binary < 0:
+ interpreter.wrap(interpreter.file, (file, name))
+ else:
+ chunkSize = _binary
+ interpreter.wrap(interpreter.binary, (file, name, chunkSize))
+ # If we're supposed to go interactive afterwards, do it.
+ if _interactive:
+ interpreter.interact()
+ finally:
+ interpreter.shutdown()
+ # Finally, if we should pause at the end, do it.
+ if _pauseAtEnd:
+ try:
+ raw_input()
+ except EOFError:
+ pass
+
+def main():
+ invoke(sys.argv[1:])
+
+if __name__ == '__main__': main()