Edgewall Software

Ticket #190: match_unbuffered3.diff

File match_unbuffered3.diff, 18.7 kB (added by cmlenz, 7 months ago)

Unbuffered match templates, almost complete this time

  • genshi/template/base.py

     
    254254        """Pop the top-most scope from the stack.""" 
    255255 
    256256 
    257 def _apply_directives(stream, ctxt, directives): 
     257def _apply_directives(stream, directives, ctxt, **bind): 
    258258    """Apply the given directives to the stream. 
    259259     
    260260    :param stream: the stream the directives should be applied to 
     
    263263    :return: the stream with the given directives applied 
    264264    """ 
    265265    if directives: 
    266         stream = directives[0](iter(stream), ctxt, directives[1:]) 
     266        stream = directives[0](iter(stream), directives[1:], ctxt, **bind) 
    267267    return stream 
    268268 
    269269 
     
    426426        :return: a markup event stream representing the result of applying 
    427427                 the template to the context data. 
    428428        """ 
     429        bind = {} 
    429430        if args: 
    430431            assert len(args) == 1 
    431432            ctxt = args[0] 
    432433            if ctxt is None: 
    433434                ctxt = Context(**kwargs) 
     435            else: 
     436                bind = kwargs 
    434437            assert isinstance(ctxt, Context) 
    435438        else: 
    436439            ctxt = Context(**kwargs) 
    437440 
    438441        stream = self.stream 
    439442        for filter_ in self.filters: 
    440             stream = filter_(iter(stream), ctxt) 
     443            stream = filter_(iter(stream), ctxt, **bind) 
    441444        return Stream(stream, self.serializer) 
    442445 
    443     def _eval(self, stream, ctxt): 
     446    def _eval(self, stream, ctxt, **bind): 
    444447        """Internal stream filter that evaluates any expressions in `START` and 
    445448        `TEXT` events. 
    446449        """ 
     
    460463                    else: 
    461464                        values = [] 
    462465                        for subkind, subdata, subpos in self._eval(substream, 
    463                                                                    ctxt): 
     466                                                                   ctxt, 
     467                                                                   **bind): 
    464468                            if subkind is TEXT: 
    465469                                values.append(subdata) 
    466470                        value = [x for x in values if x is not None] 
     
    470474                yield kind, (tag, Attrs(new_attrs)), pos 
    471475 
    472476            elif kind is EXPR: 
     477                if bind: ctxt.push(bind) 
    473478                result = data.evaluate(ctxt) 
     479                if bind: ctxt.pop() 
    474480                if result is not None: 
    475481                    # First check for a string, otherwise the iterable test 
    476482                    # below succeeds, and the string will be chopped up into 
     
    482488                    elif hasattr(result, '__iter__'): 
    483489                        substream = _ensure(result) 
    484490                        for filter_ in filters: 
    485                             substream = filter_(substream, ctxt) 
     491                            substream = filter_(substream, ctxt, **bind) 
    486492                        for event in substream: 
    487493                            yield event 
    488494                    else: 
     
    491497            else: 
    492498                yield kind, data, pos 
    493499 
    494     def _exec(self, stream, ctxt): 
     500    def _exec(self, stream, ctxt, **bind): 
    495501        """Internal stream filter that executes Python code blocks.""" 
    496502        for event in stream: 
    497503            if event[0] is EXEC: 
     504                if bind: ctxt.push(bind) 
    498505                event[1].execute(_ctxt2dict(ctxt)) 
     506                if bind: ctxt.pop() 
    499507            else: 
    500508                yield event 
    501509 
    502     def _flatten(self, stream, ctxt): 
     510    def _flatten(self, stream, ctxt, **bind): 
    503511        """Internal stream filter that expands `SUB` events in the stream.""" 
    504512        for event in stream: 
    505513            if event[0] is SUB: 
    506514                # This event is a list of directives and a list of nested 
    507515                # events to which those directives should be applied 
    508516                directives, substream = event[1] 
    509                 substream = _apply_directives(substream, ctxt, directives) 
    510                 for event in self._flatten(substream, ctxt): 
     517                substream = _apply_directives(substream, directives, ctxt, 
     518                                              **bind) 
     519                for event in self._flatten(substream, ctxt, **bind): 
    511520                    yield event 
    512521            else: 
    513522                yield event 
    514523 
    515     def _include(self, stream, ctxt): 
     524    def _include(self, stream, ctxt, **bind): 
    516525        """Internal stream filter that performs inclusion of external 
    517526        template files. 
    518527        """ 
     
    523532                href, cls, fallback = event[1] 
    524533                if not isinstance(href, basestring): 
    525534                    parts = [] 
    526                     for subkind, subdata, subpos in self._eval(href, ctxt): 
     535                    for subkind, subdata, subpos in self._eval(href, ctxt, 
     536                                                               **bind): 
    527537                        if subkind is TEXT: 
    528538                            parts.append(subdata) 
    529539                    href = u''.join([x for x in parts if x is not None]) 
    530540                try: 
    531541                    tmpl = self.loader.load(href, relative_to=event[2][0], 
    532542                                            cls=cls or self.__class__) 
    533                     for event in tmpl.generate(ctxt): 
     543                    for event in tmpl.generate(ctxt, **bind): 
    534544                        yield event 
    535545                except TemplateNotFound: 
    536546                    if fallback is None: 
    537547                        raise 
    538548                    for filter_ in self.filters: 
    539                         fallback = filter_(iter(fallback), ctxt) 
     549                        fallback = filter_(iter(fallback), ctxt, **bind) 
    540550                    for event in fallback: 
    541551                        yield event 
    542552            else: 
  • genshi/template/tests/directives.py

     
    631631          </body> 
    632632        </html>""", str(tmpl.generate())) 
    633633 
     634    def test_recursive_match_3(self): 
     635        tmpl = MarkupTemplate("""<test xmlns:py="http://genshi.edgewall.org/"> 
     636          <py:match path="b[@type='bullet']"> 
     637            <bullet>${select('*|text()')}</bullet> 
     638          </py:match> 
     639          <py:match path="group[@type='bullet']"> 
     640            <ul>${select('*')}</ul> 
     641          </py:match> 
     642          <py:match path="b"> 
     643            <generic>${select('*|text()')}</generic> 
     644          </py:match> 
     645 
     646          <b> 
     647            <group type="bullet"> 
     648              <b type="bullet">1</b> 
     649              <b type="bullet">2</b> 
     650            </group> 
     651          </b> 
     652        </test> 
     653        """) 
     654        self.assertEqual("""<test> 
     655            <generic> 
     656            <ul><bullet>1</bullet><bullet>2</bullet></ul> 
     657          </generic> 
     658        </test>""", str(tmpl.generate())) 
     659 
    634660    def test_not_match_self(self): 
    635661        """ 
    636662        See http://genshi.edgewall.org/ticket/77 
  • genshi/template/markup.py

     
    225225        assert len(streams) == 1 
    226226        return streams[0] 
    227227 
    228     def _match(self, stream, ctxt, match_templates=None): 
     228    def _match(self, stream, ctxt, match_templates=None, **bind): 
    229229        """Internal stream filter that applies any defined match templates 
    230230        to the stream. 
    231231        """ 
     
    272272                    # Consume and store all events until an end event 
    273273                    # corresponding to this start event is encountered 
    274274                    inner = _strip(stream) 
    275                     if 'match_once' not in hints \ 
    276                             and 'not_recursive' not in hints: 
    277                         inner = self._match(inner, ctxt, [match_templates[idx]]) 
    278                     content = list(self._include(chain([event], inner, tail), 
    279                                                  ctxt)) 
     275                    pre_match_templates = match_templates[:idx + 1] 
     276                    if 'match_once' not in hints and 'not_recursive' in hints: 
     277                        pre_match_templates.pop() 
     278                    inner = self._match(inner, ctxt, pre_match_templates, 
     279                                        **bind) 
     280                    content = self._include(chain([event], inner, tail), 
     281                                            ctxt, **bind) 
     282                    if 'not_buffered' not in hints: 
     283                        content = list(content) 
    280284 
    281285                    for test in [mt[0] for mt in match_templates]: 
    282286                        test(tail[0], namespaces, ctxt, updateonly=True) 
     
    285289                    # match template 
    286290                    def select(path): 
    287291                        return Stream(content).select(path, namespaces, ctxt) 
    288                     ctxt.push(dict(select=select)) 
     292                    bind = dict(select=select) 
    289293 
    290294                    # Recursively process the output 
    291                     template = _apply_directives(template, ctxt, directives) 
     295                    template = _apply_directives(template, directives, ctxt, 
     296                                                 **bind) 
    292297                    remaining = match_templates 
    293                     if 'match_once' not in hints: 
    294                         remaining = remaining[:idx] + remaining[idx + 1:] 
    295                     for event in self._match(self._exec( 
    296                                     self._eval(self._flatten(template, ctxt), 
    297                                     ctxt), ctxt), ctxt, remaining): 
     298                    for event in self._match( 
     299                            self._exec( 
     300                                self._eval( 
     301                                    self._flatten(template, ctxt, **bind), 
     302                                    ctxt, **bind), 
     303                                ctxt, **bind), 
     304                            ctxt, match_templates[idx + 1:], **bind): 
    298305                        yield event 
    299306 
    300                     ctxt.pop() 
    301307                    break 
    302308 
    303309            else: # no matches 
  • genshi/template/directives.py

     
    8888        return cls(value, template, namespaces, *pos[1:]), stream 
    8989    attach = classmethod(attach) 
    9090 
    91     def __call__(self, stream, ctxt, directives): 
     91    def __call__(self, stream, directives, ctxt, **bind): 
    9292        """Apply the directive to the given stream. 
    9393         
    9494        :param stream: the event stream 
    95         :param ctxt: the context data 
    9695        :param directives: a list of the remaining directives that should 
    9796                           process the stream 
     97        :param ctxt: the context data 
    9898        """ 
    9999        raise NotImplementedError 
    100100 
     
    167167    """ 
    168168    __slots__ = [] 
    169169 
    170     def __call__(self, stream, ctxt, directives): 
     170    def __call__(self, stream, directives, ctxt, **bind): 
    171171        def _generate(): 
    172172            kind, (tag, attrib), pos  = stream.next() 
     173            if bind: ctxt.push(bind) 
    173174            attrs = self.expr.evaluate(ctxt) 
     175            if bind: ctxt.pop() 
    174176            if attrs: 
    175177                if isinstance(attrs, Stream): 
    176178                    try: 
     
    186188            for event in stream: 
    187189                yield event 
    188190 
    189         return _apply_directives(_generate(), ctxt, directives) 
     191        return _apply_directives(_generate(), directives, ctxt, **bind) 
    190192 
    191193 
    192194class ContentDirective(Directive): 
     
    291293                                               namespaces, pos) 
    292294    attach = classmethod(attach) 
    293295 
    294     def __call__(self, stream, ctxt, directives): 
     296    def __call__(self, stream, directives, ctxt, **bind): 
    295297        stream = list(stream) 
    296298 
    297299        def function(*args, **kwargs): 
    298300            scope = {} 
     301            if bind: ctxt.push(bind) 
    299302            args = list(args) # make mutable 
    300303            for name in self.args: 
    301304                if args: 
     
    306309                    else: 
    307310                        val = self.defaults.get(name).evaluate(ctxt) 
    308311                    scope[name] = val 
     312            if bind: ctxt.pop() 
    309313            if not self.star_args is None: 
    310314                scope[self.star_args] = args 
    311315            if not self.dstar_args is None: 
    312316                scope[self.dstar_args] = kwargs 
    313317            ctxt.push(scope) 
    314             for event in _apply_directives(stream, ctxt, directives): 
     318            for event in _apply_directives(stream, directives, ctxt, **bind): 
    315319                yield event 
    316320            ctxt.pop() 
    317321        try: 
     
    364368                                               namespaces, pos) 
    365369    attach = classmethod(attach) 
    366370 
    367     def __call__(self, stream, ctxt, directives): 
     371    def __call__(self, stream, directives, ctxt, **bind): 
     372        if bind: ctxt.push(bind) 
    368373        iterable = self.expr.evaluate(ctxt) 
     374        if bind: ctxt.pop() 
    369375        if iterable is None: 
    370376            return 
    371377 
     
    375381        for item in iterable: 
    376382            assign(scope, item) 
    377383            ctxt.push(scope) 
    378             for event in _apply_directives(stream, ctxt, directives): 
     384            for event in _apply_directives(stream, directives, ctxt, **bind): 
    379385                yield event 
    380386            ctxt.pop() 
    381387 
     
    405411                                              namespaces, pos) 
    406412    attach = classmethod(attach) 
    407413 
    408     def __call__(self, stream, ctxt, directives): 
    409         if self.expr.evaluate(ctxt): 
    410             return _apply_directives(stream, ctxt, directives) 
     414    def __call__(self, stream, directives, ctxt, **bind): 
     415        if bind: ctxt.push(bind) 
     416        value = self.expr.evaluate(ctxt) 
     417        if bind: ctxt.pop() 
     418        if value: 
     419            return _apply_directives(stream, directives, ctxt, **bind) 
    411420        return [] 
    412421 
    413422 
     
    440449    def attach(cls, template, stream, value, namespaces, pos): 
    441450        hints = [] 
    442451        if type(value) is dict: 
     452            if value.get('buffer', '').lower() == 'false': 
     453                hints.append('not_buffered') 
    443454            if value.get('once', '').lower() == 'true': 
    444455                hints.append('match_once') 
    445456            if value.get('recursive', '').lower() == 'false': 
     
    449460               stream 
    450461    attach = classmethod(attach) 
    451462 
    452     def __call__(self, stream, ctxt, directives): 
     463    def __call__(self, stream, directives, ctxt, **bind): 
    453464        ctxt._match_templates.append((self.path.test(ignore_context=True), 
    454465                                      self.path, list(stream), self.hints, 
    455466                                      self.namespaces, directives)) 
     
    531542    """ 
    532543    __slots__ = [] 
    533544 
    534     def __call__(self, stream, ctxt, directives): 
     545    def __call__(self, stream, directives, ctxt, **bind): 
    535546        def _generate(): 
    536547            if self.expr.evaluate(ctxt): 
    537548                stream.next() # skip start tag 
     
    542553            else: 
    543554                for event in stream: 
    544555                    yield event 
    545         return _apply_directives(_generate(), ctxt, directives) 
     556        return _apply_directives(_generate(), directives, ctxt, **bind) 
    546557 
    547558    def attach(cls, template, stream, value, namespaces, pos): 
    548559        if not value: 
     
    600611                                                  namespaces, pos) 
    601612    attach = classmethod(attach) 
    602613 
    603     def __call__(self, stream, ctxt, directives): 
     614    def __call__(self, stream, directives, ctxt, **bind): 
    604615        info = [False, bool(self.expr), None] 
    605616        if self.expr: 
     617            if bind: ctxt.push(bind) 
    606618            info[2] = self.expr.evaluate(ctxt) 
     619            if bind: ctxt.pop() 
    607620        ctxt._choice_stack.append(info) 
    608         for event in _apply_directives(stream, ctxt, directives): 
     621        for event in _apply_directives(stream, directives, ctxt, **bind): 
    609622            yield event 
    610623        ctxt._choice_stack.pop() 
    611624 
     
    629642                                                namespaces, pos) 
    630643    attach = classmethod(attach) 
    631644 
    632     def __call__(self, stream, ctxt, directives): 
     645    def __call__(self, stream, directives, ctxt, **bind): 
    633646        info = ctxt._choice_stack and ctxt._choice_stack[-1] 
    634647        if not info: 
    635648            raise TemplateRuntimeError('"when" directives can only be used ' 
     
    644657        if info[1]: 
    645658            value = info[2] 
    646659            if self.expr: 
     660                if bind: ctxt.push(bind) 
    647661                matched = value == self.expr.evaluate(ctxt) 
     662                if bind: ctxt.pop() 
    648663            else: 
    649664                matched = bool(value) 
    650665        else: 
     
    653668        if not matched: 
    654669            return [] 
    655670 
    656         return _apply_directives(stream, ctxt, directives) 
     671        return _apply_directives(stream, directives, ctxt, **bind) 
    657672 
    658673 
    659674class OtherwiseDirective(Directive): 
     
    668683        Directive.__init__(self, None, template, namespaces, lineno, offset) 
    669684        self.filename = template.filepath 
    670685 
    671     def __call__(self, stream, ctxt, directives): 
     686    def __call__(self, stream, directives, ctxt, **bind): 
    672687        info = ctxt._choice_stack and ctxt._choice_stack[-1] 
    673688        if not info: 
    674689            raise TemplateRuntimeError('an "otherwise" directive can only be ' 
     
    678693            return [] 
    679694        info[0] = True 
    680695 
    681         return _apply_directives(stream, ctxt, directives) 
     696        return _apply_directives(stream, directives, ctxt, **bind) 
    682697 
    683698 
    684699class WithDirective(Directive): 
     
    722737                                                namespaces, pos) 
    723738    attach = classmethod(attach) 
    724739 
    725     def __call__(self, stream, ctxt, directives): 
     740    def __call__(self, stream, directives, ctxt, **bind): 
    726741        frame = {} 
    727742        ctxt.push(frame) 
     743        if bind: ctxt.push(bind) 
    728744        self.suite.execute(_ctxt2dict(ctxt)) 
    729         for event in _apply_directives(stream, ctxt, directives): 
     745        if bind: ctxt.pop() 
     746        for event in _apply_directives(stream, directives, ctxt, **bind): 
    730747            yield event 
    731748        ctxt.pop() 
    732749 
  • genshi/template/text.py

     
    3333                                 TemplateSyntaxError, EXEC, INCLUDE, SUB 
    3434from genshi.template.eval import Suite 
    3535from genshi.template.directives import * 
    36 from genshi.template.directives import Directive, _apply_directives 
     36from genshi.template.directives import Directive 
    3737from genshi.template.interpolation import interpolate 
    3838 
    3939__all__ = ['NewTextTemplate', 'OldTextTemplate', 'TextTemplate']