././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731448534.7941024 tempita-0.6.0/0000755000175100001770000000000014714747327012605 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448528.0 tempita-0.6.0/LICENSE0000644000175100001770000000205314714747320013603 0ustar00runnerdockerMIT License Copyright (c) 2023 TurboGears Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731448534.7941024 tempita-0.6.0/PKG-INFO0000644000175100001770000000171414714747327013705 0ustar00runnerdockerMetadata-Version: 2.1 Name: Tempita Version: 0.6.0 Summary: A very small text templating language Home-page: https://github.com/TurboGears/tempita Author: Ian Bicking Author-email: ianb@colorstudy.com License: MIT Keywords: templating template language html Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Text Processing Classifier: Programming Language :: Python :: 3 License-File: LICENSE Tempita is a small templating language for text substitution. This isn't meant to be the Next Big Thing in templating; it's just a handy little templating language for when your project outgrows ``string.Template`` or ``%`` substitution. It's small, it embeds Python in strings, and it doesn't do much else. You can read about the Language and the Interface at https://github.com/TurboGears/tempita/blob/main/README.rst, and there's nothing more to learn about it. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448528.0 tempita-0.6.0/README.rst0000644000175100001770000004033214714747320014267 0ustar00runnerdocker.. copied from https://bitbucket.org/ianb/tempita .. The old docs URL is defunct, including the Wayback redirect Tempita Templating Engine ========================= :author: Ian Bicking :source: https://github.com/TurboGears/tempita Status & License ---------------- Tempita is available under a `MIT-style license`. It is *not* actually actively developed, and not an ambitious project. It does not seek to take over the templating world, or adopt many new features. I just wanted a small templating language for cases when ``%`` and ``string.Template`` weren't enough. Why Another Templating Language ------------------------------- Surely the world has enough templating languages? So why did I write another. I initially used `Cheetah `_ as the templating language for `Paste Script `_, but this caused quite a few problems. People frequently had problems installing Cheetah because it includes a C extension. Also, the errors and invocation can be a little confusing. This might be okay for something that used Cheetah's features extensively, except that the templating was a very minor feature of the system, and many people didn't even understand or care about where templating came into the system. At the same time, I was starting to create reusable WSGI components that had some templating in them. Not a lot of templating, but enough that ``string.Template`` had become too complicated -- I need if statements and loops. Given this, I started looking around for a very small templating language, and I didn't like anything I found. Many of them seemed awkward or like toys that were more about the novelty of the implementation than the utility of the language. So one night when I felt like coding but didn't feel like working on anything I was already working on, I wrote this. It was first called ``paste.util.template``, but I decided it deserved a life of its own, hence Tempita. The Interface ------------- The interface is intended to look a lot like ``string.Template``. You can create a template object like:: >>> import tempita >>> tmpl = tempita.Template("""Hello {{name}}""") >>> tmpl.substitute(name='Bob') 'Hello Bob' Or if you want to skip the class:: >>> tempita.sub("Hello {{name}}", name='Alice') 'Hello Alice' Note that the language allows arbitrary Python to be executed, so your templates must be trusted. You can give a name to your template, which is handy when there is an error (the name will be displayed):: >>> tmpl = tempita.Template('Hi {{name}}', name='tmpl') >>> tmpl.substitute() Traceback (most recent call last): ... NameError: name 'name' is not defined at line 1 column 6 in file tmpl You can also give a namespace to use by default, which ``.substitute(...)`` will augment:: >>> tmpl = tempita.Template( ... 'Hi {{upper(name)}}', ... namespace=dict(upper=lambda s: s.upper())) >>> tmpl.substitute(name='Joe') 'Hi JOE' Lastly, you can give a dictionary-like object as the argument to ``.substitute``, like:: >>> name = 'Jane' >>> tmpl.substitute(locals()) 'Hi JANE' There's also an `HTMLTemplate`_ class that is more appropriate for templates that produce HTML. You can also instantiate a template from a filename with ``Template.from_filename(filename, namespace={}, encoding=None)``. This is like calling:: Template(open(filename, 'rb').read().decode(encoding), name=filename, namespace=namespace) Unicode ------- Tempita tries to handle unicode gracefully, for some value of "graceful". ``Template`` objects have a ``default_encoding`` attribute. It will try to use that encoding whenever ``unicode`` and ``str`` objects are mixed in the template. E.g.:: >>> tmpl = tempita.Template(u'Hi {{name}}') >>> import sys >>> if sys.version.startswith('2'): # unicode is the default in 3 -> failing test ... val = tmpl.substitute(name='Jos\xc3\xa9') ... comparison = val == u'Hi Jos\xe9' ... else: ... comparison = True >>> comparison True >>> tmpl = tempita.Template('Hi {{name}}') >>> print (tmpl.substitute(name=u'Jos\xe9')) Hi Jos爊The default encoding is UTF8. .. _tempita-lang: The `Tempita` Language ---------------------- The language is fairly simple; all the constructs look like ``{{stuff}}``. Substitution ^^^^^^^^^^^^ To insert a variable or expression, use ``{{expression}}``. You can't use ``}}`` in your expression, but if it comes up just use ``} }`` (put a space between them). You can pass your expression through *filters* with ``{{expression | filter}}``, for instance ``{{expression | repr}}``. This is entirely equivalent to ``{{repr(expression)}}``. But it might look nicer to some people; I took it from Django because I liked it. There's a shared namespace, so ``repr`` is just an object in the namespace. If you want to have ``{{`` or ``}}`` in your template, you must use the built-in variables like ``{{start_braces}}`` and ``{{end_braces}}``. There's no escape character. You may also specify the delimiters as an argument to the Template __init__ method: >>> tempita.Template(content='Hello ${name}', delimiters=('${', '}')).substitute(name='world') 'Hello world' The delimiters argument must be of length two and both items must be strings. None, as a special case, is substituted as the empty string. Also there is a command for setting default values in your template:: {{default width = 100}} You can use this so that the ``width`` variable will always have a value in your template (the number ``100``). If someone calls ``tmpl.substitute(width=200)`` then this will have no effect; only if the variable is undefined will this default matter. You can use any expression to the right of the ``=``. if ^^ You can do an if statement with:: {{if condition}} true stuff {{elif other_condition}} other stuff {{else}} final stuff {{endif}} Some of the blank lines will be removed when, as in this case, they only contain a single directive. A trailing ``:`` is optional (like ``{{if condition:}}``). for ^^^ Loops should be unsurprising:: {{for a, b in items}} {{a}} = {{b | repr}} {{endfor}} See? Unsurprising. Note that nested tuples (like ``for a, (b, c) in...``) are not supported (patches welcome). inherit & def ^^^^^^^^^^^^^ You can do template inheritance. To inherit from another template do:: {{inherit "some_other_file"}} From Python you must also pass in, to `Template`, a `get_template` function; the implementation for ``Template.from_filename(...)`` is:: def get_file_template(name, from_template): path = os.path.join(os.path.dirname(from_template.name), name) return from_template.__class__.from_filename( path, namespace=from_template.namespace, get_template=from_template.get_template) You can also pass in a constructor argument `default_inherit`, which will be the inherited template name when no ``{{inherit}}`` is in the template. The inherited template is executed with a variable ``self``, which includes ``self.body`` which is the text of the child template. You can also put in definitions in the child, like:: {{def sidebar}} sidebar links... {{enddef}} Then in the parent/inherited template:: {{self.sidebar}} If you want to make the sidebar method optional, in the inherited template use:: {{self.get.sidebar}} If ``sidebar`` is not defined then this will just result in an object that shows up as the empty string (but is also callable). This can be called like ``self.sidebar`` or ``self.sidebar()`` -- defs can have arguments (like ``{{def sidebar(name)}}``), but when there are no arguments you can leave off ``()`` (in the call and definition). Python blocks ^^^^^^^^^^^^^ For anything more complicated, you can use blocks of Python code, like:: {{py:x = 1}} {{py: lots of code }} The first form allows statements, like an assignment or raising an exception. The second form is for multiple lines. If you have multiple lines, then ``{{py:`` must be on a line of its own and the code can't start out indented (but if you have something like ``def x():`` you would indent the body). These blocks of code can't output any values, but they can calculate values and define functions. So you can do something like:: {{py: def pad(s): return s + ' '*(20-len(s)) }} {{for name, value in kw.items()}} {{s | pad}} {{value | repr}} {{endfor}} As a last detail ``{{# comments...}}`` doesn't do anything at all, because it is a comment. bunch and looper ^^^^^^^^^^^^^^^^ There's two kinds of objects provided to help you in your templates. The first is ``tempita.bunch``, which is just a dictionary that also lets you use attributes:: >>> bunch = tempita.bunch(a=1) >>> bunch.a 1 >>> list(bunch.items()) [('a', 1)] >>> bunch.default = None >>> print (bunch.b) None This can be nice for passing options into a template. The other object is for use inside the template, and is part of the default namespace, ``looper``. This can be used in ``for`` loops in some convenient ways. You basically use it like:: {{for loop, item in looper(seq)}} ... {{endfor}} The ``loop`` object has a bunch of useful methods and attributes: ``.index`` The index of the current item (like you'd get with ``enumerate()``) ``.number`` The number: ``.index + 1`` ``.item`` The item you are looking at. Which you probably already have, but it's there if you want it. ``.next`` The next item in the sequence, or None if it's the last item. ``.previous`` The previous item in the sequence, or None if it's the first item. ``.odd`` True if this is an odd item. The first item is even. ``.even`` True if it's even. ``.first`` True if this is the first item. ``.last`` True if this is the last item. ``.length`` The total length of the sequence. ``.first_group(getter=None)`` Returns true if this item is the first in the group, where the group is either of equal objects (probably boring), or when you give a getter. getter can be ``'.attribute'``, like ``'.last_name'`` -- this lets you group people by their last name. Or a method, like ``'.birth_year()'`` -- which calls the method. If it's just a string, it is expected to be a key in a dictionary, like ``'name'`` which groups on ``item['name']``. Or you can give a function which returns the value to group on. This always returns true when ``.first`` returns true. ``.last_group(getter=None)`` Like ``first_group``, only returns True when it's the last of the group. This always returns true when ``.last`` returns true. Note that there's currently a limitation in the templating language, so you can't do ``{{for loop, (key, value) in looper(d.items())}}``. You'll have to do:: {{for loop, key_value in looper(d.items())}} {{py:key, value = key_value}} ... {{endfor}} HTMLTemplate ------------ In addition to ``Template`` there is a template specialized for HTML, ``HTMLTemplate`` (and the substitution function ``sub_html``). The basic thing that it adds is automatic HTML quoting. All values substituted into your template will be quoted unless they are specially marked. You mark objects as instances of ``tempita.html``. The easiest way is ``{{some_string | html}}``, though you can also use ``tempita.html(string)`` in your functions. An example:: >>> tmpl = tempita.HTMLTemplate('''\ ... Hi {{name}}! ... {{title|html}}''') >>> name = tempita.html('') >>> href = 'Attack!">' >>> title = 'Homepage' >>> tmpl.substitute(locals()) 'Hi !\nHomepage' It also adds a couple handy builtins: ``html_quote(value)``: HTML quotes the value. Turns all unicode values into character references, so it always returns ASCII text. Also it calls ``str(value)`` or ``unicode(value)``, so you can do things like ``html_quote(1)``. ``url(value)``: Does URL quoting, similar to ``html_quote()``. ``attr(**kw)``: Inserts attributes. Use like::
Then it'll put in something like ``width="{{width}}" class={{div_class}}``. Any attribute with a value of None is left out entirely. Extending Tempita ----------------- It's not really meant for extension. Instead you should just write Python functions and classes that do what you want, and use them in the template. You can either add the namespace to the constructor, or extend ``default_namespace`` in your own subclass. The extension that ``HTMLTemplate`` uses is to subclass and override the ``_repr(value, pos)`` function. This is called on each object just before inserting it in the template. Two other methods you might want to look at are ``_eval(code, ns, pos)`` and ``_exec(code, ns, pos)``, which evaluate and execute expressions and statements. You could probably make this language safe with appropriate implementations of those methods. Command-line Use ---------------- There's also a command-line version of the program. In Python 2.5+ you can run ``python -m tempita``; in previous versions you must run ``python path/to/tempita/__init__.py``. The usage:: Usage: __init__.py [OPTIONS] TEMPLATE arg=value Use py:arg=value to set a Python value; otherwise all values are strings. Options: --version show program's version number and exit -h, --help show this help message and exit -o FILENAME, --output=FILENAME File to write output to (default stdout) --html Use HTML style filling (including automatic HTML quoting) --env Put the environment in as top-level variables So you can use it like:: $ python -m tempita --html mytemplate.tmpl \ > var1="$var1" var2="$var2" > mytemplate.html Still To Do ----------- * Currently nested structures in ``for`` loop assignments don't work, like ``for (a, b), c in x``. They should. * There's no way to handle exceptions, except in your ``py:`` code. I'm not sure what there should be, if anything. * Probably I should try to dedent ``py:`` code. * There should be some way of calling a function with a chunk of the template. Maybe like:: {{call expr}} template code... {{endcall}} That would mean ``{{expr(result_of_template_code)}}``. But maybe there should be another assignment form too, if you don't want to immediately put the output in the code (``{{x = call}}...{{endcall}}?``). For now defs could be used for this, like:: {{def something}} template code... {{enddef}} {{expr(something())}} News ---- 0.6 ^^^ * Support for Python 2 has been removed, Tempita is now Python 3 only. * Codebase has been partially modernized to ensure compatibility with modern Python versions. * Tests rewritten to use ``unittest`` instead of ``nose`` 0.5 ^^^ * Python 3 compatible. * Fixed bug where file-relative filenames wouldn't work well. * Fixed the stripping of empty lines. 0.4 ^^^ * Added a ``line_offset`` constructor argument, which can be used to adjust the line numbers reported in error messages (e.g., if a template is embedded in a file). * Allow non-dictionary namespace objects (with ``tmpl.substitute(namespace)`` (in Python 2.5+). * Instead of defining ``__name__`` in template namespaces (which has special rules, and must be a module name) the template name is put into ``__template_name__``. This became important in Python 2.5. * Fix some issues with \r 0.3 ^^^ * Added ``{{inherit}}`` and ``{{def}}`` for doing template inheritance. * Make error message annotation slightly more robust. * Fix whitespace stripping for the beginning and end of lines. 0.2 ^^^ * Added ``html_quote`` to default functions provided in ``HTMLTemplate``. * HTML literals have an ``.__html__()`` method, and the presence of that method is used to determine if values need to be quoted in ``HTMLTemplate``. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731448534.7941024 tempita-0.6.0/Tempita.egg-info/0000755000175100001770000000000014714747327015702 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448534.0 tempita-0.6.0/Tempita.egg-info/PKG-INFO0000644000175100001770000000171414714747326017001 0ustar00runnerdockerMetadata-Version: 2.1 Name: Tempita Version: 0.6.0 Summary: A very small text templating language Home-page: https://github.com/TurboGears/tempita Author: Ian Bicking Author-email: ianb@colorstudy.com License: MIT Keywords: templating template language html Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Text Processing Classifier: Programming Language :: Python :: 3 License-File: LICENSE Tempita is a small templating language for text substitution. This isn't meant to be the Next Big Thing in templating; it's just a handy little templating language for when your project outgrows ``string.Template`` or ``%`` substitution. It's small, it embeds Python in strings, and it doesn't do much else. You can read about the Language and the Interface at https://github.com/TurboGears/tempita/blob/main/README.rst, and there's nothing more to learn about it. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448534.0 tempita-0.6.0/Tempita.egg-info/SOURCES.txt0000644000175100001770000000042514714747326017566 0ustar00runnerdockerLICENSE README.rst setup.py Tempita.egg-info/PKG-INFO Tempita.egg-info/SOURCES.txt Tempita.egg-info/dependency_links.txt Tempita.egg-info/top_level.txt Tempita.egg-info/zip-safe tempita/__init__.py tempita/__main__.py tempita/_looper.py tempita/compat3.py tests/test_tempita.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448534.0 tempita-0.6.0/Tempita.egg-info/dependency_links.txt0000644000175100001770000000000114714747326021747 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448534.0 tempita-0.6.0/Tempita.egg-info/top_level.txt0000644000175100001770000000001014714747326020422 0ustar00runnerdockertempita ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448534.0 tempita-0.6.0/Tempita.egg-info/zip-safe0000644000175100001770000000000114714747326017331 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731448534.7941024 tempita-0.6.0/setup.cfg0000644000175100001770000000004614714747327014426 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448528.0 tempita-0.6.0/setup.py0000644000175100001770000000224314714747320014311 0ustar00runnerdockerfrom setuptools import setup version = '0.6.0' setup(name='Tempita', version=version, description="A very small text templating language", long_description="""\ Tempita is a small templating language for text substitution. This isn't meant to be the Next Big Thing in templating; it's just a handy little templating language for when your project outgrows ``string.Template`` or ``%`` substitution. It's small, it embeds Python in strings, and it doesn't do much else. You can read about the Language and the Interface at https://github.com/TurboGears/tempita/blob/main/README.rst, and there's nothing more to learn about it. """, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Topic :: Text Processing', 'Programming Language :: Python :: 3', ], keywords='templating template language html', author='Ian Bicking', author_email='ianb@colorstudy.com', url='https://github.com/TurboGears/tempita', license='MIT', packages=['tempita'], include_package_data=True, zip_safe=True) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731448534.7941024 tempita-0.6.0/tempita/0000755000175100001770000000000014714747327014250 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448528.0 tempita-0.6.0/tempita/__init__.py0000644000175100001770000011616514714747320016364 0ustar00runnerdocker#!/usr/bin/env python """ A small templating language This implements a small templating language. This language implements if/elif/else, for/continue/break, expressions, and blocks of Python code. The syntax is:: {{any expression (function calls etc)}} {{any expression | filter}} {{for x in y}}...{{endfor}} {{if x}}x{{elif y}}y{{else}}z{{endif}} {{py:x=1}} {{py: def foo(bar): return 'baz' }} {{default var = default_value}} {{# comment}} You use this with the ``Template`` class or the ``sub`` shortcut. The ``Template`` class takes the template string and the name of the template (for errors) and a default namespace. Then (like ``string.Template``) you can call the ``tmpl.substitute(**kw)`` method to make a substitution (or ``tmpl.substitute(a_dict)``). ``sub(content, **kw)`` substitutes the template immediately. You can use ``__name='tmpl.html'`` to set the name of the template. If there are syntax errors ``TemplateError`` will be raised. """ import os import re import sys import tokenize from html import escape as html_escape from io import StringIO from urllib.parse import quote as url_quote from tempita._looper import looper from tempita.compat3 import bytes, basestring_, next, is_unicode, coerce_text __all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate', 'sub_html', 'html', 'bunch'] in_re = re.compile(r'\s+in\s+') var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I) class TemplateError(Exception): """Exception raised while parsing a template """ def __init__(self, message, position, name=None): Exception.__init__(self, message) self.position = position self.name = name def __str__(self): msg = ' '.join(self.args) if self.position: msg = '%s at line %s column %s' % ( msg, self.position[0], self.position[1]) if self.name: msg += ' in %s' % self.name return msg class _TemplateContinue(Exception): pass class _TemplateBreak(Exception): pass def get_file_template(name, from_template): path = os.path.join(os.path.dirname(from_template.name), name) return from_template.__class__.from_filename( path, namespace=from_template.namespace, get_template=from_template.get_template) class Template(object): default_namespace = { 'start_braces': '{{', 'end_braces': '}}', 'looper': looper, } default_encoding = 'utf8' default_inherit = None def __init__(self, content, name=None, namespace=None, stacklevel=None, get_template=None, default_inherit=None, line_offset=0, delimiters=None): self.content = content # set delimiters if delimiters is None: delimiters = (self.default_namespace['start_braces'], self.default_namespace['end_braces']) else: assert len(delimiters) == 2 and all([isinstance(delimeter, str) for delimeter in delimiters]) self.default_namespace = self.__class__.default_namespace.copy() self.default_namespace['start_braces'] = delimiters[0] self.default_namespace['end_braces'] = delimiters[1] self.delimiters = delimiters self._unicode = is_unicode(content) if name is None and stacklevel is not None: try: caller = sys._getframe(stacklevel) except ValueError: pass else: globals = caller.f_globals lineno = caller.f_lineno if '__file__' in globals: name = globals['__file__'] if name.endswith('.pyc') or name.endswith('.pyo'): name = name[:-1] elif '__name__' in globals: name = globals['__name__'] else: name = '' if lineno: name += ':%s' % lineno self.name = name self._parsed = parse(content, name=name, line_offset=line_offset, delimiters=self.delimiters) if namespace is None: namespace = {} self.namespace = namespace self.get_template = get_template if default_inherit is not None: self.default_inherit = default_inherit def from_filename(cls, filename, namespace=None, encoding=None, default_inherit=None, get_template=get_file_template): f = open(filename, 'rb') c = f.read() f.close() if encoding: c = c.decode(encoding) return cls(content=c, name=filename, namespace=namespace, default_inherit=default_inherit, get_template=get_template) from_filename = classmethod(from_filename) def __repr__(self): return '<%s %s name=%r>' % ( self.__class__.__name__, hex(id(self))[2:], self.name) def substitute(self, *args, **kw): if args: if kw: raise TypeError( "You can only give positional *or* keyword arguments") if len(args) > 1: raise TypeError( "You can only give one positional argument") if not hasattr(args[0], 'items'): raise TypeError( "If you pass in a single argument, you must pass in a dictionary-like object (with a .items() method); you gave %r" % (args[0],)) kw = args[0] ns = kw ns['__template_name__'] = self.name if self.namespace: ns.update(self.namespace) result, defs, inherit = self._interpret(ns) if not inherit: inherit = self.default_inherit if inherit: result = self._interpret_inherit(result, defs, inherit, ns) return result def _interpret(self, ns): __traceback_hide__ = True parts = [] defs = {} self._interpret_codes(self._parsed, ns, out=parts, defs=defs) if '__inherit__' in defs: inherit = defs.pop('__inherit__') else: inherit = None return ''.join(parts), defs, inherit def _interpret_inherit(self, body, defs, inherit_template, ns): __traceback_hide__ = True if not self.get_template: raise TemplateError( 'You cannot use inheritance without passing in get_template', position=None, name=self.name) templ = self.get_template(inherit_template, self) self_ = TemplateObject(self.name) for name, value in defs.items(): setattr(self_, name, value) self_.body = body ns = ns.copy() ns['self'] = self_ return templ.substitute(ns) def _interpret_codes(self, codes, ns, out, defs): __traceback_hide__ = True for item in codes: if isinstance(item, basestring_): out.append(item) else: self._interpret_code(item, ns, out, defs) def _interpret_code(self, code, ns, out, defs): __traceback_hide__ = True name, pos = code[0], code[1] if name == 'py': self._exec(code[2], ns, pos) elif name == 'continue': raise _TemplateContinue() elif name == 'break': raise _TemplateBreak() elif name == 'for': vars, expr, content = code[2], code[3], code[4] expr = self._eval(expr, ns, pos) self._interpret_for(vars, expr, content, ns, out, defs) elif name == 'cond': parts = code[2:] self._interpret_if(parts, ns, out, defs) elif name == 'expr': parts = code[2].split('|') base = self._eval(parts[0], ns, pos) for part in parts[1:]: func = self._eval(part, ns, pos) base = func(base) out.append(self._repr(base, pos)) elif name == 'default': var, expr = code[2], code[3] if var not in ns: result = self._eval(expr, ns, pos) ns[var] = result elif name == 'inherit': expr = code[2] value = self._eval(expr, ns, pos) defs['__inherit__'] = value elif name == 'def': name = code[2] signature = code[3] parts = code[4] ns[name] = defs[name] = TemplateDef(self, name, signature, body=parts, ns=ns, pos=pos) elif name == 'comment': return else: assert 0, "Unknown code: %r" % name def _interpret_for(self, vars, expr, content, ns, out, defs): __traceback_hide__ = True for item in expr: if len(vars) == 1: ns[vars[0]] = item else: if len(vars) != len(item): raise ValueError( 'Need %i items to unpack (got %i items)' % (len(vars), len(item))) for name, value in zip(vars, item): ns[name] = value try: self._interpret_codes(content, ns, out, defs) except _TemplateContinue: continue except _TemplateBreak: break def _interpret_if(self, parts, ns, out, defs): __traceback_hide__ = True # @@: if/else/else gets through for part in parts: assert not isinstance(part, basestring_) name, pos = part[0], part[1] if name == 'else': result = True else: result = self._eval(part[2], ns, pos) if result: self._interpret_codes(part[3], ns, out, defs) break def _eval(self, code, ns, pos): __traceback_hide__ = True try: try: value = eval(code, self.default_namespace, ns) except SyntaxError as e: raise SyntaxError( 'invalid syntax in expression: %s' % code) return value except: exc_info = sys.exc_info() e = exc_info[1] if getattr(e, 'args', None): arg0 = e.args[0] else: arg0 = coerce_text(e) e.args = (self._add_line_info(arg0, pos),) raise exc_info[0](e).with_traceback(exc_info[2]) def _exec(self, code, ns, pos): __traceback_hide__ = True try: exec(code, self.default_namespace, ns) except: exc_info = sys.exc_info() e = exc_info[1] if e.args: e.args = (self._add_line_info(e.args[0], pos),) else: e.args = (self._add_line_info(None, pos),) raise exc_info[0](e).with_traceback(exc_info[2]) def _repr(self, value, pos): __traceback_hide__ = True try: if value is None: return '' if self._unicode: try: value = str(value) except UnicodeDecodeError: value = bytes(value) else: if not isinstance(value, basestring_): value = coerce_text(value) if (is_unicode(value) and self.default_encoding): value = value.encode(self.default_encoding) except: exc_info = sys.exc_info() e = exc_info[1] e.args = (self._add_line_info(e.args[0], pos),) raise exc_info[0](e).with_traceback(exc_info[2]) else: if self._unicode and isinstance(value, bytes): if not self.default_encoding: raise UnicodeDecodeError( 'Cannot decode bytes value %r into unicode ' '(no default_encoding provided)' % value) try: value = value.decode(self.default_encoding) except UnicodeDecodeError as e: raise UnicodeDecodeError( e.encoding, e.object, e.start, e.end, e.reason + ' in string %r' % value) elif not self._unicode and is_unicode(value): if not self.default_encoding: raise UnicodeEncodeError( 'Cannot encode unicode value %r into bytes ' '(no default_encoding provided)' % value) value = value.encode(self.default_encoding) return value def _add_line_info(self, msg, pos): msg = "%s at line %s column %s" % ( msg, pos[0], pos[1]) if self.name: msg += " in file %s" % self.name return msg def sub(content, delimiters=None, **kw): name = kw.get('__name') tmpl = Template(content, name=name, delimiters=delimiters) return tmpl.substitute(kw) def paste_script_template_renderer(content, vars, filename=None): tmpl = Template(content, name=filename) return tmpl.substitute(vars) class bunch(dict): def __init__(self, **kw): for name, value in kw.items(): setattr(self, name, value) def __setattr__(self, name, value): self[name] = value def __getattr__(self, name): try: return self[name] except KeyError: raise AttributeError(name) def __getitem__(self, key): if 'default' in self: try: return dict.__getitem__(self, key) except KeyError: return dict.__getitem__(self, 'default') else: return dict.__getitem__(self, key) def __repr__(self): items = [ (k, v) for k, v in self.items()] items.sort() return '<%s %s>' % ( self.__class__.__name__, ' '.join(['%s=%r' % (k, v) for k, v in items])) ############################################################ ## HTML Templating ############################################################ class html(object): def __init__(self, value): self.value = value def __str__(self): return self.value def __html__(self): return self.value def __repr__(self): return '<%s %r>' % ( self.__class__.__name__, self.value) def html_quote(value, force=True): if not force and hasattr(value, '__html__'): return value.__html__() if value is None: return '' if not isinstance(value, basestring_): value = coerce_text(value) if sys.version >= "3" and isinstance(value, bytes): value = html_escape(value.decode('latin1'), 1) value = value.encode('latin1') else: value = html_escape(value, 1) if sys.version < "3": if is_unicode(value): value = value.encode('ascii', 'xmlcharrefreplace') return value def url(v): v = coerce_text(v) if is_unicode(v): v = v.encode('utf8') return url_quote(v) def attr(**kw): kw = list(kw.items()) kw.sort() parts = [] for name, value in kw: if value is None: continue if name.endswith('_'): name = name[:-1] parts.append('%s="%s"' % (html_quote(name), html_quote(value))) return html(' '.join(parts)) class HTMLTemplate(Template): default_namespace = Template.default_namespace.copy() default_namespace.update(dict( html=html, attr=attr, url=url, html_quote=html_quote, )) def _repr(self, value, pos): if hasattr(value, '__html__'): value = value.__html__() quote = False else: quote = True plain = Template._repr(self, value, pos) if quote: return html_quote(plain) else: return plain def sub_html(content, **kw): name = kw.get('__name') tmpl = HTMLTemplate(content, name=name) return tmpl.substitute(kw) class TemplateDef(object): def __init__(self, template, func_name, func_signature, body, ns, pos, bound_self=None): self._template = template self._func_name = func_name self._func_signature = func_signature self._body = body self._ns = ns self._pos = pos self._bound_self = bound_self def __repr__(self): return '' % ( self._func_name, self._func_signature, self._template.name, self._pos) def __str__(self): return self() def __call__(self, *args, **kw): values = self._parse_signature(args, kw) ns = self._ns.copy() ns.update(values) if self._bound_self is not None: ns['self'] = self._bound_self out = [] subdefs = {} self._template._interpret_codes(self._body, ns, out, subdefs) return ''.join(out) def __get__(self, obj, type=None): if obj is None: return self return self.__class__( self._template, self._func_name, self._func_signature, self._body, self._ns, self._pos, bound_self=obj) def _parse_signature(self, args, kw): values = {} sig_args, var_args, var_kw, defaults = self._func_signature extra_kw = {} for name, value in kw.items(): if not var_kw and name not in sig_args: raise TypeError( 'Unexpected argument %s' % name) if name in sig_args: values[sig_args] = value else: extra_kw[name] = value args = list(args) sig_args = list(sig_args) while args: while sig_args and sig_args[0] in values: sig_args.pop(0) if sig_args: name = sig_args.pop(0) values[name] = args.pop(0) elif var_args: values[var_args] = tuple(args) break else: raise TypeError( 'Extra position arguments: %s' % ', '.join(repr(v) for v in args)) for name, value_expr in defaults.items(): if name not in values: values[name] = self._template._eval( value_expr, self._ns, self._pos) for name in sig_args: if name not in values: raise TypeError( 'Missing argument: %s' % name) if var_kw: values[var_kw] = extra_kw return values class TemplateObject(object): def __init__(self, name): self.__name = name self.get = TemplateObjectGetter(self) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.__name) class TemplateObjectGetter(object): def __init__(self, template_obj): self.__template_obj = template_obj def __getattr__(self, attr): return getattr(self.__template_obj, attr, Empty) def __repr__(self): return '<%s around %r>' % (self.__class__.__name__, self.__template_obj) class _Empty(object): def __call__(self, *args, **kw): return self def __str__(self): return '' def __repr__(self): return 'Empty' def __unicode__(self): return '' def __iter__(self): return iter(()) def __bool__(self): return False if sys.version < "3": __nonzero__ = __bool__ Empty = _Empty() del _Empty ############################################################ ## Lexing and Parsing ############################################################ def lex(s, name=None, trim_whitespace=True, line_offset=0, delimiters=None): """ Lex a string into chunks: >>> lex('hey') ['hey'] >>> lex('hey {{you}}') ['hey ', ('you', (1, 7))] >>> lex('hey {{') Traceback (most recent call last): ... TemplateError: No }} to finish last expression at line 1 column 7 >>> lex('hey }}') Traceback (most recent call last): ... TemplateError: }} outside expression at line 1 column 7 >>> lex('hey {{ {{') Traceback (most recent call last): ... TemplateError: {{ inside expression at line 1 column 10 """ if delimiters is None: delimiters = ( Template.default_namespace['start_braces'], Template.default_namespace['end_braces'] ) in_expr = False chunks = [] last = 0 last_pos = (line_offset + 1, 1) token_re = re.compile(r'%s|%s' % (re.escape(delimiters[0]), re.escape(delimiters[1]))) for match in token_re.finditer(s): expr = match.group(0) pos = find_position(s, match.end(), line_offset, last_pos) if expr == delimiters[0] and in_expr: raise TemplateError('%s inside expression' % delimiters[0], position=pos, name=name) elif expr == delimiters[1] and not in_expr: raise TemplateError('%s outside expression' % delimiters[1], position=pos, name=name) if expr == delimiters[0]: part = s[last:match.start()] if part: chunks.append(part) in_expr = True else: chunks.append((s[last:match.start()], last_pos)) in_expr = False last = match.end() last_pos = pos if in_expr: raise TemplateError('No %s to finish last expression' % delimiters[1], name=name, position=last_pos) part = s[last:] if part: chunks.append(part) if trim_whitespace: chunks = trim_lex(chunks) return chunks statement_re = re.compile(r'^(?:if |elif |for |def |inherit |default |py:)') single_statements = ['else', 'endif', 'endfor', 'enddef', 'continue', 'break'] trail_whitespace_re = re.compile(r'\n\r?[\t ]*$') lead_whitespace_re = re.compile(r'^[\t ]*\n') def trim_lex(tokens): r""" Takes a lexed set of tokens, and removes whitespace when there is a directive on a line by itself: >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False) >>> tokens [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny'] >>> trim_lex(tokens) [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y'] """ last_trim = None for i in range(len(tokens)): current = tokens[i] if isinstance(tokens[i], basestring_): # we don't trim this continue item = current[0] if not statement_re.search(item) and item not in single_statements: continue if not i: prev = '' else: prev = tokens[i - 1] if i + 1 >= len(tokens): next_chunk = '' else: next_chunk = tokens[i + 1] if (not isinstance(next_chunk, basestring_) or not isinstance(prev, basestring_)): continue prev_ok = not prev or trail_whitespace_re.search(prev) if i == 1 and not prev.strip(): prev_ok = True if last_trim is not None and last_trim + 2 == i and not prev.strip(): prev_ok = 'last' if (prev_ok and (not next_chunk or lead_whitespace_re.search(next_chunk) or (i == len(tokens) - 2 and not next_chunk.strip()))): if prev: if ((i == 1 and not prev.strip()) or prev_ok == 'last'): tokens[i - 1] = '' else: m = trail_whitespace_re.search(prev) # +1 to leave the leading \n on: prev = prev[:m.start() + 1] tokens[i - 1] = prev if next_chunk: last_trim = i if i == len(tokens) - 2 and not next_chunk.strip(): tokens[i + 1] = '' else: m = lead_whitespace_re.search(next_chunk) next_chunk = next_chunk[m.end():] tokens[i + 1] = next_chunk return tokens def find_position(string, index, last_index, last_pos=(1, 1)): """ Given a string and index, return (line, column) """ lines = string.count('\n', last_index, index) if lines > 0: column = index - string.rfind('\n', last_index, index) else: column = last_pos[1] + (index - last_index) return (last_pos[0] + lines, column) def parse(s, name=None, line_offset=0, delimiters=None): r""" Parses a string into a kind of AST >>> parse('{{x}}') [('expr', (1, 3), 'x')] >>> parse('foo') ['foo'] >>> parse('{{if x}}test{{endif}}') [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))] >>> parse('series->{{for x in y}}x={{x}}{{endfor}}') ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])] >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}') [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])] >>> parse('{{py:x=1}}') [('py', (1, 3), 'x=1')] >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}') [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))] Some exceptions:: >>> parse('{{continue}}') Traceback (most recent call last): ... TemplateError: continue outside of for loop at line 1 column 3 >>> parse('{{if x}}foo') Traceback (most recent call last): ... TemplateError: No {{endif}} at line 1 column 3 >>> parse('{{else}}') Traceback (most recent call last): ... TemplateError: else outside of an if block at line 1 column 3 >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}') Traceback (most recent call last): ... TemplateError: Unexpected endif at line 1 column 25 >>> parse('{{if}}{{endif}}') Traceback (most recent call last): ... TemplateError: if with no expression at line 1 column 3 >>> parse('{{for x y}}{{endfor}}') Traceback (most recent call last): ... TemplateError: Bad for (no "in") in 'x y' at line 1 column 3 >>> parse('{{py:x=1\ny=2}}') Traceback (most recent call last): ... TemplateError: Multi-line py blocks must start with a newline at line 1 column 3 """ if delimiters is None: delimiters = ( Template.default_namespace['start_braces'], Template.default_namespace['end_braces'] ) tokens = lex(s, name=name, line_offset=line_offset, delimiters=delimiters) result = [] while tokens: next_chunk, tokens = parse_expr(tokens, name) result.append(next_chunk) return result def parse_expr(tokens, name, context=()): if isinstance(tokens[0], basestring_): return tokens[0], tokens[1:] expr, pos = tokens[0] expr = expr.strip() if expr.startswith('py:'): expr = expr[3:].lstrip(' \t') if expr.startswith('\n') or expr.startswith('\r'): expr = expr.lstrip('\r\n') if '\r' in expr: expr = expr.replace('\r\n', '\n') expr = expr.replace('\r', '') expr += '\n' else: if '\n' in expr: raise TemplateError( 'Multi-line py blocks must start with a newline', position=pos, name=name) return ('py', pos, expr), tokens[1:] elif expr in ('continue', 'break'): if 'for' not in context: raise TemplateError( 'continue outside of for loop', position=pos, name=name) return (expr, pos), tokens[1:] elif expr.startswith('if '): return parse_cond(tokens, name, context) elif (expr.startswith('elif ') or expr == 'else'): raise TemplateError( '%s outside of an if block' % expr.split()[0], position=pos, name=name) elif expr in ('if', 'elif', 'for'): raise TemplateError( '%s with no expression' % expr, position=pos, name=name) elif expr in ('endif', 'endfor', 'enddef'): raise TemplateError( 'Unexpected %s' % expr, position=pos, name=name) elif expr.startswith('for '): return parse_for(tokens, name, context) elif expr.startswith('default '): return parse_default(tokens, name, context) elif expr.startswith('inherit '): return parse_inherit(tokens, name, context) elif expr.startswith('def '): return parse_def(tokens, name, context) elif expr.startswith('#'): return ('comment', pos, tokens[0][0]), tokens[1:] return ('expr', pos, tokens[0][0]), tokens[1:] def parse_cond(tokens, name, context): start = tokens[0][1] pieces = [] context = context + ('if',) while 1: if not tokens: raise TemplateError( 'Missing {{endif}}', position=start, name=name) if (isinstance(tokens[0], tuple) and tokens[0][0] == 'endif'): return ('cond', start) + tuple(pieces), tokens[1:] next_chunk, tokens = parse_one_cond(tokens, name, context) pieces.append(next_chunk) def parse_one_cond(tokens, name, context): (first, pos), tokens = tokens[0], tokens[1:] content = [] if first.endswith(':'): first = first[:-1] if first.startswith('if '): part = ('if', pos, first[3:].lstrip(), content) elif first.startswith('elif '): part = ('elif', pos, first[5:].lstrip(), content) elif first == 'else': part = ('else', pos, None, content) else: assert 0, "Unexpected token %r at %s" % (first, pos) while 1: if not tokens: raise TemplateError( 'No {{endif}}', position=pos, name=name) if (isinstance(tokens[0], tuple) and (tokens[0][0] == 'endif' or tokens[0][0].startswith('elif ') or tokens[0][0] == 'else')): return part, tokens next_chunk, tokens = parse_expr(tokens, name, context) content.append(next_chunk) def parse_for(tokens, name, context): first, pos = tokens[0] tokens = tokens[1:] context = ('for',) + context content = [] assert first.startswith('for ') if first.endswith(':'): first = first[:-1] first = first[3:].strip() match = in_re.search(first) if not match: raise TemplateError( 'Bad for (no "in") in %r' % first, position=pos, name=name) vars = first[:match.start()] if '(' in vars: raise TemplateError( 'You cannot have () in the variable section of a for loop (%r)' % vars, position=pos, name=name) vars = tuple([ v.strip() for v in first[:match.start()].split(',') if v.strip()]) expr = first[match.end():] while 1: if not tokens: raise TemplateError( 'No {{endfor}}', position=pos, name=name) if (isinstance(tokens[0], tuple) and tokens[0][0] == 'endfor'): return ('for', pos, vars, expr, content), tokens[1:] next_chunk, tokens = parse_expr(tokens, name, context) content.append(next_chunk) def parse_default(tokens, name, context): first, pos = tokens[0] assert first.startswith('default ') first = first.split(None, 1)[1] parts = first.split('=', 1) if len(parts) == 1: raise TemplateError( "Expression must be {{default var=value}}; no = found in %r" % first, position=pos, name=name) var = parts[0].strip() if ',' in var: raise TemplateError( "{{default x, y = ...}} is not supported", position=pos, name=name) if not var_re.search(var): raise TemplateError( "Not a valid variable name for {{default}}: %r" % var, position=pos, name=name) expr = parts[1].strip() return ('default', pos, var, expr), tokens[1:] def parse_inherit(tokens, name, context): first, pos = tokens[0] assert first.startswith('inherit ') expr = first.split(None, 1)[1] return ('inherit', pos, expr), tokens[1:] def parse_def(tokens, name, context): first, start = tokens[0] tokens = tokens[1:] assert first.startswith('def ') first = first.split(None, 1)[1] if first.endswith(':'): first = first[:-1] if '(' not in first: func_name = first sig = ((), None, None, {}) elif not first.endswith(')'): raise TemplateError("Function definition doesn't end with ): %s" % first, position=start, name=name) else: first = first[:-1] func_name, sig_text = first.split('(', 1) sig = parse_signature(sig_text, name, start) context = context + ('def',) content = [] while 1: if not tokens: raise TemplateError( 'Missing {{enddef}}', position=start, name=name) if (isinstance(tokens[0], tuple) and tokens[0][0] == 'enddef'): return ('def', start, func_name, sig, content), tokens[1:] next_chunk, tokens = parse_expr(tokens, name, context) content.append(next_chunk) def parse_signature(sig_text, name, pos): tokens = tokenize.generate_tokens(StringIO(sig_text).readline) sig_args = [] var_arg = None var_kw = None defaults = {} def get_token(pos=False): try: tok_type, tok_string, (srow, scol), (erow, ecol), line = next(tokens) except StopIteration: return tokenize.ENDMARKER, '' if pos: return tok_type, tok_string, (srow, scol), (erow, ecol) else: return tok_type, tok_string while 1: var_arg_type = None tok_type, tok_string = get_token() if tok_type == tokenize.ENDMARKER: break if tok_type == tokenize.OP and (tok_string == '*' or tok_string == '**'): var_arg_type = tok_string tok_type, tok_string = get_token() if tok_type != tokenize.NAME: raise TemplateError('Invalid signature: (%s)' % sig_text, position=pos, name=name) var_name = tok_string tok_type, tok_string = get_token() if tok_type == tokenize.ENDMARKER or (tok_type == tokenize.OP and tok_string == ','): if var_arg_type == '*': var_arg = var_name elif var_arg_type == '**': var_kw = var_name else: sig_args.append(var_name) if tok_type == tokenize.ENDMARKER: break continue if var_arg_type is not None: raise TemplateError('Invalid signature: (%s)' % sig_text, position=pos, name=name) if tok_type == tokenize.OP and tok_string == '=': nest_type = None unnest_type = None nest_count = 0 start_pos = end_pos = None parts = [] while 1: tok_type, tok_string, s, e = get_token(True) if start_pos is None: start_pos = s end_pos = e if tok_type == tokenize.ENDMARKER and nest_count: raise TemplateError('Invalid signature: (%s)' % sig_text, position=pos, name=name) if (not nest_count and (tok_type == tokenize.ENDMARKER or (tok_type == tokenize.OP and tok_string == ','))): default_expr = isolate_expression(sig_text, start_pos, end_pos) defaults[var_name] = default_expr sig_args.append(var_name) break parts.append((tok_type, tok_string)) if nest_count and tok_type == tokenize.OP and tok_string == nest_type: nest_count += 1 elif nest_count and tok_type == tokenize.OP and tok_string == unnest_type: nest_count -= 1 if not nest_count: nest_type = unnest_type = None elif not nest_count and tok_type == tokenize.OP and tok_string in ('(', '[', '{'): nest_type = tok_string nest_count = 1 unnest_type = {'(': ')', '[': ']', '{': '}'}[nest_type] return sig_args, var_arg, var_kw, defaults def isolate_expression(string, start_pos, end_pos): srow, scol = start_pos srow -= 1 erow, ecol = end_pos erow -= 1 lines = string.splitlines(True) if srow == erow: return lines[srow][scol:ecol] parts = [lines[srow][scol:]] parts.extend(lines[srow+1:erow]) if erow < len(lines): # It'll sometimes give (end_row_past_finish, 0) parts.append(lines[erow][:ecol]) return ''.join(parts) _fill_command_usage = """\ %prog [OPTIONS] TEMPLATE arg=value Use py:arg=value to set a Python value; otherwise all values are strings. """ def fill_command(args=None): import sys import optparse import os if args is None: args = sys.argv[1:] kwargs = dict(usage=_fill_command_usage) try: import pkg_resources dist = pkg_resources.get_distribution('tempita') kwargs['version'] = coerce_text(dist) except ImportError: # pkg_resources not available pass parser = optparse.OptionParser(**kwargs) parser.add_option( '-o', '--output', dest='output', metavar="FILENAME", help="File to write output to (default stdout)") parser.add_option( '--html', dest='use_html', action='store_true', help="Use HTML style filling (including automatic HTML quoting)") parser.add_option( '--env', dest='use_env', action='store_true', help="Put the environment in as top-level variables") options, args = parser.parse_args(args) if len(args) < 1: print('You must give a template filename') sys.exit(2) template_name = args[0] args = args[1:] vars = {} if options.use_env: vars.update(os.environ) for value in args: if '=' not in value: print('Bad argument: %r' % value) sys.exit(2) name, value = value.split('=', 1) if name.startswith('py:'): name = name[:3] value = eval(value) vars[name] = value if template_name == '-': template_content = sys.stdin.read() template_name = '' else: f = open(template_name, 'rb') template_content = f.read() f.close() if options.use_html: TemplateClass = HTMLTemplate else: TemplateClass = Template template = TemplateClass(template_content, name=template_name) result = template.substitute(vars) if options.output: f = open(options.output, 'wb') f.write(result) f.close() else: sys.stdout.write(result) if __name__ == '__main__': fill_command() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448528.0 tempita-0.6.0/tempita/__main__.py0000644000175100001770000000006114714747320016330 0ustar00runnerdockerfrom tempita import fill_command fill_command() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448528.0 tempita-0.6.0/tempita/_looper.py0000644000175100001770000001010114714747320016243 0ustar00runnerdocker""" Helper for looping over sequences, particular in templates. Often in a loop in a template it's handy to know what's next up, previously up, if this is the first or last item in the sequence, etc. These can be awkward to manage in a normal Python loop, but using the looper you can get a better sense of the context. Use like:: >>> for loop, item in looper(['a', 'b', 'c']): ... print loop.number, item ... if not loop.last: ... print '---' 1 a --- 2 b --- 3 c """ import sys from tempita.compat3 import basestring_ __all__ = ['looper'] class looper(object): """ Helper for looping (particularly in templates) Use this like:: for loop, item in looper(seq): if loop.first: ... """ def __init__(self, seq): self.seq = seq def __iter__(self): return looper_iter(self.seq) def __repr__(self): return '<%s for %r>' % ( self.__class__.__name__, self.seq) class looper_iter(object): def __init__(self, seq): self.seq = list(seq) self.pos = 0 def __iter__(self): return self def __next__(self): if self.pos >= len(self.seq): raise StopIteration result = loop_pos(self.seq, self.pos), self.seq[self.pos] self.pos += 1 return result if sys.version < "3": next = __next__ class loop_pos(object): def __init__(self, seq, pos): self.seq = seq self.pos = pos def __repr__(self): return '' % ( self.seq[self.pos], self.pos) def index(self): return self.pos index = property(index) def number(self): return self.pos + 1 number = property(number) def item(self): return self.seq[self.pos] item = property(item) def __next__(self): try: return self.seq[self.pos + 1] except IndexError: return None __next__ = property(__next__) if sys.version < "3": next = __next__ def previous(self): if self.pos == 0: return None return self.seq[self.pos - 1] previous = property(previous) def odd(self): return not self.pos % 2 odd = property(odd) def even(self): return self.pos % 2 even = property(even) def first(self): return self.pos == 0 first = property(first) def last(self): return self.pos == len(self.seq) - 1 last = property(last) def length(self): return len(self.seq) length = property(length) def first_group(self, getter=None): """ Returns true if this item is the start of a new group, where groups mean that some attribute has changed. The getter can be None (the item itself changes), an attribute name like ``'.attr'``, a function, or a dict key or list index. """ if self.first: return True return self._compare_group(self.item, self.previous, getter) def last_group(self, getter=None): """ Returns true if this item is the end of a new group, where groups mean that some attribute has changed. The getter can be None (the item itself changes), an attribute name like ``'.attr'``, a function, or a dict key or list index. """ if self.last: return True return self._compare_group(self.item, self.__next__, getter) def _compare_group(self, item, other, getter): if getter is None: return item != other elif (isinstance(getter, basestring_) and getter.startswith('.')): getter = getter[1:] if getter.endswith('()'): getter = getter[:-2] return getattr(item, getter)() != getattr(other, getter)() else: return getattr(item, getter) != getattr(other, getter) elif hasattr(getter, '__call__'): return getter(item) != getter(other) else: return item[getter] != other[getter] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448528.0 tempita-0.6.0/tempita/compat3.py0000644000175100001770000000150614714747320016163 0ustar00runnerdockerimport sys __all__ = ['b', 'basestring_', 'bytes', 'next', 'is_unicode'] if sys.version < "3": b = bytes = str basestring_ = str else: def b(s): if isinstance(s, str): return s.encode('latin1') return bytes(s) basestring_ = (bytes, str) bytes = bytes text = str if sys.version < "3": def next(obj): return obj.__next__() else: next = next if sys.version < "3": def is_unicode(obj): return isinstance(obj, str) else: def is_unicode(obj): return isinstance(obj, str) def coerce_text(v): if not isinstance(v, basestring_): if sys.version < "3": attr = '__unicode__' else: attr = '__str__' if hasattr(v, attr): return str(v) else: return bytes(v) return v ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731448534.7941024 tempita-0.6.0/tests/0000755000175100001770000000000014714747327013747 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731448528.0 tempita-0.6.0/tests/test_tempita.py0000644000175100001770000000033414714747320017014 0ustar00runnerdockerimport os import unittest import doctest class TestTempita(unittest.TestCase): def test_readme(self): doctest.testfile('../README.rst') def test_templating(self): doctest.testfile('tests.txt')