Andriy Kornatskyy avatar Andriy Kornatskyy committed 352f7eb

Working on documentation.

Comments (0)

Files changed (10)

   of Python with minimal markup required to denote python statements.
 * **Do Not Repeat Yourself:** Master layout templates for inheritance;
   include and import directives for maximum reuse.
+* **Blazingly Fast:** Maximum rendering performance: ultimate speed and 
+  context preprocessor features.
 
 Simple template::
 
     $ virtualenv env
     $ env/bin/easy_install wheezy.template
 
+Big Table
+---------
+
+The big table demo compares `wheezy.template` with other template
+engines in terms of how fast a table with 10 columns x 1000 rows can be
+generated::
+
+    @require(table)
+    <table>
+        @for row in table:
+        <tr>
+            @for key, value in row.items():
+            <td>@key!h</td><td>@value!s</td>
+            @end
+        </tr>
+        @end
+    </table>
+
+Install packages used in benchmark test::
+
+    env/bin/easy_install -O2 jinja2 mako tenjin \
+      tornado wheezy.html wheezy.template
+
+Download `bigtable.py`_ source code and run it::
+
+    $ env/bin/python bigtable.py
+    jinja2                         40.22ms  24.86rps
+    list_append                    19.85ms  50.39rps
+    list_extend                    18.71ms  53.46rps
+    mako                           36.19ms  27.63rps
+    tenjin                         28.97ms  34.52rps
+    tornado                        55.91ms  17.89rps
+    wheezy_template                19.99ms  50.02rps
+
+.. image:: static/bench1.png
+
+Real World
+----------
+
+There is real world example available in `wheezy.web`_ package. It can be found
+in `demo.template`_ application.
 
 .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
+.. _`bigtable.py`: https://bitbucket.org/akorn/wheezy.template/src/tip/demos/bigtable/bigtable.py
+.. _`wheezy.web`: http://pypi.python.org/pypi/wheezy.web
+.. _`demo.template`: https://bitbucket.org/akorn/wheezy.web/src/tip/demos/template
   of Python with minimal markup required to denote python statements.
 * **Do Not Repeat Yourself:** Master layout templates for inheritance;
   include and import directives for maximum reuse.
+* **Blazingly Fast:** The most effective python code offers the maximum
+  rendering performance; ultimate speed and context preprocessor features.
 
 Simple template::
 
 .. automodule:: wheezy.template.lexer
    :members:
 
+wheezy.template.loader
+----------------------
+
+.. automodule:: wheezy.template.loader
+   :members:
+
 wheezy.template.parser
 ----------------------
 
 .. automodule:: wheezy.template.parser
    :members:
 
+wheezy.template.preprocessor
+----------------------------
+
+.. automodule:: wheezy.template.preprocessor
+   :members:
+
 wheezy.template.utils
 ---------------------
 
Added
New image

doc/userguide.rst

 User Guide
 ==========
 
-:ref:`wheezy.template`
+:ref:`wheezy.template` uses :py:class:`~wheezy.template.engine.Engine` to store
+configuration information and load templates. Here is a typical example that
+loads templates from file system::
+
+    from wheezy.template.engine import Engine
+    from wheezy.template.ext.core import CoreExtension
+    from wheezy.template.loader import FileLoader
+
+    searchpath = ['content/templates-wheezy']
+    engine = Engine(
+        loader=FileLoader(searchpath),
+        extensions=[CoreExtension()]
+    )
+    template = engine.get_template('template.html')
+
+Loaders
+-------
+
+Loader is used to provide template content to :py:class:`~wheezy.template.engine.Engine`
+by some name requested by application. So what is name and how each loader
+interprets it is up to loader implementation.
+
+:ref:`wheezy.template` comes with the following loaders:
+
+* :py:class:`~wheezy.template.loader.FileLoader` - loads templates from file
+  system (``directories`` - search path of directories to scan for template,
+  ``encoding`` - template content encoding).
+* :py:class:`~wheezy.template.loader.DictLoader` - loads templates from python
+  dictionary (``templates`` - a dict where key corresponds to template name
+  and value to template content).
+* :py:class:`~wheezy.template.loader.ChainLoader` - loads templates from
+  ``loaders`` until first succeed.
+
+Core Extension
+--------------
+
+The :py:class:`~wheezy.template.ext.core.CoreExtension` includes support for
+basic python statements, variables processing and markup.
+
+Context
+~~~~~~~
+
+In order to use variables passed to template you use ``require`` statement and
+list names you need to pick from context. These names becomes visible to the
+end of the template scope (imagine a single template is a python function).
+
+Context access syntax::
+
+    @require(var1, var2, ...)
+
+Variables
+~~~~~~~~~
+
+The application pass variables to template render via context. Variable access
+syntax::
+
+    @variable_name
+
+Example::
+
+    from wheezy.template.engine import Engine
+    from wheezy.template.ext.core import CoreExtension
+    from wheezy.template.loader import DictLoader
+
+    template = """\
+    @require(name)
+    Hello, @name"""
+
+    engine = Engine(
+        loader=DictLoader({'x': template}),
+        extensions=[CoreExtension()]
+    )
+    template = engine.get_template('x')
+
+    print(template.render({'name': 'John'}))
+
+Variable syntax is not limitted to a single name access. You are able to use
+full power of python to access items in dict, attributes, function calls, etc.
+
+Filters
+~~~~~~~
+
+Variables can be formatted by filters. Filters are separated from the variable
+by ``!`` symbol. Filter syntax::
+
+    @variable_name!filter1!filter2
+
+The filters are applied from left to right so above syntax is equvivalent to
+the following call::
+
+    @filter1(filter2(variable_name))
+
+Example::
+
+    @user.age!s
+
+Assuming the age property of user is integer we apply string filter.
+
+You are able to use custom filters, here is an example how to use html escape
+filter::
+
+    try:
+        from wheezy.html.utils import escape_html as escape
+    except ImportError:
+        import cgi
+        escape = cgi.escape
+
+    # ... initialize Engine.
+    engine.global_vars.update({'e': escape})
+
+First we try import optimized version of html escape from `wheezy.html`_
+package and if it is not available fallback to one from ``cgi`` package. Next we
+update engine global variables by escape function which is accessible as ``e``
+filter name in template::
+
+    @user.name!e
+
+You are able use engine ``global_vars`` dictionary in order to simplify your
+template access to some commonly used variables.
+
+Line Statements
+~~~~~~~~~~~~~~~
+
+The following python line statements are supported: `if`, `else`, `elif`,
+`for`. Here is simple example::
+
+    @require(items)
+    @if items:
+        @for i in items:
+            @i.name: $i.price!s.
+        @end
+    @else:
+        No items found.
+    @end
+
+Comments
+~~~~~~~~
+
+Only single line comments are supported::
+
+    @# TODO:
+
+Line Join
+~~~~~~~~~
+
+In case you need continue a long line without breaking it with new line during
+rendering use line join (``\``)::
+
+    @if menu_name == active:
+        <li class='active'> \
+    @else:
+        <li> \
+    @endif
+
+Inheritance
+~~~~~~~~~~~
+
+Template inheritance allows you build a master template that contains common
+layout of your site and defines areas that child templates can override.
+
+
+Master Template
+^^^^^^^^^^^^^^^
+
+Master template is used to provide common layout of your site. Let define
+master template (name ``shared/master.html``)::
+
+    <html>
+        <head>
+            <title>
+            @def title():
+            @end
+            @title() - My Site</title>
+        </head>
+        <body>
+            <div id="content">
+                @def content():
+                @end
+                @content()
+            </div>
+            <div id="footer">
+                @def footer():
+                &copy; Copyright 2012 by Me.
+                @end
+                footer()
+            </div>
+        </body>
+    </html>
+
+In this example, the @def tags define python functions (substitution areas).
+These functions are inserted into a specific places (right after definition).
+These places become place holders for child templates. The @footer place holder
+defines default content while @title and @content are just empty.
+
+Child Template
+^^^^^^^^^^^^^^
+
+Child templates are used to extend master templates via place holders defined::
+
+    @extends("shared/master.html")
+
+    @def title():
+        Welcome
+    @end
+
+    @def content():
+        <h1>Home</h1>
+        <p>
+            Welcome to My Site!
+        </p>
+    @end
+
+In this example, the @title and @content place holders are overriden by child
+templates.
+
+Include
+~~~~~~~
+
+The include is useful to insert a template content just in place of call::
+
+    @include("shared/snippet/script.html")
+
+Import
+~~~~~~
+
+The import is used to reuse some code stored in other files. So you are able
+import all functions defined by that template::
+
+    @import "shared/forms.html" as forms
+
+    @forms.textbox('username')
+
+or just certain name::
+
+    @from "shared/forms.html" import textbox
+
+    @textbox(name='username')
+
+Once imported you use these names as variables in template.
+
+Code Extension
+--------------
+
+The :py:class:`~wheezy.template.ext.code.CodeExtension` includes support for
+embedded python code. Syntax::
+
+    @(
+        # any python code
+    )
+
+
+Preprocessor
+------------
+
+The :py:class:`~wheezy.template.preprocessor.Preprocessor` process templates
+with syntax for preprocessor engine and vary runtime templates (with runtime
+engine factory) by some key function that is context driven. Here is an
+example::
+
+    from wheezy.html.utils import html_escape
+    from wheezy.template.engine import Engine
+    from wheezy.template.ext.core import CoreExtension
+    from wheezy.template.ext.determined import DeterminedExtension
+    from wheezy.template.loader import FileLoader
+    from wheezy.template.preprocessor import Preprocessor
+
+    def runtime_engine_factory(loader):
+        engine = Engine(
+            loader=loader,
+            extensions=[
+                CoreExtension(),
+            ])
+        engine.global_vars.update({
+            'h': html_escape,
+        })
+        return engine
+
+    searchpath = ['content/templates']
+    engine = Engine(
+        loader=FileLoader(searchpath),
+        extensions=[
+            CoreExtension('#', line_join=None),
+            DeterminedExtension(['path_for', '_']),
+        ])
+    engine.global_vars.update({
+    })
+    engine = Preprocessor(runtime_engine_factory, engine,
+                          key_factory=lambda ctx: ctx['locale'])
+
+In this example, the :py:class:`~wheezy.template.preprocessor.Preprocessor` is
+defined to use engine where token start
+is defined as '#', so any directives started with ``#`` are processed one time
+by preprocessor engine. The ``key_factory`` is dependent on runtime context
+and particularly on 'locale'. This way runtime engine factory is varied by
+locale so locale dependent functions (``_`` and ``path_for``) processed only
+once by preprocessor. See complete example in `wheezy.web`_ `demo.template`_
+applicaiton.
+
+
+.. _`wheezy.html`: http://pypi.python.org/pypi/wheezy.html
+.. _`wheezy.web`: http://pypi.python.org/pypi/wheezy.web
+.. _`demo.template`: https://bitbucket.org/akorn/wheezy.web/src/tip/demos/template/

src/wheezy/template/engine.py

 from wheezy.template.builder import SourceBuilder
 from wheezy.template.builder import builder_scan
 from wheezy.template.comp import allocate_lock
+from wheezy.template.compiler import Compiler
 from wheezy.template.lexer import Lexer
 from wheezy.template.lexer import lexer_scan
 from wheezy.template.parser import Parser
 
 
 class Engine(object):
+    """ The core component of template engine.
+    """
 
-    def __init__(self, loader, extensions, template_class=None,
-                 compiler_class=None):
+    def __init__(self, loader, extensions, template_class=None):
         self.lock = allocate_lock()
         self.templates = {}
         self.renders = {}
         }
         self.loader = loader
         self.template_class = template_class or Template
-        if not compiler_class:
-            from wheezy.template.compiler import Compiler as compiler_class
-        self.compiler = compiler_class(self.global_vars, -2)
+        self.compiler = Compiler(self.global_vars, -2)
         self.lexer = Lexer(**lexer_scan(extensions))
         self.parser = Parser(**parser_scan(extensions))
         self.builder = SourceBuilder(**builder_scan(extensions))
 
     def get_template(self, name):
+        """ Returns compiled template.
+        """
         try:
             return self.templates[name]
         except KeyError:
             return self.templates[name]
 
     def render(self, name, ctx, local_defs, super_defs):
+        """ Renders template by name in given context.
+        """
         try:
             return self.renders[name](ctx, local_defs, super_defs)
         except KeyError:
             return self.renders[name](ctx, local_defs, super_defs)
 
     def remove(self, name):
+        """ Removes given ``name`` from internal cache.
+        """
         self.lock.acquire(1)
         try:
             if name in self.renders:
 
 
 class Template(object):
+    """ Simple template class.
+    """
 
     def __init__(self, name, render_template):
         self.name = name

src/wheezy/template/ext/determined.py

 # region: core extension
 
 class DeterminedExtension(object):
-    """ Tranlates funcation calls between template engines.
+    """ Tranlates function calls between template engines.
 
         Strictly determined known calls are converted to preprocessor
         calls, e.g.::

src/wheezy/template/loader.py

 
 
 class FileLoader(object):
+    """ Loads templates from file system.
+
+        ``directories`` - search path of directories to scan for template.
+        ``encoding`` - decode template content per encoding.
+    """
 
     def __init__(self, directories, encoding='UTF-8'):
         searchpath = []
         self.encoding = encoding
 
     def list_names(self):
+        """ Return a list of names relative to directories. Ignores any files
+            and directories that start with dot.
+        """
         names = []
         for path in self.searchpath:
             pathlen = len(path) + 1
         return tuple(names)
 
     def get_fullname(self, name):
+        """ Returns a full path by a template name.
+        """
         for path in self.searchpath:
             filename = os.path.join(path, name)
             if not os.path.exists(filename):
             None
 
     def load(self, name):
+        """ Loads a template by name from file system.
+        """
         filename = self.get_fullname(name)
         if filename:
             f = open(filename, 'rb')
 
 
 class DictLoader(object):
+    """ Loads templates from python dictionary.
+
+        ``templates`` - a dict where key corresponds to template name and
+        value to template content.
+    """
 
     def __init__(self, templates):
         self.templates = templates
 
     def list_names(self):
+        """ List all keys from internal dict.
+        """
         return tuple(self.templates.keys())
 
     def load(self, name):
+        """ Returns template by name.
+        """
         if name not in self.templates:
             return None
         return self.templates[name]
 
 
 class ChainLoader(object):
+    """ Loads templates from ``loaders`` until first succeed.
+    """
 
     def __init__(self, loaders):
         self.loaders = loaders
 
     def list_names(self):
+        """ Returns as list of names from all loaders.
+        """
         names = set()
         for loader in self.loaders:
             names |= set(loader.list_names())
         return tuple(names)
 
     def load(self, name):
+        """ Returns template by name from the first loader that succeed.
+        """
         for loader in self.loaders:
             source = loader.load(name)
             if source:
 
 
 def autoreload(engine, enabled=True):
+    """ Auto reload template if changes are detected in file. Limitation:
+        inherited and imported templates.
+    """
     if not enabled:
         return engine
 

src/wheezy/template/preprocessor.py

 
 
 class Preprocessor(object):
+    """ Preprocess templates with ``engine`` and vary runtime templates
+        by ``key_factory`` function using ``runtime_engine_factory``.
+    """
 
     def __init__(self, runtime_engine_factory, engine, key_factory):
         self.lock = allocate_lock()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.