# HG changeset patch # User zzzeek # Date 1162837898 0 # Node ID ca91b55eb6df95ad3471acd6d27f1ea46395b3c6 # Parent 6d3a2ba56de13c78355f62f7235874c3ce659043 match/select functionality added. continued development of generators/streams/adaptation. diff --git a/genshi/codegen/adapters.py b/genshi/codegen/adapters.py --- a/genshi/codegen/adapters.py +++ b/genshi/codegen/adapters.py @@ -1,6 +1,8 @@ """a set of Inline adapters, which convert from inlined structures to Genshi core structures""" from genshi.path import Path +from genshi.core import Attrs +from genshi import template def get_attrib(attrib, name, default=None): """return an 'attribute' name from a list of tuples, similar to genshi.core.Attrib""" @@ -10,14 +12,10 @@ return default class InlineStream(object): - """works similarly to genshi.core.Stream""" - def __init__(self, generator, context): - self.code = generator.code - self.filters = generator.filters - self.context = context - self.stream = self.code.go(self.context) + def __init__(self, stream): + self.stream = stream def __iter__(self): - return list(self.stream) + return iter(self.stream) def __or__(self, function): return InlineStream(function(self)) @@ -36,13 +34,10 @@ return output def select(self, path, namespaces=None, variables=None): - return InlinedPath(path).select(self, namespaces, variables) + return InlinePath(path).select(self, namespaces, variables) def serialize(self, method='xml', **kwargs): - stream = self.stream - for filter_ in self.filters: - stream = filter_(stream) - for evt in stream: + for evt in self.stream: yield evt[3] def __str__(self): @@ -51,19 +46,67 @@ def __unicode__(self): return self.render(encoding=None) -class InlineQName(object): +class InlineEvent(object): + __eventtypes__ = {} + def __new__(cls, event): + return object.__new__(InlineEvent.__eventtypes__.get(event[0], InlineEvent), event) + def __init__(self, event): + self.event = event + def to_genshi(self): + return self.event[0:3] + +class InlineStartEvent(InlineEvent): + def to_genshi(self): + return (self.event[0], (InlineQName(self.event), Attrs(self.event[1][2])), self.event[2]) +InlineEvent.__eventtypes__[template.START] = InlineStartEvent + +class InlineQName(unicode): """creates a QName-like object from a START event""" - def __init__(self, event): + def __new__(cls, event): + self = unicode.__new__(cls, u'{%s}%s' % (event[1][0], event[1][1])) self.namespace = event[1][0] self.localname = event[1][1] + return self -class InlinedPath(Path): +class InlinePath(Path): """overrides Path.test to adapt incoming events from inlined to Genshi.""" def test(self, ignore_context=False): - t = super(InlinedPath, self).test(ignore_context=ignore_context) + t = super(InlinePath, self).test(ignore_context=ignore_context) def _test(event, namespaces, variables, updateonly=False): - if event[0] is START: - return t((event[0], (InlineQName(event), event[1][1]), event[2]), namespaces, variables, updateonly=updateonly) - else: - return t(event[0:3], namespaces, variables, updateonly=updateonly) + return t(InlineEvent(event).to_genshi(), namespaces, variables, updateonly=updateonly) return _test + def select(self, stream, namespaces=None, variables=None): + if namespaces is None: + namespaces = {} + if variables is None: + variables = {} + stream = iter(stream) + def _generate(): + test = self.test() + for event in stream: + result = test(event, namespaces, variables) + if result is True: + yield event + depth = 1 + while depth > 0: + subevent = stream.next() + if subevent[0] is template.START: + depth += 1 + elif subevent[0] is template.END: + depth -= 1 + yield subevent + test(subevent, namespaces, variables, updateonly=True) + # assume 3-tupled return events are just the event that was tested. + # TODO: Path tests will no longer return these ? + elif isinstance(result, tuple): + yield event + elif result: + # in genshi.path.Path, this could be an Attrs or a 3-tupled event. + # here, we only want Attrs to come out. + yield result + return InlineStream(_generate()) + + def __repr__(self): + return "InlinePath(%s, %s, %s)" % (repr(self.source), repr(self.filename), repr(self.lineno)) + + \ No newline at end of file diff --git a/genshi/codegen/generator.py b/genshi/codegen/generator.py --- a/genshi/codegen/generator.py +++ b/genshi/codegen/generator.py @@ -17,7 +17,7 @@ from genshi.path import Path from genshi.core import QName from genshi.codegen.printer import PythonPrinter, PYTHON_LINE, PYTHON_COMMENT, PYTHON_BLOCK -from genshi.codegen import serialize, adapters, output +from genshi.codegen import serialize, adapters, output, interp from compiler import ast, parse, visitor import sets, re @@ -55,6 +55,22 @@ return list(s.identifiers) ForDirectivePrinter() +class MatchDirectivePrinter(DirectivePrinter): + __directive__ = template.MatchDirective + def produce_directive(self, gencontext, directive, event, substream): + yield (PYTHON_LINE, "def do_match(select):") + for evt in gencontext.gen_stream(substream): + yield evt + yield (PYTHON_LINE, "") + index = gencontext.path_index_by_directive[directive] + yield (PYTHON_LINE, """context._match_templates.append( + (INLINE_PATHS[%d].test(ignore_context=True),INLINE_PATHS[%d], do_match, _namespaces) + )""" % (index, index ) + ) + def declared_identifiers(self, gencontext, directive, event): + return ['select'] +MatchDirectivePrinter() + class IfDirectivePrinter(DirectivePrinter): __directive__ = template.IfDirective def produce_directive(self, gencontext, directive, event, substream): @@ -103,6 +119,7 @@ 'xhtml': serialize.XHTMLSerializeFilter, 'html': serialize.HTMLSerializeFilter, 'text': serialize.TextSerializeFilter}[method]()) + self.code = self._generate_module() self.filters = filters or [] if strip_whitespace: @@ -116,8 +133,11 @@ assert isinstance(ctxt, Context) else: ctxt = Context(**kwargs) - - return adapters.InlineStream(self, ctxt) + + stream = interp._match(self.code.go(ctxt), ctxt) + for _filter in self.filters: + stream = _filter(stream) + return adapters.InlineStream(stream) def _generate_code_events(self): return PythonPrinter( @@ -152,10 +172,14 @@ self.serializer = serializer self.defs_without_params = sets.Set() self.defs = sets.Set() + self.path_index_by_directive = {} def generate(self): - for evt in self.start(): + for evt in self.produce_preamble(): yield evt stream = list(self.stream) + yield (PYTHON_LINE, "INLINE_PATHS = [%s]" % ','.join(["adapters." + repr(p) for p in self.find_paths(stream)])) + yield (PYTHON_LINE, "def go(context):") + yield (PYTHON_LINE, "_namespaces = {u'py': u'http://genshi.edgewall.org/'}") for expr in self.find_identifiers(stream, sets.ImmutableSet()): yield expr for evt in self.gen_stream(stream): @@ -163,6 +187,17 @@ for evt in self.end(): yield evt + def find_paths(self, stream): + preexec_paths = [] + for evt in stream: + if evt[0] is template.SUB: + for directive in evt[1][0]: + if isinstance(directive, template.MatchDirective): + ip = adapters.InlinePath(directive.path.source, directive.path.filename, directive.path.lineno) + preexec_paths.append(ip) + self.path_index_by_directive[directive] = len(preexec_paths) -1 + return preexec_paths + def find_identifiers(self, stream, stack): """locate undeclared python identifiers in the given stream. stack is an empty set.""" for evt in stream: @@ -201,12 +236,18 @@ elif kind is template.TEXT: for evt in self.produce_text_event(event): yield evt + elif kind is template.START_NS: + for evt in self.produce_start_ns_event(event): + yield evt + elif kind is template.END_NS: + for evt in self.produce_end_ns_event(event): + yield evt def produce_preamble(self): for line in [ "from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT, DOCTYPE, Stream", "from genshi.template import Context, Template", "from genshi.path import Path", - "from genshi.codegen import interp", + "from genshi.codegen import interp, adapters", "EXPR = Template.EXPR" ]: yield (PYTHON_LINE, line) @@ -217,10 +258,6 @@ def produce_directive(self, directive, event, substream): for evt in _directive_printers[directive.__class__].produce_directive(self, directive, event, substream): yield evt - def start(self): - for evt in self.produce_preamble(): - yield evt - yield (PYTHON_LINE, "def go(context):") def end(self): yield (PYTHON_LINE, "") def produce_start_event(self, event): @@ -254,4 +291,20 @@ repr(event[2]), repr(unicode(event[3])) )) + + def produce_start_ns_event(self, event): + yield (PYTHON_LINE, "yield (START_NS, (%s), %s, %s)" % ( + repr(unicode(event[1])), + repr(event[2]), + repr(unicode('')) + )) + yield (PYTHON_LINE, "_namespaces[%s] = %s" % (repr(event[1][0]), repr(unicode(event[1][1])))) + def produce_end_ns_event(self, event): + yield (PYTHON_LINE, "del _namespaces[%s]" % (repr(event[1]))) + yield (PYTHON_LINE, "yield (START_NS, (%s), %s, %s)" % ( + repr(unicode(event[1])), + repr(event[2]), + repr(unicode('')) + )) + diff --git a/genshi/codegen/interp.py b/genshi/codegen/interp.py --- a/genshi/codegen/interp.py +++ b/genshi/codegen/interp.py @@ -1,6 +1,9 @@ +"""defines resources available to generated modules""" + from genshi.template import MarkupTemplate, Template, Context from genshi.path import Path from genshi.output import HTMLSerializer +from genshi.codegen import adapters from compiler import ast, parse, visitor from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT, DOCTYPE, QName, Stream EXPR = Template.EXPR @@ -33,6 +36,7 @@ break for event in stream: +# print "TESTING EVENT", event if not match_templates or (event[0] is not START and event[0] is not END): yield event @@ -52,22 +56,16 @@ #for filter_ in self.filters[3:]: # content = filter_(content, ctxt) content = list(content) - + for test in [mt[0] for mt in match_templates]: test(tail[0][0:3], namespaces, ctxt, updateonly=True) def select(path): - return render_inline(InlinedPath(path).select(Stream(content), namespaces, ctxt)) + return adapters.InlinePath(path).select(Stream(content), namespaces, ctxt) - # ctxt.push()/pop() is usually not going to be necessary for inlined execution - # since python scopes will handle most pushing/popping - ctxt.push(dict(select=select)) - # similarly, no need for _eval (eval is inlined) as well as _flatten (inlined code already "flattened") - for event in _match(template, ctxt, match_templates[:idx] + match_templates[idx + 1:]): + for event in _match(template(select), ctxt, match_templates[:idx] + match_templates[idx + 1:]): yield event - - ctxt.pop() break else: yield event @@ -82,7 +80,3 @@ else: yield TEXT, unicode(result), pos, result -def run_inlined(module, data): - context = Context(**data) - for item in _match(module.go(context), context): - yield item[3] diff --git a/genshi/codegen/output.py b/genshi/codegen/output.py --- a/genshi/codegen/output.py +++ b/genshi/codegen/output.py @@ -1,3 +1,4 @@ +"""output filters that apply to inline-generated streams""" from itertools import chain try: frozenset diff --git a/genshi/codegen/tests/template.py b/genshi/codegen/tests/template.py --- a/genshi/codegen/tests/template.py +++ b/genshi/codegen/tests/template.py @@ -13,6 +13,7 @@ class MarkupTemplateAdapter(object): def __init__(self, text): self.generator = Generator(RealMarkupTemplate(text), strip_whitespace=True) + print u''.join(self.generator._generate_code_events()) def generate(self, *args, **kwargs): return self.generator.generate(*args, **kwargs) diff --git a/genshi/codegen/tests/test_generator.py b/genshi/codegen/tests/test_generator.py --- a/genshi/codegen/tests/test_generator.py +++ b/genshi/codegen/tests/test_generator.py @@ -25,6 +25,11 @@ xmlns:xi="http://www.w3.org/2001/XInclude" lang="en"> + + + matched the message, which was ${select('*|text()')} + +
${lala + 'hi'}
@@ -55,7 +60,8 @@ data = {'lala':'hi', 'items':lambda:["one", "two", "three"], 'foo':['f1', 'f2', 'f3']} t = MarkupTemplate(text) -print t.generate(**data).render() +#print t.generate(**data).render() +#sys.exit() g = generator.Generator(t) pycode = u''.join(g._generate_code_events()) @@ -63,6 +69,7 @@ print str(g.generate(**data)) + print "Running MarkupTemplate.generate()/HTMLSerializer..." now = time.time() for x in range(1,1000): diff --git a/genshi/path.py b/genshi/path.py --- a/genshi/path.py +++ b/genshi/path.py @@ -78,6 +78,8 @@ @param text: the path expression """ self.source = text + self.filename = filename + self.lineno = lineno self.paths = PathParser(text, filename, lineno).parse() def __repr__(self): @@ -200,7 +202,6 @@ # The node test matched if matched: - # Check all the predicates for this step if predicates: for predicate in predicates: @@ -266,7 +267,6 @@ cutoff[:] = [] break cursors[-1] = cursor - return retval return _test