pax_global_header00006660000000000000000000000064146047141220014513gustar00rootroot0000000000000052 comment=10e5d32e7b974cd5150e2963bcf915ebfd6f4683 chameleon-4.5.4/000077500000000000000000000000001460471412200134605ustar00rootroot00000000000000chameleon-4.5.4/.editorconfig000066400000000000000000000020101460471412200161260ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python # # EditorConfig Configuration file, for more details see: # http://EditorConfig.org # EditorConfig is a convention description, that could be interpreted # by multiple editors to enforce common coding conventions for specific # file types # top-most EditorConfig file: # Will ignore other EditorConfig files in Home directory or upper tree level. root = true [*] # For All Files # Unix-style newlines with a newline ending every file end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true # Set default charset charset = utf-8 # Indent style default indent_style = space # Max Line Length - a hard line wrap, should be disabled max_line_length = off [*.{py,cfg,ini}] # 4 space indentation indent_size = 4 [*.{yml,zpt,pt,dtml,zcml}] # 2 space indentation indent_size = 2 [{Makefile,.gitmodules}] # Tab indentation (no size specified, but view as 4 spaces) indent_style = tab indent_size = unset tab_width = unset chameleon-4.5.4/.github/000077500000000000000000000000001460471412200150205ustar00rootroot00000000000000chameleon-4.5.4/.github/workflows/000077500000000000000000000000001460471412200170555ustar00rootroot00000000000000chameleon-4.5.4/.github/workflows/main.yml000066400000000000000000000043201460471412200205230ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python name: tests on: push: pull_request: schedule: - cron: '0 12 * * 0' # run once a week on Sunday # Allow to run this workflow manually from the Actions tab workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: python setup.py sdist - run: | rm -rf src/chameleon/tests python setup.py bdist_wheel - uses: actions/upload-artifact@v4 with: name: Chameleon path: ./dist/ test: strategy: # We want to see all failures: fail-fast: false matrix: os: - ["ubuntu-latest", "windows-latest"] config: # [Python version, tox env] - ["3.9", "lint"] - ["3.9", "py39"] - ["3.10", "py310"] - ["3.11", "py311"] - ["3.12", "py312"] - ["3.13.0-alpha - 3.13.99", "py313"] - ["pypy-3.9", "pypy3"] - ["3.9", "docs"] - ["3.9", "coverage"] - ["3.12", "mypy"] - ["3.11", "z3c.macro"] - ["3.11", "z3c.pt"] runs-on: ${{ matrix.os[1] }} if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: ${{ matrix.config[1] }} steps: - run: git config --global core.autocrlf false - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.config[0] }} - name: Pip cache uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.config[0] }}- ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox - name: Test run: tox -e ${{ matrix.config[1] }} - name: Coverage if: matrix.config[1] == 'coverage' run: | pip install coveralls coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} chameleon-4.5.4/.gitignore000066400000000000000000000005561460471412200154560ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python *.dll *.egg-info/ *.profraw *.pyc *.pyo *.so .coverage .coverage.* .eggs/ .installed.cfg .mr.developer.cfg .mypy_cache/ .tox/ .vscode/ __pycache__/ bin/ build/ coverage.xml develop-eggs/ develop/ dist/ docs/_build eggs/ etc/ lib/ lib64 log/ parts/ pyvenv.cfg testing.log var/ chameleon-4.5.4/.meta.toml000066400000000000000000000015411460471412200153620ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [meta] template = "pure-python" commit-id = "c7a64084" [python] with-sphinx-doctests = false with-docs = true with-future-python = false with-pypy = true with-macos = false with-windows = false [tox] use-flake8 = true testenv-deps = [ "zope.testrunner", ] [coverage] fail-under = 86 [coverage-run] additional-config = [ "omit =", " src/chameleon/benchmark.py", ] [manifest] additional-rules = [ "include Makefile", "recursive-include benchmarks *.py", "recursive-include src *.html", "recursive-include src *.pt", "recursive-include src *.txt", "recursive-include src *.xml", ] [flake8] additional-config = [ "# F401 imported but unused", "per-file-ignores =", " src/chameleon/__init__.py: F401", ] chameleon-4.5.4/.readthedocs.yaml000066400000000000000000000010731460471412200167100ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.11" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # We recommend specifying your dependencies to enable reproducible builds: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - requirements: docs/requirements.txt chameleon-4.5.4/CHANGES.rst000066400000000000000000001400571460471412200152710ustar00rootroot00000000000000Changes ======= 4.5.4 (2024-04-08) ------------------ - Fix an issue where $-sign interpolation escaping would not work correctly when more than two such symbols appeared next to each other. (`#422 `_) 4.5.3 (2024-04-05) ------------------ - Minor optimization when rendering translations with a static message id (don't need to test if it's non-empty). - Fix a bug where a macro could not be used correctly to render a translation name. (`#419 `_) 4.5.2 (2024-01-29) ------------------ - Fix a regression where a static symbol would not get correctly imported. (`#414 `_) 4.5.1 (2024-01-28) ------------------ - Add Python 3.13 classifier. - Fix a regression where `default_extension` was no longer permitted as a positional argument to `PageTemplateLoader`. (`#411 `_) 4.5.0 (2024-01-18) ------------------ - Chameleon now has type annotations! - The list of names previously disallowed for use as variables in templates such as "int" and "float" has been trimmed significantly, not because it's a good idea to use such names but because the list of disallowed names was not exhaustive and complicated the compiler code; and perhaps more importantly, the technical reason for disallowing the names in the first place no longer applies. - Fix a regression where generated template code would suboptimal due to incorrect handling of internal variables. - Always cook templates in debug mode, even when using `CHAMELEON_CACHE` option to persist generated code on disk. - Parsing the AST back to Python code now uses the built-in `ast.unparse` function. This change is not directly surfaced but means that the unparsing code is now more correctly tracking changes to the interpreter. - Drop support for platforms where AST nodes aren't weakref-capable (e.g., older PyPy). 4.4.4 (2024-01-18) ------------------ - Fix a PyPy compatibility issue having to do with determining the set of versions when some packages (e.g. `cffi`) would not carry a version. 4.4.3 (2023-12-30) ------------------ - Remove `zope.interface`. - Fix an issue where `auto_reload` is enabled and a file is loaded from a package that wasn't zip-compressed. (`#402 `_) 4.4.2 (2023-12-18) ------------------ - Fix brown bag release. 4.4.1 (2023-12-18) ------------------ - Fix brown bag release. 4.4.0 (2023-12-12) ------------------ - Add optional parameter ``package_name`` which allows loading a template relative to a package. - Drop support for Python 3.7. - Fix regression where Chameleon would not load templates correctly on Windows. - Fix names of dependencies for ``importlib_resources`` and ``importlib_metadata``. (`#394 `_) 4.3.0 (2023-12-04) ------------------ - Local variable scope is now iterable, completing dict interface. (`#390 `_) - Minor optimizations to rendering logic. - Implicit translation now provides the translation context, domain, and target language to the translation function (if applicable). Previously, the target language was provided, but this did not respect a change via `i18n:target`. (`#369 `_) - Replace ``pkg_resources`` with newer and faster ``importlib.resources`` and ``importlib.metadata``. Just importing ``pkg_resources`` becomes slower and slower the more packages are installed. 4.2.0 (2023-09-25) ------------------ - An XML document provided as a string (i.e. decoded) now correctly has its content encoding parsed. - Boolean attributes are now automatically configured for templates in non-XML mode, presuming that we're being used to generate HTML. This means that the same loading mechanism can be used for both XML- and HTML-based templates. 4.1.0 (2023-08-29) ------------------ - Boolean attributes (those configured using the optional `boolean_attributes` parameter) now work with $-expression interpolation. Unlike content and regular attributes, a special check for a falsy value is now done for boolean attributes, such that boolean logic can be used for interpolation expressions (only a truthy value will include the attribute). This reverts a change in behavior introduced in 3.8.0. Additionally, dynamic attributes now respect the boolean attributes configuration as well. 4.0.1 (2023-06-19) ------------------ - Fix format spec applying for f-strings. (`#376 `_) 4.0.0 (2023-03-06) ------------------ - Drop support for Python 2.7, 3.5, 3.6. - Add support for set- and dict comprehensions (`#367 `_) - Remove the following functions resp. modules: + ``.utils.text_()`` + ``.utils.unescape()`` + ``.compat`` 3.10.2 (2022-12-16) ------------------- - Fix handling of eager compilation with subclassing (e.g. Pyramid integration). 3.10.1 (2022-05-17) ------------------- - Fix __str__ method of chameleon.exc.TemplateError (`#356 `_) 3.10.0 (2022-04-06) ------------------- - Make scope class dict-like (`#305 `_) - Work in FIPS enabled environments (`#345 `_) - Fix tab handling issue (`#350 `_) 3.9.1 (2021-05-14) ------------------ - Avoid various traceback reference cycles. 3.9.0 (2021-02-26) ------------------ - Removed compatibility code for unsupported Python versions - Dropped support for obsolete Python 3.4 - Fixed namespace handling when redefining a variable in the same ``tal:define`` (`#237 `_) - Fixed failure computing a template's digest if ``template.filename`` is None (`#254 `_) 3.8.1 (2020-07-06) ------------------ - Added code optimization to reduce sequential appends of static text. - The `default` symbol in dynamic attributes is now symbolic. Previously, it was assigned the string value of the default attribute text. A similar change has been made for switch/case expressions. - The built-in `attrs` dictionary of static element attributes now correctly works with `tal:define`, etc. - Fix slice code generation compatibility issue on Python 3.9. 3.8.0 (2020-06-25) ------------------ - Expose default marker as importable symbol `chameleon.tales.DEFAULT_MARKER`. - Removed legacy flag `literal_false`. To get a similar behavior, use `boolean_attributes`. 3.7.4 (2020-06-17) ------------------ - Fix brown-bag release. 3.7.3 (2020-06-17) ------------------ - Fix regression introduced in 3.6.2 where the default marker would incorrectly change its value between templates, causing issues in software which depends on the value being treated as a global object. 3.7.2 (2020-05-31) ------------------ - Allow setting a custom value representation function, allowing custom formatting of variables during exception formatting. 3.7.1 (2020-05-10) ------------------ - Fix compatibility issue with Python 3.9. 3.7.0 (2020-03-26) ------------------ - Fixed garbage collection issue with variable scope objects (issue #301). - Fixed issue where setting a global variable would not be available locally. - A `RepeatDict` no longer inherits from `dict` since it does not actually provide that interface in a meaningful way. - Added feature gate `enable_comment_interpolation` which controls whether expression interpolation is enabled inside HTML comments (default is enabled). - Added support for Python 3.6+ f-strings (issue #294). 3.6.2 (2019-06-22) ------------------ - Fix SyntaxWarnings in Python 3.8 resulting from comparing literals with 'is'. See https://github.com/plone/Products.CMFPlone/issues/2890. 3.6.1 (2019-04-01) ------------------ - Fix limited search expression for illegal double hyphens in HTML comments to fix issue #289. 3.6 (2019-02-19) ---------------- - Exclude `RuntimeError` (or `RecursionError` when available) from exception wrapping. - Fix double dollar '$$' escaping such that a double dollar is always resolved, either as an interpolation expression, or as an escape where it is substituted by a single dollar symbol. This is now consistent with Zope's handling of this character. Backslash-escaping of dollar-based string interpolation is no longer supported. The documentation has been updated to reflect this change. This fixes issue #283. Note that this reverses some of the changes introduced to fix issue #265. - Drop support for Python 3.3. 3.5 (2018-10-17) ---------------- - Add support for Python 3.8. - Add support for TAL attributes in an XML declaration tag. This fixes issue #269. - Add support for custom exception handling for the `tal:on-error` statement. There is now an option `on_error_handler` available as a template configuration (issue #266). - Fix issue where double '$$' escaping would affect non-interpolation expressions such as the bare '$$' (issue #265). - Fix an issue where backslash dollar escaping would leave the backslash character still in place. 3.4 (2018-07-14) ---------------- Bugfixes: - Fix regression with translations in case of multiple nodes. 3.3 (2018-05-23) ---------------- Bugfixes: - Reset error token when rendering internal macro calls. - Fix edge case in exception handler causing recursion. [MatthewWilkes] 3.2 (2017-10-06) ---------------- Features: - Add the automatic variable ``macroname`` that's bound to the name of the executing macro. Fixes https://github.com/malthe/chameleon/issues/238 - A tokenizer can now be configured on the template class. This is useful in the case where the template file input is modified before parsing (for example, where some tags are stripped away) such that token positions need to be offset accordingly for error locations to be rendered correctly. - Expression errors now display source marker (previously only filename, line and column was shown). - No longer require Python source files to import modules. [mrh1997] Optimizations: - Exception tracking now defers metadata allocation to time of error. 3.1 (2017-02-21) ---------------- Features: - Add option ``restricted_namespace`` which controls whether to restrict namespaces to those defined and used by the page template language. [hansroh] Bugs: - Fixed attribute HTML entity escaping issue where an entity such as ``&`` would be encoded twice. Optimizations: - Simplify exception tracking, reducing bytecode size significantly. - Avoid checking if a static string is ``None`` during expression interpolation. 3.0 (2016-12-07) ---------------- Bugs: - Fix issue on Python 2 where an exception was not cleared when using the pipe operator and was thus accessible through `sys.exc_info()`. - The "exists" expression no longer leaks error information. - Escape '$$' into '$' in both content and string expressions. - Fix use of macro definition inside translation block. Improvements: - Allow unquoted attribute values. - Wrap attribute error thrown when trying to use a non-macro as a macro as a `RenderError` to get proper error output. - Throw a parse error if '--' (double hyphen) appears in an XML comment. - The `i18n:target` attribute now overrides a default `target_language` variable and is passed to the translation function. - Include filename in the on-disk cache module name. Previously, only the SHA digest in hex representation would be used, making it difficult to see where the module came from. This fixes issue #132. - Add support for non-ascii attribute names. [sank] Compatibility: - Drop support for Python 2.6, 3.1, and 3.2. 2.25 (2016-09-24) ----------------- - Add explicit support / testing for Python 3.5. - Add ``\r`` to negative regex matches to the chameleon parser, where ``\n`` is used but ``\r`` was missing. Fixes a case, where the tag name was parsed into ``html\r`` instead of ``html``. Fixes: https://github.com/malthe/chameleon/issues/219 2.24 (2015-10-28) ----------------- - Fixed Python 3.5 compatibility. - Fixed brown bag release. 2.23 (2015-10-26) ----------------- - Added ``enable_data_attributes`` option that allows using HTML5 data attributes as control attributes instead or in addition to XML namespace attributes. 2.22 (2015-02-06) ----------------- - Fix brown bag release. 2.21 (2015-02-06) ----------------- - Added ``RenderError`` exception which indicates that an error occurred during the evaluation of an expression. - Clean up ``TemplateError`` exception implementation. 2.20 (2015-01-12) ----------------- - Pass ``search_path`` to template class when loaded using ``TemplateLoader`` (or one of the derived classes). [faassen] 2.19 (2015-01-06) ----------------- - Fix logging deprecation. - Fix environment-based configuration logging error. 2.18 (2014-11-03) ----------------- - Fix minor compilation error. 2.17 (2014-11-03) ----------------- - Add support for ``i18n:context``. [wiggy] - Add missing 'parity' repeat property. [voxspox] - Don't modify environment when getting variables from it. [fschulze] 2.16 (2014-05-06) ----------------- - If a repeat expression evaluates to ``None`` then it is now equivalent to an empty set. This changes a behavior introduced in 2.14. This fixes issue #172. - Remove fossil test dependency on deprecated ``distribute``. - Add explicit support / testing for Python 3.3 / 3.4. - Drop explicit support for Python 2.5 (out of maintenance, and no longer supported by ``tox`` or ``Travis-CI``). 2.15 (2014-03-11) ----------------- - Add Support for Python 3.4's ``NameConstant``. [brakhane] 2.14 (2013-11-28) ----------------- - Element repetition using the ``TAL`` namespace no longer includes whitespace. This fixes issue #110. - Use absolute import for ``chameleon.interfaces`` module. This fixes issue #161. 2.13-1 (2013-10-24) ------------------- - Fixing brown bag release. 2.13 (2013-10-21) ----------------- Bugfixes: - The template cache mechanism now includes additional configuration settings as part of the cache key such as ``strict`` and ``trim_attribute_space``. [ossmkitty] - Fix cache issue where sometimes cached templates would not load correctly. [ossmkitty] - In debug-mode, correctly remove temporary files when the module loader is garbage-collected (on ``__del__``). [graffic] - Fix error message when duplicate i18n:name directives are used in a translation. - Using the three-argument form of ``getattr`` on a ``chameleon.tal.RepeatDict`` no longer raises ``KeyError``, letting the default provided to ``getattr`` be used. This fixes attempting to adapt a ``RepeatDict`` to a Zope interface under PyPy. 2.12 (2013-03-26) ----------------- Changes: - When a ``tal:case`` condition succeeds, no other case now will. Bugfixes: - Implicit translation now correctly extracts and normalizes complete sentences, instead of words. [witsch] - The ``default`` symbol in a ``tal:case`` condition now allows the element only if no other case succeeds. 2.11 (2012-11-15) ----------------- Bugfixes: - An issue was resolved where a METAL statement was combined with a ``tal:on-error`` handler. - Fix minor parser issue with incorrectly formatted processing instructions. - Provide proper error handling for Python inline code blocks. Features: - The simple translation function now supports the ``translationstring`` interface. Optimizations: - Minor optimization which correctly detects when an element has no attributes. 2.10 (2012-10-12) ----------------- Deprecations: - The ``fast_translate`` function has been deprecated. Instead, the default translation function is now always a function that simply interpolates the mapping onto the message default or id. The motivation is that since version 2.9, the ``context`` argument is non-trivial: the ``econtext`` mapping is passed. This breaks an expectation on the Zope platform that the ``context`` parameter is the HTTP request. Previously, with Chameleon this parameter was simply not provided and so that did not cause issues as such. - The ``ast24`` module has been renamed to ``ast25``. This should help clear up any confusion that Chameleon 2.x might be support a Python interpreter less than version 2.5 (it does not). Features: - The ``ProxyExpr`` expression class (and hence the ``load:`` expression type) is now a TALES-expression. In practical terms, this means that the expression type (which computes a string result using the standard ``"${...}"`` interpolation syntax and proxies the result through a function) now supports fallback using the pipe operator (``"|"``). This fixes issue #128. - An attempt to interpolate using the empty string as the expression (i.e. ``${}``) now does nothing: the string ``${}`` is simply output as is. - Added support for adding, modifying, and removing attributes using a dictionary expression in ``tal:attributes`` (analogous to Genshi's ``py:attrs`` directive)::
In the example above, ``name`` is an identifier, while ``value`` and ``attrs`` are Python expressions. However, ``attrs`` must evaluate to a Python dictionary object (more concisely, the value must implement the dictionary API-methods ``update()`` and ``items()``). Optimizations: - In order to cut down on the size of the compiled function objects, some conversion and quoting statements have been put into functions. In one measurement, the reduction was 35%. The benchmark suite does *not* report of an increased render time (actually slightly decreased). Bugfixes: - An exception is now raised if a trivial string is passed for ``metal:fill-slot``. This fixes issue #89. - An empty string is now never translated. Not really a bug, but it's been reported in as an issue (#92) because some translation frameworks handle this case incorrectly. - The template module loader (file cache) now correctly encodes generated template source code as UTF-8. This fixes issue #125. - Fixed issue where a closure might be reused unsafely in nested template rendering. - Fixed markup class ``__repr__`` method. This fixes issue #124. - Added missing return statement to fix printing the non-abbreviated filename in case of an exception. [tomo] 2.9.2 (2012-06-06) ------------------ Bugfixes: - Fixed a PyPy incompatibility. - Fixed issue #109 which caused testing failures on some platforms. 2.9.1 (2012-06-01) ------------------ Bugfixes: - Fixed issue #103. The ``tal:on-error`` statement now always adds an explicit end-tag to the element, even with a substitution content of nothing. - Fixed issue #113. The ``tal:on-error`` statement now works correctly also for dynamic attributes. That is, the fallback tag now includes only static attributes. - Fixed name error which prevented the benchmark from running correctly. Compatibility: - Fixed deprecation warning on Python 3 for zope interface implements declaration. This fixes issue #116. 2.9.0 (2012-05-31) ------------------ Features: - The translation function now gets the ``econtext`` argument as the value for ``context``. Note that historically, this was usually an HTTP request which might provide language negotiation data through a dictionary interface. [alvinyue] Bugfixes: - Fixed import alias issue which would lead to a syntax error in generated Python code. Fixes issue #114. 2.8.5 (2012-05-02) ------------------ Bugfixes: - Fixed minor installation issues on Python 2.5 and 3. [ppaez] - Ensure output is unicode even when trivial (an empty string). 2.8.4 (2012-04-18) ------------------ Features: - In exception output, long filenames are now truncated to 60 characters of output, preventing line wrap which makes it difficult to scan the exception output. Bugfixes: - Include filename and location in exception output for exceptions raised during compilation. - If a trivial translation substitution variable is given (i.e. an empty string), simply ignore it. This fixes issue #106. 2.8.3 (2012-04-16) ------------------ Features: - Log template source on debug-level before cooking. - The `target_language` argument, if given, is now available as a variable in templates. 2.8.2 (2012-03-30) ------------------ Features: - Temporary caches used in debug mode are cleaned up eagerly, rather than waiting for process termination. [mitchellrj] Bugfixes: - The `index`, `start` and `end` methods on the TAL repeat object are now callable. This fixes an incompatibility with ZPT. - The loader now correctly handles absolute paths on Windows. [rdale] 2.8.1 (2012-03-29) ------------------ Features: - The exception formatter now lists errors in 'wrapping order'. This means that the innermost, and presumably most relevant exception is shown last. Bugfixes: - The exception formatter now correctly recognizes nested errors and does not rewrap the dynamically generated exception class. - The exception formatter now correctly sets the ``__module__`` attribute to that of the original exception class. 2.8.0 (2012-02-29) ------------------ Features: - Added support for code blocks using the `` processing instruction syntax. The scope is name assignments is up until the nearest macro definition, or the template itself if macros are not used. Bugfixes: - Fall back to the exception class' ``__new__`` method to safely create an exception object that is not implemented in Python. - The exception formatter now keeps track of already formatted exceptions, and ignores them from further output. 2.7.4 (2012-02-27) ------------------ - The error handler now invokes the ``__init__`` method of ``BaseException`` instead of the possibly overridden method (which may take required arguments). This fixes issue #97. [j23d, malthe] 2.7.3 (2012-01-16) ------------------ Bugfixes: - The trim whitespace option now correctly trims actual whitespace to a single character, appearing either to the left or to the right of an element prefix or suffix string. 2.7.2 (2012-01-08) ------------------ Features: - Added option ``trim_attribute_space`` that decides whether attribute whitespace is stripped (at most down to a single space). This option exists to provide compatibility with the reference implementation. Fixes issue #85. Bugfixes: - Ignore unhashable builtins when generating a reverse builtin map to quickly look up a builtin value. [malthe] - Apply translation mapping even when a translation function is not available. This fixes issue #83. [malthe] - Fixed issue #80. The translation domain for a slot is defined by the source document, i.e. the template providing the content for a slot whether it be the default or provided through ``metal:fill-slot``. [jcbrand] - In certain circumstances, a Unicode non-breaking space character would cause a define clause to fail to parse. 2.7.1 (2011-12-29) ------------------ Features: - Enable expression interpolation in CDATA. - The page template class now implements dictionary access to macros:: template[name] This is a short-hand for:: template.macros[name] Bugfixes: - An invalid define clause would be silently ignored; we now raise a language error exception. This fixes issue #79. - Fixed regression where ``${...}`` interpolation expressions could not span multiple lines. This fixes issue #77. 2.7.0 (2011-12-13) ------------------ Features: - The ``load:`` expression now derives from the string expression such that the ``${...}`` operator can be used for expression interpolation. - The ``load:`` expression now accepts asset specs; these are resolved by the ``pkg_resources.resource_filename`` function:: : An example from the test suite:: chameleon:tests/inputs/hello_world.pt Bugfixes: - If an attribute name for translation was not a valid Python identifier, the compiler would generate invalid code. This has been fixed, and the compiler now also throws an exception if an attribute specification contains a comma. (Note that the only valid separator character is the semicolon, when specifying attributes for translation via the ``i18n:translate`` statement). This addresses issue #76. 2.6.2 (2011-12-08) ------------------ Bugfixes: - Fixed issue where ``tal:on-error`` would not respect ``tal:omit-tag`` or namespace elements which are omitted by default (such as ````). - Fixed issue where ``macros`` attribute would not be available on file-based templates due to incorrect initialization. - The ``TryExcept`` and ``TryFinally`` AST nodes are not available on Python 3.3. These have been aliased to ``Try``. This fixes issue #75. Features: - The TAL repeat item now makes a security declaration that grants access to unprotected subobjects on the Zope 2 platform:: __allow_access_to_unprotected_subobjects__ = True This is required for legacy compatibility and does not affect other environments. - The template object now has a method ``write(body)`` which explicitly decodes and cooks a string input. - Added configuration option ``loader_class`` which sets the class used to create the template loader object. The class (essentially a callable) is created at template construction time. 2.6.1 (2011-11-30) ------------------ Bugfixes: - Decode HTML entities in expression interpolation strings. This fixes issue #74. - Allow ``xml`` and ``xmlns`` attributes on TAL, I18N and METAL namespace elements. This fixes issue #73. 2.6.0 (2011-11-24) ------------------ Features: - Added support for implicit translation: The ``implicit_i18n_translate`` option enables implicit translation of text. The ``implicit_i18n_attributes`` enables implicit translation of attributes. The latter must be a set and for an attribute to be implicitly translated, its lowercase string value must be included in the set. - Added option ``strict`` (enabled by default) which decides whether expressions are required to be valid at compile time. That is, if not set, an exception is only raised for an invalid expression at evaluation time. - An expression error now results in an exception only if the expression is attempted evaluated during a rendering. - Added a configuration option ``prepend_relative_search_path`` which decides whether the path relative to a file-based template is prepended to the load search path. The default is ``True``. - Added a configuration option ``search_path`` to the file-based template class, which adds additional paths to the template load instance bound to the ``load:`` expression. The option takes a string path or an iterable yielding string paths. The default value is the empty set. Bugfixes: - Exception instances now support pickle/unpickle. - An attributes in i18n:attributes no longer needs to match an existing or dynamic attribute in order to appear in the element. This fixes issue #66. 2.5.3 (2011-10-23) ------------------ Bugfixes: - Fixed an issue where a nested macro slot definition would fail even though there existed a parent macro definition. This fixes issue #69. 2.5.2 (2011-10-12) ------------------ Bugfixes: - Fixed an issue where technically invalid input would result in a compiler error. Features: - The markup class now inherits from the unicode string type such that it's compatible with the string interface. 2.5.1 (2011-09-29) ------------------ Bugfixes: - The symbol names "convert", "decode" and "translate" are now no longer set as read-only *compiler internals*. This fixes issue #65. - Fixed an issue where a macro extension chain nested two levels (a template uses a macro that extends a macro) would lose the middle slot definitions if slots were defined nested. The compiler now throws an error if a nested slot definition is used outside a macro extension context. 2.5.0 (2011-09-23) ------------------ Features: - An expression type ``structure:`` is now available which wraps the expression result as *structure* such that it is not escaped on insertion, e.g.::
${structure: context.body}
This also means that the ``structure`` keyword for ``tal:content`` and ``tal:replace`` now has an alternative spelling via the expression type ``structure:``. - The string-based template constructor now accepts encoded input. 2.4.6 (2011-09-23) ------------------ Bugfixes: - The ``tal:on-error`` statement should catch all exceptions. - Fixed issue that would prevent escaping of interpolation expression values appearing in text. 2.4.5 (2011-09-21) ------------------ Bugfixes: - The ``tal:on-error`` handler should have a ``error`` variable defined that has the value of the exception thrown. - The ``tal:on-error`` statement is a substitution statement and should support the "text" and "structure" insertion methods. 2.4.4 (2011-09-15) ------------------ Bugfixes: - An encoding specified in the XML document preamble is now read and used to decode the template input to unicode. This fixes issue #55. - Encoded expression input on Python 3 is now correctly decoded. Previously, the string representation output would be included instead of an actually decoded string. - Expression result conversion steps are now correctly included in error handling such that the exception output points to the expression location. 2.4.3 (2011-09-13) ------------------ Features: - When an encoding is provided, pass the 'ignore' flag to avoid decoding issues with bad input. Bugfixes: - Fixed pypy compatibility issue (introduced in previous release). 2.4.2 (2011-09-13) ------------------ Bugfixes: - Fixed an issue in the compiler where an internal variable (such as a translation default value) would be cached, resulting in variable scope corruption (see issue #49). 2.4.1 (2011-09-08) ------------------ Bugfixes: - Fixed an issue where a default value for an attribute would sometimes spill over into another attribute. - Fixed issue where the use of the ``default`` name in an attribute interpolation expression would print the attribute value. This is unexpected, because it's an expression, not a static text suitable for output. An attribute value of ``default`` now correctly drops the attribute. 2.4.0 (2011-08-22) ------------------ Features: - Added an option ``boolean_attributes`` to evaluate and render a provided set of attributes using a boolean logic: if the attribute is a true value, the value will be the attribute name, otherwise the attribute is dropped. In the reference implementation, the following attributes are configured as boolean values when the template is rendered in HTML-mode:: "compact", "nowrap", "ismap", "declare", "noshade", "checked", "disabled", "readonly", "multiple", "selected", "noresize", "defer" Note that in Chameleon, these attributes must be manually provided. Bugfixes: - The carriage return character (used on Windows platforms) would incorrectly be included in Python comments. It is now replaced with a line break. This fixes issue #44. 2.3.8 (2011-08-19) ------------------ - Fixed import error that affected Python 2.5 only. 2.3.7 (2011-08-19) ------------------ Features: - Added an option ``literal_false`` that disables the default behavior of dropping an attribute for a value of ``False`` (in addition to ``None``). This modified behavior is the behavior exhibited in reference implementation. Bugfixes: - Undo attribute special HTML attribute behavior (see previous release). This turned out not to be a compatible behavior; rather, boolean values should simply be coerced to a string. Meanwhile, the reference implementation does support an HTML mode in which the special attribute behavior is exhibited. We do not currently support this mode. 2.3.6 (2011-08-18) ------------------ Features: - Certain HTML attribute names now have a special behavior for a attribute value of ``True`` (or ``default`` if no default is defined). For these attributes, this return value will result in the name being printed as the value:: will be rendered as:: This behavior is compatible with the reference implementation. 2.3.5 (2011-08-18) ------------------ Features: - Added support for the set operator (``{item, item, ...}``). Bugfixes: - If macro is defined on the same element as a translation name, this no longer results in a "translation name not allowed outside translation" error. This fixes issue #43. - Attribute fallback to dictionary lookup now works on multiple items (e.g. ``d1.d2.d2``). This fixes issue #42. 2.3.4 (2011-08-16) ------------------ Features: - When inserting content in either attributes or text, a value of ``True`` (like ``False`` and ``None``) will result in no action. - Use statically assigned variables for ``"attrs"`` and ``"default"``. This change yields a performance improvement of 15-20%. - The template loader class now accepts an optional argument ``default_extension`` which accepts a filename extension which will be appended to the filename if there's not already an extension. Bugfixes: - The default symbol is now ``True`` for an attribute if the attribute default is not provided. Note that the result is that the attribute is dropped. This fixes issue #41. - Fixed an issue where assignment to a variable ``"type"`` would fail. This fixes issue #40. - Fixed an issue where an (unsuccessful) assignment for a repeat loop to a compiler internal name would not result in an error. - If the translation function returns the identical object, manually coerce it to string. This fixes a compatibility issue with translation functions which do not convert non-string objects to a string value, but simply return them unchanged. 2.3.3 (2011-08-15) ------------------ Features: - The ``load:`` expression now passes the initial keyword arguments to its template loader (e.g. ``auto_reload`` and ``encoding``). - In the exception output, string variable values are now limited to a limited output of characters, single line only. Bugfixes: - Fixed horizontal alignment of exception location info (i.e. 'String:', 'Filename:' and 'Location:') such that they match the template exception formatter. 2.3.2 (2011-08-11) ------------------ Bugfixes: - Fixed issue where i18n:domain would not be inherited through macros and slots. This fixes issue #37. 2.3.1 (2011-08-11) ------------------ Features: - The ``Builtin`` node type may now be used to represent any Python local or global name. This allows expression compilers to refer to e.g. ``get`` or ``getitem``, or to explicit require a builtin object such as one from the ``extra_builtins`` dictionary. Bugfixes: - Builtins which are not explicitly disallowed may now be redefined and used as variables (e.g. ``nothing``). - Fixed compiler issue with circular node annotation loop. 2.3 (2011-08-10) ---------------- Features: - Added support for the following syntax to disable inline evaluation in a comment: Note that the initial question mark character (?) will be omitted from output. - The parser now accepts '<' and '>' in attributes. Note that this is invalid markup. Previously, the '<' would not be accepted as a valid attribute value, but this would result in an 'unexpected end tag' error elsewhere. This fixes issue #38. - The expression compiler now provides methods ``assign_text`` and ``assign_value`` such that a template engine might configure this value conversion to support e.g. encoded strings. Note that currently, the only client for the ``assign_text`` method is the string expression type. - Enable template loader for string-based template classes. Note that the ``filename`` keyword argument may be provided on initialization to identify the template source by filename. This fixes issue #36. - Added ``extra_builtins`` option to the page template class. These builtins are added to the default builtins dictionary at cook time and may be provided at initialization using the ``extra_builtins`` keyword argument. Bugfixes: - If a translation domain is set for a fill slot, use this setting instead of the macro template domain. - The Python expression compiler now correctly decodes HTML entities ``'gt'`` and ``'lt'``. This fixes issue #32. - The string expression compiler now correctly handles encoded text (when support for encoded strings is enabled). This fixes issue #35. - Fixed an issue where setting the ``filename`` attribute on a file-based template would not automatically cause an invalidation. - Exceptions raised by Chameleon can now be copied via ``copy.copy``. This fixes issue #36. [leorochael] - If copying the exception fails in the exception handler, simply re-raise the original exception and log a warning. 2.2 (2011-07-28) ---------------- Features: - Added new expression type ``load:`` that allows loading a template. Both relative and absolute paths are supported. If the path given is relative, then it will be resolved with respect to the directory of the template. - Added support for dynamic evaluation of expressions. Note that this is to support legacy applications. It is not currently wired into the provided template classes. - Template classes now have a ``builtins`` attribute which may be used to define built-in variables always available in the template variable scope. Incompatibilities: - The file-based template class no longer accepts a parameter ``loader``. This parameter would be used to load a template from a relative path, using a ``find(filename)`` method. This was however, undocumented, and probably not very useful since we have the ``TemplateLoader`` mechanism already. - The compiled template module now contains an ``initialize`` function which takes values that map to the template builtins. The return value of this function is a dictionary that contains the render functions. Bugfixes: - The file-based template class no longer verifies the existence of a template file (using ``os.lstat``). This now happens implicitly if eager parsing is enabled, or otherwise when first needed (e.g. at render time). This is classified as a bug fix because the previous behavior was probably not what you'd expect, especially if an application initializes a lot of templates without needing to render them immediately. 2.1.1 (2011-07-28) ------------------ Features: - Improved exception display. The expression string is now shown in the context of the original source (if available) with a marker string indicating the location of the expression in the template source. Bugfixes: - The ``structure`` insertion mode now correctly decodes entities for any expression type (including ``string:``). This fixes issue #30. - Don't show internal variables in the exception formatter variable listing. 2.1 (2011-07-25) ---------------- Features: - Expression interpolation (using the ``${...}`` operator and previously also ``$identifier``) now requires braces everywhere except inside the ``string:`` expression type. This change is motivated by a number of legacy templates in which the interpolation format without braces ``$identifier`` appears as text. 2.0.2 (2011-07-25) ------------------ Bugfixes: - Don't use dynamic variable scope for lambda-scoped variables (#27). - Avoid duplication of exception class and message in traceback. - Fixed issue where a ``metal:fill-slot`` would be ignored if a macro was set to be used on the same element (#16). 2.0.1 (2011-07-23) ------------------ Bugfixes: - Fixed issue where global variable definition from macro slots would fail (they would instead be local). This also affects error reporting from inside slots because this would be recorded internally as a global. - Fixed issue with template cache digest (used for filenames); modules are now invalidated whenever any changes are made to the distribution set available (packages on ``sys.path``). - Fixed exception handler to better let exceptions propagate through the renderer. - The disk-based module compiler now mangles template source filenames such that the output Python module is valid and at root level (dots and hyphens are replaced by an underscore). This fixes issue #17. - Fixed translations (i18n) on Python 2.5. 2.0 (2011-07-14) ---------------- - Point release. 2.0-rc14 (2011-07-13) --------------------- Bugfixes: - The tab character (``\t``) is now parsed correctly when used inside tags. Features: - The ``RepeatDict`` class now works as a proxy behind a separate dictionary instance. - Added template constructor option ``keep_body`` which is a flag (also available as a class attribute) that controls whether to save the template body input in the ``body`` attribute. This is disabled by default, unless debug-mode is enabled. - The page template loader class now accepts an optional ``formats`` argument which can be used to select an alternative template class. 2.0-rc13 (2011-07-07) --------------------- Bugfixes: - The backslash character (followed by optional whitespace and a line break) was not correctly interpreted as a continuation for Python expressions. Features: - The Python expression implementation is now more flexible for external subclassing via a new ``parse`` method. 2.0-rc12 (2011-07-04) --------------------- Bugfixes: - Initial keyword arguments passed to a template now no longer "leak" into the template variable space after a macro call. - An unexpected end tag is now an unrecoverable error. Features: - Improve exception output. 2.0-rc11 (2011-05-26) --------------------- Bugfixes: - Fixed issue where variable names that begin with an underscore were seemingly allowed, but their use resulted in a compiler error. Features: - Template variable names are now allowed to be prefixed with a single underscore, but not two or more (reserved for internal use). Examples of valid names:: item ITEM _item camelCase underscore_delimited help - Added support for Genshi's comment "drop" syntax:: Note the additional exclamation (!) character. This fixes addresses issue #10. 2.0-rc10 (2011-05-24) --------------------- Bugfixes: - The ``tal:attributes`` statement now correctly operates case-insensitive. The attribute name given in the statement will replace an existing attribute with the same name, without respect to case. Features: - Added ``meta:interpolation`` statement to control expression interpolation setting. Strings that disable the setting: ``"off"`` and ``"false"``. Strings that enable the setting: ``"on"`` and ``"true"``. - Expression interpolation now works inside XML comments. 2.0-rc9 (2011-05-05) -------------------- Features: - Better debugging support for string decode and conversion. If a naive join fails, each element in the output will now be attempted coerced to unicode to try and trigger the failure near to the bad string. 2.0-rc8 (2011-04-11) -------------------- Bugfixes: - If a macro defines two slots with the same name, a caller will now fill both with a single usage. - If a valid of ``None`` is provided as the translation function argument, we now fall back to the class default. 2.0-rc7 (2011-03-29) -------------------- Bugfixes: - Fixed issue with Python 2.5 compatibility AST. This affected at least PyPy 1.4. Features: - The ``auto_reload`` setting now defaults to the class value; the base template class gives a default value of ``chameleon.config.AUTO_RELOAD``. This change allows a subclass to provide a custom default value (such as an application-specific debug mode setting). 2.0-rc6 (2011-03-19) -------------------- Features: - Added support for ``target_language`` keyword argument to render method. If provided, the argument will be curried onto the translation function. Bugfixes: - The HTML entities 'lt', 'gt' and 'quot' appearing inside content subtition expressions are now translated into their native character values. This fixes an issue where you could not dynamically create elements using the ``structure`` (which is possible in ZPT). The need to create such structure stems from the lack of an expression interpolation operator in ZPT. - Fixed duplicate file pointer issue with test suite (affected Windows platforms only). This fixes issue #9. [oliora] - Use already open file using ``os.fdopen`` when trying to write out the module source. This fixes LP #731803. 2.0-rc5 (2011-03-07) -------------------- Bugfixes: - Fixed a number of issues concerning the escaping of attribute values: 1) Static attribute values are now included as they appear in the source. This means that invalid attribute values such as ``"true && false"`` are now left alone. It's not the job of the template engine to correct such markup, at least not in the default mode of operation. 2) The string expression compiler no longer unescapes values. Instead, this is left to each expression compiler. Currently only the Python expression compiler unescapes its input. 3) The dynamic escape code sequence now correctly only replaces ampersands that are part of an HTML escape format. Imports: - The page template classes and the loader class can now be imported directly from the ``chameleon`` module. Features: - If a custom template loader is not provided, relative paths are now resolved using ``os.abspath`` (i.e. to the current working directory). - Absolute paths are normalized using ``os.path.normpath`` and ``os.path.expanduser``. This ensures that all paths are kept in their "canonical" form. 2.0-rc4 (2011-03-03) -------------------- Bugfixes: - Fixed an issue where the output of an end-to-end string expression would raise an exception if the expression evaluated to ``None`` (it should simply output nothing). - The ``convert`` function (which is configurable on the template class level) now defaults to the ``translate`` function (at run-time). This fixes an issue where message objects were not translated (and thus converted to a string) using the a provided ``translate`` function. - Fixed string interpolation issue where an expression immediately succeeded by a right curly bracket would not parse. This fixes issue #5. - Fixed error where ``tal:condition`` would be evaluated after ``tal:repeat``. Features: - Python expression is now a TALES expression. That means that the pipe operator can be used to chain two or more expressions in a try-except sequence. This behavior was ported from the 1.x series. Note that while it's still possible to use the pipe character ("|") in an expression, it must now be escaped. - The template cache can now be shared by multiple processes. 2.0-rc3 (2011-03-02) -------------------- Bugfixes: - Fixed ``atexit`` handler. This fixes issue #3. - If a cache directory is specified, it will now be used even when not in debug mode. - Allow "comment" attribute in the TAL namespace. This fixes an issue in the sense that the reference engine allows any attribute within the TAL namespace. However, only "comment" is in common use. - The template constructor now accepts a flag ``debug`` which puts the template *instance* into debug-mode regardless of the global setting. This fixes issue #1. Features: - Added exception handler for exceptions raised while evaluating an expression. This handler raises (or attempts to) a new exception of the type ``RenderError``, with an additional base class of the original exception class. The string value of the exception is a formatted error message which includes the expression that caused the exception. If we are unable to create the exception class, the original exception is re-raised. 2.0-rc2 (2011-02-28) -------------------- - Fixed upload issue. 2.0-rc1 (2011-02-28) -------------------- - Initial public release. See documentation for what's new in this series. chameleon-4.5.4/COPYRIGHT.txt000066400000000000000000000002611460471412200155700ustar00rootroot00000000000000Copyright (c) 2024 Malthe Borch and Contributors. All Rights Reserved. Portions (c) Zope Foundation and contributors (http://www.zope.org/). Portions (c) 2008 Armin Ronacher. chameleon-4.5.4/LICENSE.txt000066400000000000000000000204601460471412200153050ustar00rootroot00000000000000The majority of the code in Chameleon is supplied under this license: A copyright notice accompanies this license document that identifies the copyright holders. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. Portions of the code in Chameleon are supplied under the ZPL (headers within individual files indicate that these portions are licensed under the ZPL): Zope Public License (ZPL) Version 2.1 ------------------------------------- A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. Portions of the code in Chameleon are supplied under the BSD license (headers within individual files indicate that these portions are licensed under this license): All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. Portions of the code in Chameleon are supplied under the Python License (headers within individual files indicate that these portions are licensed under this license): PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. chameleon-4.5.4/MANIFEST.in000066400000000000000000000007551460471412200152250ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python include *.md include *.rst include *.txt include *.yaml include buildout.cfg include tox.ini recursive-include docs *.py recursive-include docs *.rst recursive-include docs *.txt recursive-include docs Makefile recursive-include src *.py include Makefile recursive-include benchmarks *.py recursive-include src *.html recursive-include src *.pt recursive-include src *.txt recursive-include src *.xml chameleon-4.5.4/Makefile000066400000000000000000000060771460471412200151320ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = docs SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Chameleon.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Chameleon.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." chameleon-4.5.4/README.rst000066400000000000000000000015241460471412200151510ustar00rootroot00000000000000Chameleon ========= .. image:: https://img.shields.io/pypi/pyversions/Chameleon :alt: PyPI - Python Version .. image:: https://img.shields.io/github/actions/workflow/status/malthe/chameleon/main.yml :alt: GitHub Actions Workflow Status Chameleon is an HTML/XML template engine for `Python `_. It uses the *page templates* language. You can use it in any Python application with just about any version of Python (3.9+ and up, plus `pypy 3 `_). Visit the `documentation `_ for more information. License and Copyright --------------------- This software is made available as-is under a BSD-like license [1]_ (see included copyright notice). Notes ----- .. [1] This software is licensed under the `Repoze `_ license. chameleon-4.5.4/benchmarks/000077500000000000000000000000001460471412200155755ustar00rootroot00000000000000chameleon-4.5.4/benchmarks/bm_chameleon.py000066400000000000000000000077071460471412200205730ustar00rootroot00000000000000#!/usr/bin/python2 """ Benchmark for test the performance of Chameleon page template engine. """ __author__ = "mborch@gmail.com (Malthe Borch)" # Python imports import os import sys import optparse import time # Local imports import util def relative(*args): return os.path.join(os.path.dirname(os.path.abspath(__file__)), *args) sys.path.insert(0, relative('..', 'src')) # Chameleon imports from chameleon import PageTemplate LOREM_IPSUM = """Quisque lobortis hendrerit posuere. Curabitur aliquet consequat sapien molestie pretium. Nunc adipiscing luc tus mi, viverra porttitor lorem vulputate et. Ut at purus sem, sed tincidunt ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent pulvinar sodales justo at congue. Praesent aliquet facilisis nisl a molestie. Sed tempus nisl ut augue eleifend tincidunt. Sed a lacinia nulla. Cras tortor est, mollis et consequat at, vulputate et orci. Nulla sollicitudin""" BASE_TEMPLATE = '''
${col}
${alt}
${title.strip()} ''' PAGE_TEMPLATE = ''' images:

${lorem}

''' CONTENT_TEMPLATE = ''' fun1fun2fun3fun4fun5fun6

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam laoreet justo in velit faucibus lobortis. Sed dictum sagittis volutpat. Sed adipiscing vestibulum consequat. Nullam laoreet, ante nec pretium varius, libero arcu porttitor orci, id cursus odio nibh nec leo. Vestibulum dapibus pellentesque purus, sed bibendum tortor laoreet id. Praesent quis sodales ipsum. Fusce ut ligula sed diam pretium sagittis vel at ipsum. Nulla sagittis sem quam, et volutpat velit. Fusce dapibus ligula quis lectus ultricies tempor. Pellente

''' def test_mako(count): template = PageTemplate(CONTENT_TEMPLATE) base = PageTemplate(BASE_TEMPLATE) page = PageTemplate(PAGE_TEMPLATE) table = [range(150) for i in range(150)] paragraphs = range(50) title = 'Hello world!' times = [] for i in range(count): t0 = time.time() data = template.render( table=table, paragraphs=paragraphs, lorem=LOREM_IPSUM, title=title, img_count=50, base=base, page=page, ) t1 = time.time() times.append(t1-t0) return times if __name__ == "__main__": parser = optparse.OptionParser( usage="%prog [options]", description=("Test the performance of Chameleon templates.")) util.add_standard_options_to(parser) (options, args) = parser.parse_args() util.run_benchmark(options, options.num_runs, test_mako) chameleon-4.5.4/benchmarks/bm_mako.py000066400000000000000000000074421460471412200175630ustar00rootroot00000000000000#!/usr/bin/python """ Benchmark for test the performance of Mako templates engine. Includes: -two template inherences -HTML escaping, XML escaping, URL escaping, whitespace trimming -function definitions and calls -forloops """ __author__ = "virhilo@gmail.com (Lukasz Fidosz)" # Python imports import os import sys import optparse import time # Local imports import util def relative(*args): return os.path.join(os.path.dirname(os.path.abspath(__file__)), *args) sys.path.insert(0, relative('..', 'lib')) # Mako imports from mako.template import Template from mako.lookup import TemplateLookup LOREM_IPSUM = """Quisque lobortis hendrerit posuere. Curabitur aliquet consequat sapien molestie pretium. Nunc adipiscing luc tus mi, viverra porttitor lorem vulputate et. Ut at purus sem, sed tincidunt ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent pulvinar sodales justo at congue. Praesent aliquet facilisis nisl a molestie. Sed tempus nisl ut augue eleifend tincidunt. Sed a lacinia nulla. Cras tortor est, mollis et consequat at, vulputate et orci. Nulla sollicitudin""" BASE_TEMPLATE = """ <%def name="render_table(table)">
% for row in table: % for col in row: % endfor % endfor
${col|h}
<%def name="img(src, alt)"> ${alt} ${title|h,trim} ${next.body()} """ PAGE_TEMPLATE = """ <%inherit file="base.mako"/> % for row in table: % for col in row: % endfor % endfor
${col}
% for nr in range(img_count): ${parent.img('/foo/bar/baz.png', 'no image :o')} % endfor ${next.body()} % for nr in paragraphs:

${lorem|x}

% endfor ${parent.render_table(table)} """ CONTENT_TEMPLATE = """ <%inherit file="page.mako"/> <%def name="fun1()"> fun1 <%def name="fun2()"> fun2 <%def name="fun3()"> foo3 <%def name="fun4()"> foo4 <%def name="fun5()"> foo5 <%def name="fun6()"> foo6

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam laoreet justo in velit faucibus lobortis. Sed dictum sagittis volutpat. Sed adipiscing vestibulum consequat. Nullam laoreet, ante nec pretium varius, libero arcu porttitor orci, id cursus odio nibh nec leo. Vestibulum dapibus pellentesque purus, sed bibendum tortor laoreet id. Praesent quis sodales ipsum. Fusce ut ligula sed diam pretium sagittis vel at ipsum. Nulla sagittis sem quam, et volutpat velit. Fusce dapibus ligula quis lectus ultricies tempor. Pellente

${fun1()} ${fun2()} ${fun3()} ${fun4()} ${fun5()} ${fun6()} """ def test_mako(count): lookup = TemplateLookup() lookup.put_string('base.mako', BASE_TEMPLATE) lookup.put_string('page.mako', PAGE_TEMPLATE) template = Template(CONTENT_TEMPLATE, lookup=lookup) table = [range(150) for i in range(150)] paragraphs = range(50) title = 'Hello world!' times = [] for i in range(count): t0 = time.time() data = template.render(table=table, paragraphs=paragraphs, lorem=LOREM_IPSUM, title=title, img_count=50) t1 = time.time() times.append(t1-t0) return times if __name__ == "__main__": parser = optparse.OptionParser( usage="%prog [options]", description=("Test the performance of Mako templates.")) util.add_standard_options_to(parser) (options, args) = parser.parse_args() util.run_benchmark(options, options.num_runs, test_mako) chameleon-4.5.4/benchmarks/util.py000066400000000000000000000035151460471412200171300ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function """Utility code for benchmark scripts.""" __author__ = "collinwinter@google.com (Collin Winter)" import math import operator import functools def run_benchmark(options, num_runs, bench_func, *args): """Run the given benchmark, print results to stdout. Args: options: optparse.Values instance. num_runs: number of times to run the benchmark bench_func: benchmark function. `num_runs, *args` will be passed to this function. This should return a list of floats (benchmark execution times). """ if options.profile: import cProfile prof = cProfile.Profile() prof.runcall(bench_func, num_runs, *args) prof.print_stats(sort=options.profile_sort) else: data = bench_func(num_runs, *args) if options.take_geo_mean: product = functools.reduce(operator.mul, data, 1) print (math.pow(product, 1.0 / len(data))) else: for x in data: print (x) def add_standard_options_to(parser): """Add a bunch of common command-line flags to an existing OptionParser. This function operates on `parser` in-place. Args: parser: optparse.OptionParser instance. """ parser.add_option("-n", action="store", type="int", default=100, dest="num_runs", help="Number of times to run the test.") parser.add_option("--profile", action="store_true", help="Run the benchmark through cProfile.") parser.add_option("--profile_sort", action="store", type="str", default="time", help="Column to sort cProfile output by.") parser.add_option("--take_geo_mean", action="store_true", help="Return the geo mean, rather than individual data.") chameleon-4.5.4/docs/000077500000000000000000000000001460471412200144105ustar00rootroot00000000000000chameleon-4.5.4/docs/conf.py000066400000000000000000000152231460471412200157120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Chameleon documentation build configuration file, created by # sphinx-quickstart on Sun Nov 1 16:08:00 2009. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from importlib.metadata import version as distribution_version import sys import os sys.path.append(os.path.abspath('../src')) chameleon_version = distribution_version('Chameleon') # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Chameleon' copyright = u'2008-2017 by Malthe Borch and the Repoze Community' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '%s.%s' % tuple(map(int, chameleon_version.split('.')[:2])) # The full version, including alpha/beta/rc tags. release = chameleon_version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "Chameleon %s documentation" % version # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bchameleonm, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'chameleondoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'chameleon.tex', u'Chameleon Documentation', u'Malthe Borch et. al', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # Configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'https://docs.python.org/': None, } chameleon-4.5.4/docs/configuration.rst000066400000000000000000000021151460471412200200100ustar00rootroot00000000000000Configuration ============= Most settings can be provided as keyword-arguments to the template constructor classes. There are certain settings which are required at environment level. Acceptable values are ``"0"``, ``"1"``, or the literals ``"true"`` or ``"false"`` (case-insensitive). General usage ------------- The following settings are useful in general. ``CHAMELEON_EAGER`` Parse and compile templates on instantiation. ``CHAMELEON_CACHE`` When set to a file system path, the template compiler will write its output to files in this directory and use it as a cache. This not only enables you to see the compiler output, but also speeds up startup. ``CHAMELEON_RELOAD`` This setting controls the default value of the ``auto_reload`` parameter. Development ----------- The following settings are mostly useful during development or debugging of the library itself. ``CHAMELEON_DEBUG`` Enables a set of debugging settings which make it easier to discover and research issues with the engine itself. This implicitly enables auto-reload for any template. chameleon-4.5.4/docs/index.rst000066400000000000000000000156151460471412200162610ustar00rootroot00000000000000Chameleon ========= Chameleon is an HTML/XML template engine for `Python `_. It's designed to generate the document output of a web application, typically HTML markup or XML. The language used is *page templates*, originally a `Zope `_ invention [1]_, but available here as a :ref:`standalone library ` that you can use in any script or application running Python 2.7 and up, including 3.4+ and `pypy `_). It comes with a set of :ref:`new features `, too. The template engine compiles templates into Python byte-code and is optimized for speed. For a complex template language, the performance is :ref:`very good `. *Found a bug?* Please report issues to the `issue tracker `_. *Need help?* Post to the Pylons `discussion list `_ or join the ``#pyramid`` channel on `Freenode IRC `_. Getting the code ---------------- You can `download `_ the package from the Python package index:: $ pip install Chameleon .. _no-dependencies: There are no required library dependencies on Python 3.11 and up [2]_. The project is hosted in a `GitHub repository `_. Code contributions are welcome. The easiest way is to use the `pull request `_ interface. Introduction ------------ The *page templates* language is used within your document structure as special element attributes and text markup. Using a set of simple language constructs, you control the document flow, element repetition, text replacement and translation. .. note:: If you've used page templates in a Zope environment previously, note that Chameleon uses Python as the default expression language (instead of *path* expressions). The basic language (known as the *template attribute language* or TAL) is simple enough to grasp from an example: .. code-block:: genshi

Hello, ${'world'}!

${row.capitalize()} ${col}
The ``${...}`` notation is short-hand for text insertion [3]_. The Python-expression inside the braces is evaluated and the result included in the output. By default, the string is escaped before insertion. To avoid this, use the ``structure:`` prefix: .. code-block:: genshi
${structure: ...}
Note that if the expression result is an object that implements an ``__html__()`` method [4]_, this method will be called and the result treated as "structure". An example of such an object is the ``Markup`` class that's included as a utility:: from chameleon.utils import Markup username = Markup("%s" % username) The macro language (known as the *macro expansion language* or METAL) provides a means of filling in portions of a generic template. On the left, the macro template; on the right, a template that loads and uses the macro, filling in the "content" slot: .. code-block:: genshi

${structure: document.body}

Example — ${document.title}

${document.title}

In the example, the expression type :ref:`load ` is used to retrieve a template from the file system using a path relative to the calling template. The METAL system works with TAL such that you can for instance fill in a slot that appears in a ``tal:repeat`` loop, or refer to variables defined using ``tal:define``. The third language subset is the translation system (known as the *internationalization language* or I18N): .. code-block:: genshi ...
You have ${round(amount, 2)} dollars in your account.
... Each translation message is marked up using ``i18n:translate`` and values can be mapped using ``i18n:name``. Attributes are marked for translation using ``i18n:attributes``. The template engine generates `gettext `_ translation strings from the markup:: "You have ${amount} dollars in your account." If you use a web framework such as `Pyramid `_, the translation system is set up automatically and will negotiate on a *target language* based on the HTTP request or other parameter. If not, then you need to configure this manually. Next steps ---------- This was just an introduction. There are a number of other basic statements that you need to know in order to use the language. This is all covered in the :ref:`language reference `. If you're already familiar with the page template language, you can skip ahead to the :ref:`getting started ` section to learn how to use the template engine in your code. To learn about integration with your favorite web framework see the section on :ref:`framework integration `. License ------- This software is made available under a BSD-like license. Contents ======== .. toctree:: :maxdepth: 2 library.rst reference.rst integration.rst configuration.rst Indices and Tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` Notes ===== .. [1] The template language specifications and API for the Page Templates engine are based on Zope Page Templates (see in particular `zope.pagetemplate `_). However, the Chameleon compiler and Page Templates engine is an entirely new codebase, packaged as a standalone distribution. It does not require a Zope software environment. .. [2] The translation system in Chameleon is pluggable and based on `gettext `_. There is built-in support for the `zope.i18n `_ package. If this package is installed, it will be used by default. The `translationstring `_ package offers some of the same helper and utility classes, without the Zope application interface. .. [3] This syntax was taken from `Genshi `_. .. [4] See the `WebHelpers `_ library which provide a simple wrapper around this method. chameleon-4.5.4/docs/integration.rst000066400000000000000000000025611460471412200174710ustar00rootroot00000000000000.. _framework-integration: Integration =========== Integration with Chameleon is available for a number of popular web frameworks. The framework will usually provide loading mechanisms and translation (internationalization) configuration. Pyramid ------- `pyramid_chameleon `_ is a set of bindings that make templates written for the Chameleon templating system work under the Pyramid web framework. Zope 2 / Plone -------------- Install the `five.pt `_ package to replace the reference template engine (globally). Zope Toolkit (ZTK) ------------------ Install the `z3c.pt `_ package for applications based on the `Zope Toolkit `_ (ZTK). Note that you need to explicit use the template classes from this package. Grok ---- Support for the `Grok `_ framework is available in the `grokcore.chameleon `_ package. This package will setup Grok's policy for templating integration and associate the Chameleon template components for the ``.cpt`` template filename extension. Django ------ Install the `django-chameleon-templates `_ app to enable Chameleon as a template engine. chameleon-4.5.4/docs/library.rst000066400000000000000000000126731460471412200166170ustar00rootroot00000000000000Library Documentation ===================== This section documents the package as a Python library. To learn about the page template language, consult the :ref:`language reference `. .. _getting-started-with-cpt: Getting started --------------- There are several template constructor classes available, one for each of the combinations *text* or *xml*, and *string* or *file*. The file-based constructor requires an absolute path. To set up a templates directory *once*, use the template loader class:: import os path = os.path.dirname(__file__) from chameleon import PageTemplateLoader templates = PageTemplateLoader(os.path.join(path, "templates")) Then, to load a template relative to the provided path, use dictionary syntax:: template = templates['hello.pt'] Alternatively, use the appropriate template class directly. Let's try with a string input:: from chameleon import PageTemplate template = PageTemplate("
Hello, ${name}.
") All template instances are callable. Provide variables by keyword argument:: >>> template(name='John') '
Hello, John.
' .. _fast: Performance ----------- The template engine compiles (or *translates*) template source code into Python byte-code. In simple templates this yields an increase in performance of about 7 times in comparison to the reference implementation. In benchmarks for the content management system `Plone `_, switching to Chameleon yields a request to response improvement of 20-50%. Extension --------- You can extend the language through the expression engine by writing your own expression compiler. Let's try and write an expression compiler for an expression type that will simply uppercase the supplied value. We'll call it ``upper``. You can write such a compiler as a closure: .. code-block:: python import ast def uppercase_expression(string): def compiler(target, engine): uppercased = self.string.uppercase() value = ast.Str(uppercased) return [ast.Assign(targets=[target], value=value)] return compiler To make it available under a certain prefix, we'll add it to the expression types dictionary. .. code-block:: python from chameleon import PageTemplate PageTemplate.expression_types['upper'] = uppercase_expression Alternatively, you could subclass the template class and set the attribute ``expression_types`` to a dictionary that includes your expression: .. code-block:: python from chameleon import PageTemplateFile from chameleon.tales import PythonExpr class MyPageTemplateFile(PageTemplateFile): expression_types = { 'python': PythonExpr, 'upper': uppercase_expression } You can now uppercase strings *natively* in your templates::
It's probably best to stick with a Python expression::
API reference ------------- This section describes the documented API of the library. Templates ~~~~~~~~~ Use the ``PageTemplate*`` template classes to define a template from a string or file input: .. automodule:: chameleon .. autoclass:: chameleon.PageTemplate Note: The remaining classes take the same general configuration arguments. .. automethod:: render .. autoclass:: chameleon.PageTemplateFile(filename, **config) .. autoclass:: chameleon.PageTextTemplate .. autoclass:: chameleon.PageTextTemplateFile Loader ~~~~~~ Some systems have framework support for loading templates from files. The following loader class is directly compatible with the Pylons framework and may be adapted to other frameworks: .. class:: chameleon.PageTemplateLoader(search_path=None, default_extension=None, **config) Load templates from ``search_path`` (must be a string or a list of strings):: templates = PageTemplateLoader(path) example = templates['example.pt'] If ``default_extension`` is provided, this will be added to inputs that do not already have an extension:: templates = PageTemplateLoader(path, ".pt") example = templates['example'] Any additional keyword arguments will be passed to the template constructor:: templates = PageTemplateLoader(path, debug=True, encoding="utf-8") .. automethod:: load Exceptions ~~~~~~~~~~ Chameleon may raise exceptions during both the cooking and the rendering phase, but those raised during the cooking phase (parse and compile) all inherit from a single base class: .. class:: chameleon.TemplateError(msg, token) This exception is the base class of all exceptions raised by the template engine in the case where a template has an error. It may be raised during rendering since templates are processed lazily (unless eager loading is enabled). An error that occurs during the rendering of a template is wrapped in an exception class to disambiguate the two cases: .. class:: chameleon.RenderError(*args) Indicates an exception that resulted from the evaluation of an expression in a template. A complete traceback is attached to the exception beginning with the expression that resulted in the error. The traceback includes a string representation of the template variable scope for further reference. Expressions ~~~~~~~~~~~ For advanced integration, the compiler module provides support for dynamic expression evaluation: .. automodule:: chameleon.compiler .. autoclass:: chameleon.compiler.ExpressionEvaluator chameleon-4.5.4/docs/reference.rst000066400000000000000000001504561460471412200171130ustar00rootroot00000000000000.. _language-reference: .. highlight:: xml Language Reference ================== The language reference is structured such that it can be read as a general introduction to the *page templates* language. It's split into parts that correspond to each of the main language features. Syntax ------ You can safely :ref:`skip this section ` if you're familiar with how template languages work or just want to learn by example. An *attribute language* is a programming language designed to render documents written in XML or HTML markup. The input must be a well-formed document. The output from the template is usually XML-like but isn't required to be well-formed. The statements of the language are document tags with special attributes, and look like this::

...

In the above example, the attribute ``namespace-prefix:command="argument"`` is the statement, and the entire paragraph tag is the statement's element. The statement's element is the portion of the document on which this statement operates. The namespace prefixes are typically declared once, at the top of a template (note that prefix declarations for the template language namespaces are omitted from the template output):: ... Thankfully, sane namespace prefix defaults are in place to let us skip most of the boilerplate::

...

Note how ``tal`` is used without an explicit namespace declaration. Chameleon sets up defaults for ``metal`` and ``i18n`` as well. .. note:: Default prefixes are a special feature of Chameleon. If the ``enable_data_attributes`` option is set then you can use ``data-prefix-command="argument"`` in addition to the namespace prefix attributes. .. _tal: Basics (TAL) ------------ The *template attribute language* is used to create dynamic XML-like content. It allows elements of a document to be replaced, repeated, or omitted. Statements ~~~~~~~~~~ These are the available statements: ================== ============== Statement Description ================== ============== ``tal:define`` Define variables. ``tal:switch`` Defines a switch condition ``tal:condition`` Include element only if expression is true. ``tal:repeat`` Repeat an element. ``tal:case`` Includes element only if expression is equal to parent switch. ``tal:content`` Substitute the content of an element. ``tal:replace`` Replace the element with dynamic content. ``tal:omit-tag`` Omit the element tags, leaving only the inner content. ``tal:attributes`` Dynamically change or insert element attributes. ``tal:on-error`` Substitute the content of an element if processing fails. ================== ============== When there is only one TAL statement per element, the order in which they are executed is simple. Starting with the root element, each element's statements are executed, then each of its child elements is visited, in order, to do the same:: </meta> <body> <div tal:condition="items"> <p>These are your items:</p> <ul> <li tal:repeat="item items" tal:content="item" /> </ul> </div> </body> </html> Any combination of statements may appear on the same element, except that the ``tal:content`` and ``tal:replace`` statements may not be used on the same element. .. note:: The ``tal:case`` and ``tal:switch`` statements are available in Chameleon only. TAL does not use the order in which statements are written in the tag to determine the order in which they are executed. When an element has multiple statements, they are executed in the order printed in the table above. There is a reasoning behind this ordering. Because users often want to set up variables for use in other statements contained within this element or subelements, ``tal:define`` is executed first. Then any switch statement. ``tal:condition`` follows, then ``tal:repeat``, then ``tal:case``. We are now rendering an element; first ``tal:content`` or ``tal:replace``. Finally, before ``tal:attributes``, we have ``tal:omit-tag`` (which is implied with ``tal:replace``). .. note:: *TALES* is used as the expression language for the "stuff in the quotes". The default syntax is simply Python, but other inputs are possible --- see the section on :ref:`expressions <tales>`. ``tal:attributes`` ^^^^^^^^^^^^^^^^^^ Removes, updates or inserts element attributes. :: tal:attributes="href request.url" Syntax ++++++ ``tal:attributes`` syntax:: argument ::= attribute_statement [';' attribute_statement]* attribute_statement ::= (attribute_name expression | expression) attribute_name ::= [namespace-prefix ':'] Name namespace-prefix ::= Name Description +++++++++++ The ``tal:attributes`` statement replaces the value of an attribute (or drops, or creates an attribute) with a dynamic value. The value of each expression is converted to a string, if necessary. .. note:: You can qualify an attribute name with a namespace prefix, for example ``html:table``, if you are generating an XML document with multiple namespaces. If an attribute expression evaluates to ``None``, the attribute is deleted from the statement element (or simply not inserted). If an attribute statement is just an expression, it must evaluate to a Python dict (or implement the methods ``update()`` and ``items()`` from the dictionary specification). If the expression evaluates to the symbol ``default`` (a symbol which is always available when evaluating attributes), its value is defined as the default static attribute value. If there is no such default value, a return value of ``default`` will drop the attribute. If you use ``tal:attributes`` on an element with an active ``tal:replace`` command, the ``tal:attributes`` statement is ignored. If you use ``tal:attributes`` on an element with a ``tal:repeat`` statement, the replacement is made on each repetition of the element, and the replacement expression is evaluated fresh for each repetition. .. note:: If you want to include a semicolon (";") in an expression, it must be escaped by doubling it (";;"). Similarly, you can escape expression interpolation using the "$" symbol by doubling it ("$$"). Examples ++++++++ Replacing a link:: <a href="/sample/link.html" tal:attributes="href context.url()" > ... </a> Replacing two attributes:: <textarea rows="80" cols="20" tal:attributes="rows request.rows();cols request.cols()" /> A checkbox input:: <input type="checkbox" tal:attributes="checked True" /> This requires ``boolean_attributes`` to be activated, see :py:class:`PageTemplate configuration options<chameleon.PageTemplate>`:: template = PageTemplate("<input type="checkbox" tal:attributes="checked True" />", boolean_attributes={"selected", "checked"}) // or templates = PageTemplateLoader(path, boolean_attributes={"selected", "checked"}) ``tal:condition`` ^^^^^^^^^^^^^^^^^ Conditionally includes or omits an element:: <div tal:condition="comments"> ... </div> Syntax ++++++ ``tal:condition`` syntax:: argument ::= expression Description +++++++++++ The ``tal:condition`` statement includes the statement element in the template only if the condition is met, and omits it otherwise. If its expression evaluates to a *true* value, then normal processing of the element continues, otherwise the statement element is immediately removed from the template. For these purposes, the value ``nothing`` is false, and ``default`` has the same effect as returning a true value. .. note:: Like Python itself, ZPT considers None, zero, empty strings, empty sequences, empty dictionaries, and instances which return a nonzero value from ``__len__`` or which return false from ``__nonzero__``; all other values are true, including ``default``. Examples ++++++++ Test a variable before inserting it:: <p tal:condition="request.message" tal:content="request.message" /> Testing for odd/even in a repeat-loop:: <div tal:repeat="item range(10)"> <p tal:condition="repeat.item.even">Even</p> <p tal:condition="repeat.item.odd">Odd</p> </div> ``tal:content`` ^^^^^^^^^^^^^^^ Replaces the content of an element. Syntax ++++++ ``tal:content`` syntax:: argument ::= (['text'] | 'structure') expression Description +++++++++++ Rather than replacing an entire element, you can insert text or structure in place of its children with the ``tal:content`` statement. The statement argument is exactly like that of ``tal:replace``, and is interpreted in the same fashion. If the expression evaluates to ``nothing``, the statement element is left childless. If the expression evaluates to ``default``, then the element's contents are evaluated. The default replacement behavior is ``text``, which replaces angle-brackets and ampersands with their HTML entity equivalents. The ``structure`` keyword passes the replacement text through unchanged, allowing HTML/XML markup to be inserted. This can break your page if the text contains unanticipated markup (eg. text submitted via a web form), which is the reason that it is not the default. .. note:: The ``structure`` keyword exists to provide backwards compatibility. In Chameleon, the ``structure:`` expression type provides the same functionality (also for inline expressions). Examples ++++++++ Inserting the user name:: <p tal:content="user.getUserName()">Fred Farkas</p> Inserting HTML/XML:: <p tal:content="structure context.getStory()"> Marked <b>up</b> content goes here. </p> ``tal:define`` ^^^^^^^^^^^^^^ Defines local variables. Syntax ++++++ ``tal:define`` syntax:: variable_name ::= Name | '(' Name [',' Name]* ')' define_var ::= variable_name expression define_scope ::= (['local'] | 'global') define_var argument ::= define_scope [';' define_scope]* Description +++++++++++ The ``tal:define`` statement defines variables. When you define a local variable in a statement element, you can use that variable in that element and the elements it contains. If you redefine a variable in a contained element, the new definition hides the outer element's definition within the inner element. Note that valid variable names are any Python identifier string including underscore, although two or more leading underscores are disallowed (used internally by the compiler). Further, names are case-sensitive. Variable names support basic iterable unpacking when surrounded by parenthesis. This also applies to the variable established by ``tal:repeat``. .. note:: This is a Chameleon-specific :ref:`language extension <new-features>`. Python builtins are always "in scope", but most of them may be redefined (such as ``help``). Exceptions are:: ``float``, ``int``, ``len``, ``long``, ``str``, ``None``, ``True`` and ``False``. In addition, the following names are reserved: ``econtext``, ``rcontext``, ``translate``, ``decode`` and ``convert``. If the expression associated with a variable evaluates to ``nothing``, then that variable has the value ``nothing``, and may be used as such in further expressions. Likewise, if the expression evaluates to ``default``, then the variable has the value ``default``, and may be used as such in further expressions. You can define two different kinds of variables: *local* and *global*. When you define a local variable in a statement element, you can only use that variable in that element and the elements it contains. If you redefine a local variable in a contained element, the new definition hides the outer element's definition within the inner element. When you define a global variables, you can use it in any element processed after the defining element. If you redefine a global variable, you replace its definition for the rest of the template. .. tip:: Global variables may be changed by the execution of a macro if that macro also declares the variable to be global. To set the definition scope of a variable, use the keywords ``local`` or ``global`` in front of the assignment. The default setting is ``local``; thus, in practice, only the ``global`` keyword is used. .. note:: If you want to include a semicolon (";") in an expression, it must be escaped by doubling it (";;"). Examples ++++++++ Defining a variable:: tal:define="company_name 'Zope Corp, Inc.'" Defining two variables, where the second depends on the first:: tal:define="mytitle context.title; tlen len(mytitle)" Defining a local and global variable:: tal:define="global mytitle context.title; tlen len(mytitle)" Unpacking a sequence:: tal:define="(key,value) ('a', 42)" ``tal:switch`` and ``tal:case`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Defines a switch clause. :: <ul tal:switch="len(items) % 2"> <li tal:case="True">odd</li> <li tal:case="False">even</li> </ul> Syntax ++++++ ``tal:case`` and ``tal:switch`` syntax:: argument ::= expression Description +++++++++++ The *switch* and *case* construct is a short-hand syntax for matching a set of expressions against a single parent. The ``tal:switch`` statement is used to set a new parent expression and the contained ``tal:case`` statements are then matched in sequence such that only the first match succeeds. Note that the symbol ``default`` affirms the case precisely when no previous case has been successful. It should therefore be placed last. .. note:: These statements are only available in Chameleon 2.x and not part of the ZPT specification. Examples ++++++++ :: <ul tal:switch="item.type"> <li tal:case="'document'"> Document </li> <li tal:case="'folder'"> Folder </li> <li tal:case="default"> Other </li> </ul> ``tal:omit-tag`` ^^^^^^^^^^^^^^^^ Removes an element, leaving its contents. Syntax ++++++ ``tal:omit-tag`` syntax:: argument ::= [ expression ] Description +++++++++++ The ``tal:omit-tag`` statement leaves the contents of an element in place while omitting the surrounding start and end tags. If the expression evaluates to a *false* value, then normal processing of the element continues and the tags are not omitted. If the expression evaluates to a *true* value, or no expression is provided, the statement element is replaced with its contents. .. note:: Like Python itself, ZPT considers None, zero, empty strings, empty sequences, empty dictionaries, and instances which return a nonzero value from ``__len__`` or which return false from ``__nonzero__``; all other values are true, including ``default``. Examples ++++++++ Unconditionally omitting a tag:: <div tal:omit-tag="" comment="This tag will be removed"> <i>...but this text will remain.</i> </div> Conditionally omitting a tag:: <b tal:omit-tag="not:bold">I may be bold.</b> The above example will omit the ``b`` tag if the variable ``bold`` is false. Creating ten paragraph tags, with no enclosing tag:: <span tal:repeat="n range(10)" tal:omit-tag=""> <p tal:content="n">1</p> </span> .. _tal_repeat: ``tal:repeat`` ^^^^^^^^^^^^^^ Repeats an element. Syntax ++++++ ``tal:repeat`` syntax:: argument ::= variable_name expression variable_name ::= Name Description +++++++++++ The ``tal:repeat`` statement replicates a sub-tree of your document once for each item in a sequence. The expression should evaluate to a sequence. If the sequence is empty, then the statement element is deleted, otherwise it is repeated for each value in the sequence. If the expression is ``default``, then the element is left unchanged, and no new variables are defined. The ``variable_name`` is used to define a local variable and a repeat variable. For each repetition, the local variable is set to the current sequence element, and the repeat variable is set to an iteration object. Repeat variables +++++++++++++++++ You use repeat variables to access information about the current repetition (such as the repeat index). The repeat variable has the same name as the local variable, but is only accessible through the built-in variable named ``repeat``. The following information is available from the repeat variable: ================== ============== Attribute Description ================== ============== ``index`` Repetition number, starting from zero. ``number`` Repetition number, starting from one. ``even`` True for even-indexed repetitions (0, 2, 4, ...). ``odd`` True for odd-indexed repetitions (1, 3, 5, ...). ``parity`` For odd-indexed repetitions, this is 'odd', else 'even'. ``start`` True for the starting repetition (index 0). ``end`` True for the ending, or final, repetition. ``length`` Length of the sequence, which will be the total number of repetitions. ``letter`` Repetition number as a lower-case letter: "a" - "z", "aa" - "az", "ba" - "bz", ..., "za" - "zz", "aaa" - "aaz", and so forth. ``Letter`` Upper-case version of *letter*. ``roman`` Repetition number as a lower-case roman numeral: "i", "ii", "iii", "iv", "v", etc. ``Roman`` Upper-case version of *roman*. ================== ============== You can access the contents of the repeat variable using either dictionary- or attribute-style access, e.g. ``repeat['item'].start`` or ``repeat.item.start``. .. note:: For legacy compatibility, the attributes ``odd``, ``even``, ``number``, ``letter``, ``Letter``, ``roman``, and ``Roman`` are callable (returning ``self``). .. note:: Earlier versions of this document, and the `Zope Page Templates Reference <https://zope.readthedocs.io/en/latest/zopebook/AppendixC.html#repeat-variables>`_, referred to ``first`` and ``last`` attributes for use with sorted sequences. These are not implemented in Chameleon or the Zope reference implementation zope.tales. Instead, you can use :func:`itertools.groupby`, as in the example below. Examples ++++++++ Iterating over a sequence of strings:: <p tal:repeat="txt ('one', 'two', 'three')"> <span tal:replace="txt" /> </p> Inserting a sequence of table rows, and using the repeat variable to number the rows:: <table> <tr tal:repeat="item here.cart"> <td tal:content="repeat.item.number">1</td> <td tal:content="item.description">Widget</td> <td tal:content="item.price">$1.50</td> </tr> </table> Nested repeats:: <table border="1"> <tr tal:repeat="row range(10)"> <td tal:repeat="column range(10)"> <span tal:define="x repeat.row.number; y repeat.column.number; z x * y" tal:replace="string:$x * $y = $z">1 * 1 = 1</span> </td> </tr> </table> Grouping objects by type, drawing a rule between elements of different types:: <div tal:repeat="(type,objects) list(map(lambda g: (g[0], list(g[1])), itertools.groupby(objects, key=lambda o: o.meta_type)))" tal:define="itertools import:itertools"> <h2 tal:content="type">Meta Type</h2> <p tal:repeat="object objects" tal:content="object.id">Object ID</p> <hr /> </div> .. caution:: It is important to fully realize the iterator produced by :func:`itertools.groupby`, as well as the iterator produced for each group, in the expression passed to ``tal:repeat``. This is because the implementation of certain repeat variables, such as ``length`` and ``end`` requires Chameleon to look ahead in the iterator, consuming it faster than is visible. The iterator returned by :func:`itertools.groupby` is shared among all of its subgroups, so without the full reification of all the iterators, incorrect results will be produced. ``tal:replace`` ^^^^^^^^^^^^^^^ Replaces an element. Syntax ++++++ ``tal:replace`` syntax:: argument ::= ['structure'] expression Description +++++++++++ The ``tal:replace`` statement replaces an element with dynamic content. It replaces the statement element with either text or a structure (unescaped markup). The body of the statement is an expression with an optional type prefix. The value of the expression is converted into an escaped string unless you provide the 'structure' prefix. Escaping consists of converting ``&`` to ``&amp;``, ``<`` to ``&lt;``, and ``>`` to ``&gt;``. .. note:: If the inserted object provides an ``__html__`` method, that method is called with the result inserted as structure. This feature is not implemented by ZPT. If the expression evaluates to ``None``, the element is simply removed. If the value is ``default``, then the element is left unchanged. Examples ++++++++ Inserting a title:: <span tal:replace="context.title">Title</span> Inserting HTML/XML:: <div tal:replace="structure table" /> .. _tales: Expressions (TALES) ------------------- The *Template Attribute Language Expression Syntax* (TALES) standard describes expressions that supply :ref:`tal` and :ref:`metal` with data. TALES is *one* possible expression syntax for these languages, but they are not bound to this definition. Similarly, TALES could be used in a context having nothing to do with TAL or METAL. TALES expressions are described below with any delimiter or quote markup from higher language layers removed. Here is the basic definition of TALES syntax:: Expression ::= [type_prefix ':'] String type_prefix ::= Name Here are some simple examples:: 1 + 2 None string:Hello, ${view.user_name} The optional *type prefix* determines the semantics and syntax of the *expression string* that follows it. A given implementation of TALES can define any number of expression types, with whatever syntax you like. It also determines which expression type is indicated by omitting the prefix. Types ~~~~~ These are the available TALES expression types: ============= ============== Prefix Description ============= ============== ``exists`` Evaluate the result inside an exception handler; if one of the exceptions ``AttributeError``, ``LookupError``, ``TypeError``, ``NameError``, or ``KeyError`` is raised during evaluation, the result is ``False``, otherwise ``True``. Note that the original result is discarded in any case. ``import`` Import a global symbol using dotted notation. ``load`` Load a template relative to the current template or absolute. ``not`` Negate the expression result ``python`` Evaluate a Python expression ``string`` Format a string ``structure`` Wraps the expression result as *structure*. ============= ============== .. note:: The default expression type is ``python``. .. warning:: The Zope reference engine defaults to a ``path`` expression type, which is closely tied to the Zope framework. This expression is not implemented in Chameleon (but it's available in a Zope framework compatibility package, `z3c.pt <http://pypi.python.org/pypi/z3c.pt>`_). There's a mechanism to allow fallback to alternative expressions, if one should fail (raise an exception). The pipe character ('|') is used to separate two expressions:: <div tal:define="page request.GET['page'] | 0"> This mechanism applies only to the ``python`` expression type, and by derivation ``string``. .. _tales_built_in_names: ``python`` ^^^^^^^^^^ Evaluates a Python expression. Syntax ++++++ Python expression syntax:: Any valid Python language expression Description +++++++++++ Python expressions are executed natively within the translated template source code. There is no built-in security apparatus. ``string`` ^^^^^^^^^^ Syntax ++++++ String expression syntax:: string_expression ::= ( plain_string | [ varsub ] )* varsub ::= ( '$' Variable ) | ( '${ Expression }' ) plain_string ::= ( '$$' | non_dollar )* non_dollar ::= any character except '$' Description +++++++++++ String expressions interpret the expression string as text. If no expression string is supplied the resulting string is *empty*. The string can contain variable substitutions of the form ``$name`` or ``${expression}``, where ``name`` is a variable name, and ``expression`` is a TALES-expression. The escaped string value of the expression is inserted into the string. .. note:: To prevent a ``$`` from being interpreted this way, it must be escaped as ``$$``. Using a backslash-escape is not supported. Examples ++++++++ Basic string formatting:: <span tal:replace="string:$this and $that"> Spam and Eggs </span> <p tal:content="string:${request.form['total']}"> total: 12 </p> Including a dollar sign:: <p tal:content="string:$$$cost"> cost: $42.00 </p> .. _import-expression: ``import`` ^^^^^^^^^^ Imports a module global. .. _structure-expression: ``structure`` ^^^^^^^^^^^^^ Wraps the expression result as *structure*: The replacement text is inserted into the document without escaping, allowing HTML/XML markup to be inserted. This can break your page if the text contains unanticipated markup (eg. text submitted via a web form), which is the reason that it is not the default. .. _load-expression: ``load`` ^^^^^^^^ Loads a template instance. Syntax ++++++ Load expression syntax:: Relative or absolute file path Description +++++++++++ The template will be loaded using the same template class as the calling template. Examples ++++++++ Loading a template and using it as a macro:: <div tal:define="master load: ../master.pt" metal:use-macro="master" /> Built-in names ~~~~~~~~~~~~~~ These are the names always available in the TALES expression namespace: - ``default`` - special value used to specify that existing text or attributes should not be replaced. See the documentation for individual TAL statements for details on how they interpret *default*. - ``repeat`` - the *repeat* variables; see :ref:`tal_repeat` for more information. - ``template`` - reference to the template which was first called; this symbol is carried over when using macros. - ``macros`` - reference to the macros dictionary that corresponds to the current template. .. _metal: Macros (METAL) -------------- The *Macro Expansion Template Attribute Language* (METAL) standard is a facility for HTML/XML macro preprocessing. It can be used in conjunction with or independently of TAL and TALES. Macros provide a way to define a chunk of presentation in one template, and share it in others, so that changes to the macro are immediately reflected in all of the places that share it. Additionally, macros are always fully expanded, even in a template's source text, so that the template appears very similar to its final rendering. A single Page Template can accommodate multiple macros. Namespace ~~~~~~~~~ The METAL namespace URI and recommended alias are currently defined as:: xmlns:metal="http://xml.zope.org/namespaces/metal" Just like the TAL namespace URI, this URI is not attached to a web page; it's just a unique identifier. This identifier must be used in all templates which use METAL. Note that elements that appear in a template with the METAL namespace are omitted from the output where they appear. This is useful when defining a macro:: <metal:block define-macro="hello"> ... </metal:block> In the example above the element is named `block` but any name can be used to the same effect as long as it is qualified with the METAL namespace. Statements ~~~~~~~~~~ METAL defines a number of statements: * ``metal:define-macro`` Define a macro. * ``metal:use-macro`` Use a macro. * ``metal:extend-macro`` Extend a macro. * ``metal:define-slot`` Define a macro customization point. * ``metal:fill-slot`` Customize a macro. Although METAL does not define the syntax of expression non-terminals, leaving that up to the implementation, a canonical expression syntax for use in METAL arguments is described in TALES Specification. ``define-macro`` ^^^^^^^^^^^^^^^^ Defines a macro. Syntax ++++++ ``metal:define-macro`` syntax:: argument ::= Name Description +++++++++++ The ``metal:define-macro`` statement defines a macro. The macro is named by the statement expression, and is defined as the element and its sub-tree. Examples ++++++++ Simple macro definition:: <p metal:define-macro="copyright"> Copyright 2011, <em>Foobar</em> Inc. </p> ``define-slot`` ^^^^^^^^^^^^^^^ Defines a macro customization point. Syntax ++++++ ``metal:define-slot`` syntax:: argument ::= Name Description +++++++++++ The ``metal:define-slot`` statement defines a macro customization point or *slot*. When a macro is used, its slots can be replaced, in order to customize the macro. Slot definitions provide default content for the slot. You will get the default slot contents if you decide not to customize the macro when using it. The ``metal:define-slot`` statement must be used inside a ``metal:define-macro`` statement. Slot names must be unique within a macro. Examples ++++++++ Simple macro with slot:: <p metal:define-macro="hello"> Hello <b metal:define-slot="name">World</b> </p> This example defines a macro with one slot named ``name``. When you use this macro you can customize the ``b`` element by filling the ``name`` slot. ``fill-slot`` ^^^^^^^^^^^^^ Customize a macro. Syntax ++++++ ``metal:fill-slot`` syntax:: argument ::= Name Description +++++++++++ The ``metal:fill-slot`` statement customizes a macro by replacing a *slot* in the macro with the statement element (and its content). The ``metal:fill-slot`` statement must be used inside a ``metal:use-macro`` statement. Slot names must be unique within a macro. If the named slot does not exist within the macro, the slot contents will be silently dropped. Examples ++++++++ Given this macro:: <p metal:define-macro="hello"> Hello <b metal:define-slot="name">World</b> </p> You can fill the ``name`` slot like so:: <p metal:use-macro="container['master.html'].macros.hello"> Hello <b metal:fill-slot="name">Kevin Bacon</b> </p> ``use-macro`` ^^^^^^^^^^^^^ Use a macro. Syntax ++++++ ``metal:use-macro`` syntax:: argument ::= expression Description +++++++++++ The ``metal:use-macro`` statement replaces the statement element with a macro. The statement expression describes a macro definition. The ``macroname`` variable will be bound to the defined name of the macro being used. .. note:: In Chameleon the expression may point to a template instance; in this case it will be rendered in its entirety. ``extend-macro`` ^^^^^^^^^^^^^^^^ Extends a macro. Syntax ++++++ ``metal:extend-macro`` syntax:: argument ::= expression Description +++++++++++ To extend an existing macro, choose a name for the macro and add a define-macro attribute to a document element with the name as the argument. Add an extend-macro attribute to the document element with an expression referencing the base macro as the argument. The extend-macro must be used in conjunction with define-macro, and must not be used with use-macro. The element's subtree is the macro body. Examples ++++++++ :: <div metal:define-macro="page-header" metal:extend-macro="standard_macros['page-header']"> <div metal:fill-slot="breadcrumbs"> You are here: <div metal:define-slot="breadcrumbs"/> </div> </div> .. _i18n: Translation (I18N) ------------------ Translation of template contents and attributes is supported via the ``i18n`` namespace and message objects. Messages ~~~~~~~~ The translation machinery defines a message as *any object* which is not a string or a number and which does not provide an ``__html__`` method. When any such object is inserted into the template, the translate function is invoked first to see if it needs translation. The result is always coerced to a native string before it's inserted into the template. Translation function ~~~~~~~~~~~~~~~~~~~~ The simplest way to hook into the translation machinery is to provide a translation function to the template constructor or at render-time. In either case it should be passed as the keyword argument ``translate``. The function has the following signature: .. code-block:: python def translate(msgid, domain=None, mapping=None, context=None, target_language=None, default=None): ... The result should be a string or ``None``. If another type of object is returned, it's automatically coerced into a string. If `zope.i18n <http://pypi.python.org/pypi/zope.i18n>`_ is available, the translation machinery defaults to using its translation function. Note that this function requires messages to conform to the message class from `zope.i18nmessageid <http://pypi.python.org/pypi/zope.i18nmessageid>`_; specifically, messages must have attributes ``domain``, ``mapping`` and ``default``. Example use: .. code-block:: python from zope.i18nmessageid import MessageFactory _ = MessageFactory("food") apple = _(u"Apple") There's currently no further support for other translation frameworks. Using Zope's translation framework ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The translation function from ``zope.i18n`` relies on *translation domains* to provide translations. These are components that are registered for some translation domain identifier and which implement a ``translate`` method that translates messages for that domain. .. note:: To register translation domain components, the Zope Component Architecture must be used (see `zope.component <http://pypi.python.org/pypi/zope.component>`_). The easiest way to configure translation domains is to use the the ``registerTranslations`` ZCML-directive; this requires the use of the `zope.configuration <http://pypi.python.org/pypi/zope.configuration>`_ package. This will set up translation domains and gettext catalogs automatically: .. code-block:: xml <configure xmlns="http://namespaces.zope.org/zope" xmlns:i18n="http://xml.zope.org/namespaces/i18n"> <i18n:registerTranslations directory="locales" /> </configure> The ``./locales`` directory must follow a particular directory structure: .. code-block:: bash ./locales/en/LC_MESSAGES ./locales/de/LC_MESSAGES ... In each of the ``LC_MESSAGES`` directories, one `GNU gettext <http://en.wikipedia.org/wiki/GNU_gettext>`_ file in the ``.po`` format must be present per translation domain: .. code-block:: po # ./locales/de/LC_MESSAGES/food.po msgid "" msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Apple" msgstr "Apfel" It may be necessary to compile the message catalog using the ``msgfmt`` utility. This will produce a ``.mo`` file. Translation domains without gettext ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following example demonstrates how to manually set up and configure a translation domain for which messages are provided directly:: from zope import component from zope.i18n.simpletranslationdomain import SimpleTranslationDomain food = SimpleTranslationDomain("food", { ('de', u'Apple'): u'Apfel', }) component.provideUtility(food, food.domain) An example of a custom translation domain class:: from zope import interface class TranslationDomain(object): interface.implements(ITranslationDomain) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): ... component.provideUtility(TranslationDomain(), name="custom") This approach can be used to integrate other translation catalog implementations. .. highlight:: xml Namespace ~~~~~~~~~ The ``i18n`` namespace URI and recommended prefix are currently defined as:: xmlns:i18n="http://xml.zope.org/namespaces/i18n" This is not a URL, but merely a unique identifier. Do not expect a browser to resolve it successfully. Statements ~~~~~~~~~~ The allowable ``i18n`` statements are: - ``i18n:translate`` - ``i18n:domain`` - ``i18n:context`` - ``i18n:source`` - ``i18n:target`` - ``i18n:name`` - ``i18n:attributes`` - ``i18n:data`` - ``i18n:comment`` - ``i18n:ignore`` - ``i18n:ignore-attributes`` ``i18n:translate`` ^^^^^^^^^^^^^^^^^^ This attribute is used to mark units of text for translation. If this attribute is specified with an empty string as the value, the message ID is computed from the content of the element bearing this attribute. Otherwise, the value of the element gives the message ID. ``i18n:domain`` ^^^^^^^^^^^^^^^ The ``i18n:domain`` attribute is used to specify the domain to be used to get the translation. If not specified, the translation services will use a default domain. The value of the attribute is used directly; it is not a TALES expression. ``i18n:context`` ^^^^^^^^^^^^^^^^ The ``i18n:context`` attribute is used to specify the context to be used to get the translation. If not specified, the translation services will use a default context. The context is generally use to distinguish identical texts in different context (because in a translation this may not be the case.) The value of the attribute is used literally; it is not an expression. ``i18n:source`` ^^^^^^^^^^^^^^^ The ``i18n:source`` attribute specifies the language of the text to be translated. The default is ``nothing``, which means we don't provide this information to the translation services. ``i18n:target`` ^^^^^^^^^^^^^^^ The ``i18n:target`` attribute specifies the language of the translation we want to get. If the value is ``default``, the language negotiation services will be used to choose the destination language. If the value is ``nothing``, no translation will be performed; this can be used to suppress translation within a larger translated unit. Any other value must be a language code. The attribute value is a TALES expression; the result of evaluating the expression is the language code or one of the reserved values. .. note:: ``i18n:target`` is primarily used for hints to text extraction tools and translation teams. If you had some text that should only be translated to e.g. German, then it probably shouldn't be wrapped in an ``i18n:translate`` span. ``i18n:name`` ^^^^^^^^^^^^^ Name the content of the current element for use in interpolation within translated content. This allows a replaceable component in content to be re-ordered by translation. For example:: <span i18n:translate=''> <span tal:replace='context.name' i18n:name='name' /> was born in <span tal:replace='context.country_of_birth' i18n:name='country' />. </span> would cause this text to be passed to the translation service:: "${name} was born in ${country}." ``i18n:attributes`` ^^^^^^^^^^^^^^^^^^^ This attribute will allow us to translate attributes of HTML tags, such as the ``alt`` attribute in the ``img`` tag. The ``i18n:attributes`` attribute specifies a list of attributes to be translated with optional message IDs for each; if multiple attribute names are given, they must be separated by semicolons. Message IDs used in this context must not include whitespace. Note that the value of the particular attributes come either from the HTML attribute value itself or from the data inserted by ``tal:attributes``. If an attribute is to be both computed using ``tal:attributes`` and translated, the translation service is passed the result of the TALES expression for that attribute. An example:: <img src="http://foo.com/logo" alt="Visit us" tal:attributes="alt context.greeting" i18n:attributes="alt" > In this example, we let ``tal:attributes`` set the value of the ``alt`` attribute to the text "Stop by for a visit!". This text will be passed to the translation service, which uses the result of language negotiation to translate "Stop by for a visit!" into the requested language. The example text in the template, "Visit us", will simply be discarded. Another example, with explicit message IDs:: <img src="../icons/uparrow.png" alt="Up" i18n:attributes="src up-arrow-icon; alt up-arrow-alttext" > Here, the message ID ``up-arrow-icon`` will be used to generate the link to an icon image file, and the message ID 'up-arrow-alttext' will be used for the "alt" text. ``i18n:data`` ^^^^^^^^^^^^^ Since TAL always returns strings, we need a way in ZPT to translate objects, one of the most obvious cases being ``datetime`` objects. The ``data`` attribute will allow us to specify such an object, and ``i18n:translate`` will provide us with a legal format string for that object. If ``data`` is used, ``i18n:translate`` must be used to give an explicit message ID, rather than relying on a message ID computed from the content. ``i18n:comment`` ^^^^^^^^^^^^^^^^ The ``i18n:comment`` attribute can be used to add extra comments for translators. It is not used by Chameleon for processing, but will be picked up by tools like `lingua <http://pypi.python.org/pypi/lingua>`_. An example:: <h3 i18n:comment="Header for the news section" i18n:translate="">News</h3> ``i18n:ignore`` ^^^^^^^^^^^^^^^ The ``i18n:ignore`` attribute can be used to inform translation extraction tools like `i18ndude <http://pypi.python.org/pypi/i18ndude>`_ to not give a warning/error on the given tag if there is no ``i18n:translate`` attribute. An example:: <h1 i18n:ignore="">News</h3> ``i18n:ignore-attributes`` ^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``i18n:ignore-attributes``, just like ``i18n:ignore`` is expected to be used by translation extraction tools like `i18ndude <http://pypi.python.org/pypi/i18ndude>`_. If ``i18n:ignore`` makes text within a tag to be ignored, ``i18n:ignore-attributes`` marks the given attributes as ignored. An example:: <a href="http://python.org" title="Python!" i18n:ignore-attributes="title">Python website</a> Relation with TAL processing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The attributes defined in the ``i18n`` namespace modify the behavior of the TAL interpreter for the ``tal:attributes``, ``tal:content``, ``tal:repeat``, and ``tal:replace`` attributes, but otherwise do not affect TAL processing. Since these attributes only affect TAL processing by causing translations to occur at specific times, using these with a TAL processor which does not support the ``i18n`` namespace degrades well; the structural expectations for a template which uses the ``i18n`` support is no different from those for a page which does not. The only difference is that translations will not be performed in a legacy processor. Relation with METAL processing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When using translation with METAL macros, the internationalization context is considered part of the specific documents that page components are retrieved from rather than part of the combined page. This makes the internationalization context lexical rather than dynamic, making it easier for a site builder to understand the behavior of each element with respect to internationalization. Let's look at an example to see what this means:: <html i18n:translate='' i18n:domain='EventsCalendar' metal:use-macro="container['master.html'].macros.thismonth"> <div metal:fill-slot='additional-notes'> <ol tal:condition="context.notes"> <li tal:repeat="note context.notes"> <tal:block tal:omit-tag="" tal:condition="note.heading"> <strong tal:content="note.heading"> Note heading goes here </strong> <br /> </tal:block> <span tal:replace="note/description"> Some longer explanation for the note goes here. </span> </li> </ol> </div> </html> And the macro source:: <html i18n:domain='CalendarService'> <div tal:replace='python:DateTime().Month()' i18n:translate=''>January</div> <!-- really hairy TAL code here ;-) --> <div define-slot="additional-notes"> Place for the application to add additional notes if desired. </div> </html> Note that the macro is using a different domain than the application (which it should be). With lexical scoping, no special markup needs to be applied to cause the slot-filler in the application to be part of the same domain as the rest of the application's page components. If dynamic scoping were used, the internationalization context would need to be re-established in the slot-filler. Extracting translatable message ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Translators use `PO files <http://www.gnu.org/software/hello/manual/gettext/PO-Files.html>`_ when translating messages. To create and update PO files you need to do two things: *extract* all messages from python and templates files and store them in a ``.pot`` file, and for each language *update* its ``.po`` file. Chameleon facilitates this by providing extractors for `Babel <http://babel.edgewall.org/>`_. To use this you need modify ``setup.py``. For example: .. code-block:: python from setuptools import setup setup(name="mypackage", install_requires = [ "Babel", ], message_extractors = { "src": [ ("**.py", "chameleon_python", None ), ("**.pt", "chameleon_xml", None ), ]}, ) This tells Babel to scan the ``src`` directory while using the ``chameleon_python`` extractor for all ``.py`` files and the ``chameleon_xml`` extractor for all ``.pt`` files. You can now use Babel to manage your PO files: .. code-block:: bash python setup.py extract_messages --output-file=i18n/mydomain.pot python setup.py update_catalog \ -l nl \ -i i18n/mydomain.pot \ -o i18n/nl/LC_MESSAGES/mydomain.po python setup.py compile_catalog \ --directory i18n --locale nl You can also configure default options in a ``setup.cfg`` file. For example:: [compile_catalog] domain = mydomain directory = i18n [extract_messages] copyright_holder = Acme Inc. output_file = i18n/mydomain.pot charset = UTF-8 [init_catalog] domain = mydomain input_file = i18n/mydomain.pot output_dir = i18n [update_catalog] domain = mydomain input_file = i18n/mydomain.pot output_dir = i18n previous = true You can now use the Babel commands directly:: python setup.py extract_messages python setup.py update_catalog python setup.py compile_catalog ${...} operator --------------- The ``${...}`` notation is short-hand for text insertion. The Python-expression inside the braces is evaluated and the result included in the output (all inserted text is escaped by default): .. code-block:: html <div id="section-${index + 1}"> ${content} </div> To escape this behavior, prefix the notation with a backslash character: ``\${...}``. Note that if an object implements the ``__html__`` method, the result of this method will be inserted as-is (without XML escaping). Code blocks ----------- The ``<?python ... ?>`` notation allows you to embed Python code in templates: .. code-block:: html <div> <?python numbers = map(str, range(1, 10)) ?> Please input a number from the range ${", ".join(numbers)}. </div> The scope of name assignments is up to the nearest macro definition, or the template, if macros are not used. Note that code blocks can span multiple line and start on the next line of where the processing instruction begins: .. code-block:: html <?python foo = [1, 2, 3] ?> You can use this to debug templates: .. code-block:: html <div> <?python import pdb; pdb.set_trace() ?> </div> Markup comments --------------- You can apply the "!" and "?" modifiers to change how comments are processed: Drop ``<!--! This comment will be dropped from output -->`` Verbatim ``<!--? This comment will be included verbatim -->`` That is, evaluation of ``${...}`` expressions is disabled if the comment opens with the "?" character. .. _new-features: Language extensions ------------------- Chameleon extends the *page template* language with a new expression types and language features. Some take inspiration from `Genshi <http://genshi.edgewall.org/>`_. *New expression types* The :ref:`structure <structure-expression>` expression wraps an expression result as *structure*:: <div>${structure: body.text}</div> The :ref:`import <import-expression>` expression imports module globals:: <div tal:define="compile import: re.compile"> ... </div> The :ref:`load <load-expression>` expression loads templates relative to the current template:: <div tal:define="compile load: main.pt"> ... </div> *Tuple unpacking* The ``tal:define`` and ``tal:repeat`` statements support tuple unpacking:: tal:define="(a, b, c) [1, 2, 3]" Extended `iterable unpacking <http://www.python.org/dev/peps/pep-3132/>`_ using the asterisk character is not currently supported (even for versions of Python that support it natively). *Dictionary lookup as fallback after attribute error* If attribute lookup (using the ``obj.<name>`` syntax) raises an ``AttributeError`` exception, a secondary lookup is attempted using dictionary lookup --- ``obj['<name>']``. Behind the scenes, this is done by rewriting all attribute-lookups to a custom lookup call: .. code-block:: python def lookup_attr(obj, key): try: return getattr(obj, key) except AttributeError as exc: try: get = obj.__getitem__ except AttributeError: raise exc try: return get(key) except KeyError: raise exc *Inline string substitution* In element attributes and in the text or tail of an element, string expression interpolation is available using the ``${...}`` syntax:: <span class="content-${item_type}"> ${title or item_id} </span> *Code blocks* Using ``<?python ... ?>`` notation, you can embed Python statements in your templates: .. code-block:: html <div> <?python numbers = map(str, range(1, 10)) ?> Please input a number from the range ${", ".join(numbers)}. </div> *Literal content* While the ``tal:content`` and ``tal:repeat`` attributes both support the ``structure`` keyword which inserts the content as a literal (without XML-escape), an object may also provide an ``__html__`` method to the same effect. The result of the method will be inserted as *structure*. *Switch statement* Two new attributes have been added: ``tal:switch`` and ``tal:case``. A case attribute works like a condition and only allows content if the value matches that of the nearest parent switch value. Incompatibilities and differences --------------------------------- There are a number of incompatibilities and differences between the Chameleon language implementation and the Zope reference implementation (ZPT): *Default expression* The default expression type is Python. *Template arguments* Arguments passed by keyword to the render- or call method are inserted directly into the template execution namespace. This is different from ZPT where these are only available through the ``options`` dictionary. Zope:: <div tal:content="options/title" /> Chameleon:: <div tal:content="title" /> *Special symbols* The ``CONTEXTS`` symbol is not available. The `z3c.pt <http://pypi.python.org/pypi/z3c.pt>`_ package works as a compatibility layer. The template classes in this package provide a implementation which is fully compatible with ZPT. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/docs/requirements.txt���������������������������������������������������������������0000664�0000000�0000000�00000000042�14604714122�0017670�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Chameleon>=4.4.0 sphinx-rtd-theme ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/pyproject.toml����������������������������������������������������������������������0000664�0000000�0000000�00000001613�14604714122�0016375�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[build-system] requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [tool.mypy] mypy_path = "$MYPY_CONFIG_FILE_DIR/src" # we may want to include tests eventually exclude = "/tests/" follow_imports = "silent" warn_redundant_casts = true warn_unused_configs = true warn_unused_ignores = true warn_return_any = true [[tool.mypy.overrides]] # strict config for fully typed modules and public API module = [ "chameleon.exc.*", "chameleon.utils.*", "chameleon.zpt.loader.*", "chameleon.zpt.template.*", ] disallow_any_unimported = true disallow_any_generics = true disallow_subclassing_any = true disallow_untyped_calls = true disallow_untyped_defs = true disallow_incomplete_defs = true disallow_untyped_decorators = true no_implicit_reexport = true strict_equality = true extra_checks = true [[tool.mypy.overrides]] module = ["zope.*"] ignore_missing_imports = true ���������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/setup.cfg���������������������������������������������������������������������������0000664�0000000�0000000�00000001067�14604714122�0015305�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [bdist_wheel] universal = 0 [flake8] doctests = 1 extend-select = TC1 [check-manifest] ignore = .editorconfig .meta.toml docs/_build/html/_sources/* [isort] force_single_line = True combine_as_imports = True sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER known_first_party = chameleon extra_standard_library = _typeshed, typing_extensions default_section = THIRDPARTY line_length = 79 lines_after_imports = 2 [tool:pytest] testpaths = src/chameleon �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/setup.py����������������������������������������������������������������������������0000664�0000000�0000000�00000004640�14604714122�0015176�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������__version__ = '4.5.4' import os from setuptools import find_packages from setuptools import setup from setuptools.command.test import test here = os.path.abspath(os.path.dirname(__file__)) try: README = open(os.path.join(here, 'README.rst')).read() CHANGES = open(os.path.join(here, 'CHANGES.rst')).read() except BaseException: # doesn't work under tox/pip README = '' CHANGES = '' install_requires = ['importlib-metadata;python_version<"3.10"'] class Benchmark(test): description = "Run benchmarks" def finalize_options(self): self.distribution.tests_require = [ 'zope.pagetemplate', 'zope.component', 'zope.i18n', 'zope.testing'] test.finalize_options(self) def run_tests(self): from chameleon import benchmark print("running benchmark...") benchmark.start() setup( name="Chameleon", version=__version__, description="Fast HTML/XML Template Compiler.", long_description="\n\n".join((README, CHANGES)), classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], author="Malthe Borch", author_email="mborch@gmail.com", url="https://chameleon.readthedocs.io", project_urls={ 'Documentation': 'https://chameleon.readthedocs.io', 'Issue Tracker': 'https://github.com/malthe/chameleon/issues', 'Sources': 'https://github.com/malthe/chameleon', }, license='BSD-like (http://repoze.org/license.html)', packages=find_packages('src'), package_dir={'': 'src'}, include_package_data=True, package_data={ 'chameleon': [ 'py.typed', ], }, python_requires='>=3.9', install_requires=install_requires, extras_require={ 'docs': { 'Sphinx', 'sphinx_rtd_theme', }, }, zip_safe=False, cmdclass={ 'benchmark': Benchmark, } ) ������������������������������������������������������������������������������������������������chameleon-4.5.4/src/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14604714122�0014247�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�14604714122�0016202�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/__init__.py�����������������������������������������������������������0000664�0000000�0000000�00000000735�14604714122�0020320�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from chameleon.exc import TemplateError from chameleon.zpt.loader import TemplateLoader as PageTemplateLoader from chameleon.zpt.template import PageTemplate from chameleon.zpt.template import PageTemplateFile from chameleon.zpt.template import PageTextTemplate from chameleon.zpt.template import PageTextTemplateFile __all__ = ( 'TemplateError', 'PageTemplateLoader', 'PageTemplate', 'PageTemplateFile', 'PageTextTemplate', 'PageTextTemplateFile', ) �����������������������������������chameleon-4.5.4/src/chameleon/astutil.py������������������������������������������������������������0000664�0000000�0000000�00000010631�14604714122�0020242�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Support code for generating code from abstract syntax trees.""" from __future__ import annotations import ast from copy import deepcopy from typing import TYPE_CHECKING from typing import Any from typing import ClassVar if TYPE_CHECKING: from collections.abc import Callable from collections.abc import Hashable from typing import Optional from chameleon.tokenize import Token _NodeTransform = Callable[[ast.AST], Optional[ast.AST]] __docformat__ = 'restructuredtext en' def parse(source, mode: str = 'eval') -> ast.AST: return compile(source, '', mode, ast.PyCF_ONLY_AST) def load(name: str) -> ast.Name: return ast.Name(id=name, ctx=ast.Load()) def store(name: str) -> ast.Name: return ast.Name(id=name, ctx=ast.Store()) def param(name: str) -> ast.Name: return ast.Name(id=name, ctx=ast.Param()) def subscript( name: str, value: ast.expr, ctx: ast.expr_context ) -> ast.Subscript: return ast.Subscript( value=value, slice=ast.Index(value=ast.Str(s=name)), ctx=ctx, ) class Node(ast.AST): """AST baseclass that gives us a convenient initialization method. We explicitly declare and use the ``_fields`` attribute.""" _fields: ClassVar[tuple[str, ...]] = () def __init__(self, *args: Any, **kwargs: Any) -> None: assert isinstance(self._fields, tuple) self.__dict__.update(kwargs) for name, value in zip(self._fields, args): setattr(self, name, value) def __repr__(self) -> str: """Poor man's single-line pretty printer.""" name = type(self).__name__ return '<{}{} at {:x}>'.format( name, "".join( " {}={!r}".format(name, getattr(self, name, "\"?\"")) for name in self._fields ), id(self), ) class Builtin(Node): """Represents a Python builtin. Used when a builtin is used internally by the compiler, to avoid clashing with a user assignment (e.g. ``help`` is a builtin, but also commonly assigned in templates). """ _fields = "id", "ctx" id: str ctx = ast.Load() class Symbol(Node): """Represents an importable symbol.""" _fields = "value", # Apart from a few builtins this should be type[Any] value: type[Any] | Hashable class Static(Node): """Represents a static value.""" _fields = "value", "name" value: ast.expr name: str | None = None class Comment(Node): _fields = "text", text: str class TokenRef(Node): """Represents a source-code token reference.""" _fields = "token", token: Token class NodeTransformerBase(ast.NodeTransformer): def __init__(self, transform: _NodeTransform): self.transform = transform def apply_transform(self, node: ast.AST) -> ast.AST: result = self.transform(node) if result is not None: return result return node class NameLookupRewriteVisitor(NodeTransformerBase): def __init__(self, transform: _NodeTransform): self.scopes: list[set[str]] = [set()] super().__init__(transform) def __call__(self, node: ast.AST) -> ast.AST: clone = deepcopy(node) return self.visit(clone) # type: ignore[no-any-return] def visit_arg(self, node: ast.arg) -> ast.AST: scope = self.scopes[-1] scope.add(node.arg) return node def visit_Name(self, node: ast.Name) -> ast.AST: scope = self.scopes[-1] if isinstance(node.ctx, ast.Param): scope.add(node.id) return node if node.id not in scope: return self.apply_transform(node) return node def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.AST: self.scopes[-1].add(node.name) return super().generic_visit(node) def visit_alias(self, node: ast.alias) -> ast.AST: name = node.asname if node.asname is not None else node.name self.scopes[-1].add(name) return super().generic_visit(node) def visit_Lambda(self, node: ast.Lambda) -> ast.AST: self.scopes.append(set()) try: return super().generic_visit(node) finally: self.scopes.pop() class ItemLookupOnAttributeErrorVisitor(NodeTransformerBase): def visit_Attribute(self, node: ast.Attribute) -> ast.AST: transformed = self.apply_transform(node) return self.generic_visit(transformed) �������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/benchmark.py����������������������������������������������������������0000664�0000000�0000000�00000035420�14604714122�0020512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import os import re import time import unittest from itertools import chain re_amp = re.compile(r'&(?!([A-Za-z]+|#[0-9]+);)') BIGTABLE_ZPT = """\ <table xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal"> <tr tal:repeat="row python: options['table']"> <td tal:repeat="c python: row.values()"> <span tal:define="d python: c + 1" tal:attributes="class python: 'column-' + str(d)" tal:content="python: d" /> </td> </tr> </table>""" MANY_STRINGS_ZPT = """\ <table xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal"> <tr tal:repeat="i python: range(1000)"> <td tal:content="string: number ${i}" /> </tr> </table> """ HELLO_WORLD_ZPT = """\ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal"> <body> <h1>Hello, world!</h1> </body> </html> """ I18N_ZPT = """\ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:i18n="http://xml.zope.org/namespaces/i18n"> <body> <div tal:repeat="i python: range(10)"> <div i18n:translate=""> Hello world! </div> <div i18n:translate="hello_world"> Hello world! </div> <div i18n:translate=""> <sup>Hello world!</sup> </div> </div> </body> </html> """ def benchmark(title): def decorator(f): def wrapper(*args): print( "==========================\n " "%s\n==========================" % title) return f(*args) return wrapper return decorator def timing(func, *args, **kwargs): t1 = t2 = time.time() i = 0 while t2 - t1 < 3: func(**kwargs) func(**kwargs) func(**kwargs) func(**kwargs) i += 4 t2 = time.time() return float(10 * (t2 - t1)) / i START = 0 END = 1 TAG = 2 def yield_tokens(table=None): index = [] tag = index.append _re_amp = re_amp tag(START) yield "<", "html", "", ">\n" for r in table: tag(START) yield "<", "tr", "", ">\n" for c in r.values(): d = c + 1 tag(START) yield "<", "td", "", ">\n" _tmp5 = d if not isinstance(_tmp5, str): _tmp5 = str(_tmp5) if ('&' in _tmp5): if (';' in _tmp5): _tmp5 = _re_amp.sub('&', _tmp5) else: _tmp5 = _tmp5.replace('&', '&') if ('<' in _tmp5): _tmp5 = _tmp5.replace('<', '<') if ('>' in _tmp5): _tmp5 = _tmp5.replace('>', '>') if ('"' in _tmp5): _tmp5 = _tmp5.replace('"', '"') _tmp5 = "column-%s" % _tmp5 _tmp = d if (_tmp.__class__ not in (str, int, float, )): raise if (_tmp is not None): if not isinstance(_tmp, str): _tmp = str(_tmp) if ('&' in _tmp): if (';' in _tmp): _tmp = _re_amp.sub('&', _tmp) else: _tmp = _tmp.replace('&', '&') if ('<' in _tmp): _tmp = _tmp.replace('<', '<') if ('>' in _tmp): _tmp = _tmp.replace('>', '>') tag(START) t = ["classicism"] yield "<", "span", " ", t[0], '="', _tmp5, '"', ">\n" tag(END) yield "</", "span", ">\n" tag(END) yield "</", "td", ">\n" tag(END) yield "</", "tr", ">\n" tag(END) yield "</", "html", ">\n" def yield_tokens_dict_version(**kwargs): index = [] tag = index.append _re_amp = re_amp tag(START) yield "<", "html", "", ">\n" for r in kwargs['table']: kwargs['r'] = r tag(START) yield "<", "tr", "", ">\n" for c in kwargs['r'].values(): kwargs['d'] = c + 1 tag(START) yield "<", "td", "", ">\n" _tmp5 = kwargs['d'] if not isinstance(_tmp5, str): _tmp5 = str(_tmp5) if ('&' in _tmp5): if (';' in _tmp5): _tmp5 = _re_amp.sub('&', _tmp5) else: _tmp5 = _tmp5.replace('&', '&') if ('<' in _tmp5): _tmp5 = _tmp5.replace('<', '<') if ('>' in _tmp5): _tmp5 = _tmp5.replace('>', '>') if ('"' in _tmp5): _tmp5 = _tmp5.replace('"', '"') _tmp5 = "column-%s" % _tmp5 _tmp = kwargs['d'] if (_tmp.__class__ not in (str, int, float, )): raise if (_tmp is not None): if not isinstance(_tmp, str): _tmp = str(_tmp) if ('&' in _tmp): if (';' in _tmp): _tmp = _re_amp.sub('&', _tmp) else: _tmp = _tmp.replace('&', '&') if ('<' in _tmp): _tmp = _tmp.replace('<', '<') if ('>' in _tmp): _tmp = _tmp.replace('>', '>') tag(START) t = ["classicism"] yield "<", "span", " ", t[0], '="', _tmp5, '"', ">\n" tag(END) yield "</", "span", ">\n" tag(END) yield "</", "td", ">\n" tag(END) yield "</", "tr", ">\n" tag(END) yield "</", "html", ">\n" def yield_stream(table=None): _re_amp = re_amp yield START, ("html", "", "\n"), None for r in table: yield START, ("tr", "", "\n"), None for c in r.values(): d = c + 1 yield START, ("td", "", "\n"), None _tmp5 = d if not isinstance(_tmp5, str): _tmp5 = str(_tmp5) if ('&' in _tmp5): if (';' in _tmp5): _tmp5 = _re_amp.sub('&', _tmp5) else: _tmp5 = _tmp5.replace('&', '&') if ('<' in _tmp5): _tmp5 = _tmp5.replace('<', '<') if ('>' in _tmp5): _tmp5 = _tmp5.replace('>', '>') if ('"' in _tmp5): _tmp5 = _tmp5.replace('"', '"') _tmp5 = "column-%s" % _tmp5 _tmp = d if (_tmp.__class__ not in (str, int, float, )): raise if (_tmp is not None): if not isinstance(_tmp, str): _tmp = str(_tmp) if ('&' in _tmp): if (';' in _tmp): _tmp = _re_amp.sub('&', _tmp) else: _tmp = _tmp.replace('&', '&') if ('<' in _tmp): _tmp = _tmp.replace('<', '<') if ('>' in _tmp): _tmp = _tmp.replace('>', '>') yield START, ("span", "", _tmp, " ", "class", _tmp5), None yield END, ("span", "", "\n"), None yield END, ("td", "", "\n"), None yield END, ("tr", "", "\n"), None yield END, ("html", "", "\n"), None def bigtable_python_tokens(table=None, renderer=None): iterable = renderer(table=table) stream = chain(*iterable) return "".join(stream) def bigtable_python_stream(table=None, renderer=None): stream = renderer(table=table) return "".join(stream_output(stream)) def bigtable_python_stream_with_filter(table=None, renderer=None): stream = renderer(table=table) return "".join(stream_output(uppercase_filter(stream))) def uppercase_filter(stream): for kind, data, pos in stream: if kind is START: data = (data[0], data[1], data[2].upper(),) + data[3:] elif kind is END: data = (data[0], data[1], data[2].upper()) elif kind is TAG: raise NotImplementedError yield kind, data, pos def stream_output(stream): for kind, data, pos in stream: if kind is START: tag = data[0] yield "<%s" % tag l_ = len(data) # optimize for common cases if l_ == 3: pass elif l_ == 6: yield '{}{}="{}"'.format(data[3], data[4], data[5]) else: i = 3 while i < l_: yield '{}{}="{}"'.format(data[i], data[i + 1], data[i + 2]) i += 3 yield "{}>{}".format(data[1], data[2]) elif kind is END: yield "</%s%s>%s" % data elif kind is TAG: raise NotImplementedError class Benchmarks(unittest.TestCase): table = [dict(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10) for x in range(1000)] def setUp(self): # set up i18n component from zope.i18n import translate from zope.i18n.interfaces import INegotiator from zope.i18n.interfaces import ITranslationDomain from zope.i18n.negotiator import Negotiator from zope.i18n.simpletranslationdomain import SimpleTranslationDomain from zope.i18n.tests.test_negotiator import Env from zope.tales.tales import Context self.env = Env(('klingon', 'da', 'en', 'fr', 'no')) class ZopeI18NContext(Context): def translate(self, msgid, domain=None, context=None, mapping=None, default=None): context = self.vars['options']['env'] return translate(msgid, domain, mapping, context=context, default=default) def _getContext(self, contexts=None, **kwcontexts): if contexts is not None: if kwcontexts: kwcontexts.update(contexts) else: kwcontexts = contexts return ZopeI18NContext(self, kwcontexts) def _pt_getEngineContext(namespace): self = namespace['template'] engine = self.pt_getEngine() return _getContext(engine, namespace) import zope.component zope.component.provideUtility(Negotiator(), INegotiator) catalog = SimpleTranslationDomain('domain') zope.component.provideUtility(catalog, ITranslationDomain, 'domain') self.files = os.path.abspath(os.path.join(__file__, '..', 'input')) @staticmethod def _chameleon(body, **kwargs): from chameleon.zpt.template import PageTemplate return PageTemplate(body, **kwargs) @staticmethod def _zope(body): from zope.pagetemplate.pagetemplatefile import PageTemplate template = PageTemplate() template.pt_edit(body, 'text/xhtml') return template @benchmark("BIGTABLE [python]") def test_bigtable(self): options = {'table': self.table} t_chameleon = timing(self._chameleon(BIGTABLE_ZPT), options=options) print("chameleon: %7.2f" % t_chameleon) t_chameleon_utf8 = timing( self._chameleon(BIGTABLE_ZPT, encoding='utf-8'), options=options) print("chameleon (utf-8): %7.2f" % t_chameleon_utf8) t_tokens = timing( bigtable_python_tokens, table=self.table, renderer=yield_tokens) print("token: %7.2f" % t_tokens) t_tokens_dict_version = timing( bigtable_python_tokens, table=self.table, renderer=yield_tokens_dict_version) print("token (dict): %7.2f" % t_tokens_dict_version) t_stream = timing( bigtable_python_stream, table=self.table, renderer=yield_stream) print("stream: %7.2f" % t_stream) t_zope = timing(self._zope(BIGTABLE_ZPT), table=self.table) print("zope.pagetemplate: %7.2f" % t_zope) print(" %7.1fX" % (t_zope / t_chameleon)) print("--------------------------") print("check: %d vs %d" % ( len(self._chameleon(BIGTABLE_ZPT)(options=options)), len(self._zope(BIGTABLE_ZPT)(table=self.table)))) print("--------------------------") @benchmark("MANY STRINGS [python]") def test_many_strings(self): t_chameleon = timing(self._chameleon(MANY_STRINGS_ZPT)) print("chameleon: %7.2f" % t_chameleon) t_zope = timing(self._zope(MANY_STRINGS_ZPT)) print("zope.pagetemplate: %7.2f" % t_zope) print(" %7.1fX" % (t_zope / t_chameleon)) print("--------------------------") print("check: %d vs %d" % ( len(self._chameleon(MANY_STRINGS_ZPT)()), len(self._zope(MANY_STRINGS_ZPT)()))) print("--------------------------") @benchmark("HELLO WORLD") def test_hello_world(self): t_chameleon = timing(self._chameleon(HELLO_WORLD_ZPT)) * 1000 print("chameleon: %7.2f" % t_chameleon) t_zope = timing(self._zope(HELLO_WORLD_ZPT)) * 1000 print("zope.pagetemplate: %7.2f" % t_zope) print(" %7.1fX" % (t_zope / t_chameleon)) print("--------------------------") print("check: %d vs %d" % ( len(self._chameleon(HELLO_WORLD_ZPT)()), len(self._zope(HELLO_WORLD_ZPT)()))) print("--------------------------") @benchmark("I18N") def test_i18n(self): from zope.i18n import translate t_chameleon = timing( self._chameleon(I18N_ZPT), translate=translate, language="klingon") * 1000 print("chameleon: %7.2f" % t_chameleon) t_zope = timing(self._zope(I18N_ZPT), env=self.env) * 1000 print("zope.pagetemplate: %7.2f" % t_zope) print(" %7.1fX" % (t_zope / t_chameleon)) @benchmark("COMPILATION") def test_compilation(self): template = self._chameleon(HELLO_WORLD_ZPT) def chameleon_cook_and_render(template=template): template.cook(HELLO_WORLD_ZPT) template() t_chameleon = timing(chameleon_cook_and_render) * 1000 print("chameleon: %7.2f" % t_chameleon) template = self._zope(HELLO_WORLD_ZPT) def zope_cook_and_render(templte=template): template._cook() template() t_zope = timing(zope_cook_and_render) * 1000 print("zope.pagetemplate: %7.2f" % t_zope) print(" %0.3fX" % (t_zope / t_chameleon)) def start(): result = unittest.TestResult() test = unittest.defaultTestLoader.loadTestsFromTestCase(Benchmarks) test.run(result) for error in result.errors: print("Error in %s...\n" % error[0]) print(error[1]) for failure in result.failures: print("Failure in %s...\n" % failure[0]) print(failure[1]) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/codegen.py������������������������������������������������������������0000664�0000000�0000000�00000014325�14604714122�0020165�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import builtins import re import textwrap import types from ast import AST from ast import Assign from ast import Constant from ast import Expr from ast import FunctionDef from ast import Import from ast import ImportFrom from ast import Module from ast import NodeTransformer from ast import alias from ast import unparse from typing import TYPE_CHECKING from typing import Any from chameleon.astutil import Builtin from chameleon.astutil import Symbol from chameleon.astutil import load from chameleon.astutil import parse from chameleon.astutil import store from chameleon.exc import CompilationError if TYPE_CHECKING: import ast from collections.abc import Hashable from chameleon.astutil import Comment from chameleon.astutil import Static reverse_builtin_map: dict[type[Any] | Hashable, str] = {} for name, value in builtins.__dict__.items(): try: hash(value) except TypeError: continue reverse_builtin_map[value] = name def template( source, mode='exec', is_func: bool = False, func_args=(), func_defaults=(), **kw, ): def wrapper(*vargs, **kwargs): symbols = dict(zip(args, vargs + defaults)) symbols.update(kwargs) class Transformer(NodeTransformer): def visit_FunctionDef(self, node) -> AST: name = symbols.get(node.name, self) if name is self: return self.generic_visit(node) return FunctionDef( name=name, args=node.args, body=list(map(self.visit, node.body)), decorator_list=getattr(node, "decorator_list", []), lineno=None, ) def visit_Name(self, node: ast.Name) -> AST: value = symbols.get(node.id, self) if value is self: if node.id == 'None' or \ getattr(builtins, node.id, None) is not None: return Builtin(node.id) return node if isinstance(value, type) or value in reverse_builtin_map: name = reverse_builtin_map.get(value) if name is not None: return Builtin(name) return Symbol(value) if isinstance(value, str): value = load(value) return value # type: ignore[no-any-return] expr = parse(textwrap.dedent(source), mode=mode) Transformer().visit(expr) return expr.body assert isinstance(source, str) defaults = func_defaults args = func_args if is_func: return wrapper else: return wrapper(**kw) class TemplateCodeGenerator(NodeTransformer): """Generate code from AST tree. The syntax tree has been extended with internal nodes. We first transform the tree to process the internal nodes before generating the code string. """ names = () imports: dict[type[Any] | Hashable, ast.Name] def __init__(self, tree): self.comments = [] self.defines = {} self.imports = {} # Run transform. tree = self.visit(tree) # Generate code. code = unparse(tree) # Fix-up comments. comments = iter(self.comments) code = re.sub( r'^(\s*)\.\.\.$', lambda m: "\n".join( (m.group(1) + "#" + line) for line in next(comments).replace("\r", "\n").split("\n") ), code, flags=re.MULTILINE ) self.code = code def define(self, name, node): assert node is not None value = self.defines.get(name) if value is node: pass elif value is None: self.defines[name] = node else: raise CompilationError( "Duplicate symbol name for define.", name) return load(name) def require(self, value: type[Any] | Hashable) -> ast.Name: node = self.imports.get(value) if node is None: # we come up with a unique symbol based on the class name name = ( "_%s" % getattr(value, '__name__', str(value)).rsplit('.', 1)[-1] ) node = load(name) self.imports[value] = store(node.id) return node def visit_Module(self, module: Module) -> AST: assert isinstance(module, Module) module = super().generic_visit(module) # type: ignore[assignment] preamble: list[AST] = [] for name, node in self.defines.items(): assignment = Assign(targets=[store(name)], value=node, lineno=None) preamble.append(self.visit(assignment)) imports: list[AST] = [] for value, node in self.imports.items(): stmt: AST if isinstance(value, types.ModuleType): stmt = Import( names=[alias(name=value.__name__, asname=node.id)]) elif hasattr(value, '__name__'): path = reverse_builtin_map.get(value) if path is None: path = value.__module__ name = value.__name__ stmt = ImportFrom( module=path, names=[alias(name=name, asname=node.id)], level=0, ) else: raise TypeError(value) imports.append(stmt) return Module(imports + preamble + module.body, ()) def visit_Comment(self, node: Comment) -> AST: self.comments.append(node.text) return Expr(Constant(...)) def visit_Builtin(self, node: Builtin) -> AST: name = load(node.id) return self.visit(name) # type: ignore[no-any-return] def visit_Symbol(self, node: Symbol) -> AST: return self.require(node.value) def visit_Static(self, node: Static) -> AST: if node.name is None: name = "_static_%s" % str(id(node.value)).replace('-', '_') else: name = node.name node = self.define(name, node.value) return self.visit(node) # type: ignore[no-any-return] �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/compiler.py�����������������������������������������������������������0000664�0000000�0000000�00000164044�14604714122�0020377�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import ast import builtins import collections import functools import itertools import logging import pickle import re import sys import textwrap import threading from chameleon.astutil import Builtin from chameleon.astutil import Comment from chameleon.astutil import NameLookupRewriteVisitor from chameleon.astutil import Node from chameleon.astutil import Static from chameleon.astutil import Symbol from chameleon.astutil import TokenRef from chameleon.astutil import load from chameleon.astutil import param from chameleon.astutil import store from chameleon.astutil import subscript from chameleon.codegen import TemplateCodeGenerator from chameleon.codegen import template from chameleon.exc import ExpressionError from chameleon.exc import TranslationError from chameleon.i18n import simple_translate from chameleon.nodes import And from chameleon.nodes import Assignment from chameleon.nodes import Context from chameleon.nodes import Equals from chameleon.nodes import Is from chameleon.nodes import IsNot from chameleon.nodes import Logical from chameleon.nodes import Module from chameleon.nodes import Substitution from chameleon.nodes import Text from chameleon.nodes import Value from chameleon.parser import groupdict from chameleon.tal import NAME from chameleon.tal import ErrorInfo from chameleon.tokenize import Token from chameleon.utils import ListDictProxy from chameleon.utils import char2entity from chameleon.utils import decode_htmlentities from chameleon.utils import join from chameleon.utils import safe_native log = logging.getLogger('chameleon.compiler') # Disallowing the use of the following symbols to avoid misunderstandings. COMPILER_INTERNALS_OR_DISALLOWED = { "econtext", "rcontext", } RE_MANGLE = re.compile(r'[^\w_]') RE_NAME = re.compile('^%s$' % NAME) def identifier(prefix: str, suffix: str | None = None) -> str: return "__{}_{}".format(prefix, mangle(suffix or id(prefix))) def mangle(string: int | str) -> str: return RE_MANGLE.sub('_', str(string)).replace('\n', '').replace('-', '_') def load_econtext(name): return template("getname(KEY)", KEY=ast.Str(s=name), mode="eval") def store_econtext(name: object) -> ast.Subscript: name = str(name) return subscript(name, load("econtext"), ast.Store()) def store_rcontext(name: object) -> ast.Subscript: name = str(name) return subscript(name, load("rcontext"), ast.Store()) def eval_token(token): try: line, column = token.location except AttributeError: line, column = 0, 0 string = safe_native(token) return template( "(string, line, col)", string=ast.Str(s=string), line=ast.Num(n=line), col=ast.Num(n=column), mode="eval" ) def indent(s: str | None) -> str: return textwrap.indent(s, " ") if s else "" emit_node_if_non_trivial = template(is_func=True, func_args=('node',), source=r""" if node is not None: __append(node) """) emit_bool = template(is_func=True, func_args=('target', 's', 'default_marker', 'default'), func_defaults=(None, None), source=r""" if target is default_marker: target = default elif target: target = s else: target = None""") emit_convert = template(is_func=True, func_args=('target', 'encoded', 'str', 'type', 'default_marker', 'default'), func_defaults=(bytes, str, type, None), source=r""" if target is None: pass elif target is default_marker: target = default else: __tt = type(target) if __tt is encoded: target = decode(target) elif __tt is not str: if __tt is int or __tt is float: target = str(target) else: __markup = getattr(target, "__html__", None) if __markup is None: __converted = translate( target, domain=__i18n_domain, context=__i18n_context, target_language=target_language ) target = str(target) \ if target is __converted \ else __converted else: target = __markup()""") emit_func_convert = template( is_func=True, func_args=( 'func', 'encoded', 'str', 'type'), func_defaults=( bytes, str, type), source=r""" def func(target): if target is None: return __tt = type(target) if __tt is encoded: target = decode(target) elif __tt is not str: if __tt is int or __tt is float: target = str(target) else: __markup = getattr(target, "__html__", None) if __markup is None: __converted = translate( target, domain=__i18n_domain, context=__i18n_context, target_language=target_language ) target = str(target) \ if target is __converted \ else __converted else: target = __markup() return target""" ) emit_translate = template(is_func=True, func_args=('target', 'msgid', 'default'), func_defaults=(None,), source=r""" target = translate( msgid, default=default, domain=__i18n_domain, context=__i18n_context, target_language=target_language )""") emit_func_convert_and_escape = template( is_func=True, func_args=('func', 'str', 'type', 'encoded'), func_defaults=(str, type, bytes,), source=r""" def func(target, quote, quote_entity, default, default_marker): if target is None: return if target is default_marker: return default __tt = type(target) if __tt is encoded: target = decode(target) elif __tt is not str: if __tt is int or __tt is float: return str(target) __markup = getattr(target, "__html__", None) if __markup is None: __converted = translate( target, domain=__i18n_domain, context=__i18n_context, target_language=target_language ) target = str(target) if target is __converted \ else __converted else: return __markup() if target is not None: try: escape = __re_needs_escape(target) is not None except TypeError: pass else: if escape: # Character escape if '&' in target: target = target.replace('&', '&') if '<' in target: target = target.replace('<', '<') if '>' in target: target = target.replace('>', '>') if quote is not None and quote in target: target = target.replace(quote, quote_entity) return target""") class EmitText(Node): """Append text to output.""" _fields = "s", class TranslationContext(Node): """Set a local output context. This is used for the translation machinery. """ _fields = "body", "append", "stream" body = None append = None stream = None class Interpolator: braces_required_regex = re.compile( r'\$({(?P<expression>.*)})', re.DOTALL ) braces_optional_regex = re.compile( r'\$({(?P<expression>.*)}|(?P<variable>[A-Za-z][A-Za-z0-9_]*))', re.DOTALL, ) def __init__( self, expression, braces_required, translate: bool = False, decode_htmlentities: bool = False, ) -> None: self.expression = expression self.regex = ( self.braces_required_regex if braces_required else self.braces_optional_regex ) self.translate = translate self.decode_htmlentities = decode_htmlentities def __call__(self, name, engine): """The strategy is to find possible expression strings and call the ``validate`` function of the parser to validate. For every possible starting point, the longest possible expression is tried first, then the second longest and so forth. Example 1: ${'expressions use the ${<expression>} format'} The entire expression is attempted first and it is also the only one that validates. Example 2: ${'Hello'} ${'world!'} Validation of the longest possible expression (the entire string) will fail, while the second round of attempts, ``${'Hello'}`` and ``${'world!'}`` respectively, validate. """ body = [] nodes = [] text = self.expression expr_map = {} translate = self.translate while text: matched = text m = self.regex.search(matched) if m is None: text = text.replace('$$', '$') nodes.append(ast.Str(s=text)) break part = text[:m.start()] text = text[m.start():] if part: i = 0 length = len(part) while i < length and part[-i - 1] == '$': i += 1 skip = i & 1 part = part.replace('$$', '$') node = ast.Str(s=part) nodes.append(node) if skip: text = text[1:] continue if not body: target = name else: target = store("%s_%d" % (name.id, text.pos)) while True: d = groupdict(m, matched) string = d["expression"] or d.get("variable") or "" if self.decode_htmlentities: string = decode_htmlentities(string) if string: try: compiler = engine.parse(string) body += compiler.assign_text(target) except ExpressionError: matched = matched[m.start():m.end() - 1] m = self.regex.search(matched) if m is None: raise continue else: s = m.group() assign = ast.Assign(targets=[target], value=ast.Str(s=s)) body += [assign] break # If one or more expressions are not simple names, we # disable translation. if RE_NAME.match(string) is None: translate = False # if this is the first expression, use the provided # assignment name; otherwise, generate one (here based # on the string position) node = load(target.id) nodes.append(node) expr_map[node] = safe_native(string) text = text[len(m.group()):] if len(nodes) == 1: target = nodes[0] if translate and isinstance(target, ast.Str): target = template( "translate(msgid, domain=__i18n_domain, context=__i18n_context, target_language=target_language)", # noqa: E501 line too long msgid=target, mode="eval", ) else: if translate: formatting_string = "" keys = [] values = [] for node in nodes: if isinstance(node, ast.Str): formatting_string += node.s else: string = expr_map[node] formatting_string += "${%s}" % string keys.append(ast.Str(s=string)) values.append(node) target = template( "translate(msgid, mapping=mapping, domain=__i18n_domain, context=__i18n_context, target_language=target_language)", # noqa: E501 line too long msgid=ast.Str( s=formatting_string), mapping=ast.Dict( keys=keys, values=values), mode="eval") else: nodes = [ node if isinstance(node, ast.Str) else template( "NODE if NODE is not None else ''", NODE=node, mode="eval" ) for node in nodes ] target = ast.BinOp( left=ast.Str(s="%s" * len(nodes)), op=ast.Mod(), right=ast.Tuple(elts=nodes, ctx=ast.Load())) body += [ast.Assign(targets=[name], value=target)] return body class ExpressionEngine: """Expression engine. This test demonstrates how to configure and invoke the engine. >>> from chameleon import tales >>> parser = tales.ExpressionParser({ ... 'python': tales.PythonExpr, ... 'not': tales.NotExpr, ... 'exists': tales.ExistsExpr, ... 'string': tales.StringExpr, ... }, 'python') >>> engine = ExpressionEngine(parser) An expression evaluation function: >>> eval = lambda expression: tales.test( ... tales.IdentityExpr(expression), engine) We have provided 'python' as the default expression type. This means that when no prefix is given, the expression is evaluated as a Python expression: >>> eval('not False') True Note that the ``type`` prefixes bind left. If ``not`` and ``exits`` are two expression type prefixes, consider the following:: >>> eval('not: exists: int(None)') True The pipe operator binds right. In the following example, but arguments are evaluated against ``not: exists: ``. >>> eval('not: exists: help') False """ supported_char_escape_set = {'&', '<', '>'} def __init__( self, parser, char_escape=(), default=None, default_marker=None, literal_false: bool = True, ) -> None: self._parser = parser self._char_escape = char_escape self._default = default self._default_marker = default_marker self._literal_false = literal_false def __call__(self, string, target): # BBB: This method is deprecated. Instead, a call should first # be made to ``parse`` and then one of the assignment methods # ("value" or "text"). compiler = self.parse(string) return compiler(string, target) def parse(self, string, handle_errors: bool = True, char_escape=None): expression = self._parser(string) compiler = self.get_compiler( expression, string, handle_errors, char_escape ) return ExpressionCompiler(compiler, self) def get_compiler(self, expression, string, handle_errors, char_escape): if char_escape is None: char_escape = self._char_escape def compiler(target, engine, result_type=None, *args): stmts = expression(target, engine) if result_type is not None: method = getattr(self, '_convert_%s' % result_type) steps = method(target, char_escape, *args) if not self._literal_false: steps = [ ast.If( ast.UnaryOp( op=ast.Not(), operand=target ), [ast.Assign( targets=[store(target.id)], value=load('None') )], steps ) ] stmts.extend(steps) if handle_errors and isinstance(string, Token): stmts.insert(0, TokenRef(string.strip())) return stmts return compiler def _convert_bool(self, target, char_escape, s): """Converts value given by ``target`` to a string ``s`` if the target is a true value, otherwise ``None``. """ return emit_bool( target, ast.Str(s=s), default=self._default, default_marker=self._default_marker ) def _convert_structure(self, target, char_escape): """Converts value given by ``target`` to structure output.""" return emit_convert( target, default=self._default, default_marker=self._default_marker, ) def _convert_text(self, target, char_escape): """Converts value given by ``target`` to text.""" if not char_escape: return self._convert_structure(target, char_escape) # This is a cop-out - we really only support a very select # set of escape characters other = set(char_escape) - self.supported_char_escape_set if other: for supported in '"', '\'', '': if supported in char_escape: quote = supported break else: raise RuntimeError( "Unsupported escape set: %s." % repr(char_escape) ) else: quote = '\0' entity = char2entity(quote or '\0') return template( "TARGET = __quote(TARGET, QUOTE, Q_ENTITY, DEFAULT, MARKER)", TARGET=target, QUOTE=ast.Str(s=quote), Q_ENTITY=ast.Str(s=entity), DEFAULT=self._default, MARKER=self._default_marker, ) class ExpressionCompiler: def __init__(self, compiler, engine) -> None: self.compiler = compiler self.engine = engine def assign_bool(self, target, s): return self.compiler(target, self.engine, "bool", s) def assign_text(self, target): return self.compiler(target, self.engine, "text") def assign_value(self, target): return self.compiler(target, self.engine) class ExpressionEvaluator: """Evaluates dynamic expression. This is not particularly efficient, but supported for legacy applications. >>> from chameleon import tales >>> parser = tales.ExpressionParser({'python': tales.PythonExpr}, 'python') >>> engine = functools.partial(ExpressionEngine, parser) >>> evaluator = ExpressionEvaluator(engine, { ... 'foo': 'bar', ... }) We'll use the following convenience function to test the expression evaluator. >>> from chameleon.utils import Scope >>> def evaluate(d, *args): ... return evaluator(Scope(d), *args) The evaluation function is passed the local and remote context, the expression type and finally the expression. >>> evaluate({'boo': 'baz'}, {}, 'python', 'foo + boo') 'barbaz' The cache is now primed: >>> evaluate({'boo': 'baz'}, {}, 'python', 'foo + boo') 'barbaz' Note that the call method supports currying of the expression argument: >>> python = evaluate({'boo': 'baz'}, {}, 'python') >>> python('foo + boo') 'barbaz' """ __slots__ = "_engine", "_cache", "_names", "_builtins" def __init__(self, engine, builtins): self._engine = engine self._names, self._builtins = zip(*builtins.items()) self._cache = {} def __call__(self, econtext, rcontext, expression_type, string=None): if string is None: return functools.partial( self.__call__, econtext, rcontext, expression_type ) expression = "{}:{}".format(expression_type, string) try: evaluate = self._cache[expression] except KeyError: assignment = Assignment(["_result"], expression, True) module = Module("evaluate", Context(assignment)) compiler = Compiler( self._engine, module, "<string>", string, ('econtext', 'rcontext') + self._names ) env = {} exec(compiler.code, env) evaluate = self._cache[expression] = env["evaluate"] evaluate(econtext, rcontext, *self._builtins) return econtext['_result'] class NameTransform: """ >>> nt = NameTransform( ... set(('foo', 'bar', )), {'boo': 'boz'}, ... ('econtext', ), ... ) >>> def test(name): ... rewritten = nt(load(name)) ... module = ast.Module([ast.fix_missing_locations(rewritten)], []) ... codegen = TemplateCodeGenerator(module) ... return codegen.code Any odd name: >>> test('frobnitz') "getname('frobnitz')" A 'builtin' name will first be looked up via ``get`` allowing fall back to the global builtin value: >>> test('foo') "get('foo', foo)" Internal names (with two leading underscores) are left alone: >>> test('__internal') '__internal' Compiler internals or disallowed names: >>> test('econtext') 'econtext' Aliased names: >>> test('boo') 'boz' """ def __init__(self, builtins, aliases, internals) -> None: self.builtins = builtins self.aliases = aliases self.internals = internals def __call__(self, node): name = node.id # Don't rewrite names that begin with an underscore; they are # internal and can be assumed to be locally defined. This # policy really should be part of the template program, not # defined here in the compiler. if name.startswith('__') or name in self.internals: return node # Some expressions allow setting variables which we transform to # storing them as template context. if isinstance(node.ctx, ast.Store): return store_econtext(name) aliased = self.aliases.get(name) if aliased is not None: return load(aliased) # If the name is a Python global, first try acquiring it from # the dynamic context, then fall back to the global. if name in self.builtins: return template( "get(key, name)", mode="eval", key=ast.Str(s=name), name=Builtin(name), ) # Otherwise, simply acquire it from the dynamic context. return load_econtext(name) class ExpressionTransform: """Internal wrapper to transform expression nodes into assignment statements. The node input may use the provided expression engine, but other expression node types are supported such as ``Builtin`` which simply resolves a built-in name. Used internally be the compiler. """ loads_symbol = Symbol(pickle.loads) def __init__( self, engine_factory, cache, visitor, strict: bool = True ) -> None: self.engine_factory = engine_factory self.cache = cache self.strict = strict self.visitor = visitor def __call__(self, expression, target): if isinstance(target, str): target = store(target) try: stmts = self._translate(expression, target) except ExpressionError as exc: if self.strict: raise p = pickle.dumps(exc, -1) stmts = template( "__exc = loads(p)", loads=self.loads_symbol, p=ast.Str(s=p) ) stmts += [ TokenRef(exc.token), ast.Raise(exc=load("__exc")) ] # Apply visitor to each statement stmts = [self.visitor(stmt) for stmt in stmts] return stmts def _translate(self, expression, target): if isinstance(target, str): target = store(target) cached = self.cache.get(expression) if cached is not None: stmts = [ast.Assign(targets=[target], value=cached)] elif isinstance(expression, ast.expr): stmts = [ast.Assign(targets=[target], value=expression)] else: # The engine interface supports simple strings, which # default to expression nodes if isinstance(expression, str): expression = Value(expression, True) kind = type(expression).__name__ visitor = getattr(self, "visit_%s" % kind) stmts = visitor(expression, target) # Add comment target_id = getattr(target, "id", target) comment = Comment(" {!r} -> {}".format(expression, target_id)) stmts.insert(0, comment) return stmts def visit_Value(self, node, target): engine = self.engine_factory( default=node.default, default_marker=node.default_marker ) compiler = engine.parse(node.value) return compiler.assign_value(target) def visit_Copy(self, node, target): return self._translate(node.expression, target) def visit_Substitution(self, node, target): engine = self.engine_factory( default=node.default, default_marker=node.default_marker, literal_false=node.literal_false, ) compiler = engine.parse(node.value, char_escape=node.char_escape) return compiler.assign_text(target) def visit_Negate(self, node, target): return self._translate(node.value, target) + \ template("TARGET = not TARGET", TARGET=target) def visit_BinOp(self, node, target): expression = self._translate(node.left, "__expression") value = self._translate(node.right, "__value") op = { Is: "is", IsNot: "is not", Equals: "==", }[node.op] return expression + value + \ template("TARGET = __expression %s __value" % op, TARGET=target) def visit_Boolean(self, node, target): engine = self.engine_factory( default=node.default, default_marker=node.default_marker, ) compiler = engine.parse(node.value) return compiler.assign_bool(target, node.s) def visit_Interpolation(self, node, target): expr = node.value if isinstance(expr, Substitution): engine = self.engine_factory( char_escape=expr.char_escape, default=expr.default, default_marker=expr.default_marker, literal_false=expr.literal_false, ) elif isinstance(expr, Value): engine = self.engine_factory( default=expr.default, default_marker=expr.default_marker ) else: raise RuntimeError("Bad value: %r." % node.value) interpolator = Interpolator( expr.value, node.braces_required, translate=node.translation, decode_htmlentities=True ) compiler = engine.get_compiler(interpolator, expr.value, True, ()) return compiler(target, engine, "text") def visit_Replace(self, node, target): stmts = self._translate(node.value, target) return stmts + template( "if TARGET: TARGET = S", TARGET=target, S=ast.Str(s=node.s) ) def visit_Translate(self, node, target): if node.msgid is not None: msgid = ast.Str(s=node.msgid) else: msgid = target return self._translate(node.node, target) + \ emit_translate(target, msgid, default=target) def visit_Static(self, node, target): return [ast.Assign(targets=[target], value=node)] def visit_Builtin(self, node, target): return [ast.Assign(targets=[target], value=node)] def visit_Symbol(self, node, target): return template("TARGET = SYMBOL", TARGET=target, SYMBOL=node) class Compiler: """Generic compiler class. Iterates through nodes and yields Python statements which form a template program. """ defaults = { 'translate': Symbol(simple_translate), 'decode': Builtin("str"), 'on_error_handler': Builtin("str"), } lock = threading.Lock() global_builtins = set(builtins.__dict__) def __init__( self, engine_factory, node, filename, source, builtins={}, strict=True, stream_factory=list, ): self._scopes = [set()] self._expression_cache = {} self._translations = [] self._builtins = builtins self._aliases = [{}] self._macros = [] self._current_slot = [] # Prepare stream factory (callable) self._new_list = ( ast.List([], ast.Load()) if stream_factory is list else ast.Call( ast.Symbol(stream_factory), args=[], kwargs=[], lineno=None, ) ) internals = COMPILER_INTERNALS_OR_DISALLOWED | set(self.defaults) transform = NameTransform( self.global_builtins | set(builtins), ListDictProxy(self._aliases), internals, ) self._visitor = visitor = NameLookupRewriteVisitor(transform) self._engine = ExpressionTransform( engine_factory, self._expression_cache, visitor, strict=strict, ) module = ast.Module([], []) module.body += self.visit(node) ast.fix_missing_locations(module) class Generator(TemplateCodeGenerator): scopes = [TranslationContext()] tokens = [] def visit_EmitText(self, node) -> ast.AST: append = load(self.scopes[-1].append or "__append") expr = ast.Expr(ast.Call( func=append, args=[ast.Str(s=node.s)], keywords=[], starargs=None, kwargs=None )) return self.visit(expr) # type: ignore[no-any-return] def visit_Name(self, node: ast.Name) -> ast.AST: if isinstance(node.ctx, ast.Load): scope = self.scopes[-1] for name in ("append", "stream"): if node.id == f"__{name}": identifier = getattr(scope, name, None) if identifier: return load(identifier) return node def visit_TranslationContext(self, node) -> list[ast.AST]: self.scopes.append(node) stmts = list(map(self.visit, node.body)) self.scopes.pop() return stmts def visit_TokenRef(self, node: TokenRef) -> ast.AST: self.tokens.append((node.token.pos, len(node.token))) return ast.Assign( [store("__token")], ast.Num(n=node.token.pos), lineno=None, ) generator = Generator(module) tokens = [ Token(source[pos:pos + length], pos, source) for pos, length in generator.tokens ] token_map_def = "__tokens = {" + ", ".join("%d: %r" % ( token.pos, (token, ) + token.location ) for token in tokens) + "}" self.code = "\n".join(( "__filename = %r\n" % filename, token_map_def, generator.code )) def visit(self, node): if node is None: return () kind = type(node).__name__ visitor = getattr(self, "visit_%s" % kind) iterator = visitor(node) result = [] for key, group in itertools.groupby( iterator, lambda node: node.__class__): nodes = list(group) if key is EmitText: text = join(node.s for node in nodes) nodes = [EmitText(text)] if key is TokenRef: nodes = [nodes[-1]] result.extend(nodes) return result def visit_Sequence(self, node): for item in node.items: yield from self.visit(item) def visit_Element(self, node): yield from self.visit(node.start) yield from self.visit(node.content) if node.end is not None: yield from self.visit(node.end) def visit_Module(self, node): body = [] body += template("import re") body += template("import functools") body += template("from itertools import chain as __chain") body += template("from sys import intern") body += template("__default = intern('__default__')") body += template("__marker = object()") body += template( "g_re_amp = re.compile(r'&(?!([A-Za-z]+|#[0-9]+);)')" ) body += template( r"g_re_needs_escape = re.compile(r'[&<>\"\']').search") body += template( r"__re_whitespace = " r"functools.partial(re.compile('\\s+').sub, ' ')", ) # Visit module content program = self.visit(node.program) body += [ast.FunctionDef( name=node.name, args=ast.arguments( args=[param(b) for b in self._builtins], defaults=[], kwonlyargs=[], posonlyargs=[], ), body=program, decorator_list=[], lineno=None, )] return body def visit_MacroProgram(self, node): functions = [] # Visit defined macros macros = getattr(node, "macros", ()) names = [] for macro in macros: stmts = self.visit(macro) function = stmts[-1] names.append(function.name) functions += stmts # Return function dictionary functions += [ast.Return(value=ast.Dict( keys=[ast.Str(s=name) for name in names], values=[load(name) for name in names], ))] return functions def visit_Context(self, node): return template("getname = econtext.get_name") + \ template("get = econtext.get") + \ self.visit(node.node) def visit_Macro(self, node): body = [] # Initialization body += template("__append = __stream.append") body += template("__re_amp = g_re_amp") body += template("__token = None") body += template("__re_needs_escape = g_re_needs_escape") body += emit_func_convert("__convert") body += emit_func_convert_and_escape("__quote") # Resolve defaults for name in self.defaults: body += template( "NAME = econtext[KEY]", NAME=name, KEY=ast.Str(s="__" + name) ) # Internal set of defined slots self._slots = set() # Visit macro body nodes = list(itertools.chain(*tuple(map(self.visit, node.body)))) # Slot resolution for name in self._slots: body += template( "try: NAME = econtext[KEY].pop()\n" "except: NAME = None", KEY=ast.Str(s=name), NAME=store(name)) exc = template( "exc_info()[1]", exc_info=Symbol(sys.exc_info), mode="eval" ) exc_handler = template( "if pos is not None: rcontext.setdefault('__error__', [])." "append(token + (__filename, exc, ))", exc=exc, token=template("__tokens[pos]", pos="__token", mode="eval"), pos="__token" ) + template("raise") # Wrap visited nodes in try-except error handler. body += [ ast.Try( body=nodes, handlers=[ast.ExceptHandler(body=exc_handler)], finalbody=[], orelse=[], ) ] function_name = "render" if node.name is None else \ "render_%s" % mangle(node.name) function = ast.FunctionDef( name=function_name, args=ast.arguments( args=[ param("__stream"), param("econtext"), param("rcontext"), param("__i18n_domain"), param("__i18n_context"), param("target_language"), ], defaults=[load("None"), load("None"), load("None")], kwonlyargs=[], posonlyargs=[], ), body=body, decorator_list=[], lineno=None, ) yield function def visit_Text(self, node): yield EmitText(node.value) # TODO Refactor! def visit_Domain(self, node): backup = "__previous_i18n_domain_%s" % mangle(id(node)) return template("BACKUP = __i18n_domain", BACKUP=backup) + \ template("__i18n_domain = NAME", NAME=ast.Str(s=node.name)) + \ self.visit(node.node) + \ template("__i18n_domain = BACKUP", BACKUP=backup) def visit_Target(self, node): backup = "__previous_i18n_target_%s" % mangle(id(node)) tmp = "__tmp_%s" % mangle(id(node)) return template("BACKUP = target_language", BACKUP=backup) + \ self._engine(node.expression, store(tmp)) + \ [ast.Assign([store("target_language")], load(tmp))] + \ self.visit(node.node) + \ template("target_language = BACKUP", BACKUP=backup) def visit_TxContext(self, node): backup = "__previous_i18n_context_%s" % mangle(id(node)) return template("BACKUP = __i18n_context", BACKUP=backup) + \ template("__i18n_context = NAME", NAME=ast.Str(s=node.name)) + \ self.visit(node.node) + \ template("__i18n_context = BACKUP", BACKUP=backup) def visit_OnError(self, node): body = [] fallback = identifier("__fallback") body += template("fallback = len(__stream)", fallback=fallback) self._enter_assignment((node.name, )) fallback_body = self.visit(node.fallback) self._leave_assignment((node.name, )) error_assignment = template( "econtext[key] = cls(__exc, __tokens[__token][1:3])\n" "if handler is not None: handler(__exc)", cls=ErrorInfo, handler=load("on_error_handler"), key=ast.Str(s=node.name), ) body += [ast.Try( body=self.visit(node.node), handlers=[ast.ExceptHandler( type=ast.Tuple(elts=[Builtin("Exception")], ctx=ast.Load()), name="__exc", body=(error_assignment + template("del __stream[fallback:]", fallback=fallback) + fallback_body ), )], finalbody=[], orelse=[], )] return body def visit_Content(self, node): name = "__content" body = self._engine(node.expression, store(name)) if node.translate: body += emit_translate(name, name) if node.char_escape: body += template( "NAME=__quote(NAME, None, '\255', None, None)", NAME=name, ) else: body += template("NAME = __convert(NAME)", NAME=name) body += template("if NAME is not None: __append(NAME)", NAME=name) return body def visit_Interpolation(self, node): name = identifier("content") return self._engine(node, name) + \ emit_node_if_non_trivial(name) def visit_Alias(self, node): assert len(node.names) == 1 name = node.names[0] target = self._aliases[-1][name] = identifier(name, id(node)) return self._engine(node.expression, target) def visit_Assignment(self, node): for name in node.names: if name in COMPILER_INTERNALS_OR_DISALLOWED: raise TranslationError( "Name disallowed by compiler.", name ) if name.startswith('__'): raise TranslationError( "Name disallowed by compiler (double underscore).", name ) assignment = self._engine(node.expression, store("__value")) if len(node.names) != 1: target = ast.Tuple( elts=[store_econtext(name) for name in node.names], ctx=ast.Store(), ) else: target = store_econtext(node.names[0]) assignment.append(ast.Assign(targets=[target], value=load("__value"))) for name in node.names: if not node.local: assignment += template( "rcontext[KEY] = __value", KEY=ast.Str( s=str(name))) return assignment def visit_Define(self, node): scope = set(self._scopes[-1]) self._scopes.append(scope) self._aliases.append(self._aliases[-1].copy()) for assignment in node.assignments: if assignment.local: yield from self._enter_assignment(assignment.names) yield from self.visit(assignment) yield from self.visit(node.node) for assignment in reversed(node.assignments): if assignment.local: yield from self._leave_assignment(assignment.names) self._scopes.pop() self._aliases.pop() def visit_Omit(self, node): return self.visit_Condition(node) def visit_Condition(self, node): target = "__condition" def step(expressions, body, condition): for i, expression in enumerate(reversed(expressions)): stmts = evaluate(expression, body) if i > 0: stmts.append( ast.If( ast.Compare( left=load(target), ops=[ast.Is()], comparators=[load(str(condition))] ), body, None ) ) body = stmts return body def evaluate(node, body=None): if isinstance(node, Logical): condition = isinstance(node, And) return step(node.expressions, body, condition) return self._engine(node, target) body = evaluate(node.expression) orelse = getattr(node, "orelse", None) body.append( ast.If( test=load(target), body=self.visit(node.node) or [ast.Pass()], orelse=self.visit(orelse) if orelse else None, ) ) return body def visit_Translate(self, node): """Translation. Visit items and assign output to a default value. Finally, compile a translation expression and use either result or default. """ body = [] # Track the blocks of this translation self._translations.append(set()) # Prepare new stream append = identifier("append", id(node)) stream = identifier("stream", id(node)) body += template("s = new_list", s=stream, new_list=self._new_list) + \ template("a = s.append", a=append, s=stream) # Visit body to generate the message body code = self.visit(node.node) body.append(TranslationContext(code, append, stream)) # Reduce white space and assign as message id msgid = identifier("msgid", id(node)) body += template( "msgid = __re_whitespace(''.join(stream)).strip()", msgid=msgid, stream=stream ) default = msgid # Compute translation block mapping if applicable names = self._translations[-1] if names: keys = [] values = [] for name in names: stream, append = self._get_translation_identifiers(name) keys.append(ast.Str(s=name)) values.append(load(stream)) # Initialize value body.insert( 0, ast.Assign( targets=[store(stream)], value=ast.Str(s=""))) mapping = ast.Dict(keys=keys, values=values) else: mapping = None # if this translation node has a name, use it as the message id if node.msgid: msgid = ast.Str(s=node.msgid) # emit the translation expression translation = template( "__append(translate(" "msgid, mapping=mapping, default=default, domain=__i18n_domain, context=__i18n_context, target_language=target_language))", # noqa: E501 line too long msgid=msgid, default=default, mapping=mapping) if not node.msgid: translation = [ast.If( test=load(msgid), body=translation, orelse=[] )] body += translation # pop away translation block reference self._translations.pop() return body def visit_Start(self, node): try: line, column = node.prefix.location except AttributeError: line, column = 0, 0 yield Comment( " %s%s ... (%d:%d)\n" " --------------------------------------------------------" % ( node.prefix, node.name, line, column)) if node.attributes: yield EmitText(node.prefix + node.name) yield from self.visit(node.attributes) yield EmitText(node.suffix) else: yield EmitText(node.prefix + node.name + node.suffix) def visit_End(self, node): yield EmitText(node.prefix + node.name + node.space + node.suffix) def visit_Attribute(self, node): attr_format = (node.space + node.name + node.eq + node.quote + "%s" + node.quote) filter_args = list(map(self._engine.cache.get, node.filters)) filter_condition = template( "NAME not in CHAIN", NAME=ast.Str(s=node.name), CHAIN=ast.Call( func=load("__chain"), args=filter_args, keywords=[], starargs=None, kwargs=None, ), mode="eval" ) # Static attributes are just outputted directly if isinstance(node.expression, ast.Str): s = attr_format % node.expression.s if node.filters: return template( "if C: __append(S)", C=filter_condition, S=ast.Str(s=s) ) else: return [EmitText(s)] target = identifier("attr", node.name) body = self._engine(node.expression, store(target)) condition = template("TARGET is not None", TARGET=target, mode="eval") if node.filters: condition = ast.BoolOp( values=[condition, filter_condition], op=ast.And(), ) return body + template( "if CONDITION: __append(FORMAT % TARGET)", FORMAT=ast.Str(s=attr_format), TARGET=target, CONDITION=condition, ) def visit_DictAttributes(self, node): target = identifier("attr", id(node)) body = self._engine(node.expression, store(target)) bool_names = Static(template( "set(LIST)", LIST=ast.List( elts=[ast.Str(s=name) for name in node.bool_names], ctx=ast.Load(), ), mode="eval" )) exclude = Static(template( "set(LIST)", LIST=ast.List( elts=[ast.Str(s=name) for name in node.exclude], ctx=ast.Load(), ), mode="eval" )) bool_cond = ( "if name in BOOL_NAMES:\n" + indent("if not bool(value): continue\n") + indent("value = name\n") ) if node.bool_names else "" body += template( "for name, value in TARGET.items():\n" + indent(bool_cond) + indent( "if name not in EXCLUDE and value is not None:\n" + indent(bool_cond) + indent( "__append(" "' ' + name + '=' + QUOTE + " "QUOTE_FUNC(value, QUOTE, QUOTE_ENTITY, None, None) + " "QUOTE)" ) ), TARGET=target, EXCLUDE=exclude, QUOTE_FUNC="__quote", QUOTE=ast.Str(s=node.quote), QUOTE_ENTITY=ast.Str(s=char2entity(node.quote or '\0')), BOOL_NAMES=bool_names ) return body def visit_Cache(self, node): body = [] for expression in node.expressions: # Skip re-evaluation if self._expression_cache.get(expression): continue name = identifier("cache", id(expression)) target = store(name) body += self._engine(expression, target) self._expression_cache[expression] = target body += self.visit(node.node) return body def visit_Cancel(self, node): body = [] for expression in node.expressions: assert self._expression_cache.get(expression) is not None name = identifier("cache", id(expression)) target = store(name) body += self._engine(node.value, target) body += self.visit(node.node) return body def visit_UseInternalMacro(self, node): if node.name is None: render = "render" else: render = "render_%s" % mangle(node.name) token_reset = template("__token = None") return token_reset + template( "f(__stream, econtext.copy(), rcontext, " "__i18n_domain, __i18n_context, target_language)", f=render) + \ template("econtext.update(rcontext)") def visit_DefineSlot(self, node): name = "__slot_%s" % mangle(node.name) body = self.visit(node.node) self._slots.add(name) orelse = template( "SLOT(__stream, econtext.copy(), rcontext)", SLOT=name) test = ast.Compare( left=load(name), ops=[ast.Is()], comparators=[load("None")] ) return [ ast.If(test=test, body=body or [ast.Pass()], orelse=orelse) ] def visit_Name(self, node): """Translation name.""" if not self._translations: raise TranslationError( "Not allowed outside of translation.", node.name) if node.name in self._translations[-1]: raise TranslationError( "Duplicate translation name: %s.", node.name) self._translations[-1].add(node.name) body = [] # prepare new stream stream, append = self._get_translation_identifiers(node.name) body += template("s = new_list", s=stream, new_list=self._new_list) + \ template("a = s.append", a=append, s=stream) # generate code code = self.visit(node.node) body.append(TranslationContext(code, append, stream)) # output msgid text = Text('${%s}' % node.name) body += self.visit(text) # Concatenate stream body += template("stream = ''.join(stream)", stream=stream) return body def visit_CodeBlock(self, node): stmts = template(textwrap.dedent(node.source.strip('\n'))) stmts = list(map(self._visitor, stmts)) stmts.insert(0, TokenRef(node.source)) return stmts def visit_UseExternalMacro(self, node): self._macros.append(node.extend) callbacks = [] for slot in node.slots: key = "__slot_%s" % mangle(slot.name) fun = "__fill_%s" % mangle(slot.name) self._current_slot.append(slot.name) body = self.visit_Context(slot) assert self._current_slot.pop() == slot.name callbacks.append( ast.FunctionDef( name=fun, args=ast.arguments( args=[ param("__stream"), param("econtext"), param("rcontext"), param("__i18n_domain"), param("__i18n_context"), param("target_language"), ], defaults=[ load("__i18n_domain"), load("__i18n_context"), load("target_language"), ], kwonlyargs=[], posonlyargs=[], ), body=body or [ast.Pass()], decorator_list=[], lineno=None, )) key = ast.Str(s=key) assignment = template( "_slots = econtext[KEY] = DEQUE((NAME,))", KEY=key, NAME=fun, DEQUE=Symbol(collections.deque), ) if node.extend: append = template("_slots.appendleft(NAME)", NAME=fun) assignment = [ast.Try( body=template("_slots = getname(KEY)", KEY=key), handlers=[ast.ExceptHandler(body=assignment)], finalbody=[], orelse=append, )] callbacks.extend(assignment) assert self._macros.pop() == node.extend assignment = self._engine(node.expression, store("__macro")) return ( callbacks + assignment + [TokenRef(node.expression.value)] + template("__m = __macro.include") + template( "__m(__stream, econtext.copy(), " "rcontext, __i18n_domain, __i18n_context, target_language)" ) + template("econtext.update(rcontext)") ) def visit_Repeat(self, node): # Used for loop variable definition and restore self._scopes.append(set()) # Variable assignment and repeat key for single- and # multi-variable repeat clause if node.local: contexts = "econtext", else: contexts = "econtext", "rcontext" for name in node.names: if name in COMPILER_INTERNALS_OR_DISALLOWED: raise TranslationError( "Name disallowed by compiler.", name ) if len(node.names) > 1: targets = [ ast.Tuple(elts=[ subscript(str(name), load(context), ast.Store()) for name in node.names], ctx=ast.Store()) for context in contexts ] key = ast.Tuple( elts=[ast.Str(s=name) for name in node.names], ctx=ast.Load()) else: name = node.names[0] targets = [ subscript(str(name), load(context), ast.Store()) for context in contexts ] key = ast.Str(s=node.names[0]) index = identifier("__index", id(node)) assignment = [ast.Assign(targets=targets, value=load("__item"))] # Make repeat assignment in outer loop names = node.names local = node.local outer = self._engine(node.expression, store("__iterator")) if local: outer[:] = list(self._enter_assignment(names)) + outer outer += template( "__iterator, INDEX = getname('repeat')(key, __iterator)", key=key, INDEX=index ) # Set a trivial default value for each name assigned to make # sure we assign a value even if the iteration is empty outer += [ast.Assign( targets=[store_econtext(name) for name in node.names], value=load("None")) ] # Compute inner body inner = self.visit(node.node) # After each iteration, decrease the index inner += template("index -= 1", index=index) # For items up to N - 1, emit repeat whitespace inner += template( "if INDEX > 0: __append(WHITESPACE)", INDEX=index, WHITESPACE=ast.Str(s=node.whitespace) ) # Main repeat loop outer += [ast.For( target=store("__item"), iter=load("__iterator"), body=assignment + inner, orelse=[], )] # Finally, clean up assignment if it's local if outer: outer += self._leave_assignment(names) self._scopes.pop() return outer def _get_translation_identifiers(self, name): assert self._translations prefix = str(id(self._translations[-1])).replace('-', '_') stream = identifier("stream_%s" % prefix, name) append = identifier("append_%s" % prefix, name) return stream, append def _enter_assignment(self, names): for name in names: yield from template( "BACKUP = get(KEY, __marker)", BACKUP=identifier("backup_%s" % name, id(names)), KEY=ast.Str(s=str(name)), ) def _leave_assignment(self, names): for name in names: yield from template( "if BACKUP is __marker: del econtext[KEY]\n" "else: econtext[KEY] = BACKUP", BACKUP=identifier("backup_%s" % name, id(names)), KEY=ast.Str(s=str(name)), ) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/config.py�������������������������������������������������������������0000664�0000000�0000000�00000003523�14604714122�0020024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import logging import os log = logging.getLogger('chameleon.config') environment = { k[10:]: v for (k, v) in ( (j.lower(), x) for (j, x) in os.environ.items()) if k.startswith('chameleon_') } # Define which values are read as true TRUE = ('y', 'yes', 't', 'true', 'on', '1') # If eager parsing is enabled, templates are parsed upon # instantiation, rather than when first called upon; this mode is # useful for verifying validity of templates across a project EAGER_PARSING = environment.pop('eager', 'false').lower() in TRUE # Debug mode is mostly useful for debugging the template engine # itself. When enabled, generated source code is written to disk to # ease step-debugging and some log levels are lowered to increase # output. Also, the generated source code is available in the # ``source`` attribute of the template instance if compilation # succeeded. DEBUG_MODE = environment.pop('debug', 'false').lower() in TRUE # If a cache directory is specified, template source code will be # persisted on disk and reloaded between sessions path = environment.pop('cache', None) CACHE_DIRECTORY: str | None if path is not None: # pragma: no cover CACHE_DIRECTORY = os.path.abspath(path) if not os.path.exists(CACHE_DIRECTORY): raise ValueError( "Cache directory does not exist: %s." % CACHE_DIRECTORY ) log.info("directory cache: %s." % CACHE_DIRECTORY) else: CACHE_DIRECTORY = None # When auto-reload is enabled, templates are reloaded on file change. AUTO_RELOAD = environment.pop('reload', 'false').lower() in TRUE for key in environment: log.warning( "unknown environment variable set: \"CHAMELEON_%s\"." % key.upper() ) # This is the slice length of the expression displayed in the # formatted exception string SOURCE_EXPRESSION_MARKER_LENGTH = 60 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/exc.py����������������������������������������������������������������0000664�0000000�0000000�00000022572�14604714122�0017343�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import traceback from typing import TYPE_CHECKING from typing import Any from chameleon.config import SOURCE_EXPRESSION_MARKER_LENGTH as LENGTH from chameleon.tokenize import Token from chameleon.utils import create_formatted_exception from chameleon.utils import safe_native if TYPE_CHECKING: from collections.abc import Callable from collections.abc import Iterable from collections.abc import Iterator from collections.abc import Mapping from typing_extensions import Self def compute_source_marker( line: str, column: int, expression: str, size: int ) -> tuple[str, str]: """Computes source marker location string. >>> def test(l, c, e, s): ... s, marker = compute_source_marker(l, c, e, s) ... out = s + '\\n' + marker ... ... # Replace dot with middle-dot to work around doctest ellipsis ... print(out.replace('...', '···')) >>> test('foo bar', 4, 'bar', 7) foo bar ^^^ >>> test('foo ${bar}', 4, 'bar', 10) foo ${bar} ^^^ >>> test(' foo bar', 6, 'bar', 6) ··· oo bar ^^^ >>> test(' foo bar baz ', 6, 'bar', 6) ··· o bar ··· ^^^ The entire expression is always shown, even if ``size`` does not accommodate for it. >>> test(' foo bar baz ', 6, 'bar baz', 10) ··· oo bar baz ^^^^^^^ >>> test(' foo bar', 10, 'bar', 5) ··· o bar ^^^ >>> test(' foo bar', 10, 'boo', 5) ··· o bar ^ """ s = line.lstrip() column -= len(line) - len(s) s = s.rstrip() try: i = s[column:].index(expression) except ValueError: # If we can't find the expression # (this shouldn't happen), simply # use a standard size marker marker = "^" else: column += i marker = "^" * len(expression) if len(expression) > size: offset = column size = len(expression) else: window = (size - len(expression)) / 2.0 f_offset = column - window f_offset -= min(3, max(0, column + window + len(expression) - len(s))) offset = int(f_offset) if offset > 0: s = s[offset:] r = s.lstrip() d = len(s) - len(r) s = "... " + r column += 4 - d column -= offset # This also adds to the displayed length size += 4 if len(s) > size: s = s[:size].rstrip() + " ..." return s, column * " " + marker def iter_source_marker_lines( source: Iterable[str], expression: str, line: int, column: int ) -> Iterator[str]: for i, l in enumerate(source): if i + 1 != line: continue s, marker = compute_source_marker( l, column, expression, LENGTH ) yield " - Source: %s" % s yield " %s" % marker break def ellipsify(string: str, limit: int) -> str: if len(string) > limit: return "... " + string[-(limit - 4):] return string class RenderError(Exception): """An error raised during rendering. This class is used as a mixin which is added to the original exception. """ class TemplateError(Exception): """An error raised by Chameleon. >>> from chameleon.tokenize import Token >>> token = Token('token') >>> message = 'message' Make sure the exceptions can be copied: >>> from copy import copy >>> copy(TemplateError(message, token)) TemplateError('message', 'token') And pickle/unpickled: >>> from pickle import dumps, loads >>> loads(dumps(TemplateError(message, token), -1)) TemplateError('message', 'token') """ args: tuple[str, Token] def __init__(self, msg: str, token: Token) -> None: if not isinstance(token, Token): token = Token(token, 0) Exception.__init__(self, msg, token) def __copy__(self) -> Self: inst = Exception.__new__(type(self)) inst.args = self.args return inst def __str__(self) -> str: text = "%s\n\n" % self.args[0] text += " - String: \"%s\"" % safe_native(self.token) if self.filename: text += "\n" text += " - Filename: %s" % self.filename lineno, column = self.location text += "\n" text += " - Location: (line %d: col %d)" % (lineno, column) lines: Iterable[str] if lineno and column: if self.token.source: lines = iter_source_marker_lines( self.token.source.splitlines(), self.token, lineno, column ) elif self.filename and not self.filename.startswith('<'): try: f = open(self.filename) except OSError: pass else: lines = iter_source_marker_lines( iter(f), self.token, lineno, column ) try: lines = list(lines) finally: f.close() else: lines = () # Prepend newlines. for line in lines: text += "\n" + safe_native(line) return text def __repr__(self) -> str: try: return "{}('{}', '{}')".format( self.__class__.__name__, self.args[0], safe_native(self.token) ) except AttributeError: return object.__repr__(self) @property def token(self) -> Token: return self.args[1] @property def filename(self) -> str: return self.token.filename @property def location(self) -> tuple[int, int]: return self.token.location @property def offset(self) -> int: return getattr(self.token, "pos", 0) class ParseError(TemplateError): """An error occurred during parsing. Indicates an error on the structural level. """ class CompilationError(TemplateError): """An error occurred during compilation. Indicates a general compilation error. """ class TranslationError(TemplateError): """An error occurred during translation. Indicates a general translation error. """ class LanguageError(CompilationError): """Language syntax error. Indicates a syntactical error due to incorrect usage of the template language. """ class ExpressionError(LanguageError): """An error occurred compiling an expression. Indicates a syntactical error in an expression. """ class ExceptionFormatter: def __init__( self, errors: list[tuple[str, int, int, str, BaseException]], econtext: Mapping[str, object], rcontext: dict[str, Any], value_repr: Callable[[object], str] ) -> None: kwargs = rcontext.copy() kwargs.update(econtext) for name in tuple(kwargs): if name.startswith('__'): del kwargs[name] self._errors = errors self._kwargs = kwargs self._value_repr = value_repr def __call__(self) -> str: # Format keyword arguments; consecutive arguments are indented # for readability formatted_args = [ "{}: {}".format(name, self._value_repr(value)) for name, value in self._kwargs.items() ] for index, string in enumerate(formatted_args[1:]): formatted_args[index + 1] = " " * 15 + string out = [] for error in self._errors: expression, line, column, filename, exc = error if isinstance(exc, UnicodeDecodeError): string = safe_native(exc.object) s, marker = compute_source_marker( string, exc.start, string[exc.start:exc.end], LENGTH ) out.append(" - Stream: %s" % s) out.append(" %s" % marker) _filename = ellipsify(filename, 60) if filename else "<string>" out.append(" - Expression: \"%s\"" % expression) out.append(" - Filename: %s" % _filename) out.append(" - Location: (line %d: col %d)" % (line, column)) if filename and not filename.startswith('<') and line and column: try: f = open(filename) except OSError: pass else: lines = iter_source_marker_lines( iter(f), expression, line, column ) try: out.extend(lines) finally: f.close() out.append(" - Arguments: %s" % "\n".join(formatted_args)) if isinstance(exc.__str__, ExceptionFormatter): # This is a nested error that has already been wrapped # We must unwrap it before trying to format it to prevent # recursion exc = create_formatted_exception( exc, type(exc), exc._original__str__) # type: ignore formatted = traceback.format_exception_only(type(exc), exc)[-1] formatted_class = "%s:" % type(exc).__name__ if formatted.startswith(formatted_class): formatted = formatted[len(formatted_class):].lstrip() return "\n".join(map(safe_native, [formatted] + out)) ��������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/i18n.py���������������������������������������������������������������0000664�0000000�0000000�00000010055�14604714122�0017334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from __future__ import annotations import re from typing import TYPE_CHECKING from chameleon.exc import CompilationError if TYPE_CHECKING: from collections.abc import Mapping NAME_RE = r"[a-zA-Z][-a-zA-Z0-9_]*" WHITELIST = frozenset([ "translate", "domain", "context", "target", "source", "attributes", "data", "name", "mode", "xmlns", "xml", "comment", "ignore", "ignore-attributes", ]) _interp_regex = re.compile(r'(?<!\$)(\$(?:(%(n)s)|{(%(n)s)}))' % ({'n': NAME_RE})) # BBB: The ``fast_translate`` function here is kept for backwards # compatibility reasons. Do not use! try: # pragma: no cover from zope.i18n import interpolate from zope.i18n import translate from zope.i18nmessageid import Message except ImportError: # pragma: no cover pass else: # pragma: no cover def fast_translate( msgid: str | None, domain: str | None = None, mapping: Mapping[str, object] | None = None, context: str | None = None, target_language: str | None = None, default: str | None = None ) -> str | None: if msgid is None: return None if target_language is not None or context is not None: result: str = translate( msgid, domain=domain, mapping=mapping, context=context, target_language=target_language, default=default) if result != msgid: return result if isinstance(msgid, Message): default = msgid.default mapping = msgid.mapping if default is None: default = str(msgid) if not isinstance(default, str): return default return interpolate(default, mapping) # type: ignore[no-any-return] def simple_translate( msgid: str, domain: str | None = None, mapping: Mapping[str, object] | None = None, context: str | None = None, target_language: str | None = None, default: str | None = None ) -> str: if default is None: default = getattr(msgid, "default", msgid) if mapping is None: mapping = getattr(msgid, "mapping", None) if mapping: def replace(match: re.Match[str]) -> str: whole, param1, param2 = match.groups() return str(mapping.get(param1 or param2, whole)) return _interp_regex.sub(replace, default) return default def parse_attributes(attrs, xml=True): d = {} # filter out empty items, eg: # i18n:attributes="value msgid; name msgid2;" # would result in 3 items where the last one is empty attrs = [spec for spec in attrs.split(";") if spec] for spec in attrs: if ',' in spec: raise CompilationError( "Attribute must not contain comma. Use semicolon to " "list multiple attributes", spec ) parts = spec.split() if len(parts) == 2: attr, msgid = parts elif len(parts) == 1: attr = parts[0] msgid = None else: raise CompilationError( "Illegal i18n:attributes specification.", spec) if not xml: attr = attr.lower() attr = attr.strip() if attr in d: raise CompilationError( "Attribute may only be specified once in i18n:attributes", attr) d[attr] = msgid return d �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/loader.py�������������������������������������������������������������0000664�0000000�0000000�00000015347�14604714122�0020034�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import contextlib import functools import importlib.resources import logging import os import posixpath import py_compile import shutil import sys import tempfile import warnings from importlib.machinery import SourceFileLoader from threading import RLock from typing import TYPE_CHECKING from typing import Any from typing import cast from chameleon.utils import encode_string if TYPE_CHECKING: from collections.abc import Callable from collections.abc import Iterator from collections.abc import Sequence from pathlib import Path from typing import Protocol from typing import TypeVar from .template import BaseTemplate _F = TypeVar('_F', bound=Callable[..., Any]) _TemplateT = TypeVar('_TemplateT', bound='BaseTemplate') class _SupportsRmtree(Protocol): def rmtree(self, path: str, /) -> object: ... lock = RLock() acquire_lock = lock.acquire release_lock = lock.release del lock log = logging.getLogger('chameleon.loader') def cache(func: _F) -> _F: def load(self: Any, *args: Any, **kwargs: Any) -> Any: template = self.registry.get(args) if template is None: self.registry[args] = template = func(self, *args, **kwargs) return template return cast('_F', load) @contextlib.contextmanager def import_package_resource(name: str) -> Iterator[Path]: # FIXME: we should restrict ourselves to the Traversable protocol # but for right now we assume that we always get a simple # filesystem resource loader which returns pathlib.Path objects path: Path = importlib.resources.files(name) # type: ignore[assignment] yield path if hasattr(path.root, 'close'): path.root.close() class TemplateLoader: """Template loader class. To load templates using relative filenames, pass a sequence of paths (or a single path) as ``search_path``. To apply a default filename extension to inputs which do not have an extension already (i.e. no dot), provide this as ``default_extension`` (e.g. ``'.pt'``). Additional keyword-arguments will be passed on to the template constructor. """ default_extension: str | None = None registry: dict[tuple[Any, ...], Any] def __init__( self, search_path: Sequence[str] | str | None = None, default_extension: str | None = None, **kwargs: Any ) -> None: if search_path is None: search_path = [] if isinstance(search_path, str): search_path = [search_path] if default_extension is not None: self.default_extension = ".%s" % default_extension.lstrip('.') self.search_path = search_path self.registry = {} self.kwargs = kwargs @cache def load( self, spec: str, cls: type[_TemplateT] = None # type: ignore[assignment] ) -> _TemplateT: if cls is None: raise ValueError("Unbound template loader.") spec = spec.strip() if self.default_extension is not None and '.' not in spec: spec += self.default_extension package_name = None if not os.path.isabs(spec): if ':' in spec: package_name, spec = spec.split(':', 1) else: for path in self.search_path: if not os.path.isabs(path) and ':' in path: package_name, path = path.split(':', 1) with import_package_resource(package_name) as files: if files.joinpath(path).joinpath(spec).exists(): spec = posixpath.join(path, spec) break else: path = os.path.join(path, spec) if os.path.exists(path): package_name = None spec = path break else: raise ValueError("Template not found: %s." % spec) return cls( spec, search_path=self.search_path, package_name=package_name, **self.kwargs ) def bind( self, cls: type[_TemplateT] ) -> Callable[[str], _TemplateT]: return functools.partial(self.load, cls=cls) class MemoryLoader: def build(self, source: str, filename: str) -> dict[str, Any]: code = compile(source, filename, 'exec') env: dict[str, Any] = {} exec(code, env) return env def get(self, name: str) -> None: return None class ModuleLoader: def __init__(self, path: str, remove: bool = False) -> None: self.path = path self.remove = remove def __del__(self, shutil: _SupportsRmtree = shutil) -> None: if not self.remove: return try: shutil.rmtree(self.path) except BaseException: warnings.warn( "Could not clean up temporary file path: %s" % (self.path,) ) def get(self, filename: str) -> dict[str, Any] | None: path = os.path.join(self.path, filename) if os.path.exists(path): log.debug("loading module from cache: %s." % filename) base, ext = os.path.splitext(filename) return self._load(base, path) else: log.debug('cache miss: %s' % filename) return None def build(self, source: str, filename: str) -> dict[str, Any]: acquire_lock() try: base, ext = os.path.splitext(filename) name = os.path.join(self.path, base + ".py") log.debug("writing source to disk (%d bytes)." % len(source)) fd, fn = tempfile.mkstemp( prefix=base, suffix='.tmp', dir=self.path) temp = os.fdopen(fd, 'wb') encoded = source.encode('utf-8') header = encode_string("# -*- coding: utf-8 -*-" + "\n") try: try: temp.write(header) temp.write(encoded) finally: temp.close() except BaseException: os.remove(fn) raise os.rename(fn, name) log.debug("compiling %s into byte-code..." % filename) py_compile.compile(name) return self._load(base, name) finally: release_lock() def _load(self, base: str, filename: str) -> dict[str, Any]: acquire_lock() try: module = sys.modules.get(base) if module is None: module = SourceFileLoader(base, filename).load_module() finally: release_lock() return module.__dict__ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/metal.py��������������������������������������������������������������0000664�0000000�0000000�00000001422�14604714122�0017655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## WHITELIST = frozenset([ "define-macro", "extend-macro", "use-macro", "define-slot", "fill-slot", "xmlns", "xml" ]) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/namespaces.py���������������������������������������������������������0000664�0000000�0000000�00000000625�14604714122�0020676�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������XML_NS = "http://www.w3.org/XML/1998/namespace" XMLNS_NS = "http://www.w3.org/2000/xmlns/" XHTML_NS = "http://www.w3.org/1999/xhtml" TAL_NS = "http://xml.zope.org/namespaces/tal" META_NS = "http://xml.zope.org/namespaces/meta" METAL_NS = "http://xml.zope.org/namespaces/metal" XI_NS = "http://www.w3.org/2001/XInclude" I18N_NS = "http://xml.zope.org/namespaces/i18n" PY_NS = "http://genshi.edgewall.org/" �����������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/nodes.py��������������������������������������������������������������0000664�0000000�0000000�00000011424�14604714122�0017666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations from typing import ClassVar from chameleon.astutil import Node class UseExternalMacro(Node): """Extend external macro.""" _fields = "expression", "slots", "extend" class Sequence(Node): """Element sequence.""" _fields = "items", def __nonzero__(self): return bool(self.items) class Content(Node): """Content substitution.""" _fields = "expression", "char_escape", "translate" class CodeBlock(Node): _fields = "source", class Value(Node): """Expression object value.""" _fields: ClassVar[tuple[str, ...]] _fields = "value", "default", "default_marker" default = None default_marker = None def __repr__(self): try: line, column = self.value.location except AttributeError: line, column = 0, 0 return "<%s %r (%d:%d)>" % ( type(self).__name__, self.value, line, column, ) class Substitution(Value): """Expression value for text substitution.""" _fields = ( "value", "char_escape", "default", "default_marker", "literal_false", ) default = None literal_false = True class Boolean(Value): _fields = "value", "s", "default", "default_marker" class Negate(Node): """Wraps an expression with a negation.""" _fields = ("value",) class Element(Node): """XML element.""" _fields = "start", "end", "content" class DictAttributes(Node): """Element attributes from one or more Python dicts.""" _fields = "expression", "char_escape", "quote", "exclude", "bool_names" class Attribute(Node): """Element attribute.""" _fields = ( "name", "expression", "quote", "eq", "space", "default", "filters", ) class Start(Node): """Start-tag.""" _fields = "name", "prefix", "suffix", "attributes" class End(Node): """End-tag.""" _fields = "name", "space", "prefix", "suffix" class Condition(Node): """Node visited only if one of the condition holds.""" _fields = "expression", "node", "orelse" class Op(Node): """An operator node.""" class Is(Op): """Object identity.""" class IsNot(Op): """Object identity.""" class Equals(Op): """Object equality.""" class Logical(Node): """Logical operator.""" _fields = ("expressions",) class And(Logical): """All terms must be met.""" class Or(Logical): """At least one term must be met.""" class BinOp(Node): """Binary comparison.""" _fields = "left", "op", "right" class Cache(Node): """Cache (evaluate only once) the value of ``expression`` inside ``node``. """ _fields: ClassVar[tuple[str, ...]] _fields = "expressions", "node" class Cancel(Cache): _fields = "expressions", "node", "value" class Copy(Node): _fields = ("expression",) class Assignment(Node): """Variable assignment.""" _fields: ClassVar[tuple[str, ...]] _fields = "names", "expression", "local" class Alias(Assignment): """Alias assignment. Note that ``expression`` should be a cached or global value. """ local = False class Define(Node): """Variable definition in scope.""" _fields = "assignments", "node" class Repeat(Assignment): """Iterate over provided assignment and repeat body.""" _fields = "names", "expression", "local", "whitespace", "node" class Macro(Node): """Macro definition.""" _fields = "name", "body" class Module(Node): _fields = ( "name", "program", ) class Context(Node): _fields = ("node",) class Text(Node): """Static text output.""" _fields = ("value",) class Interpolation(Node): """String interpolation output.""" _fields = ( "value", "braces_required", "translation", "default", "default_marker", ) class Replace(Node): """Replace non-empty value with string.""" _fields = "value", "s" class Translate(Node): """Translate node.""" _fields = "msgid", "node" class Name(Node): """Translation name.""" _fields = "name", "node" class Domain(Node): """Update translation domain.""" _fields = "name", "node" class Target(Node): """Update translation target.""" _fields = "expression", "node" class TxContext(Node): """Update translation context.""" _fields = "name", "node" class OnError(Node): _fields = "fallback", "name", "node" class UseInternalMacro(Node): """Use internal macro (defined inside same program).""" _fields = ("name",) class FillSlot(Node): """Fill a macro slot.""" _fields = "name", "node" class DefineSlot(Node): """Define a macro slot.""" _fields = "name", "node" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/parser.py�������������������������������������������������������������0000664�0000000�0000000�00000016427�14604714122�0020062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import logging import re from collections import OrderedDict from chameleon.exc import ParseError from chameleon.namespaces import XML_NS from chameleon.tokenize import Token match_double_hyphen = re.compile(r'--(?!(-)*>)') match_tag_prefix_and_name = re.compile( r'^(?P<prefix></?)(?P<name>([^:\n\t\r ]+:)?[^ \n\t\r>/]+)' r'(?P<suffix>(?P<space>\s*)/?>)?', re.UNICODE | re.DOTALL) match_single_attribute = re.compile( r'(?P<space>\s+)(?!\d)' r'(?P<name>[^ =/>\n\t\r]+)' r'((?P<eq>\s*=\s*)' r'((?P<quote>[\'"])(?P<value>.*?)(?P=quote)|' r'(?P<alt_value>[^\s\'">/]+))|' r'(?P<simple_value>(?![ \\n\\t\\r]*=)))', re.UNICODE | re.DOTALL) match_comment = re.compile( r'^<!--(?P<text>.*)-->$', re.DOTALL) match_cdata = re.compile( r'^<!\[CDATA\[(?P<text>.*)\]>$', re.DOTALL) match_declaration = re.compile( r'^<!(?P<text>[^>]+)>$', re.DOTALL) match_processing_instruction = re.compile( r'^<\?(?P<name>\w+)(?P<text>.*?)\?>', re.DOTALL) match_xml_declaration = re.compile(r'^<\?xml(?=[ /])', re.DOTALL) log = logging.getLogger('chameleon.parser') def substitute(regex, repl, token): if not isinstance(token, Token): token = Token(token) return Token( regex.sub(repl, token), token.pos, token.source, token.filename ) def groups(m, token): result = [] for i, group in enumerate(m.groups()): if group is not None: j, k = m.span(i + 1) group = token[j:k] result.append(group) return tuple(result) def groupdict(m, token): d = m.groupdict() for name, value in d.items(): if value is not None: i, j = m.span(name) d[name] = token[i:j] return d def match_tag(token, regex=match_tag_prefix_and_name): m = regex.match(token) d = groupdict(m, token) end = m.end() token = token[end:] attrs = d['attrs'] = [] for m in match_single_attribute.finditer(token): attr = groupdict(m, token) alt_value = attr.pop('alt_value', None) if alt_value is not None: attr['value'] = alt_value attr['quote'] = '' simple_value = attr.pop('simple_value', None) if simple_value is not None: attr['quote'] = '' attr['value'] = '' attr['eq'] = '' attrs.append(attr) d['suffix'] = token[m.end():] return d def parse_tag(token, namespace, restricted_namespace): node = match_tag(token) update_namespace(node['attrs'], namespace) if ':' in node['name']: prefix = node['name'].split(':')[0] else: prefix = None default = node['namespace'] = namespace.get(prefix, XML_NS) node['ns_attrs'] = unpack_attributes( node['attrs'], namespace, default, restricted_namespace ) node['ns_map'] = namespace return node def update_namespace(attributes, namespace) -> None: # possibly update namespaces; we do this in a separate step # because this assignment is irrespective of order for attribute in attributes: name = attribute['name'] value = attribute['value'] if name == 'xmlns': namespace[None] = value elif name.startswith('xmlns:'): namespace[name[6:]] = value def unpack_attributes(attributes, namespace, default, restricted_namespace): namespaced = OrderedDict() for index, attribute in enumerate(attributes): name = attribute['name'] value = attribute['value'] if ':' in name: prefix = name.split(':')[0] name = name[len(prefix) + 1:] try: ns = namespace[prefix] except KeyError: if restricted_namespace: raise KeyError( "Undefined namespace prefix: %s." % prefix) else: ns = default else: ns = default namespaced[ns, name] = value return namespaced def identify(string) -> str: if string.startswith("<"): if string.startswith("<!--"): m = match_double_hyphen.search(string[4:]) if m is not None: raise ParseError( "The string '--' is not allowed in a comment.", string[4 + m.start():4 + m.end()], ) return "comment" if string.startswith("<![CDATA["): return "cdata" if string.startswith("<!"): return "declaration" if string.startswith("<?xml"): return "xml_declaration" if string.startswith("<?"): return "processing_instruction" if string.startswith("</"): return "end_tag" if string.endswith("/>"): return "empty_tag" if string.endswith(">"): return "start_tag" return "error" return "text" class ElementParser: """Parses tokens into elements.""" def __init__(self, stream, default_namespaces, restricted_namespace=True): self.stream = stream self.queue = [] self.index = [] self.namespaces = [default_namespaces.copy()] self.restricted_namespace = restricted_namespace def __iter__(self): for token in self.stream: item = self.parse(token) self.queue.append(item) return iter(self.queue) def parse(self, token): kind = identify(token) visitor = getattr(self, "visit_%s" % kind, self.visit_default) return visitor(kind, token) def visit_comment(self, kind, token): return "comment", (token, ) def visit_cdata(self, kind, token): return "cdata", (token, ) def visit_default(self, kind, token): return "default", (token, ) def visit_processing_instruction(self, kind, token): m = match_processing_instruction.match(token) if m is None: return self.visit_default(kind, token) return "processing_instruction", (groupdict(m, token), ) def visit_text(self, kind, token): return kind, (token, ) def visit_start_tag(self, kind, token): namespace = self.namespaces[-1].copy() self.namespaces.append(namespace) node = parse_tag(token, namespace, self.restricted_namespace) self.index.append((node['name'], len(self.queue))) return kind, (node, ) def visit_end_tag(self, kind, token): try: namespace = self.namespaces.pop() except IndexError: raise ParseError("Unexpected end tag.", token) node = parse_tag(token, namespace, self.restricted_namespace) while self.index: name, pos = self.index.pop() if name == node['name']: start, = self.queue.pop(pos)[1] children = self.queue[pos:] del self.queue[pos:] break else: raise ParseError("Unexpected end tag.", token) return "element", (start, node, children) def visit_empty_tag(self, kind, token): namespace = self.namespaces[-1].copy() node = parse_tag(token, namespace, self.restricted_namespace) return "element", (node, None, []) def visit_xml_declaration(self, kind, token): return self.visit_empty_tag(kind, token) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/program.py������������������������������������������������������������0000664�0000000�0000000�00000002070�14604714122�0020222�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations from chameleon.namespaces import XML_NS from chameleon.namespaces import XMLNS_NS from chameleon.parser import ElementParser from chameleon.tokenize import iter_text from chameleon.tokenize import iter_xml class ElementProgram: DEFAULT_NAMESPACES = { 'xmlns': XMLNS_NS, 'xml': XML_NS, } tokenizers = { 'xml': iter_xml, 'text': iter_text, } restricted_namespace = True def __init__( self, source, mode="xml", filename=None, tokenizer=None ) -> None: if tokenizer is None: tokenizer = self.tokenizers[mode] tokens = tokenizer(source, filename) parser = ElementParser( tokens, self.DEFAULT_NAMESPACES, self.restricted_namespace ) self.body = [] for kind, args in parser: node = self.visit(kind, args) if node is not None: self.body.append(node) def visit(self, kind, args): visitor = getattr(self, "visit_%s" % kind) return visitor(*args) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/py.typed��������������������������������������������������������������0000664�0000000�0000000�00000000000�14604714122�0017667�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tal.py����������������������������������������������������������������0000664�0000000�0000000�00000026512�14604714122�0017342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from __future__ import annotations import copy import re from chameleon.exc import LanguageError from chameleon.namespaces import XMLNS_NS from chameleon.parser import groups from chameleon.utils import descriptorint from chameleon.utils import descriptorstr NAME = r"[a-zA-Z_][-a-zA-Z0-9_]*" DEFINE_RE = re.compile( r"(?s)\s*(?:(global|local)\s+)?" + r"({}|\({}(?:,\s*{})*\))\s+(.*)\Z".format(NAME, NAME, NAME), re.UNICODE) SUBST_RE = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S | re.UNICODE) ATTR_RE = re.compile(r"\s*([^\s{}'\"]+)\s+([^\s].*)\Z", re.S | re.UNICODE) ENTITY_RE = re.compile(r'(&(#?)(x?)(\d{1,5}|\w{1,8});)') WHITELIST = frozenset([ "define", "comment", "condition", "content", "replace", "repeat", "attributes", "on-error", "omit-tag", "script", "switch", "case", "xmlns", "xml" ]) def split_parts(arg): # Break in pieces at undoubled semicolons and # change double semicolons to singles: i = 0 while i < len(arg): m = ENTITY_RE.search(arg[i:]) if m is None: break arg = arg[:i + m.end()] + ';' + arg[i + m.end():] i += m.end() arg = arg.replace(";;", "\0") parts = arg.split(';') parts = [p.replace("\0", ";") for p in parts] if len(parts) > 1 and not parts[-1].strip(): del parts[-1] # It ended in a semicolon return parts def parse_attributes(clause): attrs = [] seen = set() for part in split_parts(clause): m = ATTR_RE.match(part) if not m: name, expr = None, part.strip() else: name, expr = groups(m, part) if name in seen: raise LanguageError( "Duplicate attribute name in attributes.", part) seen.add(name) attrs.append((name, expr)) return attrs def parse_substitution(clause): m = SUBST_RE.match(clause) if m is None: raise LanguageError( "Invalid content substitution syntax.", clause) key, expression = groups(m, clause) if not key: key = "text" return key, expression def parse_defines(clause): """ Parses a tal:define value. # Basic syntax, implicit local >>> parse_defines('hello lovely') [('local', ('hello',), 'lovely')] # Explicit local >>> parse_defines('local hello lovely') [('local', ('hello',), 'lovely')] # With global >>> parse_defines('global hello lovely') [('global', ('hello',), 'lovely')] # Multiple expressions >>> parse_defines('hello lovely; tea time') [('local', ('hello',), 'lovely'), ('local', ('tea',), 'time')] # With multiple names >>> parse_defines('(hello, howdy) lovely') [('local', ['hello', 'howdy'], 'lovely')] # With unicode whitespace >>> try: ... s = '\xc2\xa0hello lovely'.decode('utf-8') ... except AttributeError: ... s = '\xa0hello lovely' >>> parse_defines(s) == [ ... ('local', ('hello',), 'lovely') ... ] True """ defines = [] for part in split_parts(clause): m = DEFINE_RE.match(part) if m is None: raise LanguageError("Invalid define syntax", part) context, name, expr = groups(m, part) context = context or "local" if name.startswith('('): names = [n.strip() for n in name.strip('()').split(',')] else: names = (name,) defines.append((context, names, expr)) return defines def prepare_attributes(attrs, dyn_attributes, i18n_attributes, ns_attributes, drop_ns): drop = {attribute['name'] for attribute, (ns, value) in zip(attrs, ns_attributes) if ns in drop_ns or ( ns == XMLNS_NS and attribute['value'] in drop_ns)} attributes = [] normalized = {} for attribute in attrs: name = attribute['name'] if name in drop: continue attributes.append(( name, attribute['value'], attribute['quote'], attribute['space'], attribute['eq'], None, )) normalized[name.lower()] = len(attributes) - 1 for name, expr in dyn_attributes: index = normalized.get(name.lower()) if name else None if index is not None: _, text, quote, space, eq, _ = attributes[index] add = attributes.__setitem__ else: text = None quote = '"' space = " " eq = "=" index = len(attributes) add = attributes.insert if name is not None: normalized[name.lower()] = len(attributes) - 1 attribute = name, text, quote, space, eq, expr add(index, attribute) for name in i18n_attributes: attr = name.lower() if attr not in normalized: attributes.append((name, name, '"', " ", "=", None)) normalized[attr] = len(attributes) - 1 return attributes class RepeatItem: __slots__ = "length", "_iterator" __allow_access_to_unprotected_subobjects__ = True def __init__(self, iterator, length) -> None: self.length = length self._iterator = iterator def __iter__(self): return self._iterator @descriptorint def index(self): try: remaining = self._iterator.__length_hint__() except AttributeError: remaining = len(tuple(copy.copy(self._iterator))) return self.length - remaining - 1 @descriptorint def start(self): return self.index == 0 @descriptorint def end(self): return self.index == self.length - 1 @descriptorint def number(self): return self.index + 1 @descriptorstr def odd(self): """Returns a true value if the item index is odd. >>> it = RepeatItem(iter(("apple", "pear")), 2) >>> next(it._iterator) 'apple' >>> it.odd() '' >>> next(it._iterator) 'pear' >>> it.odd() 'odd' """ return self.index % 2 == 1 and 'odd' or '' @descriptorstr def even(self): """Returns a true value if the item index is even. >>> it = RepeatItem(iter(("apple", "pear")), 2) >>> next(it._iterator) 'apple' >>> it.even() 'even' >>> next(it._iterator) 'pear' >>> it.even() '' """ return self.index % 2 == 0 and 'even' or '' @descriptorstr def parity(self): """Return 'odd' or 'even' depending on the position's parity Useful for assigning CSS class names to table rows. """ return self.index % 2 == 0 and 'even' or 'odd' def next(self): raise NotImplementedError( "Method not implemented (can't update local variable).") def _letter(self, base=ord('a'), radix=26): """Get the iterator position as a lower-case letter >>> it = RepeatItem(iter(("apple", "pear", "orange")), 3) >>> next(it._iterator) 'apple' >>> it.letter() 'a' >>> next(it._iterator) 'pear' >>> it.letter() 'b' >>> next(it._iterator) 'orange' >>> it.letter() 'c' """ index = self.index if index < 0: raise TypeError("No iteration position") s = "" while True: index, off = divmod(index, radix) s = chr(base + off) + s if not index: return s letter = descriptorstr(_letter) @descriptorstr def Letter(self): """Get the iterator position as an upper-case letter >>> it = RepeatItem(iter(("apple", "pear", "orange")), 3) >>> next(it._iterator) 'apple' >>> it.Letter() 'A' >>> next(it._iterator) 'pear' >>> it.Letter() 'B' >>> next(it._iterator) 'orange' >>> it.Letter() 'C' """ return self._letter(base=ord('A')) @descriptorstr def Roman(self, rnvalues=( (1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), (90, 'XC'), (50, 'L'), (40, 'XL'), (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I'))): """Get the iterator position as an upper-case roman numeral >>> it = RepeatItem(iter(("apple", "pear", "orange")), 3) >>> next(it._iterator) 'apple' >>> it.Roman() 'I' >>> next(it._iterator) 'pear' >>> it.Roman() 'II' >>> next(it._iterator) 'orange' >>> it.Roman() 'III' """ n = self.index + 1 s = "" for v, r in rnvalues: rct, n = divmod(n, v) s = s + r * rct return s @descriptorstr def roman(self): """Get the iterator position as a lower-case roman numeral >>> it = RepeatItem(iter(("apple", "pear", "orange")), 3) >>> next(it._iterator) 'apple' >>> it.roman() 'i' >>> next(it._iterator) 'pear' >>> it.roman() 'ii' >>> next(it._iterator) 'orange' >>> it.roman() 'iii' """ return self.Roman().lower() class RepeatDict: """Repeat dictionary implementation. >>> repeat = RepeatDict({}) >>> iterator, length = repeat('numbers', range(5)) >>> length 5 >>> repeat['numbers'] <chameleon.tal.RepeatItem object at ...> >>> repeat.numbers <chameleon.tal.RepeatItem object at ...> >>> getattr(repeat, 'missing_key', None) is None True """ __slots__ = "__setitem__", "__getitem__" def __init__(self, d) -> None: self.__setitem__ = d.__setitem__ self.__getitem__ = d.__getitem__ def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(key) def __call__(self, key, iterable): """We coerce the iterable to a tuple and return an iterator after registering it in the repeat dictionary.""" iterable = list(iterable) if iterable is not None else () length = len(iterable) iterator = iter(iterable) # Insert as repeat item self[key] = RepeatItem(iterator, length) return iterator, length class ErrorInfo: """Information about an exception passed to an on-error handler.""" def __init__(self, err, position=(None, None)): if isinstance(err, Exception): self.type = err.__class__ self.value = err else: self.type = err self.value = None self.lineno = position[0] self.offset = position[1] ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tales.py��������������������������������������������������������������0000664�0000000�0000000�00000037755�14604714122�0017705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import ast import re from chameleon.astutil import Builtin from chameleon.astutil import ItemLookupOnAttributeErrorVisitor from chameleon.astutil import Symbol from chameleon.astutil import load from chameleon.astutil import parse from chameleon.astutil import store from chameleon.codegen import TemplateCodeGenerator from chameleon.codegen import reverse_builtin_map from chameleon.codegen import template from chameleon.compiler import Interpolator from chameleon.exc import ExpressionError from chameleon.parser import substitute from chameleon.tokenize import Token from chameleon.utils import ImportableMarker from chameleon.utils import Markup from chameleon.utils import lookup_attr from chameleon.utils import resolve_dotted DEFAULT_MARKER = ImportableMarker(__name__, "DEFAULT") split_parts = re.compile(r'(?<!\\)\|') match_prefix = re.compile(r'^\s*([a-z][a-z0-9\-_]*):').match re_continuation = re.compile(r'\\\s*$', re.MULTILINE) def resolve_global(value): name = reverse_builtin_map.get(value) if name is not None: return Builtin(name) return Symbol(value) def test(expression, engine=None, **env): if engine is None: engine = SimpleEngine() body = expression(store("result"), engine) module = ast.Module(body) module = ast.fix_missing_locations(module) env['rcontext'] = {} source = TemplateCodeGenerator(module).code code = compile(source, '<string>', 'exec') exec(code, env) result = env["result"] if isinstance(result, str): result = str(result) return result def transform_attribute(node): return template( "lookup(object, name)", lookup=Symbol(lookup_attr), object=node.value, name=ast.Str(s=node.attr), mode="eval" ) class TalesExpr: """Base class. This class helps implementations for the Template Attribute Language Expression Syntax (TALES). The syntax evaluates one or more expressions, separated by '|' (pipe). The first expression that succeeds, is returned. Expression: expression := (type ':')? line ('|' expression)? line := .* Expression lines may not contain the pipe character unless escaped. It has a special meaning: If the expression to the left of the pipe fails (raises one of the exceptions listed in ``catch_exceptions``), evaluation proceeds to the expression(s) on the right. Subclasses must implement ``translate`` which assigns a value for a given expression. >>> class PythonPipeExpr(TalesExpr): ... def translate(self, expression, target): ... compiler = PythonExpr(expression) ... return compiler(target, None) >>> test(PythonPipeExpr('foo | bar | 42')) 42 >>> test(PythonPipeExpr('foo|42')) 42 """ exceptions = AttributeError, \ NameError, \ LookupError, \ TypeError, \ ValueError ignore_prefix = True def __init__(self, expression): self.expression = expression def __call__(self, target, engine): remaining = self.expression assignments = [] while remaining: if self.ignore_prefix and match_prefix(remaining) is not None: compiler = engine.parse(remaining) assignment = compiler.assign_value(target) remaining = "" else: for m in split_parts.finditer(remaining): expression = remaining[:m.start()] remaining = remaining[m.end():] break else: expression = remaining remaining = "" expression = expression.replace('\\|', '|') assignment = self.translate_proxy(engine, expression, target) assignments.append(assignment) if not assignments: if not remaining: raise ExpressionError("No input:", remaining) assignments.append( self.translate_proxy(engine, remaining, target) ) for i, assignment in enumerate(reversed(assignments)): if i == 0: body = assignment else: body = [ast.Try( body=assignment, handlers=[ast.ExceptHandler( type=ast.Tuple( elts=list(map(resolve_global, self.exceptions)), ctx=ast.Load()), name=None, body=body, )], finalbody=[], orelse=[], )] return body def translate_proxy(self, engine, *args): """Default implementation delegates to ``translate`` method.""" return self.translate(*args) def translate(self, expression, target): """Return statements that assign a value to ``target``.""" raise NotImplementedError( "Must be implemented by a subclass.") class PathExpr(TalesExpr): """Path expression compiler. Syntax:: PathExpr ::= Path [ '|' Path ]* Path ::= variable [ '/' URL_Segment ]* variable ::= Name For example:: request/cookies/oatmeal nothing here/some-file 2001_02.html.tar.gz/foo root/to/branch | default When a path expression is evaluated, it attempts to traverse each path, from left to right, until it succeeds or runs out of paths. To traverse a path, it first fetches the object stored in the variable. For each path segment, it traverses from the current object to the subobject named by the path segment. Once a path has been successfully traversed, the resulting object is the value of the expression. If it is a callable object, such as a method or class, it is called. The semantics of traversal (and what it means to be callable) are implementation-dependent (see the ``translate`` method). """ def translate(self, expression, target): raise NotImplementedError( "Path expressions are not yet implemented. " "It's unclear whether a general implementation " "can be devised.") class PythonExpr(TalesExpr): r"""Python expression compiler. >>> test(PythonExpr('2 + 2')) 4 The Python expression is a TALES expression. That means we can use the pipe operator: >>> test(PythonExpr('foo | 2 + 2 | 5')) 4 To include a pipe character, use a backslash escape sequence: >>> test(PythonExpr(r'"\|"')) '|' """ transform = ItemLookupOnAttributeErrorVisitor(transform_attribute) def parse(self, string): return parse(string, 'eval').body def translate(self, expression, target): # Strip spaces string = expression.strip() # Convert line continuations to newlines string = substitute(re_continuation, '\n', string) # Convert newlines to spaces string = string.replace('\n', ' ') try: value = self.parse(string) except SyntaxError as exc: raise ExpressionError(exc.msg, string) # Transform attribute lookups to allow fallback to item lookup result = self.transform.visit(value) return [ast.Assign(targets=[target], value=result)] class ImportExpr: re_dotted = re.compile(r'^[A-Za-z.]+$') def __init__(self, expression) -> None: self.expression = expression def __call__(self, target, engine): string = self.expression.strip().replace('\n', ' ') value = template( "RESOLVE(NAME)", RESOLVE=Symbol(resolve_dotted), NAME=ast.Str(s=string), mode="eval", ) return [ast.Assign(targets=[target], value=value)] class NotExpr: """Negates the expression. >>> engine = SimpleEngine(PythonExpr) >>> test(NotExpr('False'), engine) True >>> test(NotExpr('True'), engine) False """ def __init__(self, expression) -> None: self.expression = expression def __call__(self, target, engine): compiler = engine.parse(self.expression) body = compiler.assign_value(target) return body + template("target = not target", target=target) class StructureExpr: """Wraps the expression result as 'structure'. >>> engine = SimpleEngine(PythonExpr) >>> test(StructureExpr('\"<tt>foo</tt>\"'), engine) '<tt>foo</tt>' """ wrapper_class = Symbol(Markup) def __init__(self, expression) -> None: self.expression = expression def __call__(self, target, engine): compiler = engine.parse(self.expression) body = compiler.assign_value(target) return body + template( "target = wrapper(target)", target=target, wrapper=self.wrapper_class, ) class IdentityExpr: """Identity expression. Exists to demonstrate the interface. >>> test(IdentityExpr('42')) 42 """ def __init__(self, expression) -> None: self.expression = expression def __call__(self, target, engine): compiler = engine.parse(self.expression) return compiler.assign_value(target) class StringExpr: """Similar to the built-in ``string.Template``, but uses an expression engine to support pluggable string substitution expressions. Expr string: string := (text | substitution) (string)? substitution := ('$' variable | '${' expression '}') text := .* In other words, an expression string can contain multiple substitutions. The text- and substitution parts will be concatenated back into a string. >>> test(StringExpr('Hello ${name}!'), name='world') 'Hello world!' In the default configuration, braces may be omitted if the expression is an identifier. >>> test(StringExpr('Hello $name!'), name='world') 'Hello world!' The ``braces_required`` flag changes this setting: >>> test(StringExpr('Hello $name!', True)) 'Hello $name!' To avoid interpolation, use two dollar symbols. Note that only a single symbol will appear in the output. >>> test(StringExpr('$${name}')) '${name}' In previous versions, it was possible to escape using a regular backslash coding, but this is no longer supported. >>> test(StringExpr(r'\\${name}'), name='Hello world!') '\\\\Hello world!' Multiple interpolations in one: >>> test(StringExpr("Hello ${'a'}${'b'}${'c'}!")) 'Hello abc!' Here's a more involved example taken from a javascript source: >>> result = test(StringExpr(\"\"\" ... function($$, oid) { ... $('#' + oid).autocomplete({source: ${'source'}}); ... } ... \"\"\")) >>> 'source: source' in result True As noted previously, the double-dollar escape also affects non-interpolation expressions. >>> 'function($, oid)' in result True >>> test(StringExpr('test ${1}${2}')) 'test 12' >>> test(StringExpr('test $${1}${2}')) 'test ${1}2' >>> test(StringExpr('test $$')) 'test $' >>> test(StringExpr('$$.ajax(...)')) '$.ajax(...)' >>> test(StringExpr('test $$ ${1}')) 'test $ 1' In the above examples, the expression is evaluated using the dummy engine which just returns the input as a string. As an example, we'll implement an expression engine which instead counts the number of characters in the expression and returns an integer result. >>> class engine: ... @staticmethod ... def parse(expression, char_escape=None): ... class compiler: ... @staticmethod ... def assign_text(target): ... return [ ... ast.Assign( ... targets=[target], ... value=ast.Num(n=len(expression)) ... )] ... ... return compiler This will demonstrate how the string expression coerces the input to a string. >>> expr = StringExpr( ... 'There are ${hello world} characters in \"hello world\"') We evaluate the expression using the new engine: >>> test(expr, engine) 'There are 11 characters in \"hello world\"' """ def __init__(self, expression, braces_required: bool = False) -> None: # The code relies on the expression being a token string if not isinstance(expression, Token): expression = Token(expression, 0) self.translator = Interpolator(expression, braces_required) def __call__(self, name, engine): return self.translator(name, engine) class ProxyExpr(TalesExpr): braces_required = False def __init__(self, name, expression, ignore_prefix: bool = True) -> None: super().__init__(expression) self.ignore_prefix = ignore_prefix self.name = name def translate_proxy(self, engine, expression, target): translator = Interpolator(expression, self.braces_required) assignment = translator(target, engine) return assignment + [ ast.Assign(targets=[target], value=ast.Call( func=load(self.name), args=[target], keywords=[], starargs=None, kwargs=None )) ] class ExistsExpr: """Boolean wrapper. Return 0 if the expression results in an exception, otherwise 1. As a means to generate exceptions, we set up an expression engine which evaluates the provided expression using Python: >>> engine = SimpleEngine(PythonExpr) >>> test(ExistsExpr('int(0)'), engine) 1 >>> test(ExistsExpr('int(None)'), engine) 0 """ exceptions = AttributeError, LookupError, TypeError, NameError def __init__(self, expression) -> None: self.expression = expression def __call__(self, target, engine): ignore = store("__ignore") compiler = engine.parse(self.expression, False) body = compiler.assign_value(ignore) classes = list(map(resolve_global, self.exceptions)) return [ ast.Try( body=body, handlers=[ast.ExceptHandler( type=ast.Tuple(elts=classes, ctx=ast.Load()), name=None, body=template("target = 0", target=target), )], finalbody=[], orelse=template("target = 1", target=target) ) ] class ExpressionParser: def __init__(self, factories, default) -> None: self.factories = factories self.default = default def __call__(self, expression): m = match_prefix(expression) if m is not None: prefix = m.group(1) expression = expression[m.end():] else: prefix = self.default try: factory = self.factories[prefix] except KeyError as exc: raise LookupError( "Unknown expression type: %s." % str(exc) ) return factory(expression) class SimpleEngine: expression = PythonExpr def __init__(self, expression=None) -> None: if expression is not None: self.expression = expression def parse(self, string, handle_errors=False, char_escape=None): compiler = self.expression(string) return SimpleCompiler(compiler, self) class SimpleCompiler: def __init__(self, compiler, engine) -> None: self.compiler = compiler self.engine = engine def assign_text(self, target): """Assign expression string as a text value.""" return self._assign_value_and_coerce(target, "str") def assign_value(self, target): """Assign expression string as object value.""" return self.compiler(target, self.engine) def _assign_value_and_coerce(self, target, builtin): return self.assign_value(target) + template( "target = builtin(target)", target=target, builtin=builtin ) �������������������chameleon-4.5.4/src/chameleon/template.py�����������������������������������������������������������0000664�0000000�0000000�00000034033�14604714122�0020372�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import annotations import datetime import hashlib import inspect import logging import os import pathlib import sys import tempfile from typing import TYPE_CHECKING from typing import Any from chameleon.compiler import Compiler from chameleon.config import AUTO_RELOAD from chameleon.config import CACHE_DIRECTORY from chameleon.config import DEBUG_MODE from chameleon.config import EAGER_PARSING from chameleon.exc import ExceptionFormatter from chameleon.exc import RenderError from chameleon.exc import TemplateError from chameleon.loader import MemoryLoader from chameleon.loader import ModuleLoader from chameleon.loader import import_package_resource from chameleon.nodes import Module from chameleon.utils import DebuggingOutputStream from chameleon.utils import Scope from chameleon.utils import create_formatted_exception from chameleon.utils import detect_encoding from chameleon.utils import join from chameleon.utils import mangle from chameleon.utils import raise_with_traceback from chameleon.utils import read_bytes from chameleon.utils import read_xml_encoding from chameleon.utils import value_repr PROGRAM_NAME = "initialize" if TYPE_CHECKING: from _typeshed import StrPath from abc import abstractmethod from collections.abc import Callable from collections.abc import Collection from chameleon.compiler import ExpressionEngine if sys.version_info >= (3, 10): import importlib.metadata as importlib_metadata else: import importlib_metadata def safe_get_package_version(name: str) -> str | None: try: return importlib_metadata.version(name) except importlib_metadata.PackageNotFoundError: return None def get_package_versions() -> list[tuple[str, str]]: distributions = importlib_metadata.packages_distributions().values() versions = { x: safe_get_package_version(x) or "" for x in sum(distributions, []) } return sorted(versions.items()) pkg_digest = hashlib.sha1(__name__.encode('utf-8')) for name, version in get_package_versions(): pkg_digest.update(name.encode('utf-8')) pkg_digest.update(version.encode('utf-8')) log = logging.getLogger('chameleon.template') def _make_module_loader() -> ModuleLoader: remove = False if CACHE_DIRECTORY: path = CACHE_DIRECTORY else: path = tempfile.mkdtemp() remove = True return ModuleLoader(path, remove) class BaseTemplate: """Template base class. Takes a string input which must be one of the following: - a string; - a utf-8 encoded byte string; - a byte string for an XML document that defines an encoding in the document premamble; - an HTML document that specifies the encoding via the META tag. Note that the template input is decoded, parsed and compiled on initialization. """ default_encoding = "utf-8" default_content_type: str | None = None # This attribute is strictly informational in this template class # and is used in exception formatting. It may be set on # initialization using the optional ``filename`` keyword argument. filename: StrPath = '<string>' _cooked = False loader: ModuleLoader | MemoryLoader if DEBUG_MODE or CACHE_DIRECTORY: loader = _make_module_loader() else: loader = MemoryLoader() output_stream_factory: type[list] if DEBUG_MODE: output_stream_factory = DebuggingOutputStream else: output_stream_factory = list debug = DEBUG_MODE # The ``builtins`` dictionary can be used by a template class to # add symbols which may not be redefined and which are (cheaply) # available in the template variable scope builtins: dict[str, Any] = {} # The ``builtins`` dictionary is updated with this dictionary at # cook time. Note that it can be provided at class initialization # using the ``extra_builtins`` keyword argument. extra_builtins: dict[str, Any] = {} # Expression engine must be provided by subclass if TYPE_CHECKING: @property @abstractmethod def engine(self) -> Callable[[], ExpressionEngine]: ... else: engine = None # When ``strict`` is set, expressions must be valid at compile # time. When not set, this is only required at evaluation time. strict = True # This should return a value string representation for exception # formatting. value_repr = staticmethod(value_repr) source: str | None def __init__( self, body: str | bytes | None = None, **config: Any ) -> None: self.__dict__.update(config) if body is not None: self.write(body) # This is only necessary if the ``debug`` flag was passed as a # keyword argument if self.__dict__.get('debug') is True: self.loader = _make_module_loader() def __call__(self, **kwargs: Any) -> str: return self.render(**kwargs) def __repr__(self) -> str: return "<{} {}>".format(self.__class__.__name__, self.filename) if TYPE_CHECKING: # since we add config values to our __dict__ they can appear as # arbitrary attributes, to clue the type checker into arbitary # attribute access being a possibility, we annotate __getattr__ def __getattr__(self, name: str) -> Any: ... @property def keep_body(self) -> bool: # By default, we only save the template body if we're # in debugging mode (to save memory). return self.__dict__.get('keep_body', DEBUG_MODE) # type: ignore @property def keep_source(self) -> bool: # By default, we only save the generated source code if we're # in debugging mode (to save memory). return self.__dict__.get('keep_source', DEBUG_MODE) # type: ignore def cook(self, body: str) -> None: builtins_dict = self.builtins.copy() builtins_dict.update(self.extra_builtins) names, builtins = zip(*sorted(builtins_dict.items())) digest = self.digest(body, names) program = self._cook(body, digest, names) init = program[PROGRAM_NAME] functions = init(*builtins) for name, function in functions.items(): setattr(self, "_" + name, function) self._cooked = True if self.keep_body: self.body = body def cook_check(self) -> bool | None: assert self._cooked return None def parse(self, body: str) -> Any: raise NotImplementedError("Must be implemented by subclass.") def render(self, **__kw: Any) -> str: econtext = Scope(__kw) rcontext: dict[str, Any] = {} self.cook_check() stream = self.output_stream_factory() target_language = __kw.get("target_language") try: self._render( stream, econtext, rcontext, target_language=target_language ) except RecursionError: raise except BaseException: cls, exc, tb = sys.exc_info() try: errors = rcontext.get('__error__') if errors: formatter = exc.__str__ if isinstance(formatter, ExceptionFormatter): if errors is not formatter._errors: formatter._errors.extend(errors) raise formatter = ExceptionFormatter( errors, econtext, rcontext, self.value_repr) try: exc = create_formatted_exception( exc, cls, formatter, RenderError # type: ignore ) except TypeError: pass assert exc is not None raise_with_traceback(exc, tb) raise finally: del exc, tb return join(stream) def write(self, body: str | bytes) -> None: encoding: str | None if isinstance(body, bytes): body, encoding, content_type = read_bytes( body, self.default_encoding ) elif body.startswith('<?xml'): content_type = 'text/xml' encoding = read_xml_encoding(body.encode("utf-8")) else: content_type, encoding = detect_encoding( body, self.default_encoding ) self.content_type = content_type or self.default_content_type self.content_encoding = encoding self.cook(body) def _get_module_name(self, name: str) -> str: return "%s.py" % name def _cook( self, body: str, name: str, builtins: Collection[str] ) -> dict[str, Any]: filename = self._get_module_name(name) if DEBUG_MODE or (cooked := self.loader.get(filename)) is None: try: source = self._compile(body, builtins) if self.debug: source = "# template: {}\n#\n{}".format( self.filename, source) if self.keep_source: self.source = source cooked = self.loader.build(source, filename) except TemplateError as exc: # normalize to str exc.token.filename = str(self.filename) raise elif self.keep_source: module_name = cooked.get('__name__') module = sys.modules.get(module_name) if module_name else None if module is not None: self.source = inspect.getsource(module) else: self.source = None return cooked def digest(self, body: str, names: Collection[str]) -> str: class_name = type(self).__name__.encode('utf-8') sha = pkg_digest.copy() sha.update(body.encode('utf-8', 'ignore')) sha.update(class_name) digest = sha.hexdigest() filename = str(self.filename) if filename and filename != BaseTemplate.filename: digest = os.path.splitext(filename)[0] + '-' + digest return digest def _compile(self, body: str, builtins: Collection[str]) -> str: program = self.parse(body) module = Module(PROGRAM_NAME, program) compiler = Compiler( self.engine, module, str(self.filename), body, builtins=builtins, strict=self.strict ) return compiler.code # type: ignore[no-any-return] class BaseTemplateFile(BaseTemplate): """File-based template base class. Relative path names are supported only when a template loader is provided as the ``loader`` parameter. """ # Auto reload is not enabled by default because it's a significant # performance hit auto_reload = AUTO_RELOAD _v_last_read: float | None def __init__( self, filename: StrPath, auto_reload: bool | None = None, package_name: str | None = None, post_init_hook: Callable[[], object] | None = None, **config: Any, ) -> None: if package_name is None: # Normalize filename filename = os.path.abspath( os.path.normpath(os.path.expanduser(filename)) ) self.package_name = package_name self.filename = filename # Override reload setting only if value is provided explicitly if auto_reload is not None: self.auto_reload = auto_reload super().__init__(**config) if post_init_hook is not None: post_init_hook() if EAGER_PARSING: self.cook_check() def cook_check(self) -> bool: if self.auto_reload: mtime = self.mtime() if mtime != self._v_last_read: self._v_last_read = mtime self._cooked = False if self._cooked is False: body = self.read() log.debug("cooking %r (%d bytes)..." % (self.filename, len(body))) self.cook(body) return True return False def mtime(self) -> float: filename = self.filename if self.package_name is not None: with import_package_resource(self.package_name) as path: joined = path.joinpath(filename) if isinstance(joined, pathlib.Path): return joined.stat().st_mtime # Assume this is a zip-like path object: filename = joined.at timetuple = path.root.getinfo(filename).date_time return datetime.datetime(*timetuple).timestamp() try: return os.path.getmtime(filename) except OSError: return 0 def read(self) -> str: if self.package_name is not None: with import_package_resource(self.package_name) as files: path = files.joinpath(self.filename) data = path.read_bytes() else: with open(self.filename, "rb") as f: data = f.read() body, encoding, content_type = read_bytes(data, self.default_encoding) self.content_type = content_type or self.default_content_type self.content_encoding = encoding return body def _get_module_name(self, name: str) -> str: filename = os.path.basename(str(self.filename)) mangled = mangle(filename) return "{}_{}.py".format(mangled, name) def _get_filename(self) -> StrPath: # FIXME: Shouldn't a missing filename be an error? return self.__dict__.get('filename') # type: ignore[return-value] def _set_filename(self, filename: StrPath) -> None: self.__dict__['filename'] = filename self._v_last_read = None self._cooked = False if TYPE_CHECKING: # type checkers only understand this way of defining properties @property def filename(self) -> StrPath: ... @filename.setter def filename(self, filename: StrPath) -> None: ... else: filename = property(_get_filename, _set_filename) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/����������������������������������������������������������������0000775�0000000�0000000�00000000000�14604714122�0017344�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/__init__.py�����������������������������������������������������0000664�0000000�0000000�00000000002�14604714122�0021445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/���������������������������������������������������������0000775�0000000�0000000�00000000000�14604714122�0020666�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/001-interpolation.txt������������������������������������0000664�0000000�0000000�00000000026�14604714122�0024612�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������${'<Hello world>'}<&> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/001-variable-scope.html����������������������������������0000664�0000000�0000000�00000000163�14604714122�0024746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body py:with="text 'Hello world!'"> ${text} $text </body> ${text | 'Goodbye world!'} </html> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/001-variable-scope.pt������������������������������������0000664�0000000�0000000�00000000317�14604714122�0024426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body tal:define="text 'Hello world!'"> ${text} </body> <tal:check condition="exists: text"> bad </tal:check> <tal:check condition="not: exists: text"> ok </tal:check> </html> �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/001.xml��������������������������������������������������0000664�0000000�0000000�00000000074�14604714122�0021711�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc></doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/002-repeat-scope.pt��������������������������������������0000664�0000000�0000000�00000000331�14604714122�0024116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div tal:repeat="text ('Hello', 'Goodbye')"> <span tal:repeat="char ('!', '.')">${text}${char}</span> </div> <tal:check condition="not: exists: text">ok</tal:check> </body> </html> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/002.xml��������������������������������������������������0000664�0000000�0000000�00000000075�14604714122�0021713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc ></doc> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/003-content.pt�������������������������������������������0000664�0000000�0000000�00000000772�14604714122�0023213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div tal:content="'Hello world!'" /> <div tal:content="'Hello world!'" />1 2<div tal:content="'Hello world!'" /> <div tal:content="'Hello world!'" />3 <div tal:content="'Hello world!'">4</div>5 6<div tal:content="'Hello world!'"></div> <div tal:content="1" /> <div tal:content="1.0" /> <div tal:content="True" /> <div tal:content="False" /> <div tal:content="0" /> <div tal:content="None" /> <div tal:replace="content" /> </body> </html> ������chameleon-4.5.4/src/chameleon/tests/inputs/003.xml��������������������������������������������������0000664�0000000�0000000�00000000075�14604714122�0021714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc></doc > �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/004-attributes.pt����������������������������������������0000664�0000000�0000000�00000002471�14604714122�0023726�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <span tal:attributes="class 'hello'" /> <span class="goodbye" tal:attributes="class 'hello'" /> <span CLASS="goodbye" tal:attributes="class 'hello'" /> <span data-æøå="goodbye" tal:attributes="data-æøå 'hello'" /> <span tal:attributes="class None" /> <span a="1" b="2" c="3" d=4 tal:attributes="a None"></span> <span a="1" b="2" c="3" tal:attributes="b None" /> <span a="1" b="2" c="3" tal:attributes="c None" /> <span a="1" b="2" c="3" tal:attributes="b None; c None" /> <span a="1" b="2" c="3" tal:attributes="b string:;;" /> <span a="1" b="2" c="3" tal:attributes="b string:&" /> <span class="hello" tal:attributes="class 'goodbye'" /> <span class="hello" tal:attributes="class '"goodbye"'" /> <span class="hello" tal:attributes="class '\'goodbye\''" /> <span class='hello' tal:attributes="class '\'goodbye\''" /> <span tal:attributes="{'class': 'goodbye'}" /> <span class="hello" tal:attributes="{'class': 'goodbye'}" /> <span a="1" class="hello" tal:attributes="{'class': 'goodbye'}" /> <span tal:attributes="{'class': '"goodbye"'}" /> <span tal:attributes="class 'hello'; {'class': '"goodbye"'}" /> <span tal:attributes="{'class': '"goodbye"'}; class 'hello'" /> </body> </html> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/004.xml��������������������������������������������������0000664�0000000�0000000�00000000146�14604714122�0021714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc a1 CDATA #IMPLIED> ]> <doc a1="v1"></doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/005-default.pt�������������������������������������������0000664�0000000�0000000�00000000674�14604714122�0023170�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <img class="default" tal:attributes="class default" /> <img tal:attributes="class default" /> <span tal:define="foo string:" tal:content="foo or default">Default</span> <span tal:content="unknownvar|default">Default</span> <span tal:content="True">Default</span> <span tal:content="False">Default</span> <span tal:content="default"> <em>${'Computed default'}</em> </span> </body> </html> ��������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/005.xml��������������������������������������������������0000664�0000000�0000000�00000000150�14604714122�0021710�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc a1 CDATA #IMPLIED> ]> <doc a1 = "v1"></doc> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/006-attribute-interpolation.pt���������������������������0000664�0000000�0000000�00000000617�14604714122�0026432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body class="ltr" tal:define="hash string:#"> <img src="${'#'}" alt="copyright (c) ${2010}" /> <img src="" alt="copyright (c) ${2010}" tal:attributes="src string:$hash" /> <img src="" alt="copyright (c) ${2010}" tal:attributes="src string:${hash}" /> <img src="${None}" alt="$ignored" /> <img src="" alt="${'%stype \'str\'%s' % (chr(60), chr(62))}" /> </body> </html> �����������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/006.xml��������������������������������������������������0000664�0000000�0000000�00000000146�14604714122�0021716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc a1 CDATA #IMPLIED> ]> <doc a1='v1'></doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/007-content-interpolation.pt�����������������������������0000664�0000000�0000000�00000000761�14604714122�0026102�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> ${'Hello world!'} ${literal} ${structure: literal.s} ${"%stype 'str'%s" % (chr(60), chr(62))} && <script> $$(document).ready(function(){ imagecropping.init_editor(); }); </script> ${None} ${None or 'Hello world'} $leftalone $${'fred'} $$${'fred'} $$$${'fred'} $$$$${'fred'} <div>${None}</div> <div>${1 < 2 and 'Hello world' or None}</div> <div>${} is ignored.</div> </body> </html> ���������������chameleon-4.5.4/src/chameleon/tests/inputs/007.xml��������������������������������������������������0000664�0000000�0000000�00000000101�14604714122�0021706�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc> </doc> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/008-builtins.pt������������������������������������������0000664�0000000�0000000�00000000633�14604714122�0023373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> ${attrs} ${nothing} <div tal:attributes="class string:dynamic" class="static"> ${attrs['class']} </div> <div class="static" tal:content="attrs['class']" /> <div class="static" tal:replace="attrs['class']" /> <div class="static" tal:define="x attrs">${x['class']}</div> <div tal:define="nothing string:nothing"> ${nothing} </div> </body> </html> �����������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/008.xml��������������������������������������������������0000664�0000000�0000000�00000000125�14604714122�0021715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc>&<>"'</doc> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/009-literals.pt������������������������������������������0000664�0000000�0000000�00000000061�14604714122�0023355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> ${literal} </body> </html> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/009.xml��������������������������������������������������0000664�0000000�0000000�00000000102�14604714122�0021711�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc> </doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/010-structure.pt�����������������������������������������0000664�0000000�0000000�00000000475�14604714122�0023577�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div tal:content="text string:1 < 2" /> <div tal:content="structure string:2 < 3, 2&3, 2<3, 2>3" /> <div tal:content="structure string:3 ${'<'} 4" /> <div tal:content="structure '%d < %d' % (4, 5)" /> <div tal:replace="structure content" /> </body> </html> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/010.xml��������������������������������������������������0000664�0000000�0000000�00000000147�14604714122�0021712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc a1 CDATA #IMPLIED> ]> <doc a1="v1" ></doc> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/011-messages.pt������������������������������������������0000664�0000000�0000000�00000000353�14604714122�0023342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div tal:content="text message" /> <div tal:content="structure message" /> <div tal:content="text string:${message}" /> <div tal:content="structure string:${message}" /> ${message} </body> </html> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/011.xml��������������������������������������������������0000664�0000000�0000000�00000000200�14604714122�0021701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc a1 CDATA #IMPLIED a2 CDATA #IMPLIED> ]> <doc a1="v1" a2="v2"></doc> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/012-translation.pt���������������������������������������0000664�0000000�0000000�00000001067�14604714122�0024075�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div i18n:translate=""></div> <div i18n:translate=""> Hello world! </div> <div i18n:translate="hello_world"> Hello world! </div> <div i18n:translate=""> <sup>Hello world!</sup> </div> <div i18n:translate=""> Hello <em i18n:name="first">${'world'}</em>! Goodbye <em i18n:name="second">${'planet'}</em>! </div> <div i18n:translate="hello_goodbye"> Hello <em i18n:name="first">${'world'}</em>! Goodbye <em i18n:name="second">${'planet'}</em>! </div> </body> </html> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/012.xml��������������������������������������������������0000664�0000000�0000000�00000000144�14604714122�0021711�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc : CDATA #IMPLIED> ]> <doc :="v1"></doc> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/013-repeat-nested.pt�������������������������������������0000664�0000000�0000000�00000000255�14604714122�0024276�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <table> <tr tal:repeat="i (1,2)"> <td tal:repeat="j (1,2)"> [${i},${j}] </td> </tr> </table> </body> </html> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/013.xml��������������������������������������������������0000664�0000000�0000000�00000000174�14604714122�0021715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc _.-0123456789 CDATA #IMPLIED> ]> <doc _.-0123456789="v1"></doc> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/014-repeat-nested-similar.pt�����������������������������0000664�0000000�0000000�00000000202�14604714122�0025725�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <span tal:repeat="i (3,4)"> <span tal:repeat="j (3,4)">[${i},${j}]</span> </span> </body> </html> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/014.xml��������������������������������������������������0000664�0000000�0000000�00000000226�14604714122�0021714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc abcdefghijklmnopqrstuvwxyz CDATA #IMPLIED> ]> <doc abcdefghijklmnopqrstuvwxyz="v1"></doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/015-translation-nested.pt��������������������������������0000664�0000000�0000000�00000000312�14604714122�0025350�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div i18n:translate=""> Price: <span i18n:name="price" i18n:translate=""> Per kilo <em i18n:name="amount">${12.5}</em> </span> </div> </body> </html> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/015.xml��������������������������������������������������0000664�0000000�0000000�00000000226�14604714122�0021715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc ABCDEFGHIJKLMNOPQRSTUVWXYZ CDATA #IMPLIED> ]> <doc ABCDEFGHIJKLMNOPQRSTUVWXYZ="v1"></doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/016-explicit-translation.pt������������������������������0000664�0000000�0000000�00000000626�14604714122�0025720�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div i18n:translate="" tal:content="string:Hello world!"> Hello world! </div> <img alt="${'Hello world!'}" i18n:attributes="alt" /> <img alt="${'Hello world!'}" i18n:attributes="alt hello_world" /> <img tal:attributes="alt 'Hello world!'" i18n:attributes="alt" /> <img tal:attributes="alt 'Hello world!'" i18n:attributes="alt hello_world" /> </body> </html> ����������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/016.xml��������������������������������������������������0000664�0000000�0000000�00000000102�14604714122�0021707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc><?pi?></doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/017-omit-tag.pt������������������������������������������0000664�0000000�0000000�00000000556�14604714122�0023267�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div tal:omit-tag="">Hello world!</div> <div tal:omit-tag="">1 Hello world! 2</div>3 4<div tal:omit-tag="True">Hello world!</div> <div tal:omit-tag="False">Hello world!</div> <div class="omitted" tal:omit-tag="True">Hello world!</div> <div class="${'omitted'}" tal:omit-tag="True">Hello world!</div> </body> </html> ��������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/017.xml��������������������������������������������������0000664�0000000�0000000�00000000123�14604714122�0021713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc><?pi some data ? > <??></doc> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/018-translation-nested-dynamic.pt������������������������0000664�0000000�0000000�00000000656�14604714122�0027010�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<div xmlns="http://www.w3.org/1999/xhtml" xmlns:i18n="http://xml.zope.org/namespaces/i18n"> <div i18n:translate="" tal:omit-tag=""> <span i18n:name="monthname" i18n:translate="" tal:content="'october'" tal:omit-tag="">monthname</span> <span i18n:name="year" i18n:translate="" tal:content="1982" tal:omit-tag="">year</span> </div> </div> ����������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/018.xml��������������������������������������������������0000664�0000000�0000000�00000000115�14604714122�0021715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc><![CDATA[<foo>]]></doc> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/019-replace.pt�������������������������������������������0000664�0000000�0000000�00000000575�14604714122�0023164�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div tal:replace="'Hello world!'" /> <div tal:replace="'Hello world!'" />1 2<div tal:replace="'Hello world!'" /> <div tal:replace="'Hello world!'" />3 <div tal:replace="'Hello world!'">4</div>5 6<div tal:replace="'Hello world!'"></div> <div tal:replace="1" /> <div tal:replace="1.0" /> <div tal:replace="True" /> </body> </html> �����������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/019.xml��������������������������������������������������0000664�0000000�0000000�00000000112�14604714122�0021713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc><![CDATA[<&]]></doc> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/020-on-error.pt������������������������������������������0000664�0000000�0000000�00000000610�14604714122�0023272�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div id="test" tal:attributes="class python: 'abc' + 2" tal:on-error="nothing" /> <div tal:on-error="string:${type(error.value).__name__} thrown at ${error.lineno}:${error.offset}."> <div tal:content="undefined" /> </div> <div tal:replace="undefined" tal:on-error="nothing" /> <div tal:content="undefined" tal:on-error="nothing" /> </body> </html> ������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/020.xml��������������������������������������������������0000664�0000000�0000000�00000000115�14604714122�0021706�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc><![CDATA[<&]>]]]></doc> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/021-translation-domain.pt��������������������������������0000664�0000000�0000000�00000000603�14604714122�0025335�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body i18n:domain="old"> <div i18n:domain="new" i18n:translate=""> Hello world! </div> <div i18n:translate=""> Hello world! </div> <div class="test" i18n:domain="new" i18n:attributes="class"> Hello world! </div> <div class="test" i18n:domain="new" i18n:attributes="class test_msgid"> Hello world! </div> </body> </html> �����������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/021.xml��������������������������������������������������0000664�0000000�0000000�00000000116�14604714122�0021710�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc><!-- a comment --></doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/022-switch.pt��������������������������������������������0000664�0000000�0000000�00000001066�14604714122�0023040�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div tal:switch="True"> <span tal:case="False">bad</span> <span tal:case="True">ok</span> <span tal:case="True">ok</span> <span tal:case="default">bad</span> <span tal:case="True">bad</span> ${default|string:ok} </div> <div tal:switch="True"> <span tal:case="False">bad</span> <span tal:case="default">ok</span> </div> <div tal:switch="3"> <span tal:case="1">bad</span> <span tal:case="2">bad</span> <span tal:case="default">ok</span> </div> </body> </html> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/022.xml��������������������������������������������������0000664�0000000�0000000�00000000120�14604714122�0021704�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc><!-- a comment ->--></doc> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/023-condition.pt�����������������������������������������0000664�0000000�0000000�00000000252�14604714122�0023522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body tal:condition="True"> <span tal:define="selector False" tal:condition="selector">bad</span> <span tal:condition="True">ok</span> </body> </html> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/023.xml��������������������������������������������������0000664�0000000�0000000�00000000117�14604714122�0021713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ENTITY e ""> ]> <doc>&e;</doc> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/024-namespace-elements.pt��������������������������������0000664�0000000�0000000�00000000402�14604714122�0025300�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <tal:first> <tal:second> ${'first'} </tal:second> second </tal:first> <tal:block condition="True"> ok </tal:block> <tal:block condition="False"> bad </tal:block> </body> </html> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/024.xml��������������������������������������������������0000664�0000000�0000000�00000000164�14604714122�0021716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (foo)> <!ELEMENT foo (#PCDATA)> <!ENTITY e "<foo></foo>"> ]> <doc>&e;</doc> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/025-repeat-whitespace.pt���������������������������������0000664�0000000�0000000�00000000751�14604714122�0025154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <ul> <li tal:repeat="i (1, 2, 3)" tal:content="i" /> <tal:item repeat="i (1, 2, 3)"><li tal:content="i" /></tal:item> <span tal:omit-tag="" tal:repeat="j (1, 2, 3)"><li tal:content="j" /></span> <tal:count> <tal:count-loop repeat="count (1, 2, 3)"> <span tal:replace="count" /><tal:comma condition="not repeat['count'].end">,</tal:comma> </tal:count-loop> </tal:count>. </ul> </body> </html> �����������������������chameleon-4.5.4/src/chameleon/tests/inputs/025.xml��������������������������������������������������0000664�0000000�0000000�00000000144�14604714122�0021715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (foo*)> <!ELEMENT foo (#PCDATA)> ]> <doc><foo/><foo></foo></doc> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/026-repeat-variable.pt�����������������������������������0000664�0000000�0000000�00000001135�14604714122�0024603�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<div xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal"> <ul> <li tal:attributes="class repeat['i'].even()+repeat['i'].odd()" name="${i}-${repeat.i.index}" tal:repeat="i range(3)"><span tal:replace="i" /></li> </ul> <ul> <li tal:attributes="class repeat['i'].even+repeat['i'].odd" tal:repeat="i range(3)"><span tal:replace="i" /></li> </ul> <ul> <li tal:repeat="i range(3)"><span tal:condition="repeat['i'].even" tal:replace="repeat['i'].even" /><span tal:condition="repeat['i'].odd" tal:replace="repeat['i'].odd" /></li> </ul> </div> �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/026.xml��������������������������������������������������0000664�0000000�0000000�00000000140�14604714122�0021712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (foo*)> <!ELEMENT foo EMPTY> ]> <doc><foo/><foo></foo></doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/027-attribute-replacement.pt�����������������������������0000664�0000000�0000000�00000000660�14604714122�0026043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<div xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal"> <span id="test" class="dummy" onClick="" tal:define="a 'abc'" tal:attributes="class default; style 'hij'; onClick 'alert();;'" tal:content="a + 'ghi'" /> <span tal:replace="'Hello World!'">Hello <b>Universe</b>!</span> <span tal:replace="'Hello World!'"><b>Hello Universe!</b></span> </div> ��������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/027.xml��������������������������������������������������0000664�0000000�0000000�00000000136�14604714122�0021720�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (foo*)> <!ELEMENT foo ANY> ]> <doc><foo/><foo></foo></doc> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/028-attribute-toggle.pt����������������������������������0000664�0000000�0000000�00000000400�14604714122�0025016�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<div xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal"> <option tal:attributes="selected True"></option> <option tal:attributes="selected False"></option> <option tal:attributes="selected None"></option> </div> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/028.xml��������������������������������������������������0000664�0000000�0000000�00000000123�14604714122�0021715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0"?> <!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc></doc> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/029-attribute-ordering.pt��������������������������������0000664�0000000�0000000�00000000316�14604714122�0025355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<div xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal"> <a rel="self" href="http://repoze.org" id="link-id" tal:attributes="href 'http://python.org'" /> </div> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/029.xml��������������������������������������������������0000664�0000000�0000000�00000000123�14604714122�0021716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc></doc> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/030-repeat-tuples.pt�������������������������������������0000664�0000000�0000000�00000000211�14604714122�0024317�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <div tal:repeat="(i, j) ((1, 2), (3, 4))"> ${repeat['i', 'j'].number}, ${i}, ${j} </div> </body> </html> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/030.xml��������������������������������������������������0000664�0000000�0000000�00000000125�14604714122�0021710�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version = "1.0"?> <!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc></doc> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/031-namespace-with-tal.pt��������������������������������0000664�0000000�0000000�00000000354�14604714122�0025221�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<div> <tal:example replace="'Hello World!'" /> <tal:example tal:replace="'Hello World!'" /> <tal:div content="'Hello World!'" /> <tal:multiple repeat="i range(3)" replace="i" /> <tal:div condition="True">True</tal:div> </div> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/031.xml��������������������������������������������������0000664�0000000�0000000�00000000144�14604714122�0021712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0' encoding="UTF-8"?> <!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc></doc> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/032-master-template.pt�����������������������������������0000664�0000000�0000000�00000001151�14604714122�0024637�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html i18n:domain="master" metal:define-macro="main" tal:define="content nothing"> <head> <title metal:define-slot="title" metal:define-macro="title" tal:define="has_title exists: title" tal:content="title if has_title else default">Master template
chameleon-4.5.4/src/chameleon/tests/inputs/032.xml000066400000000000000000000001441460471412200217130ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/033-use-macro-trivial.pt000066400000000000000000000001111460471412200250720ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/inputs/033.xml000066400000000000000000000001651460471412200217170ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/034-use-template-as-macro.pt000066400000000000000000000000711460471412200256420ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/inputs/034.xml000066400000000000000000000000671460471412200217210ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/035-use-macro-with-fill-slot.pt000066400000000000000000000002451460471412200263100ustar00rootroot00000000000000 ${kind} title chameleon-4.5.4/src/chameleon/tests/inputs/035.xml000066400000000000000000000000701460471412200217140ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/036-use-macro-inherits-dynamic-scope.pt000066400000000000000000000001631460471412200300100ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/inputs/036.xml000066400000000000000000000001111460471412200217110ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/037-use-macro-local-variable-scope.pt000066400000000000000000000003031460471412200274130ustar00rootroot00000000000000 ok chameleon-4.5.4/src/chameleon/tests/inputs/037.xml000066400000000000000000000001201460471412200217120ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/038-use-macro-globals.pt000066400000000000000000000002141460471412200250540ustar00rootroot00000000000000 ok chameleon-4.5.4/src/chameleon/tests/inputs/038.xml000066400000000000000000000001201460471412200217130ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/039-globals.pt000066400000000000000000000000541460471412200231660ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/inputs/039.xml000066400000000000000000000001111460471412200217140ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/040-macro-using-template-symbol.pt000066400000000000000000000015371460471412200271020ustar00rootroot00000000000000 ${foo}
chameleon-4.5.4/src/chameleon/tests/inputs/040.xml000066400000000000000000000001751460471412200217160ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/041-translate-nested-names.pt000066400000000000000000000007651460471412200261230ustar00rootroot00000000000000
Hello world!
Hello world!
Goodbye world!
chameleon-4.5.4/src/chameleon/tests/inputs/041.xml000066400000000000000000000001511460471412200217110ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/042-use-macro-fill-footer.pt000066400000000000000000000002201460471412200256430ustar00rootroot00000000000000 New footer chameleon-4.5.4/src/chameleon/tests/inputs/042.xml000066400000000000000000000001421460471412200217120ustar00rootroot00000000000000 ]> A chameleon-4.5.4/src/chameleon/tests/inputs/043-macro-nested-dynamic-vars.pt000066400000000000000000000006141460471412200265140ustar00rootroot00000000000000 ${title}
chameleon-4.5.4/src/chameleon/tests/inputs/043.xml000066400000000000000000000001541460471412200217160ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/044-tuple-define.pt000066400000000000000000000001161460471412200241170ustar00rootroot00000000000000 ${a}, ${b} chameleon-4.5.4/src/chameleon/tests/inputs/044.xml000066400000000000000000000002731460471412200217210ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/045-namespaces.pt000066400000000000000000000010471460471412200236620ustar00rootroot00000000000000 ]> ZZZ YYY XXX chameleon-4.5.4/src/chameleon/tests/inputs/045.xml000066400000000000000000000001701460471412200217160ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/046-extend-macro.pt000066400000000000000000000003451460471412200241320ustar00rootroot00000000000000 New footer chameleon-4.5.4/src/chameleon/tests/inputs/046.xml000066400000000000000000000001701460471412200217170ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/047-use-extended-macro.pt000066400000000000000000000001721460471412200252340ustar00rootroot00000000000000 Extended chameleon-4.5.4/src/chameleon/tests/inputs/047.xml000066400000000000000000000001001460471412200217110ustar00rootroot00000000000000 ]> X Y chameleon-4.5.4/src/chameleon/tests/inputs/048-use-extended-macro-fill-original.pt000066400000000000000000000002361460471412200277640ustar00rootroot00000000000000 Extended footer chameleon-4.5.4/src/chameleon/tests/inputs/048.xml000066400000000000000000000000751460471412200217250ustar00rootroot00000000000000 ]> ] chameleon-4.5.4/src/chameleon/tests/inputs/049-entities-in-attributes.pt000066400000000000000000000007071460471412200261650ustar00rootroot00000000000000
    
    
  
  
    
  
chameleon-4.5.4/src/chameleon/tests/inputs/058.xml000066400000000000000000000001571460471412200217270ustar00rootroot00000000000000

]>

chameleon-4.5.4/src/chameleon/tests/inputs/059-embedded-javascript.pt000066400000000000000000000002561460471412200254460ustar00rootroot00000000000000
  
    test
    test
  

chameleon-4.5.4/src/chameleon/tests/inputs/059.xml000066400000000000000000000003431460471412200217250ustar00rootroot00000000000000


]>





chameleon-4.5.4/src/chameleon/tests/inputs/060-macro-with-multiple-same-slots.pt000066400000000000000000000003371460471412200275310ustar00rootroot00000000000000
  
    <metal:title define-slot="title">Untitled</metal:title>
  
  
    

Untitled

chameleon-4.5.4/src/chameleon/tests/inputs/060.xml000066400000000000000000000001031460471412200217070ustar00rootroot00000000000000 ]> X Y chameleon-4.5.4/src/chameleon/tests/inputs/061-fill-one-slot-but-two-defined.pt000066400000000000000000000002301460471412200272110ustar00rootroot00000000000000 My document chameleon-4.5.4/src/chameleon/tests/inputs/061.xml000066400000000000000000000001021460471412200217070ustar00rootroot00000000000000 ]> £ chameleon-4.5.4/src/chameleon/tests/inputs/062-comments-and-expressions.pt000066400000000000000000000005641460471412200265120ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/inputs/062.xml000066400000000000000000000001271460471412200217170ustar00rootroot00000000000000 ]> เจมส์ chameleon-4.5.4/src/chameleon/tests/inputs/063-continuation.pt000066400000000000000000000001031460471412200242450ustar00rootroot00000000000000
${foo}
chameleon-4.5.4/src/chameleon/tests/inputs/063.xml000066400000000000000000000001541460471412200217200ustar00rootroot00000000000000 ]> <เจมส์> chameleon-4.5.4/src/chameleon/tests/inputs/064-tags-and-special-characters.pt000066400000000000000000000001051460471412200267670ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/inputs/064.xml000066400000000000000000000001171460471412200217200ustar00rootroot00000000000000 ]> 𐀀􏿽 chameleon-4.5.4/src/chameleon/tests/inputs/065-use-macro-in-fill.pt000066400000000000000000000004241460471412200247660ustar00rootroot00000000000000 <div metal:fill-slot="content">Content</div> </html>��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/065.xml��������������������������������������������������0000664�0000000�0000000�00000000121�14604714122�0021714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ENTITY e "<"> <!ELEMENT doc (#PCDATA)> ]> <doc></doc> �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/066-load-expression.pt�����������������������������������0000664�0000000�0000000�00000000125�14604714122�0024656�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html tal:define="hello_world load: hello_world.pt" metal:use-macro="hello_world" /> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/066.xml��������������������������������������������������0000664�0000000�0000000�00000000233�14604714122�0021721�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ATTLIST doc a1 CDATA #IMPLIED> <!-- 34 is double quote --> <!ENTITY e1 """> ]> <doc a1="&e1;"></doc> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/067-attribute-decode.pt����������������������������������0000664�0000000�0000000�00000000311�14604714122�0024764�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <img src="#" tal:attributes="class 1 > 0 and 'up' or 0 < 1 and 'down';" /> <img src="#" tal:attributes="class 0 > 1 and 'up' or 0 < 1 and 'down';" /> </body> </html> �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/067.xml��������������������������������������������������0000664�0000000�0000000�00000000101�14604714122�0021714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> ]> <doc> </doc> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/068-less-than-greater-than-in-attributes.pt��������������0000664�0000000�0000000�00000000322�14604714122�0030610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html> <body> <span tal:content="string:0 < 1 or 0 > 1" /> <span tal:content="structure string:0 < 1 or 0 > 1" /> <span class="0 < 1 or 0 > 1" /> <span>0 < 1 or 0 > 1</span> </body> </html> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/068.xml��������������������������������������������������0000664�0000000�0000000�00000000124�14604714122�0021722�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE doc [ <!ELEMENT doc (#PCDATA)> <!ENTITY e " "> ]> <doc>&e;</doc> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������chameleon-4.5.4/src/chameleon/tests/inputs/069-translation-domain-and-macro.pt����������������������0000664�0000000�0000000�00000000247�14604714122�0027214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<html metal:use-macro="load('032-master-template.pt').macros['main']"> <title metal:fill-slot="title" i18n:domain="test" i18n:translate="title">Title chameleon-4.5.4/src/chameleon/tests/inputs/069.xml000066400000000000000000000001351460471412200217250ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/070-translation-domain-and-use-macro.pt000066400000000000000000000002471460471412200277760ustar00rootroot00000000000000 Title chameleon-4.5.4/src/chameleon/tests/inputs/070.xml000066400000000000000000000001211460471412200217100ustar00rootroot00000000000000"> %e; ]> chameleon-4.5.4/src/chameleon/tests/inputs/071-html-attribute-defaults.pt000066400000000000000000000010531460471412200263110ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/inputs/071.xml000066400000000000000000000001321460471412200217130ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/072-repeat-interpolation.pt000066400000000000000000000003731460471412200257110ustar00rootroot00000000000000
  • ${i}
  • ${i}
chameleon-4.5.4/src/chameleon/tests/inputs/072.xml000066400000000000000000000001351460471412200217170ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/073-utf8-encoded.pt000066400000000000000000000002231460471412200240240ustar00rootroot00000000000000 ${'my title'} — ${'my site'} chameleon-4.5.4/src/chameleon/tests/inputs/073.xml000066400000000000000000000001361460471412200217210ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/074-encoded-template.pt000066400000000000000000000002511460471412200247530ustar00rootroot00000000000000 ${'my title'} — ${'my site'} chameleon-4.5.4/src/chameleon/tests/inputs/074.xml000066400000000000000000000001361460471412200217220ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/075-nested-macros.pt000066400000000000000000000004321460471412200243070ustar00rootroot00000000000000 foo chameleon-4.5.4/src/chameleon/tests/inputs/075.xml000066400000000000000000000001401460471412200217160ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/076-nested-macro-override.pt000066400000000000000000000002121460471412200257360ustar00rootroot00000000000000 bar chameleon-4.5.4/src/chameleon/tests/inputs/076.xml000066400000000000000000000003001460471412200217150ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/077-i18n-attributes.pt000066400000000000000000000001501460471412200245050ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/inputs/077.xml000066400000000000000000000001351460471412200217240ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/078-tags-and-newlines.pt000066400000000000000000000014361460471412200250730ustar00rootroot00000000000000 , chameleon-4.5.4/src/chameleon/tests/inputs/078.xml000066400000000000000000000001441460471412200217250ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/079-implicit-i18n.pt000066400000000000000000000010471460471412200241410ustar00rootroot00000000000000 Welcome

Welcome

An edge case: ${. Site logo Site logo
boo foo.
bar.
${Message()}
chameleon-4.5.4/src/chameleon/tests/inputs/079.xml000066400000000000000000000001451460471412200217270ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/080-xmlns-namespace-on-tal.pt000066400000000000000000000003721460471412200260250ustar00rootroot00000000000000 Hello world chameleon-4.5.4/src/chameleon/tests/inputs/080.xml000066400000000000000000000001371460471412200217200ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/081-load-spec.pt000066400000000000000000000001071460471412200234060ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/inputs/081.xml000066400000000000000000000002141460471412200217150ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/082-load-spec-computed.pt000066400000000000000000000001421460471412200252240ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/inputs/082.xml000066400000000000000000000001321460471412200217150ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/083-template-dict-to-macro.pt000066400000000000000000000001441460471412200260150ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/inputs/083.xml000066400000000000000000000001451460471412200217220ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/084-interpolation-in-cdata.pt000066400000000000000000000001711460471412200261100ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/inputs/084.xml000066400000000000000000000000661460471412200217250ustar00rootroot00000000000000]> chameleon-4.5.4/src/chameleon/tests/inputs/085-nested-translation.pt000066400000000000000000000004651460471412200253700ustar00rootroot00000000000000 Welcome

Welcome

Click here to continue.

chameleon-4.5.4/src/chameleon/tests/inputs/085.xml000066400000000000000000000001461460471412200217250ustar00rootroot00000000000000 "> ]> &e; chameleon-4.5.4/src/chameleon/tests/inputs/086-self-closing.pt000066400000000000000000000003731460471412200241360ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/inputs/086.xml000066400000000000000000000001441460471412200217240ustar00rootroot00000000000000 "> ]> &e; chameleon-4.5.4/src/chameleon/tests/inputs/087-code-blocks.pt000066400000000000000000000006731460471412200237420ustar00rootroot00000000000000
Please input a number from the range ${", ".join(numbers)}.
41 + 1 = ${function(41)}.
chameleon-4.5.4/src/chameleon/tests/inputs/087.xml000066400000000000000000000001531460471412200217250ustar00rootroot00000000000000 ]> &e; chameleon-4.5.4/src/chameleon/tests/inputs/088-python-newlines.pt000066400000000000000000000000771460471412200247170ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/inputs/088.xml000066400000000000000000000001271460471412200217270ustar00rootroot00000000000000 "> ]> &e; chameleon-4.5.4/src/chameleon/tests/inputs/089-load-fallback.pt000066400000000000000000000001741460471412200242270ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/inputs/089.xml000066400000000000000000000001541460471412200217300ustar00rootroot00000000000000 ]> &e; chameleon-4.5.4/src/chameleon/tests/inputs/090-tuple-expression.pt000066400000000000000000000004411460471412200250660ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/inputs/090.xml000066400000000000000000000002261460471412200217200ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/091-repeat-none.pt000066400000000000000000000001131460471412200237520ustar00rootroot00000000000000
error
chameleon-4.5.4/src/chameleon/tests/inputs/091.xml000066400000000000000000000002651460471412200217240ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/092.xml000066400000000000000000000001461460471412200217230ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/093.xml000066400000000000000000000000741460471412200217240ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/094.xml000066400000000000000000000001601460471412200217210ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/095.xml000066400000000000000000000002151460471412200217230ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/096.xml000066400000000000000000000001431460471412200217240ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/097.xml000066400000000000000000000002351460471412200217270ustar00rootroot00000000000000 %e; ]> chameleon-4.5.4/src/chameleon/tests/inputs/098.xml000066400000000000000000000001071460471412200217260ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/099.xml000066400000000000000000000001441460471412200217300ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/100.xml000066400000000000000000000001451460471412200217100ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/101-unclosed-tags.html000066400000000000000000000001031460471412200246150ustar00rootroot00000000000000



Hello world

chameleon-4.5.4/src/chameleon/tests/inputs/101.xml000066400000000000000000000001211460471412200217030ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/102-unquoted-attributes.html000066400000000000000000000001241460471412200261010ustar00rootroot00000000000000

Hello world

chameleon-4.5.4/src/chameleon/tests/inputs/102.xml000066400000000000000000000001471460471412200217140ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/103-simple-attribute.html000066400000000000000000000003201460471412200253420ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/inputs/103.xml000066400000000000000000000001051460471412200217070ustar00rootroot00000000000000 ]> <doc> chameleon-4.5.4/src/chameleon/tests/inputs/104.xml000066400000000000000000000001451460471412200217140ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/105.xml000066400000000000000000000001501460471412200217110ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/106.xml000066400000000000000000000001511460471412200217130ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/107.xml000066400000000000000000000001511460471412200217140ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/108.xml000066400000000000000000000001711460471412200217170ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/109.xml000066400000000000000000000001421460471412200217160ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/110.xml000066400000000000000000000002011460471412200217020ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/111.xml000066400000000000000000000001731460471412200217130ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/112.xml000066400000000000000000000001311460471412200217060ustar00rootroot00000000000000 ]>
chameleon-4.5.4/src/chameleon/tests/inputs/113.xml000066400000000000000000000001331460471412200217110ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/114.xml000066400000000000000000000001401460471412200217100ustar00rootroot00000000000000 "> ]> &e; chameleon-4.5.4/src/chameleon/tests/inputs/115.xml000066400000000000000000000001471460471412200217200ustar00rootroot00000000000000 ]> &e1; chameleon-4.5.4/src/chameleon/tests/inputs/116.xml000066400000000000000000000001121460471412200217110ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/117.xml000066400000000000000000000001261460471412200217170ustar00rootroot00000000000000 ]> ] chameleon-4.5.4/src/chameleon/tests/inputs/118.xml000066400000000000000000000001271460471412200217210ustar00rootroot00000000000000 ]> ] chameleon-4.5.4/src/chameleon/tests/inputs/119.xml000066400000000000000000000001021460471412200217130ustar00rootroot00000000000000 ]> chameleon-4.5.4/src/chameleon/tests/inputs/120-translation-context.pt000066400000000000000000000004271460471412200255560ustar00rootroot00000000000000
Hello world!
Tab
chameleon-4.5.4/src/chameleon/tests/inputs/121-translation-comment.pt000066400000000000000000000001641460471412200255330ustar00rootroot00000000000000

Hello world!

chameleon-4.5.4/src/chameleon/tests/inputs/122-translation-ignore.pt000066400000000000000000000003411460471412200253520ustar00rootroot00000000000000

Hello world!

Python is a programming language. chameleon-4.5.4/src/chameleon/tests/inputs/123-html5-data-attributes.pt000066400000000000000000000001321460471412200256560ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/inputs/124-translation-target.pt000066400000000000000000000003111460471412200253540ustar00rootroot00000000000000

Hello world!

It's a big world.

${target_language}

chameleon-4.5.4/src/chameleon/tests/inputs/125-macro-translation-ordering.pt000066400000000000000000000005621460471412200270070ustar00rootroot00000000000000

h1

h2

chameleon-4.5.4/src/chameleon/tests/inputs/126-define-escaping.pt000066400000000000000000000003501460471412200245600ustar00rootroot00000000000000 ${s1} ${s2} ${structure: s1}amp; ${structure: s2}amp; chameleon-4.5.4/src/chameleon/tests/inputs/127-tags-and-attributes-special-chars.pt000066400000000000000000000001171460471412200301370ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/inputs/128-translation-macro-name.pt000066400000000000000000000003441460471412200261170ustar00rootroot00000000000000
Fancy link Now follows a fancy link: chameleon-4.5.4/src/chameleon/tests/inputs/237-double-define.pt000066400000000000000000000002551460471412200242500ustar00rootroot00000000000000

${local_value}

OUTSIDE: No value

chameleon-4.5.4/src/chameleon/tests/inputs/238-macroname.pt000066400000000000000000000003331460471412200235060ustar00rootroot00000000000000

name

chameleon-4.5.4/src/chameleon/tests/inputs/greeting.pt000066400000000000000000000000511460471412200230330ustar00rootroot00000000000000
Hello, ${name | 'undefined'}.
chameleon-4.5.4/src/chameleon/tests/inputs/hello_world.pt000066400000000000000000000000671460471412200235500ustar00rootroot00000000000000 ${'Hello world!'} chameleon-4.5.4/src/chameleon/tests/inputs/hello_world.txt000066400000000000000000000000221460471412200237330ustar00rootroot00000000000000${'Hello world!'} chameleon-4.5.4/src/chameleon/tests/inputs/multinode-implicit-i18n.pt000066400000000000000000000000571460471412200256220ustar00rootroot00000000000000 Foo ${message} chameleon-4.5.4/src/chameleon/tests/outputs/000077500000000000000000000000001460471412200210675ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/outputs/001.html000066400000000000000000000001251460471412200222530ustar00rootroot00000000000000 Hello world! Hello world! Goodbye world! chameleon-4.5.4/src/chameleon/tests/outputs/001.pt000066400000000000000000000000751460471412200217360ustar00rootroot00000000000000 Hello world! ok chameleon-4.5.4/src/chameleon/tests/outputs/001.txt000066400000000000000000000000211460471412200221210ustar00rootroot00000000000000<&> chameleon-4.5.4/src/chameleon/tests/outputs/002.pt000066400000000000000000000002771460471412200217430ustar00rootroot00000000000000
Hello! Hello.
Goodbye! Goodbye.
ok chameleon-4.5.4/src/chameleon/tests/outputs/003.pt000066400000000000000000000005431460471412200217400ustar00rootroot00000000000000
Hello world!
Hello world!
1 2
Hello world!
Hello world!
3
Hello world!
5 6
Hello world!
1
1.0
True
False
0
<div>Hello world!</div> chameleon-4.5.4/src/chameleon/tests/outputs/004.pt000066400000000000000000000012401460471412200217340ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/outputs/005.pt000066400000000000000000000003401460471412200217350ustar00rootroot00000000000000 Default Default True False Computed default chameleon-4.5.4/src/chameleon/tests/outputs/006.pt000066400000000000000000000003741460471412200217450ustar00rootroot00000000000000 copyright (c) 2010 copyright (c) 2010 copyright (c) 2010 $ignored <type 'str'> chameleon-4.5.4/src/chameleon/tests/outputs/007.pt000066400000000000000000000006231460471412200217430ustar00rootroot00000000000000 Hello world!
Hello world!
Hello world!
<type 'str'> && Hello world $leftalone ${'fred'} $fred $${'fred'} $$fred
Hello world
${} is ignored.
chameleon-4.5.4/src/chameleon/tests/outputs/008.pt000066400000000000000000000003241460471412200217420ustar00rootroot00000000000000 {}
static
static
static
static
nothing
chameleon-4.5.4/src/chameleon/tests/outputs/009.pt000066400000000000000000000000761460471412200217470ustar00rootroot00000000000000
Hello world!
chameleon-4.5.4/src/chameleon/tests/outputs/010.pt000066400000000000000000000002441460471412200217340ustar00rootroot00000000000000
1 < 2
2 < 3, 2&3, 2<3, 2>3
3 < 4
4 < 5
Hello world!
chameleon-4.5.4/src/chameleon/tests/outputs/011-en.pt000066400000000000000000000004641460471412200223410ustar00rootroot00000000000000
Message ('message' translation into 'en')
Message ('message' translation into 'en')
Message ('message' translation into 'en')
Message ('message' translation into 'en')
Message ('message' translation into 'en') chameleon-4.5.4/src/chameleon/tests/outputs/011.pt000066400000000000000000000002121460471412200217300ustar00rootroot00000000000000
Message
Message
Message
Message
Message chameleon-4.5.4/src/chameleon/tests/outputs/012-en.pt000066400000000000000000000007611460471412200223420ustar00rootroot00000000000000
Hello world! ('Hello world!' translation into 'en')
Hello world! ('hello_world' translation into 'en')
Hello world! ('Hello world!' translation into 'en')
Hello world! Goodbye planet! ('Hello ${first}! Goodbye ${second}!' translation into 'en')
Hello world! Goodbye planet! ('hello_goodbye' translation into 'en')
chameleon-4.5.4/src/chameleon/tests/outputs/012.pt000066400000000000000000000004151460471412200217360ustar00rootroot00000000000000
Hello world!
Hello world!
Hello world!
Hello world! Goodbye planet!
Hello world! Goodbye planet!
chameleon-4.5.4/src/chameleon/tests/outputs/013.pt000066400000000000000000000004251460471412200217400ustar00rootroot00000000000000
[1,1] [1,2]
[2,1] [2,2]
chameleon-4.5.4/src/chameleon/tests/outputs/014.pt000066400000000000000000000002641460471412200217420ustar00rootroot00000000000000 [3,3] [3,4] [4,3] [4,4] chameleon-4.5.4/src/chameleon/tests/outputs/015-en.pt000066400000000000000000000002631460471412200223420ustar00rootroot00000000000000
Price: Per kilo 12.5 ('Per kilo ${amount}' translation into 'en') ('Price: ${price}' translation into 'en')
chameleon-4.5.4/src/chameleon/tests/outputs/015.pt000066400000000000000000000001341460471412200217370ustar00rootroot00000000000000
Price: Per kilo 12.5
chameleon-4.5.4/src/chameleon/tests/outputs/016-en.pt000066400000000000000000000005731460471412200223470ustar00rootroot00000000000000
Hello world! ('Hello world!' translation into 'en')
Hello world! ('Hello world!' translation into 'en') Hello world! ('hello_world' translation into 'en') Hello world! ('Hello world!' translation into 'en') Hello world! ('hello_world' translation into 'en') chameleon-4.5.4/src/chameleon/tests/outputs/016.pt000066400000000000000000000002721460471412200217430ustar00rootroot00000000000000
Hello world!
Hello world! Hello world! Hello world! Hello world! chameleon-4.5.4/src/chameleon/tests/outputs/017.pt000066400000000000000000000002401460471412200217370ustar00rootroot00000000000000 Hello world! 1 Hello world! 23 4Hello world!
Hello world!
Hello world! Hello world! chameleon-4.5.4/src/chameleon/tests/outputs/018-en.pt000066400000000000000000000002611460471412200223430ustar00rootroot00000000000000
october ('october' translation into 'en') 1982 ('1982' translation into 'en') ('${monthname} ${year}' translation into 'en')
chameleon-4.5.4/src/chameleon/tests/outputs/018.pt000066400000000000000000000001011460471412200217340ustar00rootroot00000000000000
october 1982
chameleon-4.5.4/src/chameleon/tests/outputs/019.pt000066400000000000000000000002421460471412200217430ustar00rootroot00000000000000 Hello world! Hello world!1 2Hello world! Hello world!3 Hello world!5 6Hello world! 1 1.0 True chameleon-4.5.4/src/chameleon/tests/outputs/020.pt000066400000000000000000000002051460471412200217320ustar00rootroot00000000000000
NameError thrown at 5:24.
chameleon-4.5.4/src/chameleon/tests/outputs/021-en.pt000066400000000000000000000006361460471412200223430ustar00rootroot00000000000000
Hello world! ('Hello world!' translation into 'en' with domain 'new')
Hello world! ('Hello world!' translation into 'en' with domain 'old')
Hello world!
Hello world!
chameleon-4.5.4/src/chameleon/tests/outputs/021.pt000066400000000000000000000003041460471412200217330ustar00rootroot00000000000000
Hello world!
Hello world!
Hello world!
Hello world!
chameleon-4.5.4/src/chameleon/tests/outputs/022.pt000066400000000000000000000002631460471412200217400ustar00rootroot00000000000000
ok ok
ok
ok
chameleon-4.5.4/src/chameleon/tests/outputs/023.pt000066400000000000000000000000671460471412200217430ustar00rootroot00000000000000 ok chameleon-4.5.4/src/chameleon/tests/outputs/024.pt000066400000000000000000000001151460471412200217360ustar00rootroot00000000000000 first second ok chameleon-4.5.4/src/chameleon/tests/outputs/025.pt000066400000000000000000000003631460471412200217440ustar00rootroot00000000000000
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 1, 2, 3 .
chameleon-4.5.4/src/chameleon/tests/outputs/026.pt000066400000000000000000000005331460471412200217440ustar00rootroot00000000000000
  • 0
  • 1
  • 2
  • 0
  • 1
  • 2
  • even
  • odd
  • even
chameleon-4.5.4/src/chameleon/tests/outputs/027.pt000066400000000000000000000002551460471412200217460ustar00rootroot00000000000000
abcghi Hello World! Hello World!
chameleon-4.5.4/src/chameleon/tests/outputs/028.pt000066400000000000000000000002021460471412200217370ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/outputs/029.pt000066400000000000000000000001531460471412200217450ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/outputs/030.pt000066400000000000000000000001501460471412200217320ustar00rootroot00000000000000
1, 1, 2
2, 3, 4
chameleon-4.5.4/src/chameleon/tests/outputs/031.pt000066400000000000000000000001071460471412200217350ustar00rootroot00000000000000
Hello World! Hello World! Hello World! 012 True
chameleon-4.5.4/src/chameleon/tests/outputs/032.pt000066400000000000000000000002741460471412200217430ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/033.pt000066400000000000000000000002741460471412200217440ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/034.pt000066400000000000000000000002741460471412200217450ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/035.pt000066400000000000000000000002751460471412200217470ustar00rootroot00000000000000 New title
chameleon-4.5.4/src/chameleon/tests/outputs/036.pt000066400000000000000000000002661460471412200217500ustar00rootroot00000000000000 New title
chameleon-4.5.4/src/chameleon/tests/outputs/037.pt000066400000000000000000000002621460471412200217450ustar00rootroot00000000000000 Master template
ok
chameleon-4.5.4/src/chameleon/tests/outputs/038.pt000066400000000000000000000000671460471412200217510ustar00rootroot00000000000000 ok chameleon-4.5.4/src/chameleon/tests/outputs/039.pt000066400000000000000000000000001460471412200217350ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/outputs/040.pt000066400000000000000000000007121460471412200217370ustar00rootroot00000000000000 foo chameleon-4.5.4/src/chameleon/tests/outputs/041.pt000066400000000000000000000002131460471412200217340ustar00rootroot00000000000000
Hello world!
Hello world!
Goodbye
chameleon-4.5.4/src/chameleon/tests/outputs/042.pt000066400000000000000000000003131460471412200217360ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/043.pt000066400000000000000000000000701460471412200217370ustar00rootroot00000000000000 My title chameleon-4.5.4/src/chameleon/tests/outputs/044.pt000066400000000000000000000000531460471412200217410ustar00rootroot00000000000000 a, b chameleon-4.5.4/src/chameleon/tests/outputs/045.pt000066400000000000000000000007171460471412200217510ustar00rootroot00000000000000 ]> ZZZ YYY XXX chameleon-4.5.4/src/chameleon/tests/outputs/046.pt000066400000000000000000000003301460471412200217410ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/047.pt000066400000000000000000000003311460471412200217430ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/048.pt000066400000000000000000000003201460471412200217420ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/049.pt000066400000000000000000000003241460471412200217470ustar00rootroot00000000000000
amp=&amp; lt=&lt;
amp=& lt=<
chameleon-4.5.4/src/chameleon/tests/outputs/059.pt000066400000000000000000000002161460471412200217500ustar00rootroot00000000000000 test test chameleon-4.5.4/src/chameleon/tests/outputs/060.pt000066400000000000000000000001471460471412200217430ustar00rootroot00000000000000 Untitled

Untitled

chameleon-4.5.4/src/chameleon/tests/outputs/061.pt000066400000000000000000000001541460471412200217420ustar00rootroot00000000000000 My document

My document

chameleon-4.5.4/src/chameleon/tests/outputs/062.pt000066400000000000000000000004321460471412200217420ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/outputs/063.pt000066400000000000000000000000201460471412200217340ustar00rootroot00000000000000
2
chameleon-4.5.4/src/chameleon/tests/outputs/064.pt000066400000000000000000000000511460471412200217410ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/outputs/065.pt000066400000000000000000000002521460471412200217450ustar00rootroot00000000000000 Title
Content
chameleon-4.5.4/src/chameleon/tests/outputs/066.pt000066400000000000000000000000631460471412200217460ustar00rootroot00000000000000 Hello world! chameleon-4.5.4/src/chameleon/tests/outputs/067.pt000066400000000000000000000001421460471412200217450ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/outputs/068.pt000066400000000000000000000002541460471412200217520ustar00rootroot00000000000000 0 < 1 or 0 > 1 0 < 1 or 0 > 1 0 < 1 or 0 > 1 chameleon-4.5.4/src/chameleon/tests/outputs/069-en.pt000066400000000000000000000003441460471412200223530ustar00rootroot00000000000000 Title ('title' translation into 'en' with domain 'test')
chameleon-4.5.4/src/chameleon/tests/outputs/069.pt000066400000000000000000000002611460471412200217510ustar00rootroot00000000000000 Title
chameleon-4.5.4/src/chameleon/tests/outputs/070-en.pt000066400000000000000000000003441460471412200223430ustar00rootroot00000000000000 Title ('title' translation into 'en' with domain 'test')
chameleon-4.5.4/src/chameleon/tests/outputs/070.pt000066400000000000000000000002611460471412200217410ustar00rootroot00000000000000 Title
chameleon-4.5.4/src/chameleon/tests/outputs/071.pt000066400000000000000000000004731460471412200217470ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/outputs/072.pt000066400000000000000000000003361460471412200217460ustar00rootroot00000000000000
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
chameleon-4.5.4/src/chameleon/tests/outputs/073.pt000066400000000000000000000002111460471412200217370ustar00rootroot00000000000000 my title — my site chameleon-4.5.4/src/chameleon/tests/outputs/074.pt000066400000000000000000000002371460471412200217500ustar00rootroot00000000000000 my title — my site chameleon-4.5.4/src/chameleon/tests/outputs/075.pt000066400000000000000000000002601460471412200217450ustar00rootroot00000000000000 Master template
foo
chameleon-4.5.4/src/chameleon/tests/outputs/076.pt000066400000000000000000000003151460471412200217470ustar00rootroot00000000000000 Master template
bar
chameleon-4.5.4/src/chameleon/tests/outputs/077-en.pt000066400000000000000000000001701460471412200223470ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/outputs/077.pt000066400000000000000000000001051460471412200217450ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/tests/outputs/078.pt000066400000000000000000000004331460471412200217520ustar00rootroot00000000000000 1, 2, 3 chameleon-4.5.4/src/chameleon/tests/outputs/079-en.pt000066400000000000000000000012001460471412200223440ustar00rootroot00000000000000 Welcome ('Welcome' translation into 'en')

Welcome ('Welcome' translation into 'en')

An edge case: ${. ('An edge case: ${.' translation into 'en') Site logo ('Site logo' translation into 'en') Site logo ('Site logo' translation into 'en')
boo foo. ('boo foo.' translation into 'en')
bar. ('bar.' translation into 'en')
Message ('message' translation into 'fr')
chameleon-4.5.4/src/chameleon/tests/outputs/079.pt000066400000000000000000000006061460471412200217550ustar00rootroot00000000000000 Welcome

Welcome

An edge case: ${. Site logo Site logo
boo foo.
bar.
Message ('message' translation into 'fr')
chameleon-4.5.4/src/chameleon/tests/outputs/080.pt000066400000000000000000000000211460471412200217340ustar00rootroot00000000000000 Hello world chameleon-4.5.4/src/chameleon/tests/outputs/081.pt000066400000000000000000000000621460471412200217420ustar00rootroot00000000000000 Hello world! chameleon-4.5.4/src/chameleon/tests/outputs/082.pt000066400000000000000000000000621460471412200217430ustar00rootroot00000000000000 Hello world! chameleon-4.5.4/src/chameleon/tests/outputs/083.pt000066400000000000000000000002731460471412200217500ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/084.pt000066400000000000000000000001641460471412200217500ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/outputs/085-en.pt000066400000000000000000000004401460471412200223460ustar00rootroot00000000000000 Welcome

Welcome

Click here ('Click here' translation into 'en' with domain 'new') to continue. ('${click_here} to continue.' translation into 'en' with domain 'new')

chameleon-4.5.4/src/chameleon/tests/outputs/085.pt000066400000000000000000000002421460471412200217460ustar00rootroot00000000000000 Welcome

Welcome

Click here to continue.

chameleon-4.5.4/src/chameleon/tests/outputs/086.pt000066400000000000000000000004101460471412200217440ustar00rootroot00000000000000 Master template
chameleon-4.5.4/src/chameleon/tests/outputs/087.pt000066400000000000000000000003271460471412200217540ustar00rootroot00000000000000
  • 1
  • 2
  • 3
  • 5
  • 7
  • 9
Please input a number from the range 1, 2, 3, 4, 5, 6, 7, 8, 9.
41 + 1 = 42.
chameleon-4.5.4/src/chameleon/tests/outputs/088.pt000066400000000000000000000000101460471412200217420ustar00rootroot00000000000000a, b, c chameleon-4.5.4/src/chameleon/tests/outputs/089.pt000066400000000000000000000000621460471412200217520ustar00rootroot00000000000000 Hello world! chameleon-4.5.4/src/chameleon/tests/outputs/090.pt000066400000000000000000000004121460471412200217410ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/outputs/091.pt000066400000000000000000000000431460471412200217420ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/outputs/101.html000066400000000000000000000001031460471412200222500ustar00rootroot00000000000000



Hello world

chameleon-4.5.4/src/chameleon/tests/outputs/102.html000066400000000000000000000001241460471412200222540ustar00rootroot00000000000000

Hello world

chameleon-4.5.4/src/chameleon/tests/outputs/103.html000066400000000000000000000003201460471412200222530ustar00rootroot00000000000000 chameleon-4.5.4/src/chameleon/tests/outputs/120-en.pt000066400000000000000000000004551460471412200223420ustar00rootroot00000000000000
Hello world! ('Hello world!' translation into 'en')
chameleon-4.5.4/src/chameleon/tests/outputs/120.pt000066400000000000000000000002071460471412200217350ustar00rootroot00000000000000
Hello world!
Tab
chameleon-4.5.4/src/chameleon/tests/outputs/121.pt000066400000000000000000000000741460471412200217400ustar00rootroot00000000000000

Hello world!

chameleon-4.5.4/src/chameleon/tests/outputs/122.pt000066400000000000000000000002501460471412200217350ustar00rootroot00000000000000

Hello world!

Python is a programming language. chameleon-4.5.4/src/chameleon/tests/outputs/123.pt000066400000000000000000000001101460471412200217310ustar00rootroot00000000000000
Hello world!
chameleon-4.5.4/src/chameleon/tests/outputs/124-en.pt000066400000000000000000000002721460471412200223430ustar00rootroot00000000000000

Hello world! ('Hello world!' translation into 'fr')

It's a big world. ('It's a big world.' translation into 'en')

en

chameleon-4.5.4/src/chameleon/tests/outputs/124.pt000066400000000000000000000002141460471412200217370ustar00rootroot00000000000000

Hello world! ('Hello world!' translation into 'fr')

It's a big world.

chameleon-4.5.4/src/chameleon/tests/outputs/125.pt000066400000000000000000000002151460471412200217410ustar00rootroot00000000000000

h1

h2

chameleon-4.5.4/src/chameleon/tests/outputs/126.pt000066400000000000000000000001601460471412200217410ustar00rootroot00000000000000 & & & & chameleon-4.5.4/src/chameleon/tests/outputs/127.pt000066400000000000000000000000631460471412200217440ustar00rootroot00000000000000
chameleon-4.5.4/src/chameleon/tests/outputs/128-en.pt000066400000000000000000000001721460471412200223460ustar00rootroot00000000000000
Fancy link Now follows a fancy link: Fancy link ('text_with_link' translation into 'en') chameleon-4.5.4/src/chameleon/tests/outputs/128.pt000066400000000000000000000001211460471412200217400ustar00rootroot00000000000000Fancy link Now follows a fancy link: Fancy link chameleon-4.5.4/src/chameleon/tests/outputs/237.pt000066400000000000000000000000471460471412200217500ustar00rootroot00000000000000

33

OUTSIDE: No value

chameleon-4.5.4/src/chameleon/tests/outputs/238.pt000066400000000000000000000000421460471412200217440ustar00rootroot00000000000000

macros['page']

chameleon-4.5.4/src/chameleon/tests/outputs/greeting.pt000066400000000000000000000000351460471412200232360ustar00rootroot00000000000000
Hello, undefined.
chameleon-4.5.4/src/chameleon/tests/outputs/hello_world.pt000066400000000000000000000000621460471412200237440ustar00rootroot00000000000000 Hello world! chameleon-4.5.4/src/chameleon/tests/outputs/hello_world.txt000066400000000000000000000000151460471412200241360ustar00rootroot00000000000000Hello world! chameleon-4.5.4/src/chameleon/tests/outputs/multinode-en.pt000066400000000000000000000001671460471412200240400ustar00rootroot00000000000000 Foo Message ('message' translation into 'en') ('Foo ${message}' translation into 'en') chameleon-4.5.4/src/chameleon/tests/outputs/multinode.pt000066400000000000000000000000541460471412200234330ustar00rootroot00000000000000 Foo Message chameleon-4.5.4/src/chameleon/tests/test_bools_plus_sniffing.py000066400000000000000000000246141460471412200250300ustar00rootroot00000000000000import difflib import unittest from chameleon import PageTemplate xml_bytes = b"""\ """ xml_w_enc_bytes = b"""\ """ html5_bytes = b"""\ Title of document
""" html5_w_ct_n_enc_bytes = b"""\ Title of document
""" class BaseTestCase(unittest.TestCase): def get_template(self, text): template = PageTemplate(text) return template def get_template_bytes(self): return self.get_template(self.input_bytes) def get_template_str(self): return self.get_template(self.input_bytes.decode('utf-8')) def assert_same(self, s1, s2): L1 = s1.splitlines() L1 = list(filter(None, [' '.join(x.split()).strip() for x in L1])) L2 = s2.splitlines() L2 = list(filter(None, [' '.join(x.split()).strip() for x in L2])) diff = '\n'.join(list(difflib.unified_diff(L1, L2))) assert diff == '', diff class XMLTestCase(BaseTestCase): input_bytes = xml_bytes encoding = None def test_bytes_content_type(self): template = self.get_template_bytes() self.assertEqual(template.content_type, 'text/xml') def test_bytes_encoding(self): template = self.get_template_bytes() self.assertEqual(template.content_encoding, 'utf-8') def test_str_content_type(self): template = self.get_template_str() self.assertEqual(template.content_type, 'text/xml') def test_str_encoding(self): template = self.get_template_str() self.assertEqual(template.content_encoding, self.encoding) def test_bytes_checked_true(self): template = self.get_template_bytes() expected = """ """ result = template(checked=True) self.assert_same(expected, result) def test_bytes_checked_false(self): template = self.get_template_bytes() expected = """ """ result = template(checked=False) self.assert_same(expected, result) def test_bytes_checked_None(self): template = self.get_template_bytes() expected = """ """ result = template(checked=None) self.assert_same(expected, result) def test_bytes_checked_default(self): template = self.get_template_bytes() expected = """ """ result = template(checked=template.default_marker.value) self.assert_same(expected, result) def test_str_checked_true(self): template = self.get_template_str() expected = """ """ result = template(checked=True) self.assert_same(expected, result) def test_str_checked_false(self): template = self.get_template_str() expected = """ """ result = template(checked=False) self.assert_same(expected, result) def test_str_checked_None(self): template = self.get_template_str() expected = """ """ result = template(checked=None) self.assert_same(expected, result) def test_str_checked_default(self): template = self.get_template_str() expected = """ """ result = template(checked=template.default_marker.value) self.assert_same(expected, result) class XMLWithEncodingTestCase(BaseTestCase): input_bytes = xml_w_enc_bytes encoding = 'ascii' def test_bytes_encoding(self): template = self.get_template_bytes() self.assertEqual(template.content_encoding, self.encoding) def test_str_encoding(self): template = self.get_template_str() self.assertEqual(template.content_encoding, self.encoding) class HTML5TestCase(BaseTestCase): input_bytes = html5_bytes def test_bytes_content_type(self): template = self.get_template_bytes() self.assertEqual(template.content_type, 'text/html') def test_bytes_encoding(self): template = self.get_template_bytes() self.assertEqual(template.content_encoding, 'utf-8') def test_str_content_type(self): template = self.get_template_str() self.assertEqual(template.content_type, 'text/html') def test_str_encoding(self): template = self.get_template_str() self.assertEqual(template.content_encoding, 'utf-8') def test_bytes_checked_true(self): template = self.get_template_bytes() expected = """ Title of document
""" result = template(checked=True) self.assert_same(expected, result) def test_bytes_checked_false(self): template = self.get_template_bytes() expected = """ Title of document
""" result = template(checked=False) self.assert_same(expected, result) def test_bytes_checked_None(self): template = self.get_template_bytes() expected = """ Title of document
""" result = template(checked=None) self.assert_same(expected, result) def test_bytes_checked_default(self): template = self.get_template_bytes() expected = """ Title of document
""" result = template(checked=template.default_marker.value) self.assert_same(expected, result) def test_str_checked_true(self): template = self.get_template_str() expected = """ Title of document
""" result = template(checked=True) self.assert_same(expected, result) def test_str_checked_false(self): template = self.get_template_str() expected = """ Title of document
""" result = template(checked=False) self.assert_same(expected, result) def test_str_checked_None(self): template = self.get_template_str() expected = """ Title of document
""" result = template(checked=None) self.assert_same(expected, result) def test_str_checked_default(self): template = self.get_template_str() expected = """ Title of document
""" result = template(checked=template.default_marker.value) self.assert_same(expected, result) class HTML5WithContentTypeAndEncodingTestCase(BaseTestCase): input_bytes = html5_w_ct_n_enc_bytes def test_bytes_content_type(self): template = self.get_template_bytes() self.assertEqual(template.content_type, 'foo/bar') def test_bytes_encoding(self): template = self.get_template_bytes() self.assertEqual(template.content_encoding, 'utf-8') def test_str_content_type(self): template = self.get_template_str() self.assertEqual(template.content_type, 'foo/bar') def test_str_encoding(self): template = self.get_template_str() self.assertEqual(template.content_encoding, 'utf-8') chameleon-4.5.4/src/chameleon/tests/test_exc.py000066400000000000000000000015501460471412200215350ustar00rootroot00000000000000from unittest import TestCase from chameleon import exc from chameleon import tokenize class TestTemplateError(TestCase): def test_keep_token_location_info(self): # tokens should not lose information when passed to a TemplateError token = tokenize.Token('stuff', 5, 'more\nstuff', 'mystuff.txt') error = exc.TemplateError('message', token) s = str(error) self.assertTrue( '- Location: (line 2: col 0)' in s, 'No location data found\n%s' % s) def test_umlaut_exc_to_string(self): # test if an exception is convertible to a string body = '

uumlaut:\xfc

' string = body[3:-4] token = tokenize.Token(string, 3, body) e = exc.LanguageError('Invalid define syntax', token) # its fine if we get no exception from the following line str(e) chameleon-4.5.4/src/chameleon/tests/test_imports.py000066400000000000000000000015671460471412200224630ustar00rootroot00000000000000# flake8: noqa: F401 unused class TestImports: def test_pagetemplates(self): from chameleon import PageTemplate from chameleon import PageTemplateFile from chameleon import PageTemplateLoader def test_pagetexttemplates(self): from chameleon import PageTextTemplate from chameleon import PageTextTemplateFile def test_exceptions(self): from chameleon.exc import ExpressionError def test_compiler_utils(self): from chameleon.astutil import Builtin from chameleon.astutil import NameLookupRewriteVisitor from chameleon.astutil import Symbol from chameleon.codegen import template from chameleon.compiler import ExpressionEngine from chameleon.compiler import ExpressionEvaluator from chameleon.tales import ExpressionParser from chameleon.utils import Scope chameleon-4.5.4/src/chameleon/tests/test_loader.py000066400000000000000000000163241460471412200222310ustar00rootroot00000000000000import os import sys import tempfile import unittest import zipimport from pathlib import Path class LoadTests: def _makeOne(self, *args, **kwargs): klass = self._getTargetClass() return klass(*args, **kwargs) def _getTargetClass(self): from chameleon.loader import TemplateLoader return TemplateLoader def test_load_relative(self): import os here = os.path.join(os.path.dirname(__file__), "inputs") loader = self._makeOne(search_path=[here]) result = self._load(loader, 'hello_world.pt') self.assertEqual( result.filename, os.path.join(here, 'hello_world.pt')) def test_load_relative_default_extension(self): import os here = os.path.join(os.path.dirname(__file__), "inputs") loader = self._makeOne([here], ".pt") result = self._load(loader, 'hello_world') self.assertEqual( result.filename, os.path.join(here, 'hello_world.pt')) def test_consecutive_loads(self): import os here = os.path.join(os.path.dirname(__file__), "inputs") loader = self._makeOne(search_path=[here]) self.assertTrue( self._load(loader, 'hello_world.pt') is self._load(loader, 'hello_world.pt')) def test_load_relative_badpath_in_searchpath(self): import os here = os.path.join(os.path.dirname(__file__), "inputs") loader = self._makeOne(search_path=[os.path.join(here, 'none'), here]) result = self._load(loader, 'hello_world.pt') self.assertEqual( result.filename, os.path.join(here, 'hello_world.pt')) def test_load_abs(self): import os here = os.path.join(os.path.dirname(__file__), "inputs") loader = self._makeOne() abs = os.path.join(here, 'hello_world.pt') result = self._load(loader, abs) self.assertEqual(result.filename, abs) def test_load_egg(self): self._test_load_package("bdist_egg", ".egg") def test_load_wheel(self): self._test_load_package("bdist_wheel", ".whl") def _test_load_package(self, command, pkg_extension): with tempfile.TemporaryDirectory() as tmpdir: pkg_name = 'chameleon_test_pkg' basedir = Path(tmpdir) / pkg_name pkgdir = basedir / 'src' / pkg_name templatesdir = pkgdir / 'templates' templatesdir.mkdir(parents=True) olddir = os.getcwd() os.chdir(basedir) try: with open('MANIFEST.in', 'w') as f: f.write('recursive-include src *.pt') pkgdir.joinpath('__init__.py').touch() with templatesdir.joinpath('test.pt').open('w') as f: f.write("${content}") with templatesdir.joinpath('macro1.pt').open('w') as f: f.write( f'' ) with templatesdir.joinpath('macro2.pt').open('w') as f: f.write( '' ) from setuptools import find_packages from setuptools import setup setup( name=pkg_name, version="1.0", packages=find_packages('src'), package_dir={'': 'src'}, include_package_data=True, script_args=[command], ) finally: os.chdir(olddir) (package_path,) = basedir.glob('dist/*' + pkg_extension) importer = zipimport.zipimporter(str(package_path)) importer.load_module(pkg_name) try: self._test_pkg(pkg_name) finally: # Manually clean up archive. # See https://github.com/python/cpython/issues/87319. os.unlink(importer.archive) # Remove imported module. sys.modules.pop(pkg_name, None) def _test_pkg(self, pkg_name): loader = self._makeOne(auto_reload=True) # we use auto_reload to trigger a call of mtime result = self._load( loader, f'{pkg_name}:templates/test.pt') self.assertIsNone(result._v_last_read) output = result(content='foo') self.assertIsNotNone(result._v_last_read) old_v_last_read = result._v_last_read self.assertIn("foo", output) # make sure the template isn't recooked output = result(content='bar') self.assertEqual(result._v_last_read, old_v_last_read) macro1 = self._load(loader, f'{pkg_name}:templates/macro1.pt') macro1_output = macro1(content='bar') self.assertEqual(output, macro1_output) macro2 = self._load(loader, f'{pkg_name}:templates/macro2.pt') macro2_output = macro2(content='bar') self.assertEqual(output, macro2_output) class LoadPageTests(unittest.TestCase, LoadTests): def _load(self, loader, spec): from chameleon.zpt import template return loader.load(spec, template.PageTemplateFile) class ZPTLoadPageTests(unittest.TestCase, LoadTests): def _getTargetClass(self): from chameleon.zpt.loader import TemplateLoader return TemplateLoader def _load(self, loader, spec): return loader.load(spec) class ModuleLoadTests(unittest.TestCase): def _makeOne(self, *args, **kwargs): from chameleon.loader import ModuleLoader return ModuleLoader(*args, **kwargs) def test_build(self): import tempfile path = tempfile.mkdtemp() loader = self._makeOne(path) source = "def function(): return %r" % "\xc3\xa6\xc3\xb8\xc3\xa5" module = loader.build(source, "test.xml") result1 = module['function']() d = {} code = compile(source, 'test.py', 'exec') exec(code, d) result2 = d['function']() self.assertEqual(result1, result2) import os self.assertTrue("test.py" in os.listdir(path)) import shutil shutil.rmtree(path) class ZPTLoadTests(unittest.TestCase): def _makeOne(self, *args, **kwargs): import os here = os.path.join(os.path.dirname(__file__), "inputs") from chameleon.zpt import loader return loader.TemplateLoader(here, **kwargs) def test_load_xml(self): loader = self._makeOne() template = loader.load("hello_world.pt", "xml") from chameleon.zpt.template import PageTemplateFile self.assertTrue(isinstance(template, PageTemplateFile)) def test_load_text(self): loader = self._makeOne() template = loader.load("hello_world.txt", "text") from chameleon.zpt.template import PageTextTemplateFile self.assertTrue(isinstance(template, PageTextTemplateFile)) def test_load_getitem_gets_xml_file(self): loader = self._makeOne() template = loader["hello_world.pt"] from chameleon.zpt.template import PageTemplateFile self.assertTrue(isinstance(template, PageTemplateFile)) chameleon-4.5.4/src/chameleon/tests/test_parser.py000066400000000000000000000066411460471412200222600ustar00rootroot00000000000000from unittest import TestCase from chameleon.namespaces import PY_NS from chameleon.namespaces import XML_NS from chameleon.namespaces import XMLNS_NS class ParserTest(TestCase): def test_comment_double_hyphen_parsing(self): from chameleon.parser import match_double_hyphen self.assertFalse(match_double_hyphen.match('->')) self.assertFalse(match_double_hyphen.match('-->')) self.assertFalse(match_double_hyphen.match('--->')) self.assertFalse(match_double_hyphen.match('---->')) self.assertFalse(match_double_hyphen.match('- >')) self.assertTrue(match_double_hyphen.match('-- >')) def test_sample_files(self): import os import traceback path = os.path.join(os.path.dirname(__file__), "inputs") for filename in os.listdir(path): if not filename.endswith('.html'): continue with open(os.path.join(path, filename), 'rb') as f: source = f.read() from chameleon.utils import read_encoded try: want = read_encoded(source) except UnicodeDecodeError as exc: self.fail("{} - {}".format(exc, filename)) from chameleon.parser import ElementParser from chameleon.tokenize import iter_xml try: tokens = iter_xml(want) parser = ElementParser(tokens, { 'xmlns': XMLNS_NS, 'xml': XML_NS, 'py': PY_NS, }) elements = tuple(parser) except BaseException: self.fail(traceback.format_exc()) output = [] def render(kind, args): if kind == 'element': # start tag tag, end, children = args output.append("%(prefix)s%(name)s" % tag) for attr in tag['attrs']: output.append( "%(space)s" "%(name)s" "%(eq)s" "%(quote)s" "%(value)s" "%(quote)s" % attr) output.append("%(suffix)s" % tag) # children for item in children: render(*item) # end tag output.append( "%(prefix)s%(name)s%(space)s%(suffix)s" % end ) elif kind == 'text': text = args[0] output.append(text) elif kind == 'start_tag': node = args[0] output.append( "%(prefix)s%(name)s%(space)s%(suffix)s" % node ) else: raise RuntimeError("Not implemented: %s." % kind) for kind, args in elements: render(kind, args) got = "".join(output) from doctest import OutputChecker checker = OutputChecker() if checker.check_output(want, got, 0) is False: from doctest import Example example = Example(f.name, want) diff = checker.output_difference( example, got, 0) self.fail("({}) - \n{}".format(f.name, diff)) chameleon-4.5.4/src/chameleon/tests/test_sniffing.py000066400000000000000000000071221460471412200225620ustar00rootroot00000000000000import os import shutil import tempfile import unittest from chameleon.utils import encode_string class TypeSniffingTestCase(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp(prefix='chameleon-tests') def tearDown(self): shutil.rmtree(self.tempdir) def _get_temporary_file(self): filename = os.path.join(self.tempdir, 'template.py') assert not os.path.exists(filename) f = open(filename, 'w') f.flush() f.close() return filename def get_template(self, text): fn = self._get_temporary_file() with open(fn, 'wb') as tmpfile: tmpfile.write(text) from chameleon.template import BaseTemplateFile class DummyTemplateFile(BaseTemplateFile): def cook(self, body): self.body = body template = DummyTemplateFile(fn) template.cook_check() return template def check_content_type(self, text, expected_type): from chameleon.utils import read_bytes content_type = read_bytes(text, 'ascii')[2] self.assertEqual(content_type, expected_type) def test_xml_encoding(self): from chameleon.utils import xml_prefixes document1 = "" document2 = "" for bom, encoding in xml_prefixes: try: "".encode(encoding) except LookupError: # System does not support this encoding continue self.check_content_type(document1.encode(encoding), "text/xml") self.check_content_type(document2.encode(encoding), "text/xml") HTML_PUBLIC_ID = "-//W3C//DTD HTML 4.01 Transitional//EN" HTML_SYSTEM_ID = "http://www.w3.org/TR/html4/loose.dtd" # Couldn't find the code that handles this... yet. # def test_sniffer_html_ascii(self): # self.check_content_type( # "" # % self.HTML_SYSTEM_ID, # "text/html") # self.check_content_type( # "sample document", # "text/html") # TODO: This reflects a case that simply isn't handled by the # sniffer; there are many, but it gets it right more often than # before. def donttest_sniffer_xml_simple(self): self.check_content_type("", "text/xml") def test_html_default_encoding(self): body = encode_string( '' '\xc3\x90\xc2\xa2\xc3\x90\xc2\xb5' '\xc3\x91\xc2\x81\xc3\x91\xc2\x82' '') template = self.get_template(body) self.assertEqual(template.body, body.decode('utf-8')) def test_html_encoding_by_meta(self): body = encode_string( '' '\xc3\x92\xc3\xa5\xc3\xb1\xc3\xb2' '' "") template = self.get_template(body) self.assertEqual(template.body, body.decode('windows-1251')) def test_xhtml(self): body = encode_string( '' '\xc3\x92\xc3\xa5\xc3\xb1\xc3\xb2' '' "") template = self.get_template(body) self.assertEqual(template.body, body.decode('windows-1251')) chameleon-4.5.4/src/chameleon/tests/test_templates.py000066400000000000000000000620001460471412200227510ustar00rootroot00000000000000import glob import os import re import sys from functools import partial from functools import wraps import pytest from chameleon.exc import RenderError from chameleon.exc import TemplateError from chameleon.tales import DEFAULT_MARKER ROOT = os.path.dirname(__file__) def find_files(ext): inputs = os.path.join(ROOT, "inputs") outputs = os.path.join(ROOT, "outputs") found = [] for filename in sorted(os.listdir(inputs)): name, extension = os.path.splitext(filename) if extension != ext: continue path = os.path.join(inputs, filename) # if there's no output file, treat document as static and # expect input equal to output globbed = tuple(glob.iglob(os.path.join( outputs, "{}*{}".format(name.split('-', 1)[0], ext)))) if not globbed: raise RuntimeError("Missing output for: %s." % name) for output in globbed: name, ext = os.path.splitext(output) basename = os.path.basename(name) if '-' in basename: language = basename.split('-')[1] else: language = None base = len(ROOT) + 1 found.append((path[base:], output[base:], language)) return pytest.mark.parametrize("input_path,output_path,language", found) class Message: def __str__(self): return "message" class TestTemplateFile: @property def _class(self): from chameleon.template import BaseTemplateFile class TestTemplateFile(BaseTemplateFile): cook_count = 0 def cook(self, body): self.cook_count += 1 self._cooked = True return TestTemplateFile def _get_temporary_file(self, tmp_path): filename = os.path.join(tmp_path, 'template.py') assert not os.path.exists(filename) f = open(filename, 'w') f.flush() f.close() return filename def test_cook_check(self, tmp_path): fn = self._get_temporary_file(tmp_path) template = self._class(fn) template.cook_check() assert template.cook_count == 1 def test_auto_reload(self, tmp_path): fn = self._get_temporary_file(tmp_path) # set time in past os.utime(fn, (0, 0)) template = self._class(fn, auto_reload=True) template.cook_check() # a second cook check makes no difference template.cook_check() assert template.cook_count == 1 # set current time on file os.utime(fn, None) # file is reloaded template.cook_check() assert template.cook_count == 2 def test_relative_is_expanded_to_cwd(self): template = self._class("___does_not_exist___") try: template.cook_check() except OSError as exc: assert os.getcwd() == os.path.dirname(exc.filename) else: pytest.fail("Expected OSError.") class TestZopePageTemplates: @property def from_string(self): from chameleon.zpt.template import PageTemplate return partial(PageTemplate, keep_source=True) @property def from_file(self): from chameleon.zpt.template import PageTemplateFile return partial(PageTemplateFile, keep_source=True) def template(body): def decorator(func): @wraps(func) def wrapper(self): template = self.from_string(body) return func(self, template) return wrapper return decorator def test_syntax_error_in_strict_mode(self): from chameleon.exc import ExpressionError with pytest.raises(ExpressionError): self.from_string( """""", strict=True ) def test_syntax_error_in_non_strict_mode(self): from chameleon.exc import ExpressionError body = """""" template = self.from_string(body, strict=False) try: template() except ExpressionError as exc: assert body[exc.offset:].startswith('bad ///') else: pytest.fail("Expected exception") def test_exists_error_leak(self): body = '''\ ''' template = self.from_string(body, strict=False) try: template() except RenderError as exc: assert 'var_does_not_exists' not in str(exc) else: pytest.fail("Expected exception") def test_sys_exc_info_is_clear_after_pipe(self): body = ( '
' ) template = self.from_string(body, strict=False) got = template.render(error=sys.exc_info) assert '(None, None, None)' in got def test_render_macro_include_subtemplate_containing_error(self): macro_inner = self.from_string( '''''' ) macro_wrap = self.from_string( '''''') template = self.from_string( ''' foo ''') try: template(macro=macro_wrap, macro_inner=macro_inner) except RenderError as exc: assert isinstance(exc, KeyError) assert ''''key-does-not-exist' - Expression: "dict()['key-does-not-exist']" - Filename: - Location: (line 1: col 29) - Expression: "macro_inner()" - Filename: - Location: (line 1: col 18) - Expression: "macro" - Filename: - Location: (line 4: col 38) ''' in str(exc) else: pytest.fail("Expected exception") def test_render_error_macro_include(self): body = """""" template = self.from_string(body, strict=False) try: template() except RenderError as exc: assert isinstance(exc, AttributeError) assert 'bad' in str(exc) else: pytest.fail("Expected exception") @pytest.mark.parametrize( "body,s", [ ("", 'dummy'), ("", 'foo'), ("", 'key,value'), ('''

''', 'repeat'), (''' ''', 'not_in_translation') ] ) def test_errors(self, body, s): try: self.from_string(body) except TemplateError as exc: assert body[exc.offset:].startswith(s) else: pytest.fail("Expected exception.") def test_encoded(self): filename = '074-encoded-template.pt' with open(os.path.join(ROOT, 'inputs', filename), 'rb') as f: body = f.read() self.from_string(body) def test_utf8_encoded(self): filename = '073-utf8-encoded.pt' with open(os.path.join(ROOT, 'inputs', filename), 'rb') as f: body = f.read() self.from_string(body) def test_recursion_error(self): template = self.from_string( '
' ) with pytest.raises(RecursionError): template() try: template() except RecursionError as exc: assert not isinstance(exc, RenderError) def test_unicode_decode_error(self): template = self.from_file( os.path.join(ROOT, 'inputs', 'greeting.pt') ) string = native = "the artist formerly known as ƤŗíƞĆě" try: string = string.decode('utf-8') except AttributeError: pass class name: @staticmethod def __html__(): # This raises a decoding exception string.encode('utf-8').decode('ascii') pytest.fail("Expected exception raised.") try: template(name=name) except UnicodeDecodeError as exc: formatted = str(exc) # There's a marker under the expression that has the # unicode decode error assert '^^^^^' in formatted assert native in formatted else: pytest.fail("expected error") def test_package_name_cook_check(self): template = self.from_file( "__init__.py", package_name="setuptools", auto_reload=True ) assert template.cook_check() assert not template.cook_check() def test_custom_encoding_for_str_or_bytes_in_content(self): string = '
ТеÑÑ‚${text}
' try: string = string.decode('utf-8') except AttributeError: pass template = self.from_string(string, encoding="windows-1251") text = 'ТеÑÑ‚' try: text = text.decode('utf-8') except AttributeError: pass rendered = template(text=text.encode('windows-1251')) assert rendered == string.replace('${text}', text) def test_custom_encoding_for_str_or_bytes_in_attributes(self): string = '' try: string = string.decode('utf-8') except AttributeError: pass template = self.from_string(string, encoding="windows-1251") text = 'ТеÑÑ‚' try: text = text.decode('utf-8') except AttributeError: pass rendered = template(text=text.encode('windows-1251')) assert rendered == string.replace('${text}', text) def test_null_translate_function(self): template = self.from_string('${test}', translate=None) rendered = template(test=object()) assert 'object' in rendered def test_on_error_handler(self): exc = [] handler = exc.append template = self.from_string( '${test}', on_error_handler=handler ) template() assert len(exc) == 1 assert exc[0].__class__.__name__ == "NameError" def test_object_substitution_coerce_to_str(self): template = self.from_string('${test}', translate=None) class dummy: def __repr__(inst): pytest.fail("call not expected") def __str__(inst): return '' rendered = template(test=dummy()) assert rendered == '<dummy>' def test_repr(self): template = self.from_file( os.path.join(ROOT, 'inputs', 'hello_world.pt') ) assert template.filename in repr(template) def test_underscore_variable(self): template = self.from_string( "
${_dummy}
" ) assert template(), "
foo
" def test_trim_attribute_space(self): document = '''
''' result1 = self.from_string( document)() result2 = self.from_string( document, trim_attribute_space=True)() assert result1.count(" ") == 49 assert result2.count(" ") == 4 assert " />" in result1 assert " />" in result2 def test_exception(self): from traceback import format_exception_only template = self.from_string( "
${dummy}
" ) try: template() except Exception as exc: assert RenderError in type(exc).__bases__ formatted = str(exc) assert 'NameError:' not in formatted assert 'foo' in formatted assert '(line 1: col 23)' in formatted formatted_exc = "\n".join(format_exception_only(type(exc), exc)) assert 'NameError: foo' in formatted_exc else: pytest.fail("expected error") def test_create_formatted_exception(self): from chameleon.utils import create_formatted_exception exc = create_formatted_exception(NameError('foo'), NameError, str) assert exc.args == ('foo', ) class MyNameError(NameError): def __init__(self, boo): NameError.__init__(self, boo) self.bar = boo exc = create_formatted_exception(MyNameError('foo'), MyNameError, str) assert exc.args == ('foo', ) assert exc.bar == 'foo' def test_create_formatted_exception_no_subclass(self): from chameleon.utils import create_formatted_exception class DifficultMetaClass(type): def __init__(self, class_name, bases, namespace): if not bases == (BaseException, ): raise TypeError(bases) Difficult = DifficultMetaClass( 'Difficult', (BaseException, ), {'args': ()}) exc = create_formatted_exception(Difficult(), Difficult, str) assert exc.args == () def test_error_handler_makes_safe_copy(self): calls = [] class TestException(Exception): def __init__(self, *args, **kwargs): calls.append((args, kwargs)) def _render(stream, econtext, rcontext, **kw): exc = TestException('foo', bar='baz') rcontext['__error__'] = ('expression', 1, 42, 'test.pt', exc), raise exc template = self.from_string("") template._render = _render try: template() except TestException as exc: assert calls == [(('foo', ), {'bar': 'baz'})] formatted = str(exc) assert 'TestException' in formatted assert '"expression"' in formatted assert '(line 1: col 42)' in formatted else: pytest.fail("unexpected error") def test_double_underscore_variable(self): from chameleon.exc import TranslationError with pytest.raises(TranslationError): self.from_string( "
${__dummy}
" ) def test_disable_comment_interpolation(self): template = self.from_string( '', enable_comment_interpolation=False ) assert template() == '' def test_compiler_internals_are_disallowed(self): from chameleon.compiler import COMPILER_INTERNALS_OR_DISALLOWED from chameleon.exc import TranslationError for name in COMPILER_INTERNALS_OR_DISALLOWED: body = "${{{}}}".format(name, name) with pytest.raises(TranslationError): self.from_string(body) def test_simple_translate_mapping(self): template = self.from_string( '
' 'foo' '
') assert template() == '
foo
' def test_translate_is_not_an_internal(self): macro = self.from_string('bar') template = self.from_string( ''' foo ''') result = template(macro=macro) assert 'foo' in result assert 'foo' in result def test_default_marker(self): template = self.from_string('') assert template() == str(id(DEFAULT_MARKER)), template.source def test_boolean_attributes(self): template = self.from_string( "\n".join(( '', '', '', '', '', '', '', '', # noqa: E501 line too long '', '', '', '', '', # noqa: E501 line too long )) ) rendered = template( dynamic_true={"checked": True}, dynamic_false={"checked": False}, dynamic_marker=template.default_marker.value, ) assert rendered == \ "\n".join(( '', '', '', '', '', '', '', '', '', '', '', '', '', )), \ "Output mismatch\n" + template.source def test_default_debug_flag(self): from chameleon.config import DEBUG_MODE template = self.from_file( os.path.join(ROOT, 'inputs', 'hello_world.pt'), ) assert template.debug == DEBUG_MODE assert 'debug' not in template.__dict__ def test_debug_flag_on_string(self): from chameleon.loader import ModuleLoader with open(os.path.join(ROOT, 'inputs', 'hello_world.pt')) as f: source = f.read() template = self.from_string(source, debug=True) assert template.debug assert isinstance(template.loader, ModuleLoader) def test_debug_flag_on_file(self): from chameleon.loader import ModuleLoader template = self.from_file( os.path.join(ROOT, 'inputs', 'hello_world.pt'), debug=True, ) assert template.debug assert isinstance(template.loader, ModuleLoader) def test_tag_mismatch(self): from chameleon.exc import ParseError try: self.from_string("""
""") except ParseError as exc: assert "" in str(exc) else: pytest.fail("Expected error.") def test_f_strings(self): from math import pi from math import sin template = self.from_string('${f"sin({a}) is {sin(a):.3}"}') rendered = template(sin=sin, a=pi) assert 'sin(3.141592653589793) is 1.22e-16' == rendered def test_windows_line_endings(self): template = self.from_string('') assert template() == 'bar' def test_digest(self): # Make sure ``digest`` doesn't error out when ``filename`` is something # other than a simple string data = '' template = self.from_string(data) template.filename = None assert template.digest(data, []) template.filename = '' assert template.digest(data, []) @find_files(".pt") def test_pt_files(self, input_path, output_path, language): from chameleon.zpt.template import PageTemplateFile class Literal: def __init__(self, s): self.s = s def __html__(self): return self.s def __str__(self): raise RuntimeError( "%r is a literal." % self.s) from chameleon.loader import TemplateLoader # TODO: Should take the path from the input path? loader = TemplateLoader(os.path.join(ROOT, "inputs")) self.execute( input_path, output_path, language, PageTemplateFile, literal=Literal("
Hello world!
"), content="
Hello world!
", message=Message(), load=loader.bind(PageTemplateFile), ) @find_files(".txt") def test_txt_files(self, input_path, output_path, language): from chameleon.zpt.template import PageTextTemplateFile self.execute(input_path, output_path, language, PageTextTemplateFile) def execute(self, input_path, output_path, language, factory, **kwargs): # Make friendly title so we can locate the generated # source when debugging self.shortDescription = lambda: input_path # When input path contains the string 'implicit-i18n', we # enable "implicit translation". implicit_i18n = 'implicit-i18n' in input_path implicit_i18n_attrs = ("alt", "title") if implicit_i18n else () enable_data_attributes = 'data-attributes' in input_path template = factory( os.path.join(ROOT, input_path), keep_source=True, strict=False, implicit_i18n_translate=implicit_i18n, implicit_i18n_attributes=implicit_i18n_attrs, enable_data_attributes=enable_data_attributes, ) params = kwargs.copy() params.update({ 'translate': self.translate, 'target_language': language, }) template.cook_check() try: got = template.render(**params) except BaseException: import traceback e = traceback.format_exc() pytest.fail("{}\n\n Example source:\n\n{}".format( e, "\n".join( ["%#03.d%s" % (lineno + 1, line and " " + line or "") for (lineno, line) in enumerate(template.source.split( '\n'))]))) if isinstance(got, bytes): got = got.decode('utf-8') from doctest import OutputChecker checker = OutputChecker() output_filename = os.path.join(ROOT, output_path) with open(output_filename, 'rb') as f: output = f.read() from chameleon.utils import detect_encoding from chameleon.utils import read_xml_encoding if template.content_type == 'text/xml': encoding = read_xml_encoding(output) or \ template.default_encoding else: content_type, encoding = detect_encoding( output, template.default_encoding) # Newline normalization across platforms want = '\n'.join(output.decode(encoding).splitlines()) got = '\n'.join(got.splitlines()) if checker.check_output(want, got, 0) is False: from doctest import Example example = Example(input_path, want) diff = checker.output_difference( example, got, 0) source = template.source pytest.fail("({}) - \n{}\n\nCode:\n{}".format( input_path, diff.rstrip('\n'), source)) @staticmethod def translate(msgid, domain=None, mapping=None, context=None, target_language=None, default=None): if default is None: default = str(msgid) if isinstance(msgid, Message): default = "Message" if mapping: default = re.sub(r'\${([a-z_]+)}', r'%(\1)s', default) % \ mapping if target_language is None: return default if domain is None: with_domain = "" else: with_domain = " with domain '%s'" % domain if context is None: with_context = "" else: with_context = ", context '%s'" % context stripped = default.rstrip('\n ') return "{} ('{}' translation into '{}'{}{}){}".format( stripped, msgid, target_language, with_domain, with_context, default[len(stripped):] ) chameleon-4.5.4/src/chameleon/tests/test_tokenizer.py000066400000000000000000000026731460471412200227770ustar00rootroot00000000000000from unittest import TestCase class TokenizerTest(TestCase): def test_sample_files(self): import os import traceback path = os.path.join(os.path.dirname(__file__), "inputs") for filename in os.listdir(path): if not filename.endswith('.xml'): continue f = open(os.path.join(path, filename), 'rb') source = f.read() f.close() from chameleon.utils import read_encoded try: want = read_encoded(source) except UnicodeDecodeError as exc: self.fail("{} - {}".format(exc, filename)) from chameleon.tokenize import iter_xml try: tokens = iter_xml(want) got = "".join(tokens) except BaseException: self.fail(traceback.format_exc()) from doctest import OutputChecker checker = OutputChecker() if checker.check_output(want, got, 0) is False: from doctest import Example example = Example(f.name, want) diff = checker.output_difference( example, got, 0) self.fail("({}) - \n{}".format(f.name, diff)) def test_token(self): from chameleon.tokenize import Token token = Token("abc", 1) self.assertTrue(isinstance(token[1:], Token)) self.assertEqual(token[1:].pos, 2) chameleon-4.5.4/src/chameleon/tests/test_utils.py000066400000000000000000000010011460471412200221050ustar00rootroot00000000000000import unittest class ScopeTestCase(unittest.TestCase): def setUp(self): from chameleon.utils import Scope scope = Scope() scope['a'] = 1 scope.set_global("b", 2) self.parent = scope self.child = scope.copy() def test_items(self): assert list(self.child.items()) == [('a', 1), ('b', 2)] def test_keys(self): assert list(self.child.keys()) == ['a', 'b'] def test_values(self): assert list(self.child.values()) == [1, 2] chameleon-4.5.4/src/chameleon/tokenize.py000066400000000000000000000120701460471412200204040ustar00rootroot00000000000000# http://code.activestate.com/recipes/65125-xml-lexing-shallow-parsing/ # by Paul Prescod # licensed under the PSF License # # modified to capture all non-overlapping parts of tokens from __future__ import annotations import re from typing import TYPE_CHECKING from typing import SupportsIndex from typing import cast from typing import overload if TYPE_CHECKING: from typing_extensions import Self class recollector: def __init__(self): self.res = {} def add(self, name, reg) -> None: re.compile(reg) # check that it is valid self.res[name] = reg % self.res collector = recollector() a = collector.add a("TextSE", "[^<]+") a("UntilHyphen", "[^-]*-") a("Until2Hyphens", "%(UntilHyphen)s(?:[^-]%(UntilHyphen)s)*-") a("CommentCE", "%(Until2Hyphens)s>?") a("UntilRSBs", "[^\\]]*](?:[^\\]]+])*]+") a("CDATA_CE", "%(UntilRSBs)s(?:[^\\]>]%(UntilRSBs)s)*>") a("S", "[ \\n\\t\\r]+") a("Simple", "[^\"'>/]+") a("NameStrt", "[A-Za-z_:@]|[^\\x00-\\x7F]") a("NameChar", "[A-Za-z0-9_:.-]|[^\\x00-\\x7F]") a("Name", "(?:%(NameStrt)s)(?:%(NameChar)s)*") a("QuoteSE", "\"[^\"]*\"|'[^']*'") a("DT_IdentSE", "%(S)s%(Name)s(?:%(S)s(?:%(Name)s|%(QuoteSE)s))*") a("MarkupDeclCE", "(?:[^\\]\"'><]+|%(QuoteSE)s)*>") a("S1", "[\\n\\r\\t ]") a("UntilQMs", "[^?]*\\?+") a("PI_Tail", "\\?>|%(S1)s%(UntilQMs)s(?:[^>?]%(UntilQMs)s)*>") a("DT_ItemSE", "<(?:!(?:--%(Until2Hyphens)s>|[^-]%(MarkupDeclCE)s)|" "\\?%(Name)s(?:%(PI_Tail)s))|%%%(Name)s;|%(S)s" ) a("DocTypeCE", "%(DT_IdentSE)s(?:%(S)s)?(?:\\[(?:%(DT_ItemSE)s)*](?:%(S)s)?)?>?") a("DeclCE", "--(?:%(CommentCE)s)?|\\[CDATA\\[(?:%(CDATA_CE)s)?|" "DOCTYPE(?:%(DocTypeCE)s)?") a("PI_CE", "%(Name)s(?:%(PI_Tail)s)?") a("EndTagCE", "%(Name)s(?:%(S)s)?>?") a("AttValSE", r"\"[^\"]*\"|'[^']*'|[^\s=<>`]+") a("ElemTagCE", "(%(Name)s)(?:(%(S)s)(%(Name)s)(((?:%(S)s)?=(?:%(S)s)?)" "(?:%(AttValSE)s|%(Simple)s)|(?!(?:%(S)s)?=)))*(?:%(S)s)?(/?>)?") a("MarkupSPE", "<(?:!(?:%(DeclCE)s)?|" "\\?(?:%(PI_CE)s)?|/(?:%(EndTagCE)s)?|(?:%(ElemTagCE)s)?)") a("XML_SPE", "%(TextSE)s|%(MarkupSPE)s") a("XML_MARKUP_ONLY_SPE", "%(MarkupSPE)s") a("ElemTagSPE", "<|%(Name)s") re_xml_spe = re.compile(collector.res['XML_SPE']) re_markup_only_spe = re.compile(collector.res['XML_MARKUP_ONLY_SPE']) def iter_xml(body, filename=None): for match in re_xml_spe.finditer(body): string = match.group() pos = match.start() yield Token(string, pos, body, filename) def iter_text(body, filename=None): yield Token(body, 0, body, filename) class Token(str): __slots__ = "pos", "source", "filename" pos: int source: str | None filename: str def __new__( cls, string: str, pos: int = 0, source: str | None = None, filename: str | None = None ) -> Self: inst = str.__new__(cls, string) inst.pos = pos inst.source = source # convert pathlib.Path to a str, since we rely on this # being a string downstream inst.filename = filename or "" return inst @overload # type: ignore[override] def __getitem__(self, index: slice) -> Token: ... @overload def __getitem__(self, index: SupportsIndex) -> str: ... def __getitem__(self, index: SupportsIndex | slice) -> str: s = str.__getitem__(self, index) if isinstance(index, slice): return Token( s, self.pos + (index.start or 0), self.source, self.filename) return s def __add__(self, other: str | None) -> Token: if other is None: return self return Token( str.__add__(self, other), self.pos, self.source, self.filename) def __eq__(self, other: object) -> bool: return str.__eq__(self, other) def __hash__(self) -> int: return str.__hash__(self) def replace( self, old: str, new: str, count: SupportsIndex = -1, / ) -> Token: s = str.replace(self, old, new, count) return Token(s, self.pos, self.source, self.filename) def split( # type: ignore[override] self, sep: str | None = None, maxsplit: SupportsIndex = -1 ) -> list[Token]: l_ = str.split(self, sep, maxsplit) pos = self.pos for i, s in enumerate(l_): l_[i] = Token(s, pos, self.source, self.filename) pos += len(s) return cast('list[Token]', l_) def strip(self, chars: str | None = None, /) -> Token: return self.lstrip(chars).rstrip(chars) def lstrip(self, chars: str | None = None, /) -> Token: s = str.lstrip(self, chars) return Token( s, self.pos + len(self) - len(s), self.source, self.filename) def rstrip(self, chars: str | None = None, /) -> Token: s = str.rstrip(self, chars) return Token(s, self.pos, self.source, self.filename) @property def location(self) -> tuple[int, int]: if self.source is None: return 0, self.pos body = self.source[:self.pos] line = body.count('\n') return line + 1, self.pos - body.rfind('\n', 0) - 1 chameleon-4.5.4/src/chameleon/types.py000066400000000000000000000023301460471412200177160ustar00rootroot00000000000000from __future__ import annotations from typing import TYPE_CHECKING from typing import Any from typing import Protocol from typing import TypedDict if TYPE_CHECKING: from collections.abc import Callable from collections.abc import Collection from chameleon.tokenize import Token class Tokenizer(Protocol): def __call__( self, body: str, filename: str | None = None ) -> Token: ... class TranslationFunction(Protocol): def __call__( self, msgid: str, *, domain: str | None = None, mapping: dict[str, Any] | None = None, default: str | None = None, context: str | None = None, target_language: str | None = None ) -> str: ... class PageTemplateConfig(TypedDict, total=False): auto_reload: bool default_expression: str encoding: str boolean_attributes: Collection[str] translate: TranslationFunction implicit_i18n_translate: bool implicit_i18n_attributes: set[str] on_error_handler: Callable[[BaseException], object] strict: bool trim_attribute_space: bool restricted_namespace: bool tokenizer: Tokenizer value_repr: Callable[[object], str] default_marker: Any chameleon-4.5.4/src/chameleon/utils.py000066400000000000000000000316251460471412200177230ustar00rootroot00000000000000from __future__ import annotations import codecs import logging import os import re from enum import Enum from html import entities as htmlentitydefs from typing import TYPE_CHECKING from typing import Any from typing import Generic from typing import Literal from typing import NoReturn from typing import TypeVar from typing import overload if TYPE_CHECKING: from collections.abc import Callable from collections.abc import Iterable from collections.abc import Iterator from collections.abc import Mapping from collections.abc import Sequence from types import TracebackType _KT = TypeVar('_KT') _VT_co = TypeVar('_VT_co') log = logging.getLogger('chameleon.utils') class _Marker(Enum): marker = object() # NOTE: Enums are better markers for type narrowing marker: Literal[_Marker.marker] = _Marker.marker def safe_native(s: str | bytes, encoding: str = 'utf-8') -> str: if not isinstance(s, str): s = bytes.decode(s, encoding, 'replace') return s def raise_with_traceback( exc: BaseException, tb: TracebackType | None ) -> NoReturn: exc.__traceback__ = tb raise exc def encode_string(s: str) -> bytes: return bytes(s, 'utf-8') entity_re = re.compile(r'&(#?)(x?)(\d{1,5}|\w{1,8});') module_cache = {} xml_prefixes = ( (codecs.BOM_UTF8, 'utf-8-sig'), (codecs.BOM_UTF16_BE, 'utf-16-be'), (codecs.BOM_UTF16_LE, 'utf-16-le'), (codecs.BOM_UTF16, 'utf-16'), (codecs.BOM_UTF32_BE, 'utf-32-be'), (codecs.BOM_UTF32_LE, 'utf-32-le'), (codecs.BOM_UTF32, 'utf-32'), ) def _has_encoding(encoding: str) -> bool: try: "".encode(encoding) except LookupError: return False else: return True # Precomputed prefix table _xml_prefixes = tuple( (bom, '\s*', re.IGNORECASE ) RE_ENCODING = re.compile( br'encoding\s*=\s*(?:"|\')(?P[\w\-]+)(?:"|\')', re.IGNORECASE ) def read_encoded(data: bytes) -> str: return read_bytes(data, "utf-8")[0] def read_bytes( body: bytes, default_encoding: str ) -> tuple[str, str, str | None]: for bom, prefix, encoding in _xml_prefixes: if body.startswith(bom): document = body.decode(encoding) return document, encoding, \ "text/xml" if document.startswith(" tuple[str | None, str]: if not isinstance(body, str): body = body.decode('ascii', 'ignore') match = RE_META.search(body) if match is not None: # this can be treated like tuple[str, str] since we unpack it return match.groups() # type: ignore[return-value] return None, default_encoding def read_xml_encoding(body: bytes) -> str | None: if body.startswith(b' str: """Mangles template filename into top-level Python module name. >>> mangle('hello_world.pt') 'hello_world' >>> mangle('foo.bar.baz.pt') 'foo_bar_baz' >>> mangle('foo-bar-baz.pt') 'foo_bar_baz' """ base, ext = os.path.splitext(filename) return base.replace('.', '_').replace('-', '_') def char2entity(c: str | bytes | bytearray) -> str: cp = ord(c) name = htmlentitydefs.codepoint2name.get(cp) return '&%s;' % name if name is not None else '&#%d;' % cp def substitute_entity( match: re.Match[str], n2cp: Mapping[str, int] = htmlentitydefs.name2codepoint ) -> str: ent = match.group(3) if match.group(1) == "#": if match.group(2) == '': return chr(int(ent)) elif match.group(2) == 'x': return chr(int('0x' + ent, 16)) else: # FIXME: This should be unreachable, so we can # try raising an AssertionError instead return '' else: cp = n2cp.get(ent) if cp: return chr(cp) else: return match.group() def create_formatted_exception( exc: BaseException, cls: type[object], formatter: Callable[..., str], base: type[BaseException] = Exception ) -> BaseException: try: try: new = type(cls.__name__, (cls, base), { '__str__': formatter, '_original__str__': exc.__str__, '__new__': BaseException.__new__, '__module__': cls.__module__, }) except TypeError: new = cls inst: BaseException try: inst = BaseException.__new__(new) except TypeError: inst = cls.__new__(new) BaseException.__init__(inst, *exc.args) inst.__dict__ = exc.__dict__ # type: ignore[attr-defined] return inst except ValueError: name = type(exc).__name__ log.warn("Unable to copy exception of type '%s'." % name) raise TypeError(exc) _concat = "".join def join(stream: Iterable[str]) -> str: """Concatenate stream. >>> print(join(('Hello', ' ', 'world'))) Hello world >>> join(('Hello', 0)) Traceback (most recent call last): ... TypeError: ... expected ... """ try: return _concat(stream) except BaseException: # Loop through stream and coerce each element into unicode; # this should raise an exception for element in stream: str(element) # In case it didn't, re-raise the original exception raise def decode_htmlentities(string: str) -> str: """ >>> str(decode_htmlentities('&amp;')) '&' """ decoded = entity_re.subn(substitute_entity, string)[0] # preserve input token data return string.replace(string, decoded) # Taken from zope.dottedname def _resolve_dotted(name: str, module: str | None = None) -> Any: name_parts = name.split('.') if not name_parts[0]: if module is None: raise ValueError("relative name without base module") module_parts = module.split('.') name_parts.pop(0) while not name[0]: module_parts.pop() name_parts.pop(0) name_parts = module_parts + name_parts used = name_parts.pop(0) found = __import__(used) for n in name_parts: used += '.' + n try: found = getattr(found, n) except AttributeError: __import__(used) found = getattr(found, n) return found def resolve_dotted(dotted: str) -> Any: if dotted not in module_cache: resolved = _resolve_dotted(dotted) module_cache[dotted] = resolved return module_cache[dotted] def limit_string(s: str, max_length: int = 53) -> str: if len(s) > max_length: return s[:max_length - 3] + '...' return s def value_repr(value: object) -> str: if isinstance(value, str): short = limit_string(value) return short.replace('\n', '\\n') if isinstance(value, (int, float)): return value # type: ignore[return-value] if isinstance(value, dict): return '{...} (%d)' % len(value) try: # FIXME: Is this trailing comma intentional? # it changes the formatting # vs. name = str(getattr(value, '__name__', None)), except: # noqa: E722 do not use bare 'except' name = '-' # type: ignore[assignment] return '<{} {} at {}>'.format( type(value).__name__, name, hex(abs(id(value)))) class callablestr(str): __slots__ = () def __call__(self) -> str: return self class callableint(int): __slots__ = () def __call__(self) -> int: return self class descriptorstr: __slots__ = "function", "__name__" def __init__(self, function: Callable[[Any], str]) -> None: self.function = function self.__name__ = function.__name__ def __get__(self, context: object, cls: type[object]) -> callablestr: return callablestr(self.function(context)) class descriptorint: __slots__ = "function", "__name__" def __init__(self, function: Callable[[Any], int]) -> None: self.function = function self.__name__ = function.__name__ def __get__(self, context: object, cls: type[object]) -> callableint: return callableint(self.function(context)) class DebuggingOutputStream(list[str]): def append(self, value: str) -> None: if not isinstance(value, str): raise TypeError(value) super().append(value) class Scope(dict[str, Any]): """ >>> scope = Scope() >>> scope['a'] = 1 >>> copy = scope.copy() Setting a local value and then a global value, we expect the local value to take precedence. >>> copy['a'] = 2 >>> copy.set_global('a', 3) >>> assert copy['a'] == 2 However, setting a new global value should be immediately visible. >>> copy.set_global('b', 1) >>> assert copy['b'] == 1 Make sure the objects are reference-counted, not requiring a full collection to be disposed of. >>> import gc >>> _ = gc.collect() >>> del copy >>> del scope >>> import platform >>> assert gc.collect() == ( ... 0 if platform.python_implementation() == 'CPython' else None ... ) """ __slots__ = "_root", set_local = dict.__setitem__ def __getitem__(self, key: str) -> Any: value = self.get(key, marker) if value is marker: raise KeyError(key) return value def __contains__(self, key: object) -> bool: return self.get(key, marker) is not marker # type: ignore def __iter__(self) -> Iterator[str]: root = getattr(self, "_root", marker) yield from super().__iter__() if root is not marker: for key in root: if not super().__contains__(key): yield key @overload def get(self, key: str, default: None = None) -> Any | None: ... @overload def get(self, key: str, default: object) -> Any: ... def get(self, key: str, default: object = None) -> Any: value = super().get(key, marker) if value is not marker: return value root = getattr(self, "_root", marker) if root is not marker: value = super(Scope, root).get(key, marker) if value is not marker: return value return default @property def vars(self) -> Mapping[str, Any]: return self def copy(self) -> Scope: inst = Scope(self) root = getattr(self, "_root", self) inst._root = root # type: ignore[attr-defined] return inst def set_global(self, name: str, value: Any) -> None: root = getattr(self, "_root", self) root[name] = value def get_name(self, key: str) -> Any: value = self.get(key, marker) if value is marker: raise NameError(key) return value setLocal = set_local setGlobal = set_global class ListDictProxy(Generic[_KT, _VT_co]): def __init__( self: ListDictProxy[_KT, _VT_co], _l: Sequence[Mapping[_KT, _VT_co]] ) -> None: self._l = _l def get(self, key: _KT) -> _VT_co | None: return self._l[-1].get(key) class Markup(str): """Wraps a string to always render as structure. >>> Markup('
') s'
' """ def __html__(self) -> str: return str(self) def __repr__(self) -> str: return "s'%s'" % self class ImportableMarker: def __init__(self, module: str, name: str) -> None: self.__module__ = module self.name = name @property def __name__(self) -> str: return "%s_MARKER" % self.name def __repr__(self) -> str: return '<%s>' % self.name def lookup_attr(obj: object, key: str) -> Any: try: return getattr(obj, key) except AttributeError as exc: # FIXME: What are the two try excepts here for? # We just raise the thing we catch... try: get = obj.__getitem__ # type: ignore[index] except AttributeError: raise exc try: return get(key) except KeyError: raise exc chameleon-4.5.4/src/chameleon/zpt/000077500000000000000000000000001460471412200170175ustar00rootroot00000000000000chameleon-4.5.4/src/chameleon/zpt/__init__.py000066400000000000000000000000021460471412200211200ustar00rootroot00000000000000# chameleon-4.5.4/src/chameleon/zpt/loader.py000066400000000000000000000035501460471412200206420ustar00rootroot00000000000000from __future__ import annotations from typing import TYPE_CHECKING from typing import Any from typing import Literal from typing import overload from chameleon.loader import TemplateLoader as BaseLoader from chameleon.zpt import template if TYPE_CHECKING: from collections.abc import Mapping from collections.abc import Sequence from typing_extensions import TypeAlias _FormatsMapping: TypeAlias = Mapping[str, type[template.PageTemplateFile]] class TemplateLoader(BaseLoader): formats: _FormatsMapping = { "xml": template.PageTemplateFile, "text": template.PageTextTemplateFile, } default_format: Literal['xml'] = "xml" def __init__( self, search_path: Sequence[str] | str | None = None, default_extension: str | None = None, *, formats: _FormatsMapping | None = None, **kwargs: Any ) -> None: if formats is not None: self.formats = formats super().__init__(search_path, default_extension, **kwargs) @overload # type: ignore[override] def load( self, filename: str, format: Literal['xml'] | None = None ) -> template.PageTemplateFile: ... @overload def load( self, filename: str, format: Literal['text'] ) -> template.PageTextTemplateFile: ... @overload def load( self, filename: str, format: str ) -> template.PageTemplateFile: ... def load( self, filename: str, format: str | None = None ) -> template.PageTemplateFile: """Load and return a template file. The format parameter determines will parse the file. Valid options are `xml` and `text`. """ cls = self.formats[format or self.default_format] return super().load(filename, cls) __getitem__ = load chameleon-4.5.4/src/chameleon/zpt/program.py000066400000000000000000000675161460471412200210570ustar00rootroot00000000000000from __future__ import annotations import ast import re from functools import partial from chameleon import i18n from chameleon import metal from chameleon import nodes from chameleon import tal from chameleon.astutil import Static from chameleon.astutil import Symbol from chameleon.astutil import parse from chameleon.exc import CompilationError from chameleon.exc import LanguageError from chameleon.exc import ParseError from chameleon.namespaces import I18N_NS as I18N from chameleon.namespaces import META_NS as META from chameleon.namespaces import METAL_NS as METAL from chameleon.namespaces import TAL_NS as TAL from chameleon.namespaces import XML_NS from chameleon.namespaces import XMLNS_NS from chameleon.program import ElementProgram from chameleon.utils import ImportableMarker from chameleon.utils import decode_htmlentities missing = object() re_trim = re.compile(r'($\s+|\s+^)', re.MULTILINE) EMPTY_DICT = Static(ast.Dict(keys=[], values=[])) CANCEL_MARKER = ImportableMarker(__name__, "CANCEL") def skip(node): return node def wrap(node, *wrappers): for wrapper in reversed(wrappers): node = wrapper(node) return node def validate_attributes(attributes, namespace, whitelist): for ns, name in attributes: if ns == namespace and name not in whitelist: raise CompilationError( "Bad attribute for namespace '%s'" % ns, name ) def convert_data_attributes(ns_attrs, attrs, namespaces) -> None: d = 0 for i, attr in list(enumerate(attrs)): name = attr['name'] if name.startswith('data-'): name = name[5:] if '-' not in name: continue prefix, name = name.split('-', 1) ns_attrs[namespaces[prefix], name] = attr['value'] attrs.pop(i - d) d += 1 class MacroProgram(ElementProgram): """Visitor class that generates a program for the ZPT language.""" DEFAULT_NAMESPACES = { 'xmlns': XMLNS_NS, 'xml': XML_NS, 'tal': TAL, 'metal': METAL, 'i18n': I18N, 'meta': META, } DROP_NS = TAL, METAL, I18N, META VARIABLE_BLACKLIST = "default", "repeat", "nothing", \ "convert", "decode", "translate" _interpolation_enabled = True _whitespace = "\n" _last = "" _cancel_marker = Symbol(CANCEL_MARKER) # Macro name (always trivial for a macro program) name = None # This default marker value has the semantics that if an # expression evaluates to that value, the expression default value # is returned. For an attribute, if there is no default, this # means that the attribute is dropped. default_marker = None # Escape mode (true value means XML-escape) escape = True # Attributes which should have boolean behavior (on true, the # value takes the attribute name, on false, the attribute is # dropped) boolean_attributes: set[str] = set() # If provided, this should be a set of attributes for implicit # translation. Any attribute whose name is included in the set # will be translated even without explicit markup. Note that all # values should be lowercase strings. implicit_i18n_attributes: set[str] = set() # If set, text will be translated even without explicit markup. implicit_i18n_translate = False # If set, additional attribute whitespace will be stripped. trim_attribute_space = False # If set, data attributes can be used instead of namespace # attributes, e.g. "data-tal-content" instead of "tal:content". enable_data_attributes = False # If set, XML namespaces are restricted to the list of those # defined and used by the page template language. restricted_namespace = True # If set, expression interpolation is enabled in comments. enable_comment_interpolation = True def __init__(self, *args, **kwargs): # Internal array for switch statements self._switches = [] # Internal array for current use macro level self._use_macro = [] # Internal array for current interpolation status self._interpolation = [True] # Internal dictionary of macro definitions self._macros = {} # Apply default values from **kwargs to self self._pop_defaults( kwargs, 'boolean_attributes', 'default_marker', 'escape', 'implicit_i18n_translate', 'implicit_i18n_attributes', 'trim_attribute_space', 'enable_data_attributes', 'enable_comment_interpolation', 'restricted_namespace', ) super().__init__(*args, **kwargs) @property def macros(self): macros = list(self._macros.items()) macros.append((None, nodes.Sequence(self.body))) return tuple( nodes.Macro(name, [nodes.Context(node)]) for name, node in macros ) def visit_default(self, node): return nodes.Text(node) def visit_element(self, start, end, children): ns = start['ns_attrs'] attrs = start['attrs'] if self.enable_data_attributes: attrs = list(attrs) convert_data_attributes(ns, attrs, start['ns_map']) for (prefix, attr), encoded in tuple(ns.items()): if prefix == TAL or prefix == METAL: ns[prefix, attr] = decode_htmlentities(encoded) # Validate namespace attributes validate_attributes(ns, TAL, tal.WHITELIST) validate_attributes(ns, METAL, metal.WHITELIST) validate_attributes(ns, I18N, i18n.WHITELIST) # Check attributes for language errors self._check_attributes(start['namespace'], ns) # Remember whitespace for item repetition if self._last is not None: self._whitespace = "\n" + " " * len(self._last.rsplit('\n', 1)[-1]) # Set element-local whitespace whitespace = self._whitespace # Set up switch try: clause = ns[TAL, 'switch'] except KeyError: switch = None else: value = nodes.Value(clause) switch = value self._switches.append(switch) body = [] # Include macro use_macro = ns.get((METAL, 'use-macro')) extend_macro = ns.get((METAL, 'extend-macro')) if use_macro or extend_macro: omit = True slots = [] self._use_macro.append(slots) if use_macro: inner = nodes.UseExternalMacro( nodes.Value(use_macro), slots, False ) macro_name = use_macro else: inner = nodes.UseExternalMacro( nodes.Value(extend_macro), slots, True ) macro_name = extend_macro # While the macro executes, it should have access to the name it # was called with as 'macroname'. Splitting on / mirrors zope.tal # and is a concession to the path expression syntax. macro_name = macro_name.rsplit('/', 1)[-1] inner = nodes.Define( [nodes.Assignment( ["macroname"], Static(ast.Str(macro_name)), True)], inner, ) STATIC_ATTRIBUTES = None # -or- include tag else: content = nodes.Sequence(body) # tal:content try: clause = ns[TAL, 'content'] except KeyError: pass else: key, value = tal.parse_substitution(clause) translate = ns.get((I18N, 'translate')) == '' content = self._make_content_node( value, content, key, translate, ) if end is None: # Make sure start-tag has opening suffix. start['suffix'] = ">" # Explicitly set end-tag. end = { 'prefix': '' } # i18n:translate try: clause = ns[I18N, 'translate'] except KeyError: pass else: dynamic = ns.get((TAL, 'content')) or ns.get((TAL, 'replace')) if not dynamic: content = nodes.Translate(clause, content) # tal:attributes try: clause = ns[TAL, 'attributes'] except KeyError: TAL_ATTRIBUTES = [] else: TAL_ATTRIBUTES = tal.parse_attributes(clause) # i18n:attributes try: clause = ns[I18N, 'attributes'] except KeyError: I18N_ATTRIBUTES = {} else: I18N_ATTRIBUTES = i18n.parse_attributes(clause) # Prepare attributes from TAL language prepared = tal.prepare_attributes( attrs, TAL_ATTRIBUTES, I18N_ATTRIBUTES, ns, self.DROP_NS ) # Create attribute nodes STATIC_ATTRIBUTES = self._create_static_attributes(prepared) attributes, filtering = self._create_attributes_nodes( prepared, I18N_ATTRIBUTES ) ATTRIBUTES = nodes.Sequence(attributes) # We're caching all expressions found during attribute processing # to enable the filtering functionality which exists to allow later # definitions to override previous ones. if filtering: ATTRIBUTES = nodes.Cache(filtering, ATTRIBUTES) # Start- and end nodes start_tag = nodes.Start( start['name'], self._maybe_trim(start['prefix']), self._maybe_trim(start['suffix']), ATTRIBUTES ) end_tag = nodes.End( end['name'], end['space'], self._maybe_trim(end['prefix']), self._maybe_trim(end['suffix']), ) if end is not None else None # tal:omit-tag try: clause = ns[TAL, 'omit-tag'] except KeyError: omit = False else: clause = clause.strip() if clause == "": omit = True else: expression = nodes.Negate(nodes.Value(clause)) omit = expression # Wrap start- and end-tags in condition start_tag = nodes.Condition(expression, start_tag) if end_tag is not None: end_tag = nodes.Condition(expression, end_tag) if omit is True or start['namespace'] in self.DROP_NS: inner = content else: inner = nodes.Element( start_tag, end_tag, content, ) if omit is not False: inner = nodes.Cache([omit], inner) # tal:replace try: clause = ns[TAL, 'replace'] except KeyError: pass else: key, value = tal.parse_substitution(clause) translate = ns.get((I18N, 'translate')) == '' inner = self._make_content_node( value, inner, key, translate ) # metal:define-slot try: clause = ns[METAL, 'define-slot'] except KeyError: DEFINE_SLOT = skip else: DEFINE_SLOT = partial(nodes.DefineSlot, clause) # tal:define try: clause = ns[TAL, 'define'] except KeyError: defines = [] else: defines = tal.parse_defines(clause) if defines is None: raise ParseError("Invalid define syntax.", clause) assignments = [ nodes.Assignment( names, nodes.Value(expr), context == "local") for (context, names, expr) in defines ] # Assign static attributes dictionary to "attrs" value assignments.insert( 0, nodes.Alias(["attrs"], STATIC_ATTRIBUTES or EMPTY_DICT)) DEFINE = partial(nodes.Define, assignments) # tal:case try: clause = ns[TAL, 'case'] except KeyError: CASE = skip else: value = nodes.Value(clause) for switch in reversed(self._switches): if switch is not None: break else: raise LanguageError( "Must define switch on a parent element.", clause ) def CASE(node): return nodes.Define( [nodes.Alias(["default"], self.default_marker)], nodes.Condition( nodes.And([ nodes.BinOp( switch, nodes.IsNot, self._cancel_marker), nodes.Or([ nodes.BinOp(value, nodes.Equals, switch), nodes.BinOp( value, nodes.Equals, self.default_marker) ]) ]), nodes.Cancel([switch], node, self._cancel_marker), )) # tal:repeat try: clause = ns[TAL, 'repeat'] except KeyError: REPEAT = skip else: defines = tal.parse_defines(clause) assert len(defines) == 1 context, names, expr = defines[0] expression = nodes.Value(expr) if start['namespace'] == TAL: self._last = None self._whitespace = whitespace.lstrip('\n') whitespace = "" REPEAT = partial( nodes.Repeat, names, expression, context == "local", whitespace ) # tal:condition try: clause = ns[TAL, 'condition'] except KeyError: CONDITION = skip else: expression = nodes.Value(clause) CONDITION = partial(nodes.Condition, expression) # tal:switch if switch is None: SWITCH = skip else: SWITCH = partial(nodes.Cache, [switch]) # i18n:domain try: clause = ns[I18N, 'domain'] except KeyError: DOMAIN = skip else: DOMAIN = partial(nodes.Domain, clause) # i18n:context try: clause = ns[I18N, 'context'] except KeyError: CONTEXT = skip else: CONTEXT = partial(nodes.TxContext, clause) # i18n:target try: clause = ns[I18N, 'target'] except KeyError: TARGET = skip else: TARGET = lambda node: nodes.Define( # noqa: E731 do not assign a lambda expression, use a def [nodes.Alias(["default"], "target_language")], nodes.Target(clause, node) ) # i18n:name try: clause = ns[I18N, 'name'] except KeyError: NAME = skip else: if not clause.strip(): NAME = skip else: NAME = partial(nodes.Name, clause) # The "slot" node next is the first node level that can serve # as a macro slot slot = wrap( inner, DEFINE_SLOT, DEFINE, CASE, CONDITION, REPEAT, SWITCH, DOMAIN, CONTEXT, TARGET, ) # metal:fill-slot try: clause = ns[METAL, 'fill-slot'] except KeyError: pass else: if not clause.strip(): raise LanguageError( "Must provide a non-trivial string for metal:fill-slot.", clause ) index = -(1 + int(bool(use_macro or extend_macro))) try: slots = self._use_macro[index] except IndexError: raise LanguageError( "Cannot use metal:fill-slot without metal:use-macro.", clause ) slots = self._use_macro[index] slots.append(nodes.FillSlot(clause, slot)) # metal:define-macro try: clause = ns[METAL, 'define-macro'] except KeyError: pass else: if ns.get((METAL, 'fill-slot')) is not None: raise LanguageError( "Can't have 'fill-slot' and 'define-macro' " "on the same element.", clause ) self._macros[clause] = slot slot = nodes.UseInternalMacro(clause) slot = wrap( slot, NAME ) # tal:on-error try: clause = ns[TAL, 'on-error'] except KeyError: ON_ERROR = skip else: key, value = tal.parse_substitution(clause) translate = ns.get((I18N, 'translate')) == '' fallback = self._make_content_node( value, None, key, translate, ) if omit is False and start['namespace'] not in self.DROP_NS: start_tag = nodes.Start( start['name'], self._maybe_trim(start['prefix']), self._maybe_trim(start['suffix']), nodes.Sequence( [attr for attr in attributes if isinstance(attr, nodes.Attribute) and isinstance(attr.expression, ast.Str)] ) ) if end_tag is None: # Make sure start-tag has opening suffix. We don't # allow self-closing element here. start_tag.suffix = ">" # Explicitly set end-tag. end_tag = nodes.End(start_tag.name, '', '',) fallback = nodes.Element( start_tag, end_tag, fallback, ) ON_ERROR = partial(nodes.OnError, fallback, 'error') clause = ns.get((META, 'interpolation')) if clause in ('false', 'off'): INTERPOLATION = False elif clause in ('true', 'on'): INTERPOLATION = True elif clause is None: INTERPOLATION = self._interpolation[-1] else: raise LanguageError("Bad interpolation setting.", clause) self._interpolation.append(INTERPOLATION) # Visit content body for child in children: body.append(self.visit(*child)) self._switches.pop() self._interpolation.pop() if use_macro: self._use_macro.pop() return wrap( slot, ON_ERROR ) def visit_start_tag(self, start): return self.visit_element(start, None, []) def visit_cdata(self, node): if not self._interpolation[-1] or '${' not in node: return nodes.Text(node) expr = nodes.Substitution(node, ()) return nodes.Interpolation(expr, True, False) def visit_comment(self, node): if node.startswith('