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