root/galaxy-central/eggs/Paste-1.6-py2.6.egg/paste/util/template.py @ 3

リビジョン 3, 23.7 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1"""
2A small templating language
3
4This implements a small templating language for use internally in
5Paste and Paste Script.  This language implements if/elif/else,
6for/continue/break, expressions, and blocks of Python code.  The
7syntax is::
8
9  {{any expression (function calls etc)}}
10  {{any expression | filter}}
11  {{for x in y}}...{{endfor}}
12  {{if x}}x{{elif y}}y{{else}}z{{endif}}
13  {{py:x=1}}
14  {{py:
15  def foo(bar):
16      return 'baz'
17  }}
18  {{default var = default_value}}
19  {{# comment}}
20
21You use this with the ``Template`` class or the ``sub`` shortcut.
22The ``Template`` class takes the template string and the name of
23the template (for errors) and a default namespace.  Then (like
24``string.Template``) you can call the ``tmpl.substitute(**kw)``
25method to make a substitution (or ``tmpl.substitute(a_dict)``).
26
27``sub(content, **kw)`` substitutes the template immediately.  You
28can use ``__name='tmpl.html'`` to set the name of the template.
29
30If there are syntax errors ``TemplateError`` will be raised.
31"""
32
33import re
34import sys
35import cgi
36import urllib
37from paste.util.looper import looper
38
39__all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate',
40           'sub_html', 'html', 'bunch']
41
42token_re = re.compile(r'\{\{|\}\}')
43in_re = re.compile(r'\s+in\s+')
44var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
45
46class TemplateError(Exception):
47    """Exception raised while parsing a template
48    """
49
50    def __init__(self, message, position, name=None):
51        self.message = message
52        self.position = position
53        self.name = name
54
55    def __str__(self):
56        msg = '%s at line %s column %s' % (
57            self.message, self.position[0], self.position[1])
58        if self.name:
59            msg += ' in %s' % self.name
60        return msg
61
62class _TemplateContinue(Exception):
63    pass
64
65class _TemplateBreak(Exception):
66    pass
67
68class Template(object):
69
70    default_namespace = {
71        'start_braces': '{{',
72        'end_braces': '}}',
73        'looper': looper,
74        }
75
76    default_encoding = 'utf8'
77
78    def __init__(self, content, name=None, namespace=None):
79        self.content = content
80        self._unicode = isinstance(content, unicode)
81        self.name = name
82        self._parsed = parse(content, name=name)
83        if namespace is None:
84            namespace = {}
85        self.namespace = namespace
86
87    def from_filename(cls, filename, namespace=None, encoding=None):
88        f = open(filename, 'rb')
89        c = f.read()
90        f.close()
91        if encoding:
92            c = c.decode(encoding)
93        return cls(content=c, name=filename, namespace=namespace)
94
95    from_filename = classmethod(from_filename)
96
97    def __repr__(self):
98        return '<%s %s name=%r>' % (
99            self.__class__.__name__,
100            hex(id(self))[2:], self.name)
101
102    def substitute(self, *args, **kw):
103        if args:
104            if kw:
105                raise TypeError(
106                    "You can only give positional *or* keyword arguments")
107            if len(args) > 1:
108                raise TypeError(
109                    "You can only give on positional argument")
110            kw = args[0]
111        ns = self.default_namespace.copy()
112        ns.update(self.namespace)
113        ns.update(kw)
114        result = self._interpret(ns)
115        return result
116
117    def _interpret(self, ns):
118        __traceback_hide__ = True
119        parts = []
120        self._interpret_codes(self._parsed, ns, out=parts)
121        return ''.join(parts)
122
123    def _interpret_codes(self, codes, ns, out):
124        __traceback_hide__ = True
125        for item in codes:
126            if isinstance(item, basestring):
127                out.append(item)
128            else:
129                self._interpret_code(item, ns, out)
130
131    def _interpret_code(self, code, ns, out):
132        __traceback_hide__ = True
133        name, pos = code[0], code[1]
134        if name == 'py':
135            self._exec(code[2], ns, pos)
136        elif name == 'continue':
137            raise _TemplateContinue()
138        elif name == 'break':
139            raise _TemplateBreak()
140        elif name == 'for':
141            vars, expr, content = code[2], code[3], code[4]
142            expr = self._eval(expr, ns, pos)
143            self._interpret_for(vars, expr, content, ns, out)
144        elif name == 'cond':
145            parts = code[2:]
146            self._interpret_if(parts, ns, out)
147        elif name == 'expr':
148            parts = code[2].split('|')
149            base = self._eval(parts[0], ns, pos)
150            for part in parts[1:]:
151                func = self._eval(part, ns, pos)
152                base = func(base)
153            out.append(self._repr(base, pos))
154        elif name == 'default':
155            var, expr = code[2], code[3]
156            if var not in ns:
157                result = self._eval(expr, ns, pos)
158                ns[var] = result
159        elif name == 'comment':
160            return
161        else:
162            assert 0, "Unknown code: %r" % name
163
164    def _interpret_for(self, vars, expr, content, ns, out):
165        __traceback_hide__ = True
166        for item in expr:
167            if len(vars) == 1:
168                ns[vars[0]] = item
169            else:
170                if len(vars) != len(item):
171                    raise ValueError(
172                        'Need %i items to unpack (got %i items)'
173                        % (len(vars), len(item)))
174                for name, value in zip(vars, item):
175                    ns[name] = value
176            try:
177                self._interpret_codes(content, ns, out)
178            except _TemplateContinue:
179                continue
180            except _TemplateBreak:
181                break
182
183    def _interpret_if(self, parts, ns, out):
184        __traceback_hide__ = True
185        # @@: if/else/else gets through
186        for part in parts:
187            assert not isinstance(part, basestring)
188            name, pos = part[0], part[1]
189            if name == 'else':
190                result = True
191            else:
192                result = self._eval(part[2], ns, pos)
193            if result:
194                self._interpret_codes(part[3], ns, out)
195                break
196
197    def _eval(self, code, ns, pos):
198        __traceback_hide__ = True
199        try:
200            value = eval(code, ns)
201            return value
202        except:
203            exc_info = sys.exc_info()
204            e = exc_info[1]
205            if getattr(e, 'args'):
206                arg0 = e.args[0]
207            else:
208                arg0 = str(e)
209            e.args = (self._add_line_info(arg0, pos),)
210            raise exc_info[0], e, exc_info[2]
211
212    def _exec(self, code, ns, pos):
213        __traceback_hide__ = True
214        try:
215            exec code in ns
216        except:
217            exc_info = sys.exc_info()
218            e = exc_info[1]
219            e.args = (self._add_line_info(e.args[0], pos),)
220            raise exc_info[0], e, exc_info[2]
221
222    def _repr(self, value, pos):
223        __traceback_hide__ = True
224        try:
225            if value is None:
226                return ''
227            if self._unicode:
228                try:
229                    value = unicode(value)
230                except UnicodeDecodeError:
231                    value = str(value)
232            else:
233                value = str(value)
234        except:
235            exc_info = sys.exc_info()
236            e = exc_info[1]
237            e.args = (self._add_line_info(e.args[0], pos),)
238            raise exc_info[0], e, exc_info[2]
239        else:
240            if self._unicode and isinstance(value, str):
241                if not self.decode_encoding:
242                    raise UnicodeDecodeError(
243                        'Cannot decode str value %r into unicode '
244                        '(no default_encoding provided)' % value)
245                value = value.decode(self.default_encoding)
246            elif not self._unicode and isinstance(value, unicode):
247                if not self.decode_encoding:
248                    raise UnicodeEncodeError(
249                        'Cannot encode unicode value %r into str '
250                        '(no default_encoding provided)' % value)
251                value = value.encode(self.default_encoding)
252            return value
253       
254
255    def _add_line_info(self, msg, pos):
256        msg = "%s at line %s column %s" % (
257            msg, pos[0], pos[1])
258        if self.name:
259            msg += " in file %s" % self.name
260        return msg
261
262def sub(content, **kw):
263    name = kw.get('__name')
264    tmpl = Template(content, name=name)
265    return tmpl.substitute(kw)
266    return result
267
268def paste_script_template_renderer(content, vars, filename=None):
269    tmpl = Template(content, name=filename)
270    return tmpl.substitute(vars)
271
272class bunch(dict):
273
274    def __init__(self, **kw):
275        for name, value in kw.items():
276            setattr(self, name, value)
277
278    def __setattr__(self, name, value):
279        self[name] = value
280
281    def __getattr__(self, name):
282        try:
283            return self[name]
284        except KeyError:
285            raise AttributeError(name)
286
287    def __getitem__(self, key):
288        if 'default' in self:
289            try:
290                return dict.__getitem__(self, key)
291            except KeyError:
292                return dict.__getitem__(self, 'default')
293        else:
294            return dict.__getitem__(self, key)
295
296    def __repr__(self):
297        items = [
298            (k, v) for k, v in self.items()]
299        items.sort()
300        return '<%s %s>' % (
301            self.__class__.__name__,
302            ' '.join(['%s=%r' % (k, v) for k, v in items]))
303
304############################################################
305## HTML Templating
306############################################################
307
308class html(object):
309    def __init__(self, value):
310        self.value = value
311    def __str__(self):
312        return self.value
313    def __repr__(self):
314        return '<%s %r>' % (
315            self.__class__.__name__, self.value)
316
317def html_quote(value):
318    if value is None:
319        return ''
320    if not isinstance(value, basestring):
321        if hasattr(value, '__unicode__'):
322            value = unicode(value)
323        else:
324            value = str(value)
325    value = cgi.escape(value, 1)
326    if isinstance(value, unicode):
327        value = value.encode('ascii', 'xmlcharrefreplace')
328    return value
329
330def url(v):
331    if not isinstance(v, basestring):
332        if hasattr(v, '__unicode__'):
333            v = unicode(v)
334        else:
335            v = str(v)
336    if isinstance(v, unicode):
337        v = v.encode('utf8')
338    return urllib.quote(v)
339
340def attr(**kw):
341    kw = kw.items()
342    kw.sort()
343    parts = []
344    for name, value in kw:
345        if value is None:
346            continue
347        if name.endswith('_'):
348            name = name[:-1]
349        parts.append('%s="%s"' % (html_quote(name), html_quote(value)))
350    return html(' '.join(parts))
351
352class HTMLTemplate(Template):
353
354    default_namespace = Template.default_namespace.copy()
355    default_namespace.update(dict(
356        html=html,
357        attr=attr,
358        url=url,
359        ))
360
361    def _repr(self, value, pos):
362        plain = Template._repr(self, value, pos)
363        if isinstance(value, html):
364            return plain
365        else:
366            return html_quote(plain)
367
368def sub_html(content, **kw):
369    name = kw.get('__name')
370    tmpl = HTMLTemplate(content, name=name)
371    return tmpl.substitute(kw)
372    return result
373
374
375############################################################
376## Lexing and Parsing
377############################################################
378
379def lex(s, name=None, trim_whitespace=True):
380    """
381    Lex a string into chunks:
382
383        >>> lex('hey')
384        ['hey']
385        >>> lex('hey {{you}}')
386        ['hey ', ('you', (1, 7))]
387        >>> lex('hey {{')
388        Traceback (most recent call last):
389            ...
390        TemplateError: No }} to finish last expression at line 1 column 7
391        >>> lex('hey }}')
392        Traceback (most recent call last):
393            ...
394        TemplateError: }} outside expression at line 1 column 7
395        >>> lex('hey {{ {{')
396        Traceback (most recent call last):
397            ...
398        TemplateError: {{ inside expression at line 1 column 10
399
400    """
401    in_expr = False
402    chunks = []
403    last = 0
404    last_pos = (1, 1)
405    for match in token_re.finditer(s):
406        expr = match.group(0)
407        pos = find_position(s, match.end())
408        if expr == '{{' and in_expr:
409            raise TemplateError('{{ inside expression', position=pos,
410                                name=name)
411        elif expr == '}}' and not in_expr:
412            raise TemplateError('}} outside expression', position=pos,
413                                name=name)
414        if expr == '{{':
415            part = s[last:match.start()]
416            if part:
417                chunks.append(part)
418            in_expr = True
419        else:
420            chunks.append((s[last:match.start()], last_pos))
421            in_expr = False
422        last = match.end()
423        last_pos = pos
424    if in_expr:
425        raise TemplateError('No }} to finish last expression',
426                            name=name, position=last_pos)
427    part = s[last:]
428    if part:
429        chunks.append(part)
430    if trim_whitespace:
431        chunks = trim_lex(chunks)
432    return chunks
433
434statement_re = re.compile(r'^(?:if |elif |else |for |py:)')
435single_statements = ['endif', 'endfor', 'continue', 'break']
436trail_whitespace_re = re.compile(r'\n[\t ]*$')
437lead_whitespace_re = re.compile(r'^[\t ]*\n')
438
439def trim_lex(tokens):
440    r"""
441    Takes a lexed set of tokens, and removes whitespace when there is
442    a directive on a line by itself:
443
444       >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
445       >>> tokens
446       [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
447       >>> trim_lex(tokens)
448       [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
449    """
450    for i in range(len(tokens)):
451        current = tokens[i]
452        if isinstance(tokens[i], basestring):
453            # we don't trim this
454            continue
455        item = current[0]
456        if not statement_re.search(item) and item not in single_statements:
457            continue
458        if not i:
459            prev = ''
460        else:
461            prev = tokens[i-1]
462        if i+1 >= len(tokens):
463            next = ''
464        else:
465            next = tokens[i+1]
466        if (not isinstance(next, basestring)
467            or not isinstance(prev, basestring)):
468            continue
469        if ((not prev or trail_whitespace_re.search(prev))
470            and (not next or lead_whitespace_re.search(next))):
471            if prev:
472                m = trail_whitespace_re.search(prev)
473                # +1 to leave the leading \n on:
474                prev = prev[:m.start()+1]
475                tokens[i-1] = prev
476            if next:
477                m = lead_whitespace_re.search(next)
478                next = next[m.end():]
479                tokens[i+1] = next
480    return tokens
481       
482
483def find_position(string, index):
484    """Given a string and index, return (line, column)"""
485    leading = string[:index].splitlines()
486    return (len(leading), len(leading[-1])+1)
487
488def parse(s, name=None):
489    r"""
490    Parses a string into a kind of AST
491
492        >>> parse('{{x}}')
493        [('expr', (1, 3), 'x')]
494        >>> parse('foo')
495        ['foo']
496        >>> parse('{{if x}}test{{endif}}')
497        [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
498        >>> parse('series->{{for x in y}}x={{x}}{{endfor}}')
499        ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
500        >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
501        [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
502        >>> parse('{{py:x=1}}')
503        [('py', (1, 3), 'x=1')]
504        >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}')
505        [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]
506
507    Some exceptions::
508       
509        >>> parse('{{continue}}')
510        Traceback (most recent call last):
511            ...
512        TemplateError: continue outside of for loop at line 1 column 3
513        >>> parse('{{if x}}foo')
514        Traceback (most recent call last):
515            ...
516        TemplateError: No {{endif}} at line 1 column 3
517        >>> parse('{{else}}')
518        Traceback (most recent call last):
519            ...
520        TemplateError: else outside of an if block at line 1 column 3
521        >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
522        Traceback (most recent call last):
523            ...
524        TemplateError: Unexpected endif at line 1 column 25
525        >>> parse('{{if}}{{endif}}')
526        Traceback (most recent call last):
527            ...
528        TemplateError: if with no expression at line 1 column 3
529        >>> parse('{{for x y}}{{endfor}}')
530        Traceback (most recent call last):
531            ...
532        TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
533        >>> parse('{{py:x=1\ny=2}}')
534        Traceback (most recent call last):
535            ...
536        TemplateError: Multi-line py blocks must start with a newline at line 1 column 3
537    """
538    tokens = lex(s, name=name)
539    result = []
540    while tokens:
541        next, tokens = parse_expr(tokens, name)
542        result.append(next)
543    return result
544
545def parse_expr(tokens, name, context=()):
546    if isinstance(tokens[0], basestring):
547        return tokens[0], tokens[1:]
548    expr, pos = tokens[0]
549    expr = expr.strip()
550    if expr.startswith('py:'):
551        expr = expr[3:].lstrip(' \t')
552        if expr.startswith('\n'):
553            expr = expr[1:]
554        else:
555            if '\n' in expr:
556                raise TemplateError(
557                    'Multi-line py blocks must start with a newline',
558                    position=pos, name=name)
559        return ('py', pos, expr), tokens[1:]
560    elif expr in ('continue', 'break'):
561        if 'for' not in context:
562            raise TemplateError(
563                'continue outside of for loop',
564                position=pos, name=name)
565        return (expr, pos), tokens[1:]
566    elif expr.startswith('if '):
567        return parse_cond(tokens, name, context)
568    elif (expr.startswith('elif ')
569          or expr == 'else'):
570        raise TemplateError(
571            '%s outside of an if block' % expr.split()[0],
572            position=pos, name=name)
573    elif expr in ('if', 'elif', 'for'):
574        raise TemplateError(
575            '%s with no expression' % expr,
576            position=pos, name=name)
577    elif expr in ('endif', 'endfor'):
578        raise TemplateError(
579            'Unexpected %s' % expr,
580            position=pos, name=name)
581    elif expr.startswith('for '):
582        return parse_for(tokens, name, context)
583    elif expr.startswith('default '):
584        return parse_default(tokens, name, context)
585    elif expr.startswith('#'):
586        return ('comment', pos, tokens[0][0]), tokens[1:]
587    return ('expr', pos, tokens[0][0]), tokens[1:]
588
589def parse_cond(tokens, name, context):
590    start = tokens[0][1]
591    pieces = []
592    context = context + ('if',)
593    while 1:
594        if not tokens:
595            raise TemplateError(
596                'Missing {{endif}}',
597                position=start, name=name)
598        if (isinstance(tokens[0], tuple)
599            and tokens[0][0] == 'endif'):
600            return ('cond', start) + tuple(pieces), tokens[1:]
601        next, tokens = parse_one_cond(tokens, name, context)
602        pieces.append(next)
603
604def parse_one_cond(tokens, name, context):
605    (first, pos), tokens = tokens[0], tokens[1:]
606    content = []
607    if first.endswith(':'):
608        first = first[:-1]
609    if first.startswith('if '):
610        part = ('if', pos, first[3:].lstrip(), content)
611    elif first.startswith('elif '):
612        part = ('elif', pos, first[5:].lstrip(), content)
613    elif first == 'else':
614        part = ('else', pos, None, content)
615    else:
616        assert 0, "Unexpected token %r at %s" % (first, pos)
617    while 1:
618        if not tokens:
619            raise TemplateError(
620                'No {{endif}}',
621                position=pos, name=name)
622        if (isinstance(tokens[0], tuple)
623            and (tokens[0][0] == 'endif'
624                 or tokens[0][0].startswith('elif ')
625                 or tokens[0][0] == 'else')):
626            return part, tokens
627        next, tokens = parse_expr(tokens, name, context)
628        content.append(next)
629       
630def parse_for(tokens, name, context):
631    first, pos = tokens[0]
632    tokens = tokens[1:]
633    context = ('for',) + context
634    content = []
635    assert first.startswith('for ')
636    if first.endswith(':'):
637        first = first[:-1]
638    first = first[3:].strip()
639    match = in_re.search(first)
640    if not match:
641        raise TemplateError(
642            'Bad for (no "in") in %r' % first,
643            position=pos, name=name)
644    vars = first[:match.start()]
645    if '(' in vars:
646        raise TemplateError(
647            'You cannot have () in the variable section of a for loop (%r)'
648            % vars, position=pos, name=name)
649    vars = tuple([
650        v.strip() for v in first[:match.start()].split(',')
651        if v.strip()])
652    expr = first[match.end():]
653    while 1:
654        if not tokens:
655            raise TemplateError(
656                'No {{endfor}}',
657                position=pos, name=name)
658        if (isinstance(tokens[0], tuple)
659            and tokens[0][0] == 'endfor'):
660            return ('for', pos, vars, expr, content), tokens[1:]
661        next, tokens = parse_expr(tokens, name, context)
662        content.append(next)
663
664def parse_default(tokens, name, context):
665    first, pos = tokens[0]
666    assert first.startswith('default ')
667    first = first.split(None, 1)[1]
668    parts = first.split('=', 1)
669    if len(parts) == 1:
670        raise TemplateError(
671            "Expression must be {{default var=value}}; no = found in %r" % first,
672            position=pos, name=name)
673    var = parts[0].strip()
674    if ',' in var:
675        raise TemplateError(
676            "{{default x, y = ...}} is not supported",
677            position=pos, name=name)
678    if not var_re.search(var):
679        raise TemplateError(
680            "Not a valid variable name for {{default}}: %r"
681            % var, position=pos, name=name)
682    expr = parts[1].strip()
683    return ('default', pos, var, expr), tokens[1:]
684
685_fill_command_usage = """\
686%prog [OPTIONS] TEMPLATE arg=value
687
688Use py:arg=value to set a Python value; otherwise all values are
689strings.
690"""
691
692def fill_command(args=None):
693    import sys, optparse, pkg_resources, os
694    if args is None:
695        args = sys.argv[1:]
696    dist = pkg_resources.get_distribution('Paste')
697    parser = optparse.OptionParser(
698        version=str(dist),
699        usage=_fill_command_usage)
700    parser.add_option(
701        '-o', '--output',
702        dest='output',
703        metavar="FILENAME",
704        help="File to write output to (default stdout)")
705    parser.add_option(
706        '--html',
707        dest='use_html',
708        action='store_true',
709        help="Use HTML style filling (including automatic HTML quoting)")
710    parser.add_option(
711        '--env',
712        dest='use_env',
713        action='store_true',
714        help="Put the environment in as top-level variables")
715    options, args = parser.parse_args(args)
716    if len(args) < 1:
717        print 'You must give a template filename'
718        print dir(parser)
719        assert 0
720    template_name = args[0]
721    args = args[1:]
722    vars = {}
723    if options.use_env:
724        vars.update(os.environ)
725    for value in args:
726        if '=' not in value:
727            print 'Bad argument: %r' % value
728            sys.exit(2)
729        name, value = value.split('=', 1)
730        if name.startswith('py:'):
731            name = name[:3]
732            value = eval(value)
733        vars[name] = value
734    if template_name == '-':
735        template_content = sys.stdin.read()
736        template_name = '<stdin>'
737    else:
738        f = open(template_name, 'rb')
739        template_content = f.read()
740        f.close()
741    if options.use_html:
742        TemplateClass = HTMLTemplate
743    else:
744        TemplateClass = Template
745    template = TemplateClass(template_content, name=template_name)
746    result = template.substitute(vars)
747    if options.output:
748        f = open(options.output, 'wb')
749        f.write(result)
750        f.close()
751    else:
752        sys.stdout.write(result)
753
754if __name__ == '__main__':
755    from paste.util.template import fill_command
756    fill_command()
757       
758   
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。