From 669c4259dc8a5628121f147e01b200d4be672c13 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Fri, 11 May 2007 10:33:14 +0000 Subject: Fix distcheck --- diff --git a/.gitignore b/.gitignore index 6e3a9e5..62d48ae 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ services/console/org.laptop.sugar.Console.service services/presence/org.laptop.Sugar.Presence.service bin/sugar shell/extensions/_extensions.c +data/sugar.gtkrc +data/sugar-xo.gtkrc diff --git a/data/Makefile.am b/data/Makefile.am index ebfd12a..830030a 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -1,8 +1,10 @@ sugar.gtkrc: gtkrc.em - $(srcdir)/em.py -D theme=\'sugar\' $(srcdir)/gtkrc.em > $(top_builddir)/data/sugar.gtkrc + $(srcdir)/em.py -D theme=\'sugar\' $(srcdir)/gtkrc.em > \ + $(top_builddir)/data/sugar.gtkrc sugar-xo.gtkrc: gtkrc.em - $(srcdir)/em.py -D theme=\'sugar-xo\' $(srcdir)/gtkrc.em > $(top_builddir)/data/sugar-xo.gtkrc + $(srcdir)/em.py -D theme=\'sugar-xo\' $(srcdir)/gtkrc.em > \ + $(top_builddir)/data/sugar-xo.gtkrc sugardir = $(pkgdatadir)/data sugar_DATA = \ @@ -14,5 +16,5 @@ GTKRC_FILES = \ sugar.gtkrc \ sugar-xo.gtkrc -EXTRA_DIST = $(sugar_DATA) +EXTRA_DIST = $(sugar_DATA) em.py gtkrc.em CLEANFILES = $(GTKRC_FILES) diff --git a/data/em.py b/data/em.py new file mode 100755 index 0000000..9f92124 --- /dev/null +++ b/data/em.py @@ -0,0 +1,3288 @@ +#!/usr/bin/env 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 ' +__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=", "Change prefix to something other than @"), +(" --no-prefix", "Do not do any markup processing at all"), +("-m --module=", "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=", "Specify file for output as write"), +("-a --append=", "Specify file for output as append"), +("-b --buffered-output", "Fully buffer output including open"), +(" --binary", "Treat the file as a binary"), +(" --chunk-size=", "Use this chunk size for reading binaries"), +("-P --preprocess=", "Interpret EmPy file before main processing"), +("-I --import=", "Import Python modules before processing"), +("-D --define=", "Execute Python assignment statement"), +("-E --execute=", "Execute Python statement before processing"), +("-F --execute-file=", "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=", "Set both input and output encodings"), +(" --unicode-input-encoding=", "Set input encoding"), +(" --unicode-output-encoding=", "Set output encoding"), +(" --unicode-errors=", "Set both input and output error handler"), +(" --unicode-input-errors=", "Set input error handler"), +(" --unicode-output-errors=", "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 "), +(PSEUDO_ENV, "Specify name of pseudomodule: -m "), +(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, '', 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, '')) + 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='', 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='', 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='', 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') + 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='', 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] [ [...]] +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 = '' % i + elif which == 'exec': + command = interpreter.string + target = '%s{%s}' % (_prefix, thing) + name = '' % i + elif which == 'file': + command = interpreter.string + name = '' % (i, thing) + target = '%s{execfile("""%s""")}' % (_prefix, thing) + elif which == 'import': + command = interpreter.string + name = '' % 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 = '' + 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() diff --git a/data/gtkrc.em b/data/gtkrc.em new file mode 100644 index 0000000..0c474ea --- /dev/null +++ b/data/gtkrc.em @@ -0,0 +1,14 @@ +@{ +if theme == 'sugar': + font_name = 'Sans Serif 10' + icon_sizes = 'gtk-large-toolbar=32,32' +else: + font_name = 'Sans Serif 7' + icon_sizes = 'gtk-large-toolbar=55,55' +}@ +gtk-theme-name = "sugar" +gtk-icon-theme-name = "sugar" +gtk-font-name = "@font_name" +gtk-cursor-theme-name = "sugar" +gtk-toolbar-style = GTK_TOOLBAR_ICONS +gtk-icon-sizes = "@icon_sizes" diff --git a/po/POTFILES.in b/po/POTFILES.in index f6d90c4..47f0f9c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -3,4 +3,3 @@ shell/intro/intro.py shell/view/BuddyMenu.py shell/view/clipboardmenu.py shell/view/frame/ZoomBox.py -sugar/graphics/optionmenu.py -- cgit v0.9.1