Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

git-svn-id: http://svn.twig-project.org/trunk@4 93ef8e89-cb99-4229-a87c-7fa0fa45744b
  • Loading branch information...
commit fd0646e4cfef27c6325da678863764f2f1aaa7f9 1 parent 9511782
fabien authored
Showing with 8,184 additions and 0 deletions.
  1. +9 −0 AUTHORS
  2. +31 −0 LICENSE
  3. +8 −0 README.markdown
  4. +85 −0 doc/01-Introduction.markdown
  5. +692 −0 doc/02-Twig-for-Template-Designers.markdown
  6. +284 −0 doc/03-Twig-for-Developers.markdown
  7. +290 −0 doc/04-Extending-Twig.markdown
  8. +196 −0 doc/05-Hacking-Twig.markdown
  9. +48 −0 lib/Twig/Autoloader.php
  10. +245 −0 lib/Twig/Compiler.php
  11. +36 −0 lib/Twig/CompilerInterface.php
  12. +251 −0 lib/Twig/Environment.php
  13. +21 −0 lib/Twig/Error.php
  14. +397 −0 lib/Twig/ExpressionParser.php
  15. +51 −0 lib/Twig/Extension.php
  16. +113 −0 lib/Twig/Extension/Core.php
  17. +76 −0 lib/Twig/Extension/Escaper.php
  18. +25 −0 lib/Twig/Extension/Macro.php
  19. +88 −0 lib/Twig/Extension/Sandbox.php
  20. +16 −0 lib/Twig/Extension/Set.php
  21. +55 −0 lib/Twig/ExtensionInterface.php
  22. +304 −0 lib/Twig/Lexer.php
  23. +30 −0 lib/Twig/LexerInterface.php
  24. +111 −0 lib/Twig/Loader.php
  25. +41 −0 lib/Twig/Loader/Array.php
  26. +56 −0 lib/Twig/Loader/Filesystem.php
  27. +34 −0 lib/Twig/Loader/String.php
  28. +29 −0 lib/Twig/LoaderInterface.php
  29. +47 −0 lib/Twig/Node.php
  30. +55 −0 lib/Twig/Node/AutoEscape.php
  31. +88 −0 lib/Twig/Node/Block.php
  32. +47 −0 lib/Twig/Node/BlockReference.php
  33. +45 −0 lib/Twig/Node/Call.php
  34. +22 −0 lib/Twig/Node/Expression.php
  35. +19 −0 lib/Twig/Node/Expression/AssignName.php
  36. +61 −0 lib/Twig/Node/Expression/Binary.php
  37. +18 −0 lib/Twig/Node/Expression/Binary/Add.php
  38. +18 −0 lib/Twig/Node/Expression/Binary/And.php
  39. +18 −0 lib/Twig/Node/Expression/Binary/Concat.php
  40. +18 −0 lib/Twig/Node/Expression/Binary/Div.php
  41. +18 −0 lib/Twig/Node/Expression/Binary/Mod.php
  42. +18 −0 lib/Twig/Node/Expression/Binary/Mul.php
  43. +18 −0 lib/Twig/Node/Expression/Binary/Or.php
  44. +18 −0 lib/Twig/Node/Expression/Binary/Sub.php
  45. +57 −0 lib/Twig/Node/Expression/Compare.php
  46. +38 −0 lib/Twig/Node/Expression/Conditional.php
  47. +36 −0 lib/Twig/Node/Expression/Constant.php
  48. +114 −0 lib/Twig/Node/Expression/Filter.php
  49. +56 −0 lib/Twig/Node/Expression/GetAttr.php
  50. +36 −0 lib/Twig/Node/Expression/Name.php
  51. +44 −0 lib/Twig/Node/Expression/Unary.php
  52. +18 −0 lib/Twig/Node/Expression/Unary/Neg.php
  53. +18 −0 lib/Twig/Node/Expression/Unary/Not.php
  54. +18 −0 lib/Twig/Node/Expression/Unary/Pos.php
  55. +55 −0 lib/Twig/Node/Filter.php
  56. +102 −0 lib/Twig/Node/For.php
  57. +103 −0 lib/Twig/Node/If.php
  58. +73 −0 lib/Twig/Node/Include.php
  59. +55 −0 lib/Twig/Node/Macro.php
  60. +178 −0 lib/Twig/Node/Module.php
  61. +42 −0 lib/Twig/Node/Parent.php
  62. +66 −0 lib/Twig/Node/Print.php
  63. +27 −0 lib/Twig/Node/Set.php
  64. +49 −0 lib/Twig/Node/Text.php
  65. +62 −0 lib/Twig/NodeList.php
  66. +30 −0 lib/Twig/NodeListInterface.php
  67. +42 −0 lib/Twig/NodeTransformer.php
  68. +39 −0 lib/Twig/NodeTransformer/Chain.php
  69. +84 −0 lib/Twig/NodeTransformer/Escaper.php
  70. +68 −0 lib/Twig/NodeTransformer/Filter.php
  71. +58 −0 lib/Twig/NodeTransformer/Sandbox.php
  72. +213 −0 lib/Twig/Parser.php
  73. +29 −0 lib/Twig/ParserInterface.php
  74. +22 −0 lib/Twig/RuntimeError.php
  75. +21 −0 lib/Twig/Sandbox/SecurityError.php
  76. +84 −0 lib/Twig/Sandbox/SecurityPolicy.php
  77. +24 −0 lib/Twig/Sandbox/SecurityPolicyInterface.php
  78. +51 −0 lib/Twig/SyntaxError.php
  79. +69 −0 lib/Twig/Template.php
  80. +18 −0 lib/Twig/TemplateInterface.php
  81. +111 −0 lib/Twig/Token.php
  82. +24 −0 lib/Twig/TokenParser.php
  83. +38 −0 lib/Twig/TokenParser/AutoEscape.php
  84. +52 −0 lib/Twig/TokenParser/Block.php
  85. +30 −0 lib/Twig/TokenParser/Call.php
  86. +31 −0 lib/Twig/TokenParser/Display.php
  87. +30 −0 lib/Twig/TokenParser/Extends.php
  88. +34 −0 lib/Twig/TokenParser/Filter.php
  89. +50 −0 lib/Twig/TokenParser/For.php
  90. +65 −0 lib/Twig/TokenParser/If.php
  91. +34 −0 lib/Twig/TokenParser/Include.php
  92. +37 −0 lib/Twig/TokenParser/Macro.php
  93. +29 −0 lib/Twig/TokenParser/Parent.php
  94. +20 −0 lib/Twig/TokenParser/Set.php
  95. +139 −0 lib/Twig/TokenStream.php
  96. +158 −0 lib/Twig/runtime.php
  97. +26 −0 lib/Twig/runtime_escaper.php
  98. +123 −0 lib/Twig/runtime_for.php
  99. +44 −0 test/bin/coverage.php
  100. +28 −0 test/bin/prove.php
  101. +40 −0 test/fixtures/expressions/binary.test
  102. +16 −0 test/fixtures/expressions/comparison.test
  103. +8 −0 test/fixtures/expressions/grouping.test
  104. +14 −0 test/fixtures/expressions/ternary_operator.test
  105. +10 −0 test/fixtures/expressions/unary.test
  106. +10 −0 test/fixtures/filters/date.test
  107. +10 −0 test/fixtures/filters/default.test
  108. +9 −0 test/fixtures/filters/even.test
  109. +8 −0 test/fixtures/filters/format.test
  110. +10 −0 test/fixtures/filters/length.test
  111. +10 −0 test/fixtures/filters/odd.test
  112. +10 −0 test/fixtures/filters/sort.test
  113. +22 −0 test/fixtures/tags/autoescape/basic.test
  114. +10 −0 test/fixtures/tags/autoescape/double_escaping.test
  115. +26 −0 test/fixtures/tags/autoescape/nested.test
  116. +12 −0 test/fixtures/tags/autoescape/safe.test
  117. +10 −0 test/fixtures/tags/filter/basic.test
  118. +16 −0 test/fixtures/tags/filter/nested.test
  119. +13 −0 test/fixtures/tags/filter/with_for_tag.test
  120. +29 −0 test/fixtures/tags/filter/with_if_tag.test
  121. +18 −0 test/fixtures/tags/for/context.test
  122. +21 −0 test/fixtures/tags/for/else.test
  123. +11 −0 test/fixtures/tags/for/keys.test
  124. +11 −0 test/fixtures/tags/for/keys_and_values.test
  125. +19 −0 test/fixtures/tags/for/loop_context.test
  126. +10 −0 test/fixtures/tags/for/loop_context_local.test
  127. +17 −0 test/fixtures/tags/for/nested_else.test
  128. +34 −0 test/fixtures/tags/for/objects.test
  129. +18 −0 test/fixtures/tags/for/recursive.test
  130. +11 −0 test/fixtures/tags/for/values.test
  131. +22 −0 test/fixtures/tags/if/basic.test
  132. +22 −0 test/fixtures/tags/if/expression.test
  133. +16 −0 test/fixtures/tags/include/basic.test
  134. +16 −0 test/fixtures/tags/include/expression.test
  135. +14 −0 test/fixtures/tags/inheritance/basic.test
  136. +12 −0 test/fixtures/tags/inheritance/parent.test
  137. +43 −0 test/lib/Twig_Loader_Var.php
  138. +27 −0 test/unit/Twig/AutoloaderTest.php
  139. +127 −0 test/unit/Twig/Extension/Sandbox.php
  140. +70 −0 test/unit/integrationTest.php
View
9 AUTHORS
@@ -0,0 +1,9 @@
+Twig is written and maintained by the Twig Team:
+
+Lead Developer:
+
+- Fabien Potencier <fabien.potencier@symfony-project.org>
+
+Project Founder:
+
+- Armin Ronacher <armin.ronacher@active-4.com>
View
31 LICENSE
@@ -0,0 +1,31 @@
+Copyright (c) 2009 by the Twig Team, see AUTHORS for more details.
+
+Some rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * The names of the contributors may not be used to endorse or
+ promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
8 README.markdown
@@ -0,0 +1,8 @@
+Twig, the flexible, fast, and secure template language for PHP
+==============================================================
+
+Twig is a template language for PHP, released under the new BSD license (code
+and documentation).
+
+Twig uses a syntax similar to the Django and Jinja template languages which
+inspired the Twig runtime environment.
View
85 doc/01-Introduction.markdown
@@ -0,0 +1,85 @@
+Introduction
+============
+
+This is the documentation for Twig, the flexible, fast, and secure template
+language for PHP.
+
+If you have any exposure to other text-based template languages, such as
+Smarty, Django, or Jinja, you should feel right at home with Twig. It's both
+designer and developer friendly by sticking to PHP's principles and adding
+functionality useful for templating environments.
+
+The key-features are...
+
+ * *Fast*: Twig compiles templates down to plain optimized PHP code. The
+ overhead compared to regular PHP code was reduced to the very minimum.
+
+ * *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This
+ allows Twig to be used as a templating language for applications where
+ users may modify the template design.
+
+ * *Flexible*: Twig is powered by a flexible lexer and parser. This allows the
+ developer to define its own custom tags and filters, and create its own
+ DSL.
+
+Prerequisites
+-------------
+
+Twig needs at least **PHP 5.2.4** to run.
+
+Installation
+------------
+
+You have multiple ways to install Twig. If you are unsure what to do, go with
+the tarball.
+
+### From the tarball release
+
+ 1. Download the most recent tarball from the [download page](http://www.twig-project.org/installation)
+ 2. Unpack the tarball
+ 3. Move the files somewhere in your project
+
+### Installing the development version
+
+ 1. Install Subversion
+ 2. `svn co http://svn.twig-project.org/trunk/ twig`
+
+Basic API Usage
+---------------
+
+This section gives you a brief introduction to the PHP API for Twig.
+
+The first step to use Twig is to register its autoloader:
+
+ [php]
+ require_once '/path/to/lib/Twig/Autoloader.php';
+ Twig_Autoloader::register();
+
+Replace the `/path/to/lib/` path with the path you used for Twig installation.
+
+>**NOTE**
+>Twig follows the PEAR convention names for its classes, which means you can
+>easily integrate Twig classes loading in your own autoloader.
+
+ [php]
+ $loader = new Twig_Loader_String();
+ $twig = new Twig_Environment($loader);
+
+ $template = $twig->loadTemplate('Hello {{ name }}!');
+
+ $template->display(array('name' => 'Fabien'));
+
+Twig uses a loader (`Twig_Loader_String`) to locate templates, and an
+environment (`Twig_Environment`) to store the configuration.
+
+The `loadTemplate()` method uses the loader to locate and load the template
+and returns a template object (`Twig_Template`) which is suitable for
+rendering with the `display()` method.
+
+Twig also comes with a filesystem loader:
+
+ [php]
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
+ $twig = new Twig_Environment($loader);
+
+ $template = $twig->loadTemplate('index.html');
View
692 doc/02-Twig-for-Template-Designers.markdown
@@ -0,0 +1,692 @@
+Twig for Template Designers
+===========================
+
+This document describes the syntax and semantics of the template engine and
+will be most useful as reference to those creating Twig templates.
+
+Synopsis
+--------
+
+A template is simply a text file. It can generate any text-based format (HTML,
+XML, CSV, LaTeX, etc.). It doesn't have a specific extension, `.html` or
+`.xml` are just fine.
+
+A template contains **variables** or **expressions**, which get replaced with
+values when the template is evaluated, and tags, which control the logic of
+the template.
+
+Below is a minimal template that illustrates a few basics. We will cover the
+details later in that document:
+
+ [twig]
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+ <html lang="en">
+ <head>
+ <title>My Webpage</title>
+ </head>
+ <body>
+ <ul id="navigation">
+ {% for item in navigation %}
+ <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
+ {% endfor %}
+ </ul>
+
+ <h1>My Webpage</h1>
+ {{ a_variable }}
+ </body>
+ </html>
+
+There are two kinds of delimiters: `{% ... %}` and `{{ ... }}`. The first one
+is used to execute statements such as for-loops, the latter prints the result
+of an expression to the template.
+
+Variables
+---------
+
+The application passes variables to the templates you can mess around in the
+template. Variables may have attributes or elements on them you can access
+too. How a variable looks like, heavily depends on the application providing
+those.
+
+You can use a dot (`.`) to access attributes of a variable, alternative the
+so-called "subscript" syntax (`[]`) can be used. The following lines do the
+same::
+
+ [twig]
+ {{ foo.bar }}
+ {{ foo['bar'] }}
+
+>**NOTE**
+>It's important to know that the curly braces are *not* part of the variable
+>but the print statement. If you access variables inside tags don't put the
+>braces around.
+
+If a variable or attribute does not exist you will get back a `null` value.
+
+>**SIDEBAR**
+>Implementation
+>
+>For convenience sake `foo.bar` does the following things on
+>the PHP layer:
+>
+> * check if `foo` is an array and `bar` a valid element;
+> * if not, and if `foo` is an object, check that `bar` is a valid method;
+> * if not, and if `foo` is an object, check that `getBar` is a valid method;
+> * if not, return a `null` value.
+>
+>`foo['bar']` on the other hand works mostly the same with the a small
+>difference in the order:
+>
+> * check if `foo` is an array and `bar` a valid element;
+> * if not, return a `null` value.
+>
+>Using the alternative syntax is also useful to dynamically get attributes
+>from arrays:
+>
+> [twig]
+> foo[bar]
+
+Filters
+-------
+
+Variables can by modified by **filters**. Filters are separated from the
+variable by a pipe symbol (`|`) and may have optional arguments in
+parentheses. Multiple filters can be chained. The output of one filter is
+applied to the next.
+
+`{{ name|striptags|title }}` for example will remove all HTML tags from the
+`name` and title-cases it. Filters that accept arguments have parentheses
+around the arguments, like a function call. This example will join a list by
+commas: `{{ list|join(', ') }}`.
+
+The builtin filters section below describes all the builtin filters.
+
+Comments
+--------
+
+To comment-out part of a line in a template, use the comment syntax `{# ... #}`.
+This is useful to comment out parts of the template for debugging or to
+add information for other template designers or yourself:
+
+ [twig]
+ {# note: disabled template because we no longer use this
+ {% for user in users %}
+ ...
+ {% endfor %}
+ #}
+
+Whitespace Control
+------------------
+
+In the default configuration whitespace is not further modified by the
+template engine, so each whitespace (spaces, tabs, newlines etc.) is returned
+unchanged. If the application configures Twig to `trim_blocks` the first
+newline after a template tag is removed automatically (like in PHP).
+
+Escaping
+--------
+
+It is sometimes desirable or even necessary to have Twig ignore parts it would
+otherwise handle as variables or blocks. For example if the default syntax is
+used and you want to use `{{` as raw string in the template and not start a
+variable you have to use a trick.
+
+The easiest way is to output the variable delimiter (`{{`) by using a variable
+expression:
+
+ [twig]
+ {{ '{{' }}
+
+For bigger sections it makes sense to mark a block `raw`. For example to put
+Twig syntax as example into a template you can use this snippet:
+
+ [twig]
+ {% raw %}
+ <ul>
+ {% for item in seq %}
+ <li>{{ item }}</li>
+ {% endfor %}
+ </ul>
+ {% endraw %}
+
+Template Inheritance
+--------------------
+
+The most powerful part of Twig is template inheritance. Template inheritance
+allows you to build a base "skeleton" template that contains all the common
+elements of your site and defines **blocks** that child templates can
+override.
+
+Sounds complicated but is very basic. It's easiest to understand it by
+starting with an example.
+
+### Base Template
+
+This template, which we'll call `base.html`, defines a simple HTML skeleton
+document that you might use for a simple two-column page. It's the job of
+"child" templates to fill the empty blocks with content:
+
+ [twig]
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+ <html lang="en">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ {% block head %}
+ <link rel="stylesheet" href="style.css" />
+ <title>{% block title %}{% endblock %} - My Webpage</title>
+ {% endblock %}
+ </head>
+ <body>
+ <div id="content">{% block content %}{% endblock %}</div>
+ <div id="footer">
+ {% block footer %}
+ &copy; Copyright 2009 by <a href="http://domain.invalid/">you</a>.
+ {% endblock %}
+ </div>
+ </body>
+
+In this example, the `{% block %}` tags define four blocks that child
+templates can fill in. All the `block` tag does is to tell the template engine
+that a child template may override those portions of the template.
+
+### Child Template
+
+A child template might look like this:
+
+ [twig]
+ {% extends "base.html" %}
+
+ {% block title %}Index{% endblock %}
+ {% block head %}
+ {% parent %}
+ <style type="text/css">
+ .important { color: #336699; }
+ </style>
+ {% endblock %}
+ {% block content %}
+ <h1>Index</h1>
+ <p class="important">
+ Welcome on my awesome homepage.
+ </p>
+ {% endblock %}
+
+The `{% extends %}` tag is the key here. It tells the template engine that
+this template "extends" another template. When the template system evaluates
+this template, first it locates the parent. The extends tag should be the
+first tag in the template.
+
+The filename of the template depends on the template loader. For example the
+`Twig_Loader_Filesystem` allows you to access other templates by giving the
+filename. You can access templates in subdirectories with a slash:
+
+ [twig]
+ {% extends "layout/default.html" %}
+
+But this behavior can depend on the application embedding Twig. Note that
+since the child template doesn't define the `footer` block, the value from the
+parent template is used instead.
+
+You can't define multiple `{% block %}` tags with the same name in the same
+template. This limitation exists because a block tag works in "both"
+directions. That is, a block tag doesn't just provide a hole to fill - it also
+defines the content that fills the hole in the *parent*. If there were two
+similarly-named `{% block %}` tags in a template, that template's parent
+wouldn't know which one of the blocks' content to use.
+
+If you want to print a block multiple times you can however use the `display`
+tag:
+
+ [twig]
+ <title>{% block title %}{% endblock %}</title>
+ <h1>{% display title %}</h1>
+ {% block body %}{% endblock %}
+
+Like PHP, Twig does not support multiple inheritance. So you can only have one
+extends tag called per rendering.
+
+### Parent Blocks
+
+It's possible to render the contents of the parent block by using the `parent`
+tag. This gives back the results of the parent block:
+
+ [twig]
+ {% block sidebar %}
+ <h3>Table Of Contents</h3>
+ ...
+ {% parent %}
+ {% endblock %}
+
+### Named Block End-Tags
+
+Twig allows you to put the name of the block after the end tag for better
+readability:
+
+ [twig]
+ {% block sidebar %}
+ {% block inner_sidebar %}
+ ...
+ {% endblock inner_sidebar %}
+ {% endblock sidebar %}
+
+However the name after the `endblock` word must match the block name.
+
+### Block Nesting and Scope
+
+Blocks can be nested for more complex layouts. Per default, blocks have access
+to variables from outer scopes:
+
+ [twig]
+ {% for item in seq %}
+ <li>{% block loop_item %}{{ item }}{% endblock %}</li>
+ {% endfor %}
+
+Import Context Behavior
+-----------------------
+
+Per default included templates are passed the current context.
+
+The context that is passed to the included template includes variables defined
+in the template:
+
+ [twig]
+ {% for box in boxes %}
+ {% include "render_box.html" %}
+ {% endfor %}
+
+The included template `render_box.html` is able to access `box`.
+
+HTML Escaping
+-------------
+
+When generating HTML from templates, there's always a risk that a variable
+will include characters that affect the resulting HTML. There are two
+approaches: manually escaping each variable or automatically escaping
+everything by default.
+
+Twig supports both, but what is used depends on the application configuration.
+The default configuration is no automatic escaping for various reasons:
+
+ * Escaping everything except of safe values will also mean that Twig is
+ escaping variables known to not include HTML such as numbers which is a
+ huge performance hit.
+
+ * The information about the safety of a variable is very fragile. It could
+ happen that by coercing safe and unsafe values the return value is double
+ escaped HTML.
+
+>**NOTE**
+>Escaping is only supported if the *escaper* extension has been enabled.
+
+### Working with Manual Escaping
+
+If manual escaping is enabled it's **your** responsibility to escape variables
+if needed. What to escape? If you have a variable that *may* include any of
+the following chars (`>`, `<`, `&`, or `"`) you **have to** escape it unless
+the variable contains well-formed and trusted HTML. Escaping works by piping
+the variable through the `|e` filter: `{{ user.username|e }}`.
+
+### Working with Automatic Escaping
+
+Automatic escaping is enabled when the `escaper` extension has been enabled.
+
+Whether automatic escaping is enabled or not, you can mark a section of a
+template to be escaped or not by using the `autoescape` tag:
+
+ [twig]
+ {% autoescape on %}
+ Everything will be automatically escaped in this block
+ {% endautoescape %}
+
+ {% autoescape off %}
+ Everything will be outputed as is in this block
+ {% endautoescape %}
+
+When automatic escaping is enabled everything is escaped by default except for
+values explicitly marked as safe. Those can be marked in the template by using
+the `|safe` filter.
+
+Functions returning template data (like `parent`) return safe markup always.
+
+>**NOTE**
+>Twig is smart enough to not escape an already escaped value by the `escape`
+>filter.
+
+List of Control Structures
+--------------------------
+
+A control structure refers to all those things that control the flow of a
+program - conditionals (i.e. `if`/`elseif`/`else`), `for`-loops, as well as
+things like blocks. Control structures appear inside `{% ... %}` blocks.
+
+### For
+
+Loop over each item in a sequence. For example, to display a list of users
+provided in a variable called `users`:
+
+ [twig]
+ <h1>Members</h1>
+ <ul>
+ {% for user in users %}
+ <li>{{ user.username|e }}</li>
+ {% endfor %}
+ </ul>
+
+Inside of a for loop block you can access some special variables:
+
+| Variable | Description
+| --------------------- | -------------------------------------------------------------
+| `loop.index` | The current iteration of the loop. (1 indexed)
+| `loop.index0` | The current iteration of the loop. (0 indexed)
+| `loop.revindex` | The number of iterations from the end of the loop (1 indexed)
+| `loop.revindex0` | The number of iterations from the end of the loop (0 indexed)
+| `loop.first` | True if first iteration
+| `loop.last` | True if last iteration
+| `loop.length` | The number of items in the sequence
+
+Unlike in PHP it's not possible to `break` or `continue` in a loop.
+
+If no iteration took place because the sequence was empty, you can render a
+replacement block by using `else`:
+
+ [twig]
+ <ul>
+ {% for user in users %}
+ <li>{{ user.username|e }}</li>
+ {% else %}
+ <li><em>no users found</em></li>
+ {% endif %}
+ </ul>
+
+### If
+
+The `if` statement in Twig is comparable with the if statements of PHP. In the
+simplest form you can use it to test if a variable is defined, not empty or
+not false:
+
+ [twig]
+ {% if users %}
+ <ul>
+ {% for user in users %}
+ <li>{{ user.username|e }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+
+For multiple branches `elseif` and `else` can be used like in PHP. You can use
+more complex `expressions` there too:
+
+ {% if kenny.sick %}
+ Kenny is sick.
+ {% elseif kenny.dead %}
+ You killed Kenny! You bastard!!!
+ {% else %}
+ Kenny looks okay --- so far
+ {% endif %}
+
+### Filters
+
+Filter sections allow you to apply regular Twig filters on a block of template
+data. Just wrap the code in the special `filter` section:
+
+ [twig]
+ {% filter upper %}
+ This text becomes uppercase
+ {% endfilter %}
+
+### Extends
+
+The `extends` tag can be used to extend a template from another one. You can
+have multiple of them in a file but only one of them may be executed at the
+time. There is no support for multiple inheritance. See the section about
+Template inheritance above.
+
+### Block
+
+Blocks are used for inheritance and act as placeholders and replacements at
+the same time. They are documented in detail as part of the section about
+Template inheritance above.
+
+### Include
+
+The `include` statement is useful to include a template and return the
+rendered contents of that file into the current namespace:
+
+ [twig]
+ {% include 'header.html' %}
+ Body
+ {% include 'footer.html' %}
+
+Included templates have access to the variables of the active context.
+
+An included file can be evaluated in the sandbox environment by appending
+`sandboxed` at the end if the `escaper` extension has been enabled:
+
+ [twig]
+ {% include 'user.html' sandboxed %}
+
+Expressions
+-----------
+
+Twig allows basic expressions everywhere. These work very similar to regular
+PHP and even if you're not working with PHP you should feel comfortable with
+it.
+
+### Literals
+
+The simplest form of expressions are literals. Literals are representations
+for PHP types such as strings and numbers. The following literals exist:
+
+ * `"Hello World"`: Everything between two double or single quotes is a
+ string. They are useful whenever you need a string in the template (for
+ example as arguments to function calls, filters or just to extend or
+ include a template).
+
+ * `42` / `42.23`: Integers and floating point numbers are created by just
+ writing the number down. If a dot is present the number is a float,
+ otherwise an integer.
+
+### Math
+
+Twig allows you to calculate with values. This is rarely useful in templates
+but exists for completeness' sake. The following operators are supported:
+
+ * `+`: Adds two objects together. Usually the objects are numbers but if both
+ are strings or lists you can concatenate them this way. This however is not
+ the preferred way to concatenate strings! For string concatenation have a
+ look at the `~` operator. `{{ 1 + 1 }}` is `2`.
+
+ * `-`: Substract the second number from the first one. `{{ 3 - 2 }}` is `1`.
+
+ * `/`: Divide two numbers. The return value will be a floating point number.
+ `{{ 1 / 2 }}` is `{{ 0.5 }}`.
+
+ * `//`: Divide two numbers and return the truncated integer result. `{{ 20 //
+ 7 }}` is `2`.
+
+ * `%`: Calculate the remainder of an integer division. `{{ 11 % 7 }}` is `4`.
+
+ * `*`: Multiply the left operand with the right one. `{{ 2 * 2 }}` would
+ return `4`. This can also be used to repeat a string multiple times. `{{
+ '=' * 80 }}` would print a bar of 80 equal signs.
+
+ * `**`: Raise the left operand to the power of the right operand. `{{ 2**3
+ }}` would return `8`.
+
+### Logic
+
+For `if` statements, `for` filtering or `if` expressions it can be useful to
+combine multiple expressions:
+
+ * `and`: Return true if the left and the right operand is true.
+
+ * `or`: Return true if the left or the right operand is true.
+
+ * `not`: Negate a statement.
+
+ * `(expr)`: Group an expression.
+
+### Comparisons
+
+The following comparison operators are supported in any expression: `==`,
+`!=`, `<`, `>`, `>=`, and `<=`.
+
+>**TIP**
+>Besides PHP classic comparison operators, Twig also supports a shortcut
+>notation when you want to test a value in a range:
+>
+> [twig]
+> {% if 1 < foo < 4 %}foo is between 1 and 4{% endif %}
+
+### Other Operators
+
+The following operators are very useful but don't fit into any of the other
+two categories:
+
+ * `|`: Applies a filter.
+
+ * `~`: Converts all operands into strings and concatenates them. `{{ "Hello "
+ ~ name ~ "!" }}` would return (assuming `name` is `'John'`) `Hello John!`.
+
+ * `.`, `[]`: Get an attribute of an object.
+
+ * `?:`: Twig supports the PHP ternary operator:
+
+ [twig]
+ {{ foo ? 'yes' : 'no' }}
+
+List of Builtin Filters
+-----------------------
+
+### `date`
+
+The `date` filter is able to format a date to a given format:
+
+ [twig]
+ {{ post.published_at|date("m/d/Y") }}
+
+### `format`
+
+The `format` filter formats a given string by replacing the placeholders:
+
+
+ [twig]
+ {# string is a format string like: I like %s and %s. #}
+ {{ string|format(foo, "bar") }}
+ {# returns I like foo and bar. (if the foo parameter equals to the foo string) #}
+
+### `even`
+
+The `even` filter returns `true` if the given number is even, `false`
+otherwise:
+
+ [twig]
+ {{ var|even ? 'even' : 'odd' }}
+
+### `odd`
+
+The `odd` filter returns `true` if the given number is odd, `false`
+otherwise:
+
+ [twig]
+ {{ var|odd ? 'odd' : 'even' }}
+
+### `encoding`
+
+The `encoding` filter URL encode a given string.
+
+### `title`
+
+The `title` filter returns a titlecased version of the value. I.e. words will
+start with uppercase letters, all remaining characters are lowercase.
+
+### `capitalize`
+
+The `capitalize` filter capitalizes a value. The first character will be
+uppercase, all others lowercase.
+
+### `upper`
+
+The `upper` filter converts a value to uppercase.
+
+### `lower`
+
+The `lower` filter converts a value to lowercase.
+
+### `striptags`
+
+The `striptags` filter strips SGML/XML tags and replace adjacent whitespace by
+one space.
+
+### `join`
+
+The `join` filter returns a string which is the concatenation of the strings
+in the sequence. The separator between elements is an empty string per
+default, you can define it with the optional parameter:
+
+ [twig]
+ {{ [1, 2, 3]|join('|') }}
+ {# returns 1|2|3 #}
+
+ {{ [1, 2, 3]|join }}
+ {# returns 123 #}
+
+### `reverse`
+
+The `reverse` filter reverses an array or an object if it implements the
+`Iterator` interface.
+
+### `length`
+
+The `length` filters returns the number of items of a sequence or mapping, or
+the length of a string.
+
+### `sort`
+
+The `sort` filter sorts an array.
+
+### `default`
+
+The `default` filter returns the passed default value if the value is
+undefined, otherwise the value of the variable:
+
+ [twig]
+ {{ my_variable|default('my_variable is not defined') }}
+
+### `keys`
+
+The `keys` filter returns the keys of an array. It is useful when you want to
+iterate over the keys of an array:
+
+ [twig]
+ {% for key in array|keys %}
+ ...
+ {% endfor %}
+
+### `items`
+
+The `items` filter is mainly useful when using the `for` tag to iterate over
+both the keys and the values of an array:
+
+ [twig]
+ {% for key, value in array|items %}
+ ...
+ {% endfor %}
+
+### `escape`, `e`
+
+The `escape` filter converts the characters `&`, `<`, `>`, `'`, and `"` in
+strings to HTML-safe sequences. Use this if you need to display text that
+might contain such characters in HTML.
+
+>**NOTE**
+>Internally, `escape` uses the PHP `htmlspecialchars` function.
+
+### `safe`
+
+The `safe` filter marks the value as safe which means that in an environment
+with automatic escaping enabled this variable will not be escaped.
+
+ [twig]
+ {% autoescape on }
+ {{ var|safe }} {# var won't be escaped #}
+ {% autoescape off %}
View
284 doc/03-Twig-for-Developers.markdown
@@ -0,0 +1,284 @@
+Twig for Developers
+===================
+
+This chapter describes the API to Twig and not the template language. It will
+be most useful as reference to those implementing the template interface to
+the application and not those who are creating Twig templates.
+
+Basics
+------
+
+Twig uses a central object called the **environment** (of class
+`Twig_Environment`). Instances of this class are used to store the
+configuration and extensions, and are used to load templates from the file
+system or other locations.
+
+Most applications will create one `Twig_Environment` object on application
+initialization and use that to load templates. In some cases it's however
+useful to have multiple environments side by side, if different configurations
+are in use.
+
+The simplest way to configure Twig to load templates for your application
+looks roughly like this:
+
+ [php]
+ require_once '/path/to/lib/Twig/Autoloader.php';
+ Twig_Autoloader::register();
+
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
+ $twig = new Twig_Environment($loader);
+
+This will create a template environment with the default settings and a loader
+that looks up the templates in the `/path/to/templates/` folder. Different
+loaders are available and you can also write your own if you want to load
+templates from a database or other resources.
+
+To load a template from this environment you just have to call the
+`loadTemplate()` method which then returns a `Twig_Template` instance:
+
+ [php]
+ $template = $twig->loadTemplate('index.html');
+
+To render the template with some variables, call the `render()` method:
+
+ [php]
+ echo $template->render(array('the' => 'variables', 'go' => 'here'));
+
+>**NOTE**
+>The `display()` method is a shortcut to output the template directly.
+
+Environment Options
+-------------------
+
+When creating a new `Twig_Environment` instance, you can pass an array of
+options as the constructor second argument:
+
+ [php]
+ $twig = new Twig_Environment($loader, array('debug' => true));
+
+The following options are available:
+
+ * `debug`: When set to `true`, the generated templates have a `__toString()`
+ method that you can use to display the generated nodes (default to
+ `false`).
+
+ * `trim_blocks`: Mimicks the behavior of PHP by removing the newline that
+ follows instructions if present (default to `false`).
+
+ * `charset`: The charset used by the templates (default to `utf-8`).
+
+ * `base_template_class`: The base template class to use for generated
+ templates (default to `Twig_Template`).
+
+Loaders
+-------
+
+Loaders are responsible for loading templates from a resource such as the file
+system. The environment will keep the compiled templates in memory.
+
+### Built-in Loaders
+
+Here a list of the built-in loaders Twig provides:
+
+ * `Twig_Loader_Filesystem`: Loads templates from the file system. This loader
+ can find templates in folders on the file system and is the preferred way
+ to load them.
+
+ * `Twig_Loader_String`: Loads templates from a string. It's a dummy loader as
+ you pass it the source code directly.
+
+ * `Twig_Loader_Array`: Loads a template from a PHP array. It's passed an
+ array of strings bound to template names. This loader is useful for unit
+ testing.
+
+### Create your own Loader
+
+All loaders implement the `Twig_LoaderInterface`:
+
+ [php]
+ interface Twig_LoaderInterface
+ {
+ /**
+ * Loads a template by name.
+ *
+ * @param string $name The template name
+ *
+ * @return string The class name of the compiled template
+ */
+ public function load($name);
+ }
+
+But if you want to create your own loader, you'd better inherit from the
+`Twig_Loader` class, which already provides a lot of useful features. In this
+case, you just need to implement the `getSource()` method. As an example, here
+is how the built-in `Twig_Loader_String` reads:
+
+ [php]
+ class Twig_Loader_String extends Twig_Loader
+ {
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name string The name of the template to load
+ *
+ * @return array An array consisting of the source code as the first element,
+ * and the last modification time as the second one
+ * or false if it's not relevant
+ */
+ public function getSource($name)
+ {
+ return array($name, false);
+ }
+ }
+
+Using Extensions
+----------------
+
+Twig extensions are packages that adds new features to Twig. Using an
+extension is as simple as using the `addExtension()` method:
+
+ [php]
+ $twig->addExtension('Escaper');
+
+Twig comes bundled with three extensions:
+
+ * *Core*: Defines all the core features of Twig and is automatically
+ registered when you create a new environment.
+
+ * *Escaper*: Adds automatic output-escaping and the possibility to
+ escape/unescape blocks of code.
+
+ * *Sandbox*: Adds a sandbox mode to the default Twig environment, making it
+ safe to evaluated untrusted code.
+
+Built-in Extensions
+-------------------
+
+This section describes the features added by the built-in extensions.
+
+>**TIP**
+>Read the chapter about extending Twig to learn how to create your own
+>extensions.
+
+### Core Extension
+
+The `core` extension defines all the core features of Twig:
+
+ * Tags:
+
+ * `for`
+ * `if`
+ * `extends`
+ * `include`
+ * `block`
+ * `parent`
+ * `display`
+ * `filter`
+
+ * Filters:
+
+ * `date`
+ * `format`
+ * `even`
+ * `odd`
+ * `urlencode`
+ * `title`
+ * `capitalize`
+ * `upper`
+ * `lower`
+ * `striptags`
+ * `join`
+ * `reverse`
+ * `length`
+ * `sort`
+ * `default`
+ * `keys`
+ * `items`
+ * `escape`
+ * `e`
+
+The core extension does not need to be added to the Twig environment, as it is
+registered by default.
+
+### Escaper Extension
+
+The `escaper` extension adds automatic output escaping to Twig. It defines a
+new tag, `autoescape`, and a new filter, `safe`.
+
+When creating the escaper extension, you can switch on or off the global
+output escaping strategy:
+
+ [php]
+ $escaper = new Twig_Extension_Escaper(true);
+ $twig->addExtension($escaper);
+
+If set to `true`, all variables in templates are escaped, except those using
+the `safe` filter:
+
+ [twig]
+ {{ article.to_html|safe }}
+
+You can also change the escaping mode locally by using the `autoescape` tag:
+
+ [twig]
+ {% autoescape on %}
+ {% var %}
+ {% var|safe %} {# var won't be escaped #}
+ {% var|escape %} {# var won't be doubled-escaped #}
+ {% endautoescape %}
+
+### Sandbox Extension
+
+The `sandbox` extension can be used to evaluate untrusted code. Access to
+unsafe attributes and methods is prohibited. The sandbox security is managed
+by a policy instance. By default, Twig comes with one policy class:
+`Twig_Sandbox_SecurityPolicy`. This class allows you to white-list some tags,
+filters, and methods:
+
+ [php]
+ $tags = array('if');
+ $filters = array('upper');
+ $methods = array(
+ 'Article' => array('getTitle', 'getBody'),
+ );
+ $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods);
+
+With the previous configuration, the security policy will only allow usage of
+the `if` tag, and the `upper` filter. Moreover, the templates will only be
+able to call the `getTitle()` and `getBody()` methods on `Article` objects.
+Everything else won't be allowed and will generate a
+`Twig_Sandbox_SecurityError` exception.
+
+The policy object is the first argument of the sandbox constructor:
+
+ [php]
+ $sandbox = new Twig_Extension_Sandbox($policy);
+ $twig->addExtension($sandbox);
+
+By default, the sandbox mode is disabled and should be enabled when including
+untrusted templates:
+
+ [php]
+ {% include "user.html" sandboxed %}
+
+You can sandbox all templates by passing `true` as the second argument of the
+extension constructor:
+
+ [php]
+ $sandbox = new Twig_Extension_Sandbox($policy, true);
+
+Exceptions
+----------
+
+Twig can throw exceptions:
+
+ * `Twig_Error`: The base exception for all template errors.
+
+ * `Twig_SyntaxError`: Thrown to tell the user that there is a problem with
+ the template syntax.
+
+ * `Twig_RuntimeError`: Thrown when an error occurs at runtime (when a filter
+ does not exist for instance).
+
+ * `Twig_Sandbox_SecurityError`: Thrown when an unallowed tag, filter, or
+ method is called in a sandboxed template.
View
290 doc/04-Extending-Twig.markdown
@@ -0,0 +1,290 @@
+Extending Twig
+==============
+
+Twig supports extensions that can add extra tags, filters, or even extend the
+parser itself with node transformer classes. The main motivation for writing
+an extension is to move often used code into a reusable class like adding
+support for internationalization.
+
+Most of the time, it is useful to create a single extension for your project,
+to host all the specific tags and filters you want to add to Twig.
+
+Anatomy of an Extension
+-----------------------
+
+An extension is a class that implements the `Twig_ExtensionInterface`:
+
+ [php]
+ interface Twig_ExtensionInterface
+ {
+ /**
+ * Initializes the runtime environment.
+ *
+ * This is where you can load some file that contains filter functions for instance.
+ */
+ public function initRuntime();
+
+ /**
+ * Returns the token parser instances to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParser instances
+ */
+ public function getTokenParsers();
+
+ /**
+ * Returns the node transformer instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeTransformer instances
+ */
+ public function getNodeTransformers();
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getFilters();
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName();
+ }
+
+Instead of you implementing the whole interface, your extension class can
+inherit from the `Twig_Extension` class, which provides empty implementations
+of all the above methods to keep your extension clean.
+
+>**TIP**
+>The bundled extensions are great examples of how extensions work.
+
+Defining new Filters
+--------------------
+
+The most common element you will want to add to Twig is filters. A filter is
+just a regular PHP callable that takes the left side of the filter as first
+argument and the arguments passed to the filter as extra arguments.
+
+Let's create a filter, named `rot13`, which returns the
+[rot13](http://www.php.net/manual/en/function.str-rot13.php) transformation of
+a string:
+
+ [twig]
+ {{ "Twig"|rot13 }}
+
+ {# should displays Gjvt #}
+
+Here is the simplest extension class you can create to add this filter:
+
+ [php]
+ class Project_Twig_Extension extends Twig_Extension
+ {
+ public function getFilters()
+ {
+ return array(
+ 'rot13' => array('str_rot13', false),
+ );
+ }
+
+ public function getName()
+ {
+ return 'project';
+ }
+ }
+
+Registering the new extension is like registering core extensions:
+
+ [php]
+ $twig->addExtension(new Project_Twig_Extension());
+
+You can of course use any valid PHP callable, like a method call:
+
+ [php]
+ class Project_Twig_Extension extends Twig_Extension
+ {
+ public function getFilters()
+ {
+ return array(
+ 'rot13' => array(array($this, 'computeRot13'), false),
+ );
+ }
+
+ public function computeRot13($string)
+ {
+ return str_rot13($string);
+ }
+
+ // ...
+ }
+
+Filters can also be passed the current environment. You might have noticed
+that a filter is defined by a callable and a Boolean. If you change the
+Boolean to `true`, Twig will pass the current environment as the first
+argument to the filter call:
+
+ [php]
+ class Project_Twig_Extension extends Twig_Extension
+ {
+ public function getFilters()
+ {
+ return array(
+ 'rot13' => array(array($this, 'computeRot13'), true),
+ );
+ }
+
+ public function computeRot13(Twig_Environment $env, $string)
+ {
+ // get the current charset for instance
+ $charset = $env->getCharset();
+
+ return str_rot13($string);
+ }
+
+ // ...
+ }
+
+Defining new Tags
+-----------------
+
+One of the most exiting feature of a template engine like Twig is the
+possibility to define new language constructs.
+
+Let's create a simple `set` tag that allows the definition of simple variables
+from within a template. The tag can be used like follows:
+
+ [twig]
+ {% set name "value" %}
+
+ {{ name }}
+
+ {# should output value #}
+
+First, we need to create a `Twig_TokenParser` class which will be able to
+parse this new language construct:
+
+ [php]
+ class Project_Twig_Set_TokenParser extends Twig_TokenParser
+ {
+ // ...
+ }
+
+Of course, we need to register this token parser in our extension class:
+
+ [php]
+ class Project_Twig_Extension extends Twig_Extension
+ {
+ public function getTokenParsers()
+ {
+ return array(new Project_Twig_Set_TokenParser());
+ }
+
+ // ...
+ }
+
+Now, let's see the actual code of the token parser class:
+
+ [php]
+ class Project_Twig_Set_TokenParser extends Twig_TokenParser
+ {
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+ $value = $this->parser->getExpressionParser()->parseExpression();
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Project_Twig_Set_Node($name, $value, $lineno, $this->getTag());
+ }
+
+ public function getTag()
+ {
+ return 'set';
+ }
+ }
+
+The `getTag()` method must return the tag we want to parse, here `set`. The
+`parse()` method is invoked whenever the parser encounters a `set` tag. It
+should return a `Twig_Node` instance that represents the node. The parsing
+process is simplified thanks to a bunch of methods you can call from the token
+stream (`$this->parser->getStream()`):
+
+ * `test()`: Tests the type and optionally the value of the next token and
+ returns it.
+
+ * `expect()`: Expects a token and returns it (like `test()`) or throw a
+ syntax error if not found.
+
+ * `look()`: Looks a the next token. This is how you can have a look at the
+ next token without consume it.
+
+Parsing expressions is done by calling the `parseExpression()` like we did for
+the `set` tag.
+
+>**TIP**
+>Reading the existing `TokenParser` classes is the best way to learn all the
+>nitty-gritty details of the parsing process.
+
+The `Project_Twig_Set_Node` class itself is rather simple:
+
+ [php]
+ class Project_Twig_Set_Node extends Twig_Node
+ {
+ protected $name;
+ protected $value;
+
+ public function __construct($name, Twig_Node_Expression $value, $lineno)
+ {
+ parent::__construct($lineno);
+
+ $this->name = $name;
+ $this->value = $value;
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('$context[\''.$this->name.'\'] = ')
+ ->subcompile($this->value)
+ ->raw(";\n")
+ ;
+ }
+ }
+
+The compiler implements a fluid interface and provides methods that helps the
+developer generate beautiful and readable PHP code:
+
+ * `subcompile()`: Compiles a node.
+
+ * `raw()`: Writes the given string as is.
+
+ * `write()`: Writes the given string by adding indentation at the beginning
+ of each line.
+
+ * `string()`: Writes a quoted string.
+
+ * `repr()`: Writes a PHP representation of a given value (see `Twig_Node_For`
+ for a usage example).
+
+ * `pushContext()`: Pushes the current context on the stack (see
+ `Twig_Node_For` for a usage example).
+
+ * `popContext()`: Pops a context from the stack (see `Twig_Node_For` for a
+ usage example).
+
+ * `addDebugInfo()`: Adds the line of the original template file related to
+ the current node as a comment.
+
+ * `indent()`: Indents the generated code (see `Twig_Node_Block` for a usage
+ example).
+
+ * `outdent()`: Outdents the generated code (see `Twig_Node_Block` for a usage
+ example).
+
+Creating a Node Transformer
+---------------------------
+
+To be written...
View
196 doc/05-Hacking-Twig.markdown
@@ -0,0 +1,196 @@
+Hacking Twig
+============
+
+Twig is very extensible and you can easily hack it. Keep in mind that you
+should probably try to create an extension before hacking the core, as most
+features and enhancements can be done with extensions. This chapter is also
+useful for people who want to understand how Twig works under the hood.
+
+How Twig works?
+---------------
+
+The rendering of a Twig template can be summarized into four key steps:
+
+ * **Load** the template: If the template is already compiled, load it and go
+ to the *evaluation* step, otherwise:
+
+ * First, the **lexer** tokenizes the template source code into small pieces
+ for easier processing;
+
+ * Then, the **parser** converts the token stream into a meaningful tree
+ of nodes (the Abstract Syntax Tree);
+
+ * Eventually, the *compiler* transforms the AST into PHP code;
+
+ * **Evaluate** the template: It basically means calling the `display()`
+ method of the compiled template and passing it the context.
+
+The Lexer
+---------
+
+The Twig lexer goal is to tokenize a source code into a token stream (each
+token is of class `Token`, and the stream is an instance of
+`Twig_TokenStream`). The default lexer recognizes nine different token types:
+
+ * `Twig_Token::TEXT_TYPE`
+ * `Twig_Token::BLOCK_START_TYPE`
+ * `Twig_Token::VAR_START_TYPE`
+ * `Twig_Token::BLOCK_END_TYPE`
+ * `Twig_Token::VAR_END_TYPE`
+ * `Twig_Token::NAME_TYPE`
+ * `Twig_Token::NUMBER_TYPE`
+ * `Twig_Token::STRING_TYPE`
+ * `Twig_Token::OPERATOR_TYPE`
+ * `Twig_Token::EOF_TYPE`
+
+You can manually convert a source code into a token stream by calling the
+`tokenize()` of an environment:
+
+ [php]
+ $stream = $twig->tokenize($source, $identifier);
+
+As the stream has a `__toString()` method, you can have a textual
+representation of it by echoing the object:
+
+ [php]
+ echo $stream."\n";
+
+Here is the output for the `Hello {{ name }}` template:
+
+ [txt]
+ TEXT_TYPE(Hello )
+ VAR_START_TYPE()
+ NAME_TYPE(name)
+ VAR_END_TYPE()
+ EOF_TYPE()
+
+You can change the default lexer use by Twig (`Twig_Lexer`) by calling the
+`setLexer()` method:
+
+ [php]
+ $twig->setLexer($lexer);
+
+Lexer classes must implement the `Twig_LexerInterface`:
+
+ [php]
+ interface Twig_LexerInterface
+ {
+ /**
+ * Tokenizes a source code.
+ *
+ * @param string $code The source code
+ * @param string $filename A unique identifier for the source code
+ *
+ * @return Twig_TokenStream A token stream instance
+ */
+ public function tokenize($code, $filename = 'n/a');
+ }
+
+The Parser
+----------
+
+The parser converts the token stream into an AST (Abstract Syntax Tree), or a
+node tree (of class `Twig_Node_Module`). The core extension defines the basic
+nodes like: `for`, `if`, ... and the expression nodes.
+
+You can manually convert a token stream into a node tree by calling the
+`parse()` method of an environment:
+
+ [php]
+ $nodes = $twig->parse($stream);
+
+Echoing the node object gives you a nice representation of the tree:
+
+ [php]
+ echo $nodes."\n";
+
+Here is the output for the `Hello {{ name }}` template:
+
+ [txt]
+ Twig_Node_Module(
+ Twig_Node_Text(Hello )
+ Twig_Node_Print(
+ Twig_Node_Expression_Name(name)
+ )
+ )
+
+The default parser (`Twig_TokenParser`) can be also changed by calling the
+`setParser()` method:
+
+ [php]
+ $twig->setParser($parser);
+
+All Twig parsers must implement the `Twig_ParserInterface`:
+
+ [php]
+ interface Twig_ParserInterface
+ {
+ /**
+ * Converts a token stream to a node tree.
+ *
+ * @param Twig_TokenStream $stream A token stream instance
+ *
+ * @return Twig_Node_Module A node tree
+ */
+ public function parser(Twig_TokenStream $code);
+ }
+
+The Compiler
+------------
+
+The last step is done by the compiler. It takes a node tree as an input and
+generates PHP code usable for runtime execution of the templates. The default
+compiler generates PHP classes to ease the implementation of the template
+inheritance feature.
+
+You can call the compiler by hand with the `compile()` method of an
+environment:
+
+ [php]
+ $php = $twig->compile($nodes);
+
+The `compile()` method returns the PHP source code representing the node.
+
+The generated template for a `Hello {{ name }}` template reads as follows:
+
+ [php]
+ /* Hello {{ name }} */
+ class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Twig_Template
+ {
+ public function display($context)
+ {
+ $this->env->initRuntime();
+
+ // line 1
+ echo "Hello ";
+ echo (isset($context['name']) ? $context['name'] : null);
+ }
+ }
+
+As for the lexer and the parser, the default compiler (`Twig_Compiler`) can be
+changed by calling the `setCompiler()` method:
+
+ [php]
+ $twig->setCompiler($compiler);
+
+All Twig compilers must implement the `Twig_CompilerInterface`:
+
+ [php]
+ interface Twig_CompilerInterface
+ {
+ /**
+ * Compiles a node.
+ *
+ * @param Twig_Node $node The node to compile
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function compile(Twig_Node $node);
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ public function getSource();
+ }
View
48 lib/Twig/Autoloader.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Autoloads Twig classes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Autoloader
+{
+ /**
+ * Registers Twig_Autoloader as an SPL autoloader.
+ */
+ static public function register()
+ {
+ ini_set('unserialize_callback_func', 'spl_autoload_call');
+ spl_autoload_register(array(new self, 'autoload'));
+ }
+
+ /**
+ * Handles autoloading of classes.
+ *
+ * @param string $class A class name.
+ *
+ * @return boolean Returns true if the class has been loaded
+ */
+ public function autoload($class)
+ {
+ if (0 !== strpos($class, 'Twig'))
+ {
+ return false;
+ }
+
+ require dirname(__FILE__).'/../'.str_replace('_', '/', $class).'.php';
+
+ return true;
+ }
+}
View
245 lib/Twig/Compiler.php
@@ -0,0 +1,245 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Compiles a node to PHP code.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Compiler implements Twig_CompilerInterface
+{
+ protected $lastLine;
+ protected $source;
+ protected $indentation;
+ protected $env;
+
+ /**
+ * Constructor.
+ *
+ * @param Twig_Environment $env The twig environment instance
+ */
+ public function __construct(Twig_Environment $env = null)
+ {
+ $this->env = $env;
+ }
+
+ public function setEnvironment(Twig_Environment $env)
+ {
+ $this->env = $env;
+ }
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ public function getSource()
+ {
+ return $this->source;
+ }
+
+ /**
+ * Compiles a node.
+ *
+ * @param Twig_Node $node The node to compile
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function compile(Twig_Node $node)
+ {
+ $this->lastLine = null;
+ $this->source = '';
+ $this->indentation = 0;
+
+ $node->compile($this);
+
+ return $this;
+ }
+
+ public function subcompile(Twig_Node $node)
+ {
+ $node->compile($this);
+
+ return $this;
+ }
+
+ /**
+ * Adds a raw string to the compiled code.
+ *
+ * @param string $string The string
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function raw($string)
+ {
+ $this->source .= $string;
+
+ return $this;
+ }
+
+ /**
+ * Writes a string to the compiled code by adding indentation.
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function write()
+ {
+ $strings = func_get_args();
+ foreach ($strings as $string)
+ {
+ $this->source .= str_repeat(' ', $this->indentation * 2).$string;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a quoted string to the compiled code.
+ *
+ * @param string $string The string
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function string($value)
+ {
+ $this->source .= sprintf('"%s"', addcslashes($value, "\t\""));
+
+ return $this;
+ }
+
+ /**
+ * Returns a PHP representation of a given value.
+ *
+ * @param mixed $value The value to convert
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function repr($value)
+ {
+ if (is_int($value) || is_float($value))
+ {
+ $this->raw($value);
+ }
+ else if (is_null($value))
+ {
+ $this->raw('null');
+ }
+ else if (is_bool($value))
+ {
+ $this->raw($value ? 'true' : 'false');
+ }
+ else if (is_array($value))
+ {
+ $this->raw('array(');
+ $i = 0;
+ foreach ($value as $key => $value)
+ {
+ if ($i++)
+ {
+ $this->raw(', ');
+ }
+ $this->repr($key);
+ $this->raw(' => ');
+ $this->repr($value);
+ }
+ $this->raw(')');
+ }
+ else
+ {
+ $this->string($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Pushes the current context on the stack.
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function pushContext()
+ {
+ $this->write('$context[\'_parent\'] = $context;'."\n");
+
+ return $this;
+ }
+
+ /**
+ * Pops a context from the stack.
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function popContext()
+ {
+ $this->write('$context = $context[\'_parent\'];'."\n");
+
+ return $this;
+ }
+
+ /**
+ * Adds debugging information.
+ *
+ * @param Twig_Node $node The related twig node
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function addDebugInfo(Twig_Node $node)
+ {
+ if ($node->getLine() != $this->lastLine)
+ {
+ $this->lastLine = $node->getLine();
+ $this->write("// line {$node->getLine()}\n");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Indents the generated code.
+ *
+ * @param integer $indent The number of indentation to add
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function indent($step = 1)
+ {
+ $this->indentation += $step;
+
+ return $this;
+ }
+
+ /**
+ * Outdents the generated code.
+ *
+ * @param integer $indent The number of indentation to remove
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function outdent($step = 1)
+ {
+ $this->indentation -= $step;
+
+ return $this;
+ }
+
+ /**
+ * Returns the environment instance related to this compiler.
+ *
+ * @return Twig_Environment The environment instance
+ */
+ public function getEnvironment()
+ {
+ return $this->env;
+ }
+}
View
36 lib/Twig/CompilerInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface implemented by compiler classes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_CompilerInterface
+{
+ /**
+ * Compiles a node.
+ *
+ * @param Twig_Node $node The node to compile
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function compile(Twig_Node $node);
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ public function getSource();
+}
View
251 lib/Twig/Environment.php
@@ -0,0 +1,251 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Twig_Environment
+{
+ const VERSION = '0.9-DEV';
+
+ protected $charset;
+ protected $loader;
+ protected $trimBlocks;
+ protected $debug;
+ protected $lexer;
+ protected $parser;
+ protected $compiler;
+ protected $baseTemplateClass;
+ protected $extensions;
+ protected $parsers;
+ protected $transformers;
+ protected $filters;
+
+ public function __construct(Twig_LoaderInterface $loader = null, $options = array())
+ {
+ if (null !== $loader)
+ {
+ $this->setLoader($loader);
+ }
+
+ $this->debug = isset($options['debug']) ? (bool) $options['debug'] : false;
+ $this->trimBlocks = isset($options['trim_blocks']) ? (bool) $options['trim_blocks'] : false;
+ $this->charset = isset($options['charset']) ? (bool) $options['charset'] : 'UTF-8';
+ $this->baseTemplateClass = isset($options['base_template_class']) ? (bool) $options['base_template_class'] : 'Twig_Template';
+ $this->extensions = array(new Twig_Extension_Core());
+ }
+
+ public function getBaseTemplateClass()
+ {
+ return $this->baseTemplateClass;
+ }
+
+ public function setBaseTemplateClass($class)
+ {
+ $this->baseTemplateClass = $class;
+ }
+
+ public function enableDebug()
+ {
+ $this->debug = true;
+ }
+
+ public function disableDebug()
+ {
+ $this->debug = false;
+ }
+
+ public function isDebug()
+ {
+ return $this->debug;
+ }
+
+ public function getTrimBlocks()
+ {
+ return $this->trimBlocks;
+ }
+
+ public function setTrimBlocks($bool)
+ {
+ $this->trimBlocks = (bool) $bool;
+ }
+
+ public function loadTemplate($name)
+ {
+ $cls = $this->getLoader()->load($name, $this);
+
+ return new $cls($this);
+ }
+
+ public function getLexer()
+ {
+ if (null === $this->lexer)
+ {
+ $this->lexer = new Twig_Lexer($this);
+ }
+
+ return $this->lexer;
+ }
+
+ public function setLexer(Twig_LexerInterface $lexer)
+ {
+ $this->lexer = $lexer;
+ $lexer->setEnvironment($this);
+ }
+
+ public function tokenize($source, $name = null)
+ {
+ return $this->getLexer()->tokenize($source, null === $name ? $source : $name);
+ }
+
+ public function getParser()
+ {
+ if (null === $this->parser)
+ {
+ $this->parser = new Twig_Parser($this);
+ }
+
+ return $this->parser;
+ }
+
+ public function setParser(Twig_ParserInterface $parser)
+ {
+ $this->parser = $parser;
+ $parser->setEnvironment($this);
+ }
+
+ public function parse(Twig_TokenStream $tokens)
+ {
+ return $this->getParser()->parse($tokens);
+ }
+
+ public function getCompiler()
+ {
+ if (null === $this->compiler)
+ {
+ $this->compiler = new Twig_Compiler($this);
+ }
+
+ return $this->compiler;
+ }
+
+ public function setCompiler(Twig_CompilerInterface $compiler)
+ {
+ $this->compiler = $compiler;
+ $compiler->setEnvironment($this);
+ }
+
+ public function compile(Twig_Node $node)
+ {
+ return $this->getCompiler()->compile($node)->getSource();
+ }
+
+ public function setLoader(Twig_LoaderInterface $loader)
+ {
+ $this->loader = $loader;
+ $loader->setEnvironment($this);
+ }
+
+ public function getLoader()
+ {
+ return $this->loader;
+ }
+
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+ }
+
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ public function initRuntime()
+ {
+ foreach ($this->getExtensions() as $extension)
+ {
+ $extension->initRuntime();
+ }
+ }
+
+ public function hasExtension($name)
+ {
+ return isset($this->extensions[$name]);
+ }
+
+ public function getExtension($name)
+ {
+ return $this->extensions[$name];
+ }
+
+ public function addExtension(Twig_ExtensionInterface $extension)
+ {
+ $this->extensions[$extension->getName()] = $extension;
+ }
+
+ public function removeExtension($name)
+ {
+ unset($this->extensions[$name]);
+ }
+
+ public function setExtensions(array $extensions)
+ {
+ foreach ($extensions as $extension)
+ {
+ $this->setExtension($extension);
+ }
+ }
+
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+ public function getTokenParsers()
+ {
+ if (null === $this->parsers)
+ {
+ $this->parsers = array();
+ foreach ($this->getExtensions() as $extension)
+ {
+ $this->parsers = array_merge($this->parsers, $extension->getTokenParsers());
+ }
+ }
+
+ return $this->parsers;
+ }
+
+ public function getNodeTransformers()
+ {
+ if (null === $this->transformers)
+ {
+ $this->transformers = array();
+ foreach ($this->getExtensions() as $extension)
+ {
+ $this->transformers = array_merge($this->transformers, $extension->getNodeTransformers());
+ }
+ }
+
+ return $this->transformers;
+ }
+
+ public function getFilters()
+ {
+ if (null === $this->filters)
+ {
+ $this->filters = array();
+ foreach ($this->getExtensions() as $extension)
+ {
+ $this->filters = array_merge($this->filters, $extension->getFilters());
+ }
+ }
+
+ return $this->filters;
+ }
+}
View
21 lib/Twig/Error.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Twig base exception.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Error extends Exception
+{
+}
View
397 lib/Twig/ExpressionParser.php
@@ -0,0 +1,397 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Parses expressions.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_ExpressionParser
+{
+ protected $parser;
+
+ public function __construct(Twig_Parser $parser)
+ {
+ $this->parser = $parser;
+ }
+
+ public function parseExpression()
+ {
+ return $this->parseConditionalExpression();
+ }
+
+ public function parseConditionalExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $expr1 = $this->parseOrExpression();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '?'))
+ {
+ $this->parser->getStream()->next();
+ $expr2 = $this->parseOrExpression();
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ':');
+ $expr3 = $this->parseConditionalExpression();
+ $expr1 = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $expr1;
+ }
+