pax_global_header00006660000000000000000000000064150471243140014513gustar00rootroot0000000000000052 comment=aae414692b6052e96d890e03bbeeeca0f4dc01c2 erlydtl-0.15.0/000077500000000000000000000000001504712431400132555ustar00rootroot00000000000000erlydtl-0.15.0/.github/000077500000000000000000000000001504712431400146155ustar00rootroot00000000000000erlydtl-0.15.0/.github/workflows/000077500000000000000000000000001504712431400166525ustar00rootroot00000000000000erlydtl-0.15.0/.github/workflows/main.yml000066400000000000000000000006461504712431400203270ustar00rootroot00000000000000on: push jobs: test: runs-on: ubuntu-24.04 name: Erlang/OTP ${{matrix.otp}} / rebar3 ${{matrix.rebar3}} strategy: matrix: otp: ['26.2', '27.3', '28.0'] rebar3: ['3.24', '3.25'] steps: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1.20.4 with: otp-version: ${{matrix.otp}} rebar3-version: ${{matrix.rebar3}} - run: rebar3 eunit erlydtl-0.15.0/.gitignore000066400000000000000000000004031504712431400152420ustar00rootroot00000000000000ebin *.swp erl_crash.dump examples/rendered_output src/erlydtl_parser.erl *~ ebintest tests/src/erlydtl_extension_testparser.erl tests/output deps .emacs* .eunit /.edts /.rebar/ /*.beam /.settings/ _build /.project include/yeccpre-*.hrl src/erlydtl.app.src-* erlydtl-0.15.0/CONTRIBUTING.md000066400000000000000000000055741504712431400155210ustar00rootroot00000000000000Contributing to erlydtl ----------------------- Before implementing a new feature, please submit a ticket to discuss your plans. The feature might have been rejected already, or the implementation might already be decided. Code style ---------- The following rules must be followed: * Do not introduce trailing whitespace * Do not mix spaces and tabs * Do not introduce lines longer than 120 characters The following rules should be followed: * Write small functions whenever possible * Avoid having too many clauses containing clauses containing clauses. Basically, avoid deeply nested functions. [erlang-mode (emacs)](http://www.erlang.org/doc/man/erlang.el.html) indentation is preferred. This will keep the code base consistent. vi users are encouraged to give [Vim emulation](http://emacswiki.org/emacs/Evil) ([more info](https://gitorious.org/evil/pages/Home)) a try. Pull requests and branching --------------------------- Issue pull requests for master. If it is a bug fix that also applies for the latest release, it can be back ported to the stable branch after merge. Use one topic branch per pull request. If you do that, you can add extra commits or fix up buggy commits via `git rebase -i`, and update the branch. The updated branch will be visible in the same pull request. Therefore, you should not open a new pull request when you have to fix your changes. Do not commit to master in your fork (for your own good). Provide a clean branch without merge commits. Committing your changes ----------------------- Please ensure that all commits pass all tests, and do not have extra Dialyzer warnings. To do that run `make check`. #### Structuring your commits Fixing a bug is one commit. Adding a feature is one commit. Adding two features is two commits. Two unrelated changes is two commits. If you fix a (buggy) commit, squash (`git rebase -i`) the changes as a fixup commit into the original commit. #### Writing Commit Messages It's important to write a proper commit title and description. The commit title must be at most 50 characters; it is the first line of the commit text. The second line of the commit text must be left blank. The third line and beyond is the commit message. You should write a commit message. If you do, wrap all lines at 72 characters. You should explain what the commit does, what references you used, and any other information that helps understanding your changes. Basically, structure your commit message like this:
One line summary (at most 50 characters)

Longer description (wrap at 72 characters)
##### Commit title/summary * At most 50 characters * What was changed * Imperative present tense (Fix, Add, Change) * `Fix bug 123` * `Add 'foobar' command` * `Change default timeout to 123` * No period ##### Commit description * Wrap at 72 characters * Why, explain intention and implementation approach * Present tense erlydtl-0.15.0/LICENSE000066400000000000000000000021431504712431400142620ustar00rootroot00000000000000The MIT License Copyright (c) 2008 Roberto Saccon, Evan Miller Copyright (c) 2014 Andreas Stenius Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. erlydtl-0.15.0/Makefile000066400000000000000000000024221504712431400147150ustar00rootroot00000000000000REBAR=rebar3 $(REBAR_ARGS) .PHONY: all all: compile .PHONY: compile compile: slex @$(REBAR) compile .PHONY: slex slex: src/erlydtl_scanner.erl src/erlydtl_scanner.erl: src/erlydtl_scanner.slex @$(REBAR) slex compile .PHONY: check check: test dialyze .PHONY: test test: @$(REBAR) eunit .PHONY: dialyze dialyze: compile @$(REBAR) dialyzer .PHONY: clean clean: @$(REBAR) clean .PHONY: realclean realclean: clean rm -rf _build .PHONY: committed committed: @git diff --no-ext-diff --quiet --exit-code || { echo "there are uncommitted changes in the repo." ; false ;} .PHONY: committed release: committed check @{ \ V0=$$(grep vsn src/erlydtl.app.src | sed -e 's/.*vsn,.*"\(.*\)".*/\1/') && \ V1=$$(grep '##' -m 1 NEWS.md | sed -e 's/##[^0-9]*\([0-9.-]*\).*/\1/') && \ read -e -p "OK, all tests passed, current version is $$V0, which version should we release now? ($$V1)" V2 && \ : $${V2:=$$V1} && \ echo "$$V2 it is..." && \ sed -i -e 's/vsn,.*}/vsn, "'$$V2'"}/' src/erlydtl.app.src && \ git ci -m "release v$$V2" src/erlydtl.app.src && \ git tag $$V2 && \ echo 'Updated src/erlydtl.app.src and tagged, run `git push origin master --tags` when ready' \ ;} erlydtl-0.15.0/NEWS.md000066400000000000000000000166411504712431400143630ustar00rootroot00000000000000# ErlyDTL NEWS file This file records noteworthy changes and additions to erlydtl as suggested by the [GNU Coding Standards](http://www.gnu.org/prep/standards/html_node/NEWS-File.html#NEWS-File). ## 0.14.0 (2021-03-19) * #270 Added nowarn flag to export all in erlydtl_runtime * #271 Hex wrong vsn and support OTP 23 ## 0.13.0 (2020-02-15) * #254 Semantical issue in rebar.config.script * #255 OTP20 compatibility * #261 OTP21 compatibility * #263 Ensure modules are loaded when checking function_exported * #267 Fix erlang:get_stacktrace() warning ## 0.12.1 (2016-09-21) * #248 support filters in `{{ _(var) }}`. ## 0.12.0 (2016-08-08) * #230 Use phash2/1 explicitly. It's not in compat layer * #240 custom tags parameters parsing not handling newlines * #242 Support for 19 * #246 make the 'join' filter work with numbers ## 0.11.1 (2015-10-26) Fix build issues and missed update version info in .app-file. ## 0.11.0 (2015-10-25) I've failed to keep track. Please check resolved issues on [github](https://github.com/erlydtl/erlydtl). ## 0.10.0 (2014-12-20) * Fix issue with generated code for `for` loops (#167). * Fix issue with using keywords as attributes (#177), or as variables (#194). * Fix issue when including multiple templates extending a common base template (#176). * New `w` option for toggling compile time warnings. Currently, there is only one, `non_block_tag`, which is triggered on any non-block data in an extends-template. * Add missing features to the `cycle` tag (#195) (still missing independently stepping the cycle value). * Support records in regroup tag (#191). * Support for maps (#196). ## 0.9.4 (2014-04-15) * Fix compile time variables and constants (#61) * The `vars` compile time option has been deprecated in favor of `default_vars`. * Support for translation contexts (#131) `context` is now a reserved keyword. * Support for plural forms in `blocktrans` blocks (#131) As a side effect of the this, `count` and `plural` are now reserved keywords (the latter only as the tag name). * Renamed compile options for `translation_fun` and `locales` to align with the render options counter parts. * Support `_` prefix on template variables to avoid unused variable warnings, Erlang style (#164). * Switched to `eunit_formatters` by @seancribbs for improved eunit reporting. * All tests pass on Erlang 17.0! :) ## 0.9.3 (2014-03-27) * Fix release process to work for non-git installations (#154). * Fix list indexing (#155). * New option for 0-based list/tuple access (#156) (see README for details). ## 0.9.2 (2014-03-22) * Added NEWS file. * Fixed broken `compile_dir` (#146). * The backwards incompatible change in 0.9.1 for directory-compiled templates has been reverted. A new `render` function has been added instead (#148). * Fixed broken escape feature (#150). * Added translator comments (#152, #127) (@seriyps). ## 0.9.1 (2014-03-02) This release brings a row of major internal improvements and also a few new and improved features. * Replaced most of `erl_syntax` use for merl (#123). * Refactored compiler and test suite (#134, #136). * Updated result value for directory-compiled (`compile_dir`) templates to return `{ok, Rendered} | {error, Reason}` just as for normally compiled templates. Please note that this is a **backwards incompatible change**. * Regroup tag is now Django compatible! (the formerly required `endregroup` tag has been made optional) (#101). * The `trans` tag now supports the `{% trans "text string" as Varname %}` construct to catch the translated text rather than output it in place. * `{{ block.super }}` is now supported in child blocks (#18). * `Template:variables()` now only lists referenced render variables (#118). * New `erlydtl_library` behaviour for adding custom filters and tags (#137). * New `load` tag added. Only difference to that of django is that we don't support loading a library from a package, as there are no notion of packages in Erlang (#100). * New compile options supporting the load tag: `libraries` and `default_libraries`. See the readme. (the previous `custom_tags_modules` and `custom_filters_modules` are still supported, although being marked deprecated in favour of the new options). * New compile options for aid in debugging erlydtl itself: `debug_compiler` and `debug_root` as described in the readme (#139). See milestone 0.9.1 for a complete list of closed issues in this release. Happy templating! ## 0.9.0 (2014-02-17) After some fluctuating stability around *0.8.1*, this release brings a set of improvements that has been tested not only by the test suite, but also in a couple of projects. * API changes. Added `_file` and `_template` suffixes to the compile functions to explicitly convey what is to be compiled. * Autoescape is now enabled by default. Use the relatively new `auto_escape` option if you want this disabled by default. * `blocktrans` blocks can now be translated to multiple locales in runtime. (thanks to @seriyps). * `trans` and `_(...)` tags can now be translated to multiple locales at compile time. (thanks to @seriyps). * Don't want to save the compiled .beam file? Use `{out_dir, false}` compile time option (this was possible before as well, when no out_dir was specified, but that results in a warning). * The error reporting has been fixed for certain cases. ## 0.8.2 (2014-01-29) My sincere apologies, this really should have been in the *0.8.1* release. This release brings the new error/warning options and return values also to the `compile_dir` api function. **Backwards incompatible notes:** Please notice that the new error reporting options also introduces subtle incompatible changes in the API (see #126). It's not hard to adapt, but I want you all to be aware of this when upgrading. These changes were made already in *0.8.1*, but the specs were not, so dialyzer did not pick up on them. ## 0.8.1 (2014-01-19) This release is a clean-up release with a bunch of minor issues squeezed out. * Improved options for error reporting. * new auto_escape compile option for django purists. * Cleaned out some warnings given by Dialyzer. * Major improvements to the generated code for `for`-loops. **Warning** The `compile_dir` function is not updated to the new way of reporting errors and warnings, and is in a semi-workable state. A fix is committed to *master*, and will be included in the next release. ## 0.8.0 (2014-01-10) This is a release to bring in all the changes together since *0.7.0*, and will be the starting point for a lot of future improvements. ## 0.7.0 (2011-03-21 * Support no-argument version of `date` filter. ## 0.6.0 (2010-04-28) * Implement `trans` tag with support for .po files Includes a .po parser and generator, docs, and tests. See README_I18N. Many thanks to David Garcia. ## 0.5.3 (2009-08-13) * Create "ebin" directory if it does not exist. Thanks to uri.sharf for the bug report, ja...@nialscorva.net for the patch. ## 0.5.2 (2009-02-13) * Do not require a space between a variable name and closing bracket, e.g. allow `{{var1}}`. ## 0.5.1 (2008-10-23) * Applied patch from Dan Milstein to fix a crash when rendering floats. * Added a baseline failing test case for the problem. ## 0.5.0 (2008-08-07) * Run functional tests from "make test". * Clean up the output from all test suites. * New README file. * Remove create_parser stuff because "make" handles this. * Make `erlydtl:compile/2,3` a wrapper for `erlydtl_compiler:compile/2,3`. erlydtl-0.15.0/README.markdown000066400000000000000000000503301504712431400157570ustar00rootroot00000000000000ErlyDTL [![Build Status](https://travis-ci.org/erlydtl/erlydtl.png?branch=master)](https://travis-ci.org/erlydtl/erlydtl) ======= ErlyDTL compiles Django Template Language to Erlang bytecode. Project homepage: ErlyDTL implements the Django Template Language as documented for version *1.6*, here: Despite our best efforts to be completely compatible with the Django Template Language, there are still a few [differences](https://github.com/erlydtl/erlydtl#differences-from-standard-django-template-language). Call for maintainers ==================== My current position in life does not give me the time I feel this project deserves. So, if you feel that there are things that ought to be done, and have the skills and time to do them, please get in touch. -- @kaos ### The erlydtl branches & tags We follow a [gitflow](http://nvie.com/posts/a-successful-git-branching-model/) inspired branching model, where `master` is the latest released version (a.k.a. stable), and use `develop` for stuff that may break from time to time. #### master branch Releases are made from the *master* branch, with dependency versions pinned down and a hard coded version number in the app file. #### develop branch This is were all the action is, and at times may be slightly broken. Suitable for early adopters who aren't afraid of a little debugging and hopefully also reporting issues. #### release tags Whenever *master* is deemed stable with a nice set of additions/changes, it is tagged for a new release. As we're still going for the big 1.0 release, breaking changes **may** be introduced also on minor release bumps; but after that, we'll stick with [semver](http://semver.org/). Compilation ----------- To compile ErlyDTL, run make in this directory. #### Do not use Erlang R16B03 The erl syntax tools is broken in Erlang R16B03, use R16B03-1 or any other supported version instead. #### Do use a recent version of rebar In case of compilation issues, make sure your version of rebar is up-to-date. Older versions of rebar does not support the `raw` option for dependencies, and may produce an error message like this: ``` ERROR: Invalid dependency specification {merl,".*", {git, "git://github.com/erlydtl/merl.git", "28e5b3829168199e8475fa91b997e0c03b90d280"}, [raw]} ``` Template compilation -------------------- Usage: ```erlang erlydtl:compile_file("/path/to/template.dtl", my_module_name) erlydtl:compile_file("/path/to/template.dtl", my_module_name, Options) erlydtl:compile_template("{{ foo }}", my_module_name) erlydtl:compile_template("{{ foo }}", my_module_name, Options) ``` Result: ```erlang {ok, Module} {ok, Module, Warnings} {ok, Module, Binary} {ok, Module, Binary, Warnings} error {error, Errors, Warnings} ``` Options is a proplist possibly containing: * `auto_escape` - Control automatic HTML escaping of template values. Enabled by default. * `binary` - Include the compiled template binary code in the result tuple (between the module name and any warning/error lists). Note, this option is named the same as for the Erlang compiler, with similar use, except that this option does NOT affect whether or not a .beam file is saved. * `binary_strings` - Whether to compile strings as binary terms (rather than lists). Defaults to `true`. * `compiler_options` - Proplist with extra options passed directly to `compiler:forms/2`. This can prove useful when using extensions to add extra defines etc when compiling the generated code. * `constants` - Replace template variables with a constant value when compiling the template. This can _not_ be overridden when rendering the template. See also `default_vars`. * `custom_filters_modules` **deprecated** - A list of modules to be used for handling custom filters. The modules will be searched in order and take precedence over the built-in filters. Each custom filter should correspond to an exported filter, e.g. ```erlang some_filter(Value) -> iolist() ``` If the filter takes any arguments (e.g. "foo:2"), those will be added to the call: ```erlang some_filter(Value, Arg) -> iolist() ``` * `custom_tags_dir` - Directory of DTL files (no extension) includable as tags. E.g. if `$custom_tags_dir/foo` contains `{{ bar }}`, then `{% foo bar=100 %}` will evaluate to `100`. * `custom_tags_modules` **deprecated** - A list of modules to be used for handling custom tags. The modules will be searched in order and take precedence over `custom_tags_dir`. Each custom tag should correspond to an exported function with one of the following signatures: ```erlang some_tag(TagVars) -> iolist() some_tag(TagVars, Options) -> iolist() ``` The `TagVars` are variables provided to a custom tag in the template's body (e.g. `{% foo bar=100 %}` results in `TagVars = [{bar, 100}]`). The `Options` are options passed as the second argument to the `render/2` call at render-time. (These may include any options, not just `locale` and `translation_fun`.) * `debug_compiler` - Enable compiler debug diagnostics. Currently it debug prints the options passed to `compile:forms` (i.e. if verbosity is >= 2; that is, with two or more `verbose` options) and enables the saving of the compiled template in source form to a .erl file. * `debug_info` - This option is passed to `compile:forms` to include debug information in the compiled module. * `debug_root` - Only applies when `debug_compiler` is `true`. The root directory for debug source dumps. If set to `false`, no source dump will be saved. Defaults to `undefined`, leaving the source dump next to the source template file. * `default_libraries` - A list of libraries that should be loaded by default when compiling a template. Libraries can be specified either by name (when there is a name to module mapping also provided in the `libraries` option) or by module. * `default_vars` - Provide default values for variables. Any value from the render variables takes precedence. Notice: in case the value is a `fun/0`, it will be called at compile time. See also `constants`. * `doc_root` - Included template paths will be relative to this directory; defaults to the compiled template's directory. * `extension_module` **experimental** - This is work in progress to make erlydtl extensible. * `force_recompile` - Recompile the module even if the source's checksum has not changed. Useful for debugging. * `libraries` - A list of `{Name, Module}` libraries implementing custom tags and filters. `Module` should implement the `erlydtl_library` behaviour (see [Custom tags and filters] below). * `lists_0_based` - **Compatibility warning** Defaults to `false`, giving 1-based list access, as is common practice in Erlang. Set it to `true` to get 1-based access as in Django, or to `defer` to not decide until render time, using the render option `lists_0_based`. See also `tuples_0_based`. * `locale` - Locale to translate to during compile time. May be specified multiple times as well as together with the `locales` option. * `locales` - A list of locales to be passed to `translation_fun`. Defaults to []. * `no_env` - Do not read additional options from the OS environment variable `ERLYDTL_COMPILER_OPTIONS`. * `no_load` - Do not load the compiled template. * `out_dir` - Directory to store generated .beam files. If not specified, no .beam files will be created and a warning is emitted. To silence the warning, use `{out_dir, false}`. * `reader` - {module, function} tuple that takes a path to a template, may be ReaderOptions and returns a binary with the file contents. Defaults to `{file, read_file}`. Useful for reading templates from a network resource. * `reader_options` - list of {option_name, Option} that passed as the second parameter to `reader`. ```erlang extra_reader(FileName, ReaderOptions) -> UserID = proplists:get_value(user_id, ReaderOptions, <<"IDUnknown">>), UserName = proplists:get_value(user_name, ReaderOptions, <<"NameUnknown">>), case file:read_file(FileName) of {ok, Data} when UserID == <<"007">>, UserName == <<"Agent">> -> {ok, Data}; {ok, _Data} -> {error, "Not Found"}; Err -> Err end. ``` ``` CompileResult = erlydtl:compile(Body, ModName, [return, {reader, {?MODULE, extra_reader}}, {reader_options, [{user_id, <<"007">>}, {user_name, <<"Agent">>}]} ]), ``` * `record_info` - List of records to look for when rendering the template. Each record info is a tuple with the fields of the record: ```erlang {my_record, record_info(fields, my_record)} ``` * `return` - Short form for both `return_warnings` and `return_errors`. * `return_warnings` - If this flag is set, then an extra field containing warnings is added to the tuple returned on success. * `return_errors` - If this flag is set, then an error-tuple with two extra fields containing errors and warnings is returned when there are errors. * `report` - Short form for both `report_warnings` and `report_errors`. * `report_warnings` - Print warnings as they occur. * `report_errors` - Print errors as they occur. * `translation_fun` - A two-argument fun to use for translating `blocktrans` blocks, `trans` tags and `_(..)` expressions at compile time. This will be called once for each pair of translated element and locale specified with `locales` and `locale` options. The fun should take the form: ```erlang fun (Block::string(), Locale|{Locale, Context}) -> <<"ErlyDTL code">>::binary() | default when Locale::string(), Context::string(). ``` Please, keep in mind, that if your templates where not specially designed, you probably still need render time translations. See description of the `translation_fun` render option for more details on the translation `context`. Notice, you may instead pass a `fun/0`, `{Module, Function}` or `{Module, Function, Args}` which will be called recursively until it yields a valid translation function, at which time any needed translation setup actions can be carried out prior to returning the next step (either another setup function/tuple, or the translation function). ```erlang %% sample translation setup fun () -> translation_engine:init(), fun translation_engine:translate/2 end ``` * `tuples_0_based` - **Compatibility warning** Defaults to `false`, giving 1-based tuple access, as is common practice in Erlang. Set it to `true` to get 1-based access as in Django, or to `defer` to not decide until render time, using the render option `tuples_0_based`. See also `lists_0_based`. * `vars` **deprecated** - Use `default_vars` instead. Variables (and their values) to evaluate at compile-time rather than render-time. * `verbose` - Enable verbose printing of compilation progress. Add several for even more verbose (e.g. debug) output. * `w` - Enable/Disable compile time checks. Available checks: - `non_block_tag` indicated that there is other data than `block` tags in an extends-template (e.g. a template that begins with the `extends` tag). * `warnings_as_errors` - Treat warnings as errors. Helper compilation ------------------ Helpers provide additional templating functionality and can be used in conjunction with the `custom_tags_module` option above. They can be created from a directory of templates thusly: ```erlang erlydtl:compile_dir("/path/to/dir", my_helper_module_name) erlydtl:compile_dir("/path/to/dir", my_helper_module_name, Options) ``` The resulting module will export a function for each template appearing in the specified directory. Options is the same as for `compile/3`. Compiling a helper module can be more efficient than using `custom_tags_dir` because the helper functions will be compiled only once (rather than once per template). Notice: The exported template functions return an `iolist()` on success only, failures are non-local (e.g. as a throw). To get the result in wrapped tuple `{ok, iolist()} | {error, Reason}` call one of the `render` functions: `render(Tag) | render(Tag, Vars) | render(Tag, Vars, Opts)`. Usage (of a compiled template) ------------------------------ ### render/1 ```erlang my_compiled_template:render(Variables) -> {ok, IOList} | {error, Err} ``` Variables is a proplist, dict, gb_tree, or a parameterized module (whose method names correspond to variable names). The variable values can be atoms, strings, binaries, or (nested) variables. IOList is the rendered template. ### render/2 ```erlang my_compiled_template:render(Variables, Options) -> {ok, IOList} | {error, Err} ``` Same as `render/1`, but with the following options: * `translation_fun` - A `fun/1` or `fun/2` that will be used to translate strings appearing inside `{% trans %}` and `{% blocktrans %}` tags at render-time. The simplest TranslationFun would be `fun(Val) -> Val end`. Placeholders for blocktrans variable interpolation should be wrapped in `{{` and `}}`. In case of `fun/2`, the extra argument is the current locale, possibly together with a translation context in a tuple: ```erlang fun (Val|{Val, {Plural_Val, Count}}, Locale|{Locale, Context}) -> Translated_Val end ``` The context is present when specified in the translation tag. Example: ```django {% trans "Some text to translate" context "app-specific" %} or {% blocktrans context "another-context" %} Translate this for {{ name }}. {% endblocktrans %} ``` The plural form is present when using `count` and `plural` in a `blocktrans` block: ```django {% blocktrans count counter=var|length %} There is {{ counter }} element in the list. {% plural %} There are {{ counter }} elements in the list. {% endblocktrans %} ``` Render time translation function is also used to translate dates. Date tokens mimics those used in django, so you may reuse django translations. Tokens may appear in a date are: 1. Full months names, capitalized ("January" .. "December"); 2. 3 letters months names ("jan" .. "dec"); 3. Associated Press style months ("Jan." .. "Dec." with "abbrev.month" context); 4. Alternative month name, for "E" option ("January" .. "December" with "alt. month" context); 5. Full week day names, capitalized ("Monday" .. "Sunday"); 6. Abbreviated week day names, capitalized ("Mon" .. "Sun"); 7. day time tokens ("AM", "PM", "a.m.", "p.m.", "noon", "midnight"); While date token values may be passed as lists, consider using binaries as a default string format. Here is a simple but robust translation function stub: ```erlang translation_placeholder({Val,{Plural, Count}}, {L, C}) when is_list(Val) -> translation_placeholder({list_to_binary(Val),{Plural, Count}}, LC); translation_placeholder(Val, {L, C}) when is_list(Val) -> translation_placeholder(list_to_binary(Val), LC); translation_placeholder(Val, {L, C}) when is_list(C) -> translation_placeholder(Val, list_to_binary(C)); translation_placeholder(Val, {L, C}) when is_list(C) -> io:format("Translating ~p into ~p with context ~p~n", [Val, L, C]), %% do nothing and return original value Val. ``` The translation fun can also be a `fun/0`, `{Module, Function}` or `{Module, Function, Args}` which will be called recursively until it yields a valid translation function, at which time any needed translation setup actions can be carried out prior to returning the next step (either another setup function/tuple, or the translation function). * `lists_0_based` - If the compile option `lists_0_based` was set to `defer`, pass this option (or set it to true, `{lists_0_based, true}`) to get 0-based list indexing when rendering the template. See also `tuples_0_based`. * `locale` - A string specifying the current locale, for use with the `translation_fun` compile-time option. * `tuples_0_based` - If the compile option `tuples_0_based` was set to `defer`, pass this option (or set it to true, `{tuples_0_based, true}`) to get 0-based tuple indexing when rendering the template. See also `lists_0_based`. ### translatable_strings/0 ```erlang my_compiled_template:translatable_strings() -> [String] ``` List of strings appearing in `{% trans %}` and `_(..)` tags. ### translated_blocks/0 ```erlang my_compiled_template:translated_blocks() -> [String] ``` List of strings appearing in `{% blocktrans %}...{% endblocktrans %}` blocks; the translations (which can contain ErlyDTL code) are hard-coded into the module and appear at render-time. To get a list of translatable blocks before compile-time, use the provided `blocktrans_extractor` module. ### source/0 ```erlang my_compiled_template:source() -> {FileName, CheckSum} ``` Name and checksum of the original template file. ### dependencies/0 ```erlang my_compiled_template:dependencies() -> [{FileName, CheckSum}] ``` List of names/checksums of templates included by the original template file. Useful for frameworks that recompile a template only when the template's dependencies change. ### variables/0 ```erlang my_compiled_template:variables() -> [Variable::atom()] ``` Sorted list of unique variables used in the template's body. The list can be used for determining which variable bindings need to be passed to the `render/3` function. ### default_variables/0 ```erlang my_compiled_template:default_variables() -> [Variable::atom()] ``` Like `variables/0`, but for any variable which have a default value provided at compile time. ### constants/0 ```erlang my_compiled_template:constants() -> [Variable::atom()] ``` Like `default_variables/0`, but these template variables has been replaced with a fixed value at compile time and can not be changed when rendering the template. Custom tags and filters ----------------------- Starting with release *0.9.1*, the recommended way to add custom tags and filters are to register a module implementing the `erlydtl_library` behaviour. There are two functions needed to implement a custom library: `version/0` and `inventory/1`. The `version/0` function is to be able to keep backwards compatibility in face of an evolving library behaviour, and should return the version of the behaviour the library supports. The valid range of versions is in the spec for the `version/0` callback in `erlydtl_library.erl`. ### Library version 1 The `inventory/1` function is called with either `filters` or `tags` as argument, and should return a list of all filters or tags available in that library, respectively (see spec in `erlydtl_library.erl` for details on syntax for this list). Tag functions must take a list of arguments, and may take a list of render options, and should return an `iolist()` as result value (see `custom_tags_modules` compile option). Filter functions must take an `iolist()` value to filter, and may take an argument, and should return an `iolist()` as result value (see `custom_filters_modules` compile option). Differences from standard Django Template Language -------------------------------------------------- * `csrf_token` The [Cross Site Request Forgery](https://docs.djangoproject.com/en/1.6/ref/contrib/csrf/) tag is not implemented. * `url` The [url](https://docs.djangoproject.com/en/1.6/ref/templates/builtins/#url) tag is not implemented. This should be [addressed](https://github.com/erlydtl/erlydtl/issues/115) in a future release. * List indexing is 1-based in erlydtl, while 0-based in Django (see [#156](https://github.com/erlydtl/erlydtl/issues/156)). * For an up-to-date list, see all [issues](https://github.com/erlydtl/erlydtl/issues) marked `dtl_compat`. * Erlang specifics: Template variables may be prefixed with underscore (`_`) to avoid "unused variable" warnings (see [#164](https://github.com/erlydtl/erlydtl/issues/164)). * `cycle` tags do not support independently moving the cycle value from the original loop. Tests ----- From a Unix shell, run: make tests Note that the tests will create some output in tests/output in case of regressions. License ------- ErlyDTL is released under the MIT license. erlydtl-0.15.0/README_I18N000066400000000000000000000062701504712431400147010ustar00rootroot00000000000000Generate gettext infrastructure ------------------------------- Erlydtl allows templates to use i18n features based on gettext. Standard po files can be used to generate i18ized templates. A template parser/po generator is also provided. Translation done by `{%trans%}`, `{%blocktrans%}` tags and by wrapping variables in `_(...)` construction. Translation may be applied in compile time (translated strings are embedded in compiled template code) or in runtime (template will query gettext server during template rendering). 1'st is faster in terms of template rendering time, 2'nd is more flexible (you may update locales without any template recompilation). In order to apply compile-time translation, you must pass `blocktrans_fun` plus `blocktrans_locales` (this will translate `{%blocktrans%}`) and/or `locale` (this translates `{%trans%}` and `_("string literal")` values) to `erlydtl:compile/3`. Next, you should pass `locale` option to `my_compiled_template:render/2`. If you prefer runtime translation, just don't pass `blocktrans_fun` and/or `locale` compilation options and add `translation_fun` option to `my_compiled_template:render/2`. 1. In order to enable i18n you first, you'll need gettext library to be available on your lib_path. Library can be downloaded from http://github.com/etnt/gettext 2. Then you'll need to add a parse target on your makefile (or the script used to trigger template reparsing) trans: erl -pa ./ebin ./deps/*/ebin -noshell -s reloader -run i18n_manager \ generate_pos "en,es" "./views/*/*.html,./views/*.html" rm -rf $(GETTEXT_DIR)/lang/default-old mv $(GETTEXT_DIR)/lang/default $(GETTEXT_DIR)/lang/default-old cp -rf $(GETTEXT_DIR)/lang/$(GETTEXT_TMP_NAME) $(GETTEXT_DIR)/lang/default rm -rf $(GETTEXT_DIR)/lang/$(GETTEXT_TMP_NAME)/* Mind that GETTEXT_DIR and GETTEXT_TMP_NAME must be bound to existing directories. Args passed to i18n_manager:generate_pos are locales that will be supported (generating dir structure and po files) and directories where generator will search for template files including trans tags. 3. If you wish to translate templates at compile-time, gettext server must be running before template parsing and it must be populated with the content of the po files. Consider adding this snipplet to the code before template parsing gettext_server:start(), LoadPo = fun(Lang)-> {_, Bin} = file:read_file("./lang/default/"++ Lang ++"/gettext.po"), gettext:store_pofile(Lang, Bin) end, lists:map(LoadPo, ["es","en"]). Here locales are the codes are provided to gettext. Those codes must be a subset of the locales provided to po generation process. 4. Update strings. Edit po files on $(GETTEXT_DIR)/lang/default/$(LOCALE)/gettext.po translating msgstr to the translated version of their corresponding msgstr. 5. Generate localized templates providing `blocktrans_fun` and `blocktrans_locales` compile options or use runtime translation via `translation_fun` rendering option. erlydtl-0.15.0/bin/000077500000000000000000000000001504712431400140255ustar00rootroot00000000000000erlydtl-0.15.0/bin/erlydtl_compile000077500000000000000000000012171504712431400171430ustar00rootroot00000000000000#!/usr/bin/env escript %% -*- mode: erlang -*- -export([main/1]). %% External API main([Prefix, File]) -> ensure(), Basename = filename:basename(File, ".dtl"), ModuleName = list_to_atom(string:to_lower(lists:flatten([Prefix, "_", Basename]))), erlydtl_compiler:compile(File, ModuleName, [{out_dir, filename:dirname(File)}]); main(_) -> usage(). ensure() -> code:add_patha("ebin"), code:add_patha("lib/erlydtl/ebin"), [D1,D2|_] = code:get_path(), io:format("Code Path: ~p~n", [[D1,D2]]). usage() -> io:format("usage: ~s name [destdir]~n", [filename:basename(escript:script_name())]), halt(1). erlydtl-0.15.0/include/000077500000000000000000000000001504712431400147005ustar00rootroot00000000000000erlydtl-0.15.0/include/erlydtl_ext.hrl000077500000000000000000000044741504712431400177620ustar00rootroot00000000000000 -record(error_info, { return = false, report = false, list = [] }). -record(dtl_context, { local_scopes = [], block_dict = dict:new(), trans_fun = none, trans_locales = [], auto_escape = [off], doc_root = "", parse_trail = [], vars = [], const = [], record_info = [], filters = [], tags = [], libraries = [], custom_tags_dir = [], reader = {file, read_file}, reader_options = [], module = undefined, compiler_options = [], binary_strings = true, force_recompile = false, verbose = 0, is_compiling_dir = false, extension_module = undefined, scanner_module = erlydtl_scanner, scanned_tokens = [], all_options = [], errors = #error_info{}, warnings = #error_info{}, bin = undefined, lists_0_based = false, tuples_0_based = false, checks = [non_block_tag] }). %% ALL fields of ast_info{} must be lists (see erlydtl_compiler_utils:merge_info/2) -record(ast_info, { dependencies = [], translatable_strings = [], translated_blocks= [], custom_tags = [], var_names = [], def_names = [], const_names = [] }). -record(treewalker, { counter = 0, safe = false, extension = undefined, context }). -record(scanner_state, { template=[], scanned=[], pos={1,1}, state=in_text }). -define(ERR(Err, Ctx), erlydtl_compiler_utils:add_error(?MODULE, Err, Ctx)). -define(WARN(Warn, Ctx), erlydtl_compiler_utils:add_warning(?MODULE, Warn, Ctx)). -define(V_INFO,1). -define(V_DEBUG,2). -define(V_TRACE,3). -define(LOG_INFO(Fmt, Args, Ctx), erlydtl_compiler_utils:print(?V_INFO, Fmt, Args, Ctx)). -define(LOG_DEBUG(Fmt, Args, Ctx), erlydtl_compiler_utils:print(?V_DEBUG, Fmt, Args, Ctx)). -define(LOG_TRACE(Fmt, Args, Ctx), erlydtl_compiler_utils:print(?V_TRACE, Fmt, Args, Ctx)). -ifndef(OTP_RELEASE). -define(WITH_STACKTRACE(T, R, S), T:R -> S = erlang:get_stacktrace(), ). -else. -define(WITH_STACKTRACE(T, R, S), T:R:S ->). -endif. erlydtl-0.15.0/include/erlydtl_preparser.hrl000077500000000000000000000161511504712431400211600ustar00rootroot00000000000000%% -*- mode: erlang -*- %% vim: syntax=erlang %% This file is based on parsetools/include/yeccpre.hrl %% %% The applied modifiactions are to enable the caller to recover %% after a parse error, and then resume normal parsing. %% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % The parser generator will insert appropriate declarations before this line.% -export([parse/1, parse_and_scan/1, format_error/1, resume/1]). -include("erlydtl_ext.hrl"). -type yecc_ret() :: {'error', _, _} | {'ok', _}. -spec parse(Tokens :: list()) -> yecc_ret(). parse(Tokens) -> yeccpars0(Tokens, {no_func, no_line}, 0, [], []). -spec parse_and_scan({function() | {atom(), atom()}, [_]} | {atom(), atom(), [_]}) -> yecc_ret(). parse_and_scan({F, A}) -> yeccpars0([], {{F, A}, no_line}, 0, [], []); parse_and_scan({M, F, A}) -> Arity = length(A), yeccpars0([], {{fun M:F/Arity, A}, no_line}, 0, [], []). resume([Tokens, Tzr, State, States, Vstack]) -> yeccpars0(Tokens, Tzr, State, States, Vstack). -spec format_error(any()) -> [char() | list()]. format_error(Message) -> case io_lib:deep_char_list(Message) of true -> Message; _ -> io_lib:write(Message) end. %% To be used in grammar files to throw an error message to the parser %% toplevel. Doesn't have to be exported! -compile({nowarn_unused_function, return_error/2}). -spec return_error(integer(), any()) -> no_return(). return_error(Line, Message) -> throw({error, {Line, ?MODULE, Message}}). -compile({nowarn_unused_function, return_state/0}). return_state() -> throw(return_state). -define(CODE_VERSION, "1.4"). yeccpars0(Tokens, Tzr, State, States, Vstack) -> try yeccpars1(Tokens, Tzr, State, States, Vstack) catch ?WITH_STACKTRACE(error, Error, Stacktrace) try yecc_error_type(Error, Stacktrace) of Desc -> erlang:raise(error, {yecc_bug, ?CODE_VERSION, Desc}, Stacktrace) catch _:_ -> erlang:raise(error, Error, Stacktrace) end; %% Probably thrown from return_error/2: throw: {error, {_Line, ?MODULE, _M}} = Error -> Error end. yecc_error_type(function_clause, [{?MODULE,F,ArityOrArgs,_} | _]) -> case atom_to_list(F) of "yeccgoto_" ++ SymbolL -> {ok,[{atom,_,Symbol}],_} = erl_scan:string(SymbolL), State = case ArityOrArgs of [S,_,_,_,_,_,_] -> S; _ -> state_is_unknown end, {Symbol, State, missing_in_goto_table} end. -define(checkparse(CALL, STATE), try case CALL of {error, Error} -> {error, Error, STATE}; Else -> Else end catch throw: return_state -> {ok, STATE} end). yeccpars1([Token | Tokens], Tzr, State, States, Vstack) -> ?checkparse( yeccpars2(State, element(1, Token), States, Vstack, Token, Tokens, Tzr), [[Token|Tokens], Tzr, State, States, Vstack] ); yeccpars1([], {{F, A},_Line}, State, States, Vstack) -> case apply(F, A) of {ok, Tokens, Endline} -> yeccpars1(Tokens, {{F, A}, Endline}, State, States, Vstack); {eof, Endline} -> yeccpars1([], {no_func, Endline}, State, States, Vstack); {error, Descriptor, _Endline} -> {error, Descriptor} end; yeccpars1([], {no_func, no_line}, State, States, Vstack) -> Line = 999999, yeccpars2(State, '$end', States, Vstack, yecc_end(Line), [], {no_func, Line}); yeccpars1([], {no_func, Endline}, State, States, Vstack) -> yeccpars2(State, '$end', States, Vstack, yecc_end(Endline), [], {no_func, Endline}). %% yeccpars1/7 is called from generated code. %% %% When using the {includefile, Includefile} option, make sure that %% yeccpars1/7 can be found by parsing the file without following %% include directives. yecc will otherwise assume that an old %% yeccpre.hrl is included (one which defines yeccpars1/5). yeccpars1(State1, State, States, Vstack, Token0, [Token | Tokens], Tzr) -> ?checkparse( yeccpars2(State, element(1, Token), [State1 | States], [Token0 | Vstack], Token, Tokens, Tzr), [[Token0, Token | Tokens], Tzr, State1, States, Vstack] ); yeccpars1(State1, State, States, Vstack, Token0, [], {{_F,_A}, _Line}=Tzr) -> yeccpars1([], Tzr, State, [State1 | States], [Token0 | Vstack]); yeccpars1(State1, State, States, Vstack, Token0, [], {no_func, no_line}) -> Line = yecctoken_end_location(Token0), yeccpars2(State, '$end', [State1 | States], [Token0 | Vstack], yecc_end(Line), [], {no_func, Line}); yeccpars1(State1, State, States, Vstack, Token0, [], {no_func, Line}) -> yeccpars2(State, '$end', [State1 | States], [Token0 | Vstack], yecc_end(Line), [], {no_func, Line}). %% For internal use only. yecc_end({Line,_Column}) -> {'$end', Line}; yecc_end(Line) -> {'$end', Line}. yecctoken_end_location(Token) -> try erl_anno:end_location(element(2, Token)) of undefined -> yecctoken_location(Token); Loc -> Loc catch _:_ -> yecctoken_location(Token) end. -compile({nowarn_unused_function, yeccerror/1}). yeccerror(Token) -> Text = yecctoken_to_string(Token), Location = yecctoken_location(Token), {error, {Location, ?MODULE, ["syntax error before: ", Text]}}. -compile({nowarn_unused_function, yecctoken_to_string/1}). yecctoken_to_string(Token) -> try erl_scan:text(Token) of undefined -> yecctoken2string(Token); Txt -> Txt catch _:_ -> yecctoken2string(Token) end. yecctoken_location(Token) -> try erl_scan:location(Token) catch _:_ -> element(2, Token) end. -compile({nowarn_unused_function, yecctoken2string/1}). yecctoken2string({atom, _, A}) -> io_lib:write(A); yecctoken2string({integer,_,N}) -> io_lib:write(N); yecctoken2string({float,_,F}) -> io_lib:write(F); yecctoken2string({char,_,C}) -> io_lib:write_char(C); yecctoken2string({var,_,V}) -> io_lib:format("~s", [V]); yecctoken2string({string,_,S}) -> io_lib:write_string(S); yecctoken2string({reserved_symbol, _, A}) -> io_lib:write(A); yecctoken2string({_Cat, _, Val}) -> io_lib:format("~p",[Val]); yecctoken2string({dot, _}) -> "'.'"; yecctoken2string({'$end', _}) -> []; yecctoken2string({Other, _}) when is_atom(Other) -> io_lib:write(Other); yecctoken2string(Other) -> io_lib:write(Other). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% erlydtl-0.15.0/priv/000077500000000000000000000000001504712431400142355ustar00rootroot00000000000000erlydtl-0.15.0/priv/custom_tags/000077500000000000000000000000001504712431400165655ustar00rootroot00000000000000erlydtl-0.15.0/priv/custom_tags/flashvideo000066400000000000000000000017341504712431400206410ustar00rootroot00000000000000

To view the Video:

{{ alt }}

erlydtl-0.15.0/rebar-slex.config000066400000000000000000000002131504712431400165040ustar00rootroot00000000000000%% -*- mode: erlang -*- {deps, [{slex, ".*", {git, "git://github.com/erlydtl/slex.git", {tag, "0.2.1"}}} ] }. {plugins, [rebar_slex]}. erlydtl-0.15.0/rebar-tests.config000066400000000000000000000011521504712431400166760ustar00rootroot00000000000000%% -*- mode: erlang -*- {eunit_opts, [%% This turns off the default output, MUST HAVE no_tty, %% Uses the progress formatter with ANSI-colored output {report, {eunit_progress, [colored %% uncomment to get a list of slowest running tests %%, profile ]}} ]}. {deps, [{eunit_formatters, ".*", {git, "git://github.com/seancribbs/eunit_formatters", "35e3e6ab2db48af776a1a21bba6f1461c97caacb"}} ]}. {pre_hooks, [{eunit, "erlc -I include/erlydtl_preparser.hrl -o test" " test/erlydtl_extension_testparser.yrl"} ]}. erlydtl-0.15.0/rebar.config000066400000000000000000000010011504712431400155270ustar00rootroot00000000000000%% -*- mode: erlang -*- {require_otp_vsn, "18"}. {erl_opts, [debug_info]}. {yrl_opts, [{includefile, "include/erlydtl_preparser.hrl"}]}. {project_plugins, [ {slex, {git, "https://github.com/erlydtl/slex.git", {tag, "0.2.3"}}} ]}. {profiles, [{test, [{pre_hooks, [ {compile, "erlc -I include/erlydtl_preparser.hrl -o test test/erlydtl_extension_testparser.yrl"} ]} ]} ]}. {dialyzer, [ {warnings, [underspecs, unmatched_returns, race_conditions, error_handling]} ]}. erlydtl-0.15.0/rebar.lock000066400000000000000000000000041504712431400152140ustar00rootroot00000000000000[]. erlydtl-0.15.0/src/000077500000000000000000000000001504712431400140445ustar00rootroot00000000000000erlydtl-0.15.0/src/erlydtl.app.src000066400000000000000000000004761504712431400170220ustar00rootroot00000000000000%% -*- mode: erlang -*- {application, erlydtl, [{description, "Django Template Language for Erlang"}, {vsn, "0.14.0"}, {modules, []}, {applications, [kernel, stdlib, compiler, syntax_tools]}, {registered, []}, {licenses,["MIT"]}, {links,[{"Github", "https://github.com/erlydtl/erlydtl"}]} ]}. erlydtl-0.15.0/src/erlydtl.erl000066400000000000000000000177471504712431400162470ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl.erl %%% @author Roberto Saccon [http://rsaccon.com] %%% @author Evan Miller %%% @author Andreas Stenius %%% @copyright 2008 Roberto Saccon, Evan Miller %%% @copyright 2014 Andreas Stenius %%% @doc %%% Public interface for ErlyDTL %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2008 Roberto Saccon, Evan Miller %%% Copyright (c) 2014 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2007-11-11 by Roberto Saccon, Evan Miller %%% @since 2014 by Andreas Stenius %%%------------------------------------------------------------------- -module(erlydtl). -author('rsaccon@gmail.com'). -author('emmiller@gmail.com'). -author('Andreas Stenius '). %% -------------------------------------------------------------------- %% API %% -------------------------------------------------------------------- -export([compile_file/2, compile_file/3]). -export([compile_template/2, compile_template/3]). -export([compile/2, compile/3]). -export([compile_dir/2, compile_dir/3]). -type position() :: non_neg_integer(). -type location() :: none | position() | {Line::position(), Column::position()}. -type error_info() :: {File::list(), [{location(), Module::atom(), ErrorDesc::term()}]}. -type errors() :: list(error_info()). -type warnings() :: list(error_info()). -type ok_ret() :: {ok, Module::atom()} | {ok, Module::atom(), warnings()}. -type err_ret() :: error | {error, errors(), warnings()}. -type filename() :: file:name_all(). -type compiler_options() :: compiler_option() | compile:option(). -type compiler_option() :: return | return_warnings | return_errors | report | report_warnings | report_errors | warnings_as_errors | debug_info | verbose. -type compile_options() :: [compile_option() | {atom(), term()}]. -type compile_option() :: compiler_option() | auto_escape | binary | binary_strings | force_recompile | no_env | no_load | {blocktrans_fun, Trans::fun((Block::iodata(), Locale::string()) -> iodata() | default)} | {blocktrans_locales, [string()]} | {compiler_options, [compiler_options()]} | {custom_filters_modules, [Module::atom]} | {custom_tags_dirs, [filename()]} | {custom_tags_modules, [Module::atom]} | {default_libraries, [Name::atom()]} | {doc_root, filename()} | {extension_module, Module::atom()} | {libraries, [{Name::atom(), Module::atom()}]} | {locale, string()} | {out_dir, false | filename()} | {reader, {Module::atom(), Function::atom}} | {reader_options, [{Name::atom(), iodata()}]} | {record_info, [{Name::atom(), [Field::atom()]}]} | {scanner_module, Module::atom()} | {vars, [{atom(), iodata()}]}. %% -------------------------------------------------------------------- %% Compile file %% -------------------------------------------------------------------- -spec compile_file(filename(), atom()) -> {ok, Module::atom()} | error. compile_file(File, Module) -> erlydtl_compiler:compile_file(File, Module, erlydtl_compiler:default_options()). -spec compile_file(filename(), atom(), compile_options()) -> ok_ret() | err_ret(). compile_file(File, Module, Options) -> erlydtl_compiler:compile_file(File, Module, Options). %% -------------------------------------------------------------------- %% Compile template %% -------------------------------------------------------------------- -spec compile_template(iodata(), atom()) -> {ok, Module::atom()} | error. compile_template(Template, Module) -> erlydtl_compiler:compile_template(Template, Module, erlydtl_compiler:default_options()). -spec compile_template(iodata(), atom(), compile_options()) -> ok_ret() | err_ret(). compile_template(Template, Module, Options) -> erlydtl_compiler:compile_template(Template, Module, Options). %% -------------------------------------------------------------------- %% Compile directory %% -------------------------------------------------------------------- -spec compile_dir(filename(), atom()) -> {ok, Module::atom()} | error. compile_dir(DirectoryPath, Module) -> erlydtl_compiler:compile_dir(DirectoryPath, Module, erlydtl_compiler:default_options()). -spec compile_dir(filename(), atom(), compile_options()) -> ok_ret() | err_ret(). compile_dir(DirectoryPath, Module, Options) -> erlydtl_compiler:compile_dir(DirectoryPath, Module, Options). %% -------------------------------------------------------------------- %% Legacy API %% -------------------------------------------------------------------- %% keep for backwards compatibility, with a tuple-twist to ease migration / offer alternative path.. -spec compile(FileOrTemplate, atom()) -> {ok, Module::atom()} | error when FileOrTemplate :: string() | binary() | {file, filename()} | {template, iodata()} | {dir, filename()}. compile({file, File}, Module) -> compile_file(File, Module); compile({template, Template}, Module) -> compile_template(Template, Module); compile({dir, Directory}, Module) -> compile_dir(Directory, Module); compile(FileOrTemplate, Module) when is_binary(FileOrTemplate) -> compile_template(FileOrTemplate, Module); compile(FileOrTemplate, Module) -> compile_file(FileOrTemplate, Module). -spec compile(FileOrTemplate, atom(), compile_options() ) -> ok_ret() | err_ret() when FileOrTemplate :: string() | binary() | {file, filename()} | {template, iodata()} | {dir, filename()}. compile({file, File}, Module, Options) -> compile_file(File, Module, Options); compile({template, Template}, Module, Options) -> compile_template(Template, Module, Options); compile({dir, Directory}, Module, Options) -> compile_dir(Directory, Module, Options); compile(FileOrTemplate, Module, Options) when is_binary(FileOrTemplate) -> compile_template(FileOrTemplate, Module, Options); compile(FileOrTemplate, Module, Options) -> compile_file(FileOrTemplate, Module, Options). erlydtl-0.15.0/src/erlydtl_beam_compiler.erl000066400000000000000000002334731504712431400211210ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_beam_compiler.erl %%% @author Roberto Saccon [http://rsaccon.com] %%% @author Evan Miller %%% @author Andreas Stenius %%% @copyright 2008 Roberto Saccon, Evan Miller %%% @copyright 2014 Andreas Stenius %%% @doc %%% ErlyDTL template compiler for beam targets. %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2007 Roberto Saccon, Evan Miller %%% Copyright (c) 2014 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2007-12-16 by Roberto Saccon, Evan Miller %%% @since 2014 by Andreas Stenius %%%------------------------------------------------------------------- -module(erlydtl_beam_compiler). -author('rsaccon@gmail.com'). -author('emmiller@gmail.com'). -author('Andreas Stenius '). %% -------------------------------------------------------------------- %% Definitions %% -------------------------------------------------------------------- -export([compile/3, compile_dir/2, format_error/1]). %% internal use -export([ is_up_to_date/2, format/1, value_ast/4, interpret_args/2 ]). -import(erlydtl_compiler, [parse_file/2, do_parse_template/2]). -import(erlydtl_compiler_utils, [unescape_string_literal/1, full_path/2, push_scope/2, restore_scope/2, begin_scope/1, begin_scope/2, end_scope/4, empty_scope/0, get_current_file/1, add_errors/2, add_warnings/2, merge_info/2, call_extension/3, init_treewalker/1, resolve_variable/2, resolve_variable/3, reset_block_dict/2, reset_parse_trail/2, load_library/3, load_library/4, shorten_filename/2, push_auto_escape/2, pop_auto_escape/1, token_pos/1, is_stripped_token_empty/1]). -ifdef(MERL_DEP). -include_lib("merl/include/merl.hrl"). -else. -include_lib("syntax_tools/include/merl.hrl"). -endif. -include("erlydtl_ext.hrl"). %% -------------------------------------------------------------------- %% API %% -------------------------------------------------------------------- compile(DjangoParseTree, CheckSum, Context) -> compile_to_binary(DjangoParseTree, CheckSum, Context). compile_dir(Dir, Context) -> do_compile_dir(Dir, Context). format_error(no_out_dir) -> "Compiled template not saved (need out_dir option)"; format_error(unexpected_extends_tag) -> "The extends tag must be at the very top of the template"; format_error(circular_include) -> "Circular file inclusion!"; format_error({write_file, Error}) -> io_lib:format( "Failed to write file: ~s", [file:format_error(Error)]); format_error(compile_beam) -> "Failed to compile template to BEAM code"; format_error({unknown_filter, Name, Arity}) -> io_lib:format("Unknown filter '~s' (arity ~b)", [Name, Arity]); format_error({filter_args, Name, {Mod, Fun}, Arity}) -> io_lib:format("Wrong number of arguments to filter '~s' (~s:~s): ~b", [Name, Mod, Fun, Arity]); format_error({unknown_tag, Name}) -> io_lib:format("Unknown tag '~s'", [Name]); format_error({missing_tag, Name, {Mod, Fun}}) -> io_lib:format("Custom tag '~s' not exported (~s:~s)", [Name, Mod, Fun]); format_error({bad_tag, Name, {Mod, Fun}, Arity}) -> io_lib:format("Invalid tag '~s' (~s:~s/~b)", [Name, Mod, Fun, Arity]); format_error({load_code, Error}) -> io_lib:format("Failed to load BEAM code: ~p", [Error]); format_error({reserved_variable, ReservedName}) -> io_lib:format("Variable '~s' is reserved for internal use.", [ReservedName]); format_error({translation_fun, Fun}) -> io_lib:format("Invalid translation function: ~s~n", [if is_function(Fun) -> Info = erlang:fun_info(Fun), io_lib:format("~s:~s/~b", [proplists:get_value(K, Info) || K <- [module, name, arity]]); true -> io_lib:format("~p", [Fun]) end]); format_error(non_block_tag) -> "Non-block tag in extends-template."; format_error(Error) -> erlydtl_compiler:format_error(Error). %%==================================================================== %% Internal functions %%==================================================================== do_compile_dir(Dir, Context) -> %% Find all files in Dir (recursively), matching the regex (no %% files ending in "~"). Files = filelib:fold_files(Dir, ".+[^~]$", true, fun(F1,Acc1) -> [F1 | Acc1] end, []), {ParserResults, #dtl_context{ errors=#error_info{ list=ParserErrors } }=Context1} = lists:foldl( fun (File, {ResultAcc, Ctx}) -> case filename:basename(File) of "."++_ -> {ResultAcc, Ctx}; _ -> FilePath = filename:absname(File), case filelib:is_dir(FilePath) of true -> {ResultAcc, Ctx}; false -> case parse_file(FilePath, Ctx) of up_to_date -> {ResultAcc, Ctx}; {ok, DjangoParseTree, CheckSum} -> {[{FilePath, DjangoParseTree, CheckSum}|ResultAcc], Ctx}; {error, Reason} -> {ResultAcc, ?ERR(Reason, Ctx)} end end end end, {[], Context}, Files), if length(ParserErrors) == 0 -> compile_multiple_to_binary(Dir, ParserResults, Context1); true -> Context1 end. compile_multiple_to_binary(Dir, ParserResults, Context) -> MatchAst = options_match_ast(Context), {Functions, {AstInfo, #treewalker{ context=#dtl_context{ errors=#error_info{ list=Errors } }=Context1 } } } = lists:mapfoldl( fun ({File, DjangoParseTree, CheckSum}, {AstInfo, #treewalker{ context=Ctx }=TreeWalker}) -> try FilePath = full_path(File, Ctx#dtl_context.doc_root), {{BodyAst, BodyInfo}, TreeWalker1} = with_dependency( {FilePath, CheckSum}, body_ast(DjangoParseTree, TreeWalker)), FunctionName = filename:rootname(filename:basename(File)), FunctionDefs = ?Q(["'@func'(Variables) -> _@func(Variables, []).", "'@func'(_Variables, RenderOptions) ->", " _@MatchAst, _@body."], [{func, erl_syntax:atom(FunctionName)}, {body, stringify(BodyAst, Ctx)}]), {{FunctionName, FunctionDefs}, {merge_info(AstInfo, BodyInfo), TreeWalker1}} catch throw:Error -> {error, {AstInfo, TreeWalker#treewalker{ context=?ERR(Error, Ctx) }}} end end, {#ast_info{}, init_treewalker(Context)}, ParserResults), if length(Errors) == 0 -> Forms = custom_forms(Dir, Context1#dtl_context.module, Functions, AstInfo), compile_forms(Forms, Context1); true -> Context1 end. compile_to_binary(DjangoParseTree, CheckSum, Context) -> try body_ast(DjangoParseTree, init_treewalker(Context)) of {{BodyAst, BodyInfo}, BodyTreeWalker} -> try custom_tags_ast(BodyInfo#ast_info.custom_tags, BodyTreeWalker) of {CustomTags, #treewalker{ context=#dtl_context{ errors=#error_info{ list=Errors } } }=CustomTagsTreeWalker} when length(Errors) == 0 -> Forms = forms( {BodyAst, BodyInfo}, CustomTags, CheckSum, CustomTagsTreeWalker), compile_forms(Forms, CustomTagsTreeWalker#treewalker.context); {_, #treewalker{ context=Context1 }} -> Context1 catch throw:Error -> ?ERR(Error, BodyTreeWalker#treewalker.context) end catch throw:Error -> ?ERR(Error, Context) end. compile_forms(Forms, Context) -> maybe_debug_template(Forms, Context), Options = Context#dtl_context.compiler_options, case compile:forms(Forms, [nowarn_shadow_vars|Options]) of Compiled when element(1, Compiled) =:= ok -> [ok, Module, Bin|Info] = tuple_to_list(Compiled), lists:foldl( fun (F, C) -> F(Module, Bin, C) end, Context#dtl_context{ bin=Bin }, [fun maybe_write/3, fun maybe_load/3, fun (_, _, C) -> case Info of [Ws] when length(Ws) > 0 -> add_warnings(Ws, C); _ -> C end end ]); error -> ?ERR(compile_beam, Context); {error, Es, Ws} -> add_warnings(Ws, add_errors(Es, Context)) end. maybe_write(Module, Bin, Context) -> case proplists:get_value(out_dir, Context#dtl_context.all_options) of false -> Context; undefined -> ?WARN(no_out_dir, Context); OutDir -> BeamFile = filename:join([OutDir, [Module, ".beam"]]), ?LOG_INFO("Template module: ~w -> ~s\n", [Module, BeamFile], Context), case file:write_file(BeamFile, Bin) of ok -> Context; {error, Reason} -> ?ERR({write_file, Reason}, Context) end end. maybe_load(Module, Bin, Context) -> case proplists:get_bool(no_load, Context#dtl_context.all_options) of true -> Context; false -> load_code(Module, Bin, Context) end. load_code(Module, Bin, Context) -> code:purge(Module), case code:load_binary(Module, atom_to_list(Module) ++ ".erl", Bin) of {module, Module} -> Context; Error -> ?WARN({load_code, Error}, Context) end. maybe_debug_template(Forms, Context) -> case proplists:get_bool(debug_compiler, Context#dtl_context.all_options) of false -> nop; true -> Options = Context#dtl_context.compiler_options, ?LOG_DEBUG("Compiler options: ~p~n", [Options], Context), try Source = erl_prettypr:format( erl_syntax:form_list(Forms), [{ribbon, 100}, {paper, 200}]), SourceFile = lists:concat( [proplists:get_value(source, Options),".erl"]), File = case proplists:get_value( debug_root, Context#dtl_context.all_options) of false -> undefined; undefined -> SourceFile; Dir -> Abs = filename:absname( shorten_filename( SourceFile, Context#dtl_context.doc_root), Dir), case filelib:ensure_dir(Abs) of ok -> Abs; {error, Reason} -> io:format( "Failed to ensure directories for file '~s': ~p~n", [Abs, Reason]), undefined end end, if File =/= undefined -> io:format("Saving template source to: ~s.. ~p~n", [File, file:write_file(File, Source)]); true -> ok end catch error:Err -> io:format("Pretty printing failed: ~p~n" "Context: ~n~p~n" "Forms: ~n~p~n", [Err, Context, Forms]) end end. is_up_to_date(CheckSum, Context) -> Module = Context#dtl_context.module, {M, F} = Context#dtl_context.reader, ReaderOptions = Context#dtl_context.reader_options, case catch Module:source() of {_, CheckSum} -> case catch Module:dependencies() of L when is_list(L) -> RecompileList = lists:foldl( fun ({XFile, XCheckSum}, Acc) -> case catch erlydtl_runtime:read_file_internal(M, F, XFile, ReaderOptions) of {ok, Data} -> case binary_to_list(erlang:md5(Data)) of XCheckSum -> Acc; _ -> [recompile | Acc] end; _ -> [recompile | Acc] end end, [], L), case RecompileList of [] -> true; _ -> false end; _ -> false end; _ -> false end. %%==================================================================== %% AST functions %%==================================================================== custom_tags_ast(CustomTags, TreeWalker) -> %% avoid adding the render_tag/3 fun if it isn't used, %% since we can't add a -compile({nowarn_unused_function, render_tag/3}). %% attribute due to a bug in syntax_tools. case custom_tags_clauses_ast(CustomTags, TreeWalker) of skip -> {{erl_syntax:comment( ["%% render_tag/3 is not used in this template."]), #ast_info{}}, TreeWalker}; {{CustomTagsClauses, CustomTagsInfo}, TreeWalker1} -> {{erl_syntax:function( erl_syntax:atom(render_tag), CustomTagsClauses), CustomTagsInfo}, TreeWalker1} end. custom_tags_clauses_ast([], _TreeWalker) -> skip; custom_tags_clauses_ast(CustomTags, TreeWalker) -> custom_tags_clauses_ast1(CustomTags, [], [], #ast_info{}, TreeWalker). custom_tags_clauses_ast1([], _ExcludeTags, ClauseAcc, InfoAcc, TreeWalker) -> {{DefaultAst, DefaultInfo}, TreeWalker1} = case call_extension(TreeWalker, custom_tag_ast, [TreeWalker]) of undefined -> {{?Q("(_TagName, _, _) -> []"), InfoAcc}, TreeWalker}; {{ExtAst, ExtInfo}, ExtTreeWalker} -> Clause = ?Q("(TagName, _Variables, RenderOptions) -> _@tag", [{tag, options_match_ast(ExtTreeWalker) ++ [ExtAst]}]), {{Clause, merge_info(ExtInfo, InfoAcc)}, ExtTreeWalker} end, {{lists:reverse([DefaultAst|ClauseAcc]), DefaultInfo}, TreeWalker1}; custom_tags_clauses_ast1([Tag|CustomTags], ExcludeTags, ClauseAcc, InfoAcc, TreeWalker) -> case lists:member(Tag, ExcludeTags) of true -> custom_tags_clauses_ast1(CustomTags, ExcludeTags, ClauseAcc, InfoAcc, TreeWalker); false -> Context = TreeWalker#treewalker.context, CustomTagFile = full_path(Tag, Context#dtl_context.custom_tags_dir), case filelib:is_file(CustomTagFile) of true -> case parse_file(CustomTagFile, Context) of {ok, DjangoParseTree, CheckSum} -> {{BodyAst, BodyAstInfo}, TreeWalker1} = with_dependency( {CustomTagFile, CheckSum}, body_ast(DjangoParseTree, TreeWalker)), MatchAst = options_match_ast(TreeWalker1), Clause = ?Q("(_@Tag@, _Variables, RenderOptions) -> _@MatchAst, _@BodyAst"), custom_tags_clauses_ast1( CustomTags, [Tag|ExcludeTags], [Clause|ClauseAcc], merge_info(BodyAstInfo, InfoAcc), TreeWalker1); {error, Reason} -> empty_ast(?ERR(Reason, TreeWalker)) end; false -> case call_extension(TreeWalker, custom_tag_ast, [Tag, TreeWalker]) of undefined -> custom_tags_clauses_ast1( CustomTags, [Tag | ExcludeTags], ClauseAcc, InfoAcc, ?WARN({unknown_tag, Tag}, TreeWalker) ); {{Ast, Info}, TW} -> Clause = ?Q("(_@Tag@, _Variables, RenderOptions) -> _@match, _@Ast", [{match, options_match_ast(TW)}]), custom_tags_clauses_ast1( CustomTags, [Tag | ExcludeTags], [Clause|ClauseAcc], merge_info(Info, InfoAcc), TW) end end end. custom_forms(Dir, Module, Functions, AstInfo) -> Dependencies = AstInfo#ast_info.dependencies, TranslatableStrings = AstInfo#ast_info.translatable_strings, TranslatedBlocks = AstInfo#ast_info.translated_blocks, Variables = lists:usort(AstInfo#ast_info.var_names), DefaultVariables = lists:usort(AstInfo#ast_info.def_names), Constants = lists:usort(AstInfo#ast_info.const_names), erl_syntax:revert_forms( lists:flatten( ?Q(["-module('@Module@').", "-ignore_xref('@Module@').", "-export([source_dir/0, dependencies/0, translatable_strings/0,", " translated_blocks/0, variables/0, default_variables/0,", " constants/0, render/1, render/2, render/3]).", "-export(['@__export_functions'/0]).", "source_dir() -> _@Dir@.", "dependencies() -> _@Dependencies@.", "variables() -> _@Variables@.", "default_variables() -> _@DefaultVariables@.", "constants() -> _@Constants@.", "translatable_strings() -> _@TranslatableStrings@.", "translated_blocks() -> _@TranslatedBlocks@.", "render(Tag) -> render(Tag, [], []).", "render(Tag, Vars) -> render(Tag, Vars, []).", "render(Tag, Vars, Opts) ->", " try '@Module@':Tag(Vars, Opts) of", " Val -> {ok, Val}", " catch", " Err -> {error, Err}", " end.", "'@_functions'() -> _." ], [{export_functions, erl_syntax:list( [erl_syntax:arity_qualifier(erl_syntax:atom(FName), erl_syntax:integer(Arity)) || {FName, _} <- Functions, Arity <- [1, 2]])}, {functions, [Ast || {_, Ast} <- Functions]} ])) ). stringify(BodyAst, #dtl_context{ binary_strings=BinaryStrings }) -> [?Q("erlydtl_runtime:stringify_final(_@BodyAst, '@BinaryStrings@')")]. forms({BodyAst, BodyInfo}, {CustomTagsFunctionAst, CustomTagsInfo}, CheckSum, #treewalker{ context=#dtl_context{ module=Module, parse_trail=[File|_] }=Context }=TreeWalker) -> MergedInfo = merge_info(BodyInfo, CustomTagsInfo), Dependencies = MergedInfo#ast_info.dependencies, TranslatableStrings = MergedInfo#ast_info.translatable_strings, TranslatedBlocks = MergedInfo#ast_info.translated_blocks, Variables = lists:usort(MergedInfo#ast_info.var_names), DefaultVariables = lists:usort(MergedInfo#ast_info.def_names), Constants = lists:usort(MergedInfo#ast_info.const_names), FinalBodyAst = options_match_ast(TreeWalker) ++ stringify(BodyAst, Context), erl_syntax:revert_forms( ?Q(["-module('@Module@').", "-ignore_xref('@Module@').", "-export([render/0, render/1, render/2, source/0, dependencies/0,", " translatable_strings/0, translated_blocks/0, variables/0,", " default_variables/0, constants/0]).", "source() -> {_@File@, _@CheckSum@}.", "dependencies() -> _@Dependencies@.", "variables() -> _@Variables@.", "default_variables() -> _@DefaultVariables@.", "constants() -> _@Constants@.", "translatable_strings() -> _@TranslatableStrings@.", "translated_blocks() -> _@TranslatedBlocks@.", "'@_CustomTagsFunctionAst'() -> _.", "render() -> render([], []).", "render(Variables) -> render(Variables, []).", "render(Variables, RenderOptions) ->", " try render_internal(Variables, RenderOptions) of", " Val -> {ok, Val}", " catch", " Err -> {error, Err}", " end.", "render_internal(_Variables, RenderOptions) -> _@FinalBodyAst." ])). options_match_ast(#treewalker{ context=Context }=TreeWalker) -> options_match_ast(Context, TreeWalker); options_match_ast(Context) -> options_match_ast(Context, undefined). options_match_ast(Context, TreeWalker) -> [ ?Q(["_TranslationFun = erlydtl_runtime:init_translation(", " proplists:get_value(translation_fun, RenderOptions, none))"]), ?Q("_CurrentLocale = proplists:get_value(locale, RenderOptions, default)"), ?Q("_RecordInfo = _@info", [{info, merl:term(Context#dtl_context.record_info)}]) | case call_extension(Context, setup_render_ast, [Context, TreeWalker]) of undefined -> []; Ast when is_list(Ast) -> Ast end]. %% child templates should only consist of blocks at the top level body_ast([{'extends', {string_literal, _Pos, String}} | ThisParseTree], #treewalker{ context=Context }=TreeWalker) -> ThisFile = get_current_file(Context), File = full_path(unescape_string_literal(String), Context#dtl_context.doc_root), case lists:member(File, Context#dtl_context.parse_trail) of true -> empty_ast(?ERR(circular_include, TreeWalker)); _ -> case parse_file(File, Context) of {ok, ParentParseTree, CheckSum} -> {BlockDict, Context1} = lists:foldl( fun ({block, {identifier, Pos, Name}, Contents}, {Dict, Ctx}) -> {dict:store(Name, [{ThisFile, Pos, Contents}], Dict), Ctx}; (Token, {Dict, Ctx}) -> case proplists:get_bool(non_block_tag, Ctx#dtl_context.checks) of true -> case is_stripped_token_empty(Token) of false -> {Dict, ?WARN({token_pos(Token), non_block_tag}, Ctx)}; true -> {Dict, Ctx} end; false -> {Dict, Ctx} end end, {dict:new(), Context}, ThisParseTree), {Info, TreeWalker1} = with_dependency( {File, CheckSum}, body_ast( ParentParseTree, TreeWalker#treewalker{ context=Context1#dtl_context{ block_dict = dict:merge( fun(_Key, ParentVal, ChildVal) -> ChildVal ++ ParentVal end, BlockDict, Context#dtl_context.block_dict), parse_trail = [File | Context1#dtl_context.parse_trail] } })), {Info, reset_parse_trail( Context1#dtl_context.parse_trail, reset_block_dict( Context1#dtl_context.block_dict, TreeWalker1))}; {error, Reason} -> empty_ast(?ERR(Reason, TreeWalker)) end end; body_ast(DjangoParseTree, TreeWalker) -> ScopeFun = fun ([ScopeVars|ScopeBody]) -> [?Q("(fun() -> _@ScopeVars, [_@ScopeBody] end)()")] end, body_ast(DjangoParseTree, empty_scope(), ScopeFun, TreeWalker). body_ast(DjangoParseTree, BodyScope, ScopeFun, TreeWalker) -> {ScopeId, TreeWalkerScope} = begin_scope(BodyScope, TreeWalker), BodyFun = fun ({'autoescape', {identifier, _, OnOrOff}, Contents}, TW) -> {Info, BodyTW} = body_ast(Contents, push_auto_escape(OnOrOff, TW)), {Info, pop_auto_escape(BodyTW)}; ({'block', {identifier, _Pos, Name}, Contents}, #treewalker{ context=Context }=TW) -> ContentsAst = body_ast(Contents, TW), case dict:find(Name, Context#dtl_context.block_dict) of {ok, ChildBlocks} -> lists:foldr( fun ({ChildFile, ChildPos, ChildBlock}, {{SuperAst, SuperInfo}, AccTW}) -> BlockScope = create_scope( [{block, ?Q("fun (super) -> _@SuperAst; (_) -> [] end"), safe}], ChildPos, ChildFile, AccTW), {{BlockAst, BlockInfo}, BlockTW} = body_ast(ChildBlock, BlockScope, ScopeFun, AccTW), {{BlockAst, merge_info(SuperInfo, BlockInfo)}, BlockTW} end, ContentsAst, ChildBlocks); _ -> ContentsAst end; ({'blocktrans', Args, Contents, PluralContents}, TW) -> blocktrans_ast(Args, Contents, PluralContents, TW); ({'call', {identifier, _, Name}}, TW) -> call_ast(Name, TW); ({'call', {identifier, _, Name}, With}, TW) -> call_with_ast(Name, With, TW); ({'comment', _Contents}, TW) -> empty_ast(TW); ({'comment_tag', _, _}, TW) -> empty_ast(TW); ({'cycle', Names, AsVar}, TW) -> cycle_ast(Names, AsVar, TW); ({'cycle_compat', Names}, TW) -> cycle_compat_ast(Names, TW); ({'date', 'now', {string_literal, _Pos, FormatString}}, TW) -> now_ast(FormatString, TW); ({'filter', FilterList, Contents}, TW) -> filter_tag_ast(FilterList, Contents, TW); ({'firstof', Vars}, TW) -> firstof_ast(Vars, TW); ({'for', {'in', IteratorList, Variable, Reversed}, Contents}, TW) -> {EmptyAstInfo, TW1} = empty_ast(TW), for_loop_ast(IteratorList, Variable, Reversed, Contents, EmptyAstInfo, TW1); ({'for', {'in', IteratorList, Variable, Reversed}, Contents, EmptyPartContents}, TW) -> {EmptyAstInfo, TW1} = body_ast(EmptyPartContents, TW), for_loop_ast(IteratorList, Variable, Reversed, Contents, EmptyAstInfo, TW1); ({'if', Expression, Contents, Elif}, TW) -> {IfAstInfo, TW1} = body_ast(Contents, TW), {ElifAstInfo, TW2} = body_ast(Elif, TW1), ifelse_ast(Expression, IfAstInfo, ElifAstInfo, TW2); ({'if', Expression, Contents}, TW) -> {IfAstInfo, TW1} = body_ast(Contents, TW), {ElseAstInfo, TW2} = empty_ast(TW1), ifelse_ast(Expression, IfAstInfo, ElseAstInfo, TW2); ({'ifchanged', '$undefined', Contents}, TW) -> {IfAstInfo, TW1} = body_ast(Contents, TW), {ElseAstInfo, TW2} = empty_ast(TW1), ifchanged_contents_ast(Contents, IfAstInfo, ElseAstInfo, TW2); ({'ifchanged', Values, Contents}, TW) -> {IfAstInfo, TW1} = body_ast(Contents, TW), {ElseAstInfo, TW2} = empty_ast(TW1), ifchanged_values_ast(Values, IfAstInfo, ElseAstInfo, TW2); ({'ifchangedelse', '$undefined', IfContents, ElseContents}, TW) -> {IfAstInfo, TW1} = body_ast(IfContents, TW), {ElseAstInfo, TW2} = body_ast(ElseContents, TW1), ifchanged_contents_ast(IfContents, IfAstInfo, ElseAstInfo, TW2); ({'ifchangedelse', Values, IfContents, ElseContents}, TW) -> {IfAstInfo, TW1} = body_ast(IfContents, TW), {ElseAstInfo, TW2} = body_ast(ElseContents, TW1), ifchanged_values_ast(Values, IfAstInfo, ElseAstInfo, TW2); ({'ifelse', Expression, IfContents, ElseContents}, TW) -> {IfAstInfo, TW1} = body_ast(IfContents, TW), {ElseAstInfo, TW2} = body_ast(ElseContents, TW1), ifelse_ast(Expression, IfAstInfo, ElseAstInfo, TW2); ({'ifequal', [Arg1, Arg2], Contents}, TW) -> {IfAstInfo, TW1} = body_ast(Contents, TW), {ElseAstInfo, TW2} = empty_ast(TW1), ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, TW2); ({'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}, TW) -> {IfAstInfo, TW1} = body_ast(IfContents, TW), {ElseAstInfo, TW2} = body_ast(ElseContents,TW1), ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, TW2); ({'ifnotequal', [Arg1, Arg2], Contents}, TW) -> {IfAstInfo, TW1} = body_ast(Contents, TW), {ElseAstInfo, TW2} = empty_ast(TW1), ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, TW2); ({'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}, TW) -> {IfAstInfo, TW1} = body_ast(IfContents, TW), {ElseAstInfo, TW2} = body_ast(ElseContents, TW1), ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, TW2); ({'include', {string_literal, _, File}, Args}, #treewalker{ context=Context }=TW) -> include_ast(unescape_string_literal(File), Args, Context#dtl_context.local_scopes, TW); ({'include_only', {string_literal, _, File}, Args}, TW) -> {Info, IncTW} = include_ast(unescape_string_literal(File), Args, [], TW), {Info, restore_scope(TW, IncTW)}; ({'load_libs', Libs}, TW) -> load_libs_ast(Libs, TW); ({'load_from_lib', What, Lib}, TW) -> load_from_lib_ast(What, Lib, TW); ({'regroup', {ListVariable, Grouper, {identifier, _, NewVariable}}}, TW) -> regroup_ast(ListVariable, Grouper, NewVariable, TW); ('end_regroup', TW) -> {{end_scope, #ast_info{}}, TW}; ({'spaceless', Contents}, TW) -> spaceless_ast(Contents, TW); ({'ssi', Arg}, TW) -> ssi_ast(Arg, TW); ({'ssi_parsed', {string_literal, _, FileName}}, #treewalker{ context=Context }=TW) -> include_ast(unescape_string_literal(FileName), [], Context#dtl_context.local_scopes, TW); ({'string', _Pos, String}, TW) -> string_ast(String, TW); ({'tag', Name, Args}, TW) -> tag_ast(Name, Args, TW); ({'tag', Name, Args, {identifier, _, NewTagVar}}, TW) -> tag_ast(Name, Args, NewTagVar, TW); ({'templatetag', {_, _, TagName}}, TW) -> templatetag_ast(TagName, TW); ({'trans', Value}, TW) -> translated_ast(Value, TW); ({'trans', Value, Context}, TW) -> translated_ast(Value, Context, TW); ({'widthratio', Numerator, Denominator, Scale}, TW) -> widthratio_ast(Numerator, Denominator, Scale, TW); ({'with', Args, Contents}, TW) -> with_ast(Args, Contents, TW); ({'scope_as', {identifier, _, Name}, Contents}, TW) -> scope_as(Name, Contents, TW); ({'extension', Tag}, TW) -> extension_ast(Tag, TW); ({'extends', _}, TW) -> empty_ast(?ERR(unexpected_extends_tag, TW)); ({'language', Locale, Contents}, TW) -> {{LocaleAst, LocaleInfo}, LocaleTW} = value_ast(Locale, true, false, TW), LanguageScopeFun = fun ([ScopeVars|ScopeBody]) -> [?Q("(fun(_CurrentLocale) -> _@ScopeVars, [_@ScopeBody] end)(_@LocaleAst)")] end, {{BodyAst, BodyInfo}, BodyTW} = body_ast(Contents, {[], [?Q("")]}, LanguageScopeFun, LocaleTW), {{BodyAst, merge_info(BodyInfo, LocaleInfo)}, BodyTW}; (ValueToken, TW) -> format(value_ast(ValueToken, true, true, TW)) end, {AstInfoList, TreeWalker1} = lists:mapfoldl(BodyFun, TreeWalkerScope, DjangoParseTree), {AstList, Info} = lists:mapfoldl( fun ({Ast, Info}, InfoAcc) -> {Ast, merge_info(Info, InfoAcc)} end, #ast_info{}, AstInfoList), {Ast, TreeWalker2} = end_scope(ScopeFun, ScopeId, AstList, TreeWalker1), {{erl_syntax:list(Ast), Info}, TreeWalker2}. value_ast(ValueToken, AsString, EmptyIfUndefined, TreeWalker) -> case ValueToken of {'expr', Operator, Value} -> {{ValueAst,InfoValue}, TreeWalker1} = value_ast(Value, false, EmptyIfUndefined, TreeWalker), Op = list_to_atom(Operator), Ast = ?Q("erlydtl_runtime:_@Op@(_@ValueAst)"), {{Ast, InfoValue}, TreeWalker1}; {'expr', Operator, Value1, Value2} -> {{Value1Ast,InfoValue1}, TreeWalker1} = value_ast(Value1, false, EmptyIfUndefined, TreeWalker), {{Value2Ast,InfoValue2}, TreeWalker2} = value_ast(Value2, false, EmptyIfUndefined, TreeWalker1), Op = list_to_atom(Operator), Ast = ?Q("erlydtl_runtime:_@Op@(_@Value1Ast, _@Value2Ast)"), {{Ast, merge_info(InfoValue1,InfoValue2)}, TreeWalker2}; {'string_literal', _Pos, String} -> string_ast(unescape_string_literal(String), TreeWalker); {'number_literal', _Pos, Number} -> case AsString of true -> string_ast(Number, TreeWalker); false -> {{erl_syntax:integer(Number), #ast_info{}}, TreeWalker} end; {'apply_filter', Variable, Filter} -> filter_ast(Variable, Filter, TreeWalker); {'attribute', _} = Variable -> resolve_variable_ast(Variable, EmptyIfUndefined, TreeWalker); {'variable', _} = Variable -> resolve_variable_ast(Variable, EmptyIfUndefined, TreeWalker); {'trans', Value} -> translated_ast(Value, TreeWalker); {extension, Tag} -> extension_ast(Tag, TreeWalker) end. extension_ast(Tag, TreeWalker) -> case call_extension(TreeWalker, compile_ast, [Tag, TreeWalker]) of undefined -> empty_ast(?WARN({unknown_extension, Tag}, TreeWalker)); Result -> Result end. with_dependencies([], Ast) -> Ast; with_dependencies([Dependency | Rest], Ast) -> with_dependencies(Rest, with_dependency(Dependency, Ast)). with_dependency(FilePath, {{Ast, Info}, TreeWalker}) -> Dependencies = [FilePath | Info#ast_info.dependencies], {{Ast, Info#ast_info{ dependencies = Dependencies }}, TreeWalker}. empty_ast(TreeWalker) -> {{erl_syntax:list([]), #ast_info{}}, TreeWalker}. %%% Note: Context here refers to the translation context, not the #dtl_context{} record blocktrans_ast(Args, Contents, PluralContents, TreeWalker) -> %% get args, count and context ArgList = [{Name, Value} || {{identifier, _, Name}, Value} <- proplists:get_value(args, Args, [])], Count = proplists:get_value(count, Args), Context = case proplists:get_value(context, Args) of undefined -> undefined; {string_literal, _, S} -> unescape_string_literal(S) end, Trimmed = proplists:get_value(trimmed, Args), %% add new scope using 'with' values {NewScope, {ArgInfo, TreeWalker1}} = lists:mapfoldl( fun ({LocalVarName, Value}, {AstInfoAcc, TreeWalkerAcc}) -> {{Ast, Info}, TW} = value_ast(Value, false, false, TreeWalkerAcc), {{LocalVarName, Ast}, {merge_info(AstInfoAcc, Info), TW}} end, {#ast_info{}, TreeWalker}, case Count of {{identifier, _, Name}, Value} -> [{Name, Value}|ArgList]; _ -> ArgList end), TreeWalker2 = push_scope(NewScope, TreeWalker1), %% key for translation lookup SourceText = erlydtl_unparser:unparse(Contents, Trimmed), {{DefaultAst, AstInfo}, TreeWalker3} = body_ast(Contents, TreeWalker2), MergedInfo = merge_info(AstInfo, ArgInfo), #dtl_context{ trans_fun = TFun, trans_locales = TLocales, auto_escape = AutoEscape } = TreeWalker3#treewalker.context, if TFun =:= none; PluralContents =/= undefined -> %% translate in runtime {FinalAst, FinalTW} = blocktrans_runtime_ast( {DefaultAst, MergedInfo}, SourceText, Contents, Context, AutoEscape, plural_contents(PluralContents, Count, TreeWalker3), Trimmed), {FinalAst, restore_scope(TreeWalker1, FinalTW)}; is_function(TFun, 2) -> %% translate in compile-time {FinalAstInfo, FinalTreeWalker, Clauses} = lists:foldr( fun (Locale, {AstInfoAcc, TreeWalkerAcc, ClauseAcc}) -> case TFun(SourceText, phrase_locale(Locale, Context)) of default -> {AstInfoAcc, TreeWalkerAcc, ClauseAcc}; Body -> {ok, DjangoParseTree} = do_parse_template(Body, TreeWalkerAcc#treewalker.context), {{BodyAst, BodyInfo}, BodyTreeWalker} = body_ast(DjangoParseTree, TreeWalkerAcc), Clause = erl_syntax:clause([erl_syntax:abstract(Locale)], none, [BodyAst]), {merge_info(BodyInfo, AstInfoAcc), BodyTreeWalker, [Clause | ClauseAcc]} end end, {MergedInfo, TreeWalker3, []}, TLocales), FinalAst = ?Q("case _CurrentLocale of _@_Clauses -> _; _ -> _@DefaultAst end"), {{FinalAst, FinalAstInfo#ast_info{ translated_blocks = [SourceText] }}, restore_scope(TreeWalker1, FinalTreeWalker)}; true -> empty_ast(?ERR({translation_fun, TFun}, TreeWalker3)) end. blocktrans_runtime_ast({DefaultAst, Info}, SourceText, Contents, Context, AutoEscape, {Plural, TreeWalker}, Trimmed) -> %% Contents is flat - only strings and '{{var}}' allowed. %% build sorted list (orddict) of pre-resolved variables to pass to runtime translation function USortedVariables = lists:usort(fun({variable, {identifier, _, A}}, {variable, {identifier, _, B}}) -> A =< B end, [Var || {variable, _}=Var <- Contents ++ maybe_plural_contents(Plural)]), VarBuilder = fun({variable, {identifier, _, Name}}=Var, TW) -> {{VarAst, _VarInfo}, VarTW} = resolve_variable_ast(Var, false, TW), {?Q("{_@name, _@VarAst}", [{name, merl:term(atom_to_list(Name))}]), VarTW} end, {VarAsts, TreeWalker1} = lists:mapfoldl(VarBuilder, TreeWalker, USortedVariables), VarListAst = erl_syntax:list(VarAsts), BlockTransAst = ?Q(["begin", " case erlydtl_runtime:translate_block(", " _@phrase, _@locale, _@auto_escape, ", " _@VarListAst, _TranslationFun) of", " default -> _@DefaultAst;", " Text -> Text", " end", "end"], [{phrase, phrase_ast(SourceText, Plural, Trimmed)}, {auto_escape, autoescape_ast(AutoEscape)}, {locale, phrase_locale_ast(Context)}]), {{BlockTransAst, merge_count_info(Info, Plural)}, TreeWalker1}. maybe_plural_contents(undefined) -> []; maybe_plural_contents({Contents, _}) -> Contents. merge_count_info(Info, undefined) -> Info; merge_count_info(Info, {_Contents, {_CountAst, CountInfo}}) -> merge_info(Info, CountInfo). plural_contents(undefined, _, TreeWalker) -> {undefined, TreeWalker}; plural_contents(Contents, {_CountVarName, Value}, TreeWalker) -> {CountAst, TW} = value_ast(Value, false, false, TreeWalker), {{Contents, CountAst}, TW}. phrase_ast(Text, undefined, _) -> merl:term(Text); phrase_ast(Text, {Contents, {CountAst, _CountInfo}}, Trimmed) -> erl_syntax:tuple( [merl:term(Text), erl_syntax:tuple( [merl:term(erlydtl_unparser:unparse(Contents, Trimmed)), CountAst]) ]). autoescape_ast([]) -> autoescape_ast([on]); autoescape_ast([V | _]) -> erl_syntax:atom(V == on). phrase_locale_ast(undefined) -> merl:var('_CurrentLocale'); phrase_locale_ast(Context) -> erl_syntax:tuple([merl:var('_CurrentLocale'), merl:term(Context)]). phrase_locale(Locale, undefined) -> Locale; phrase_locale(Locale, Context) -> {Locale, Context}. translated_ast(Text, TreeWalker) -> translated_ast(Text, undefined, TreeWalker). translated_ast({noop, Value}, _, TreeWalker) -> value_ast(Value, true, true, TreeWalker); translated_ast(Text, {string_literal, _, Context}, TreeWalker) -> translated_ast(Text, unescape_string_literal(Context), TreeWalker); translated_ast({string_literal, _, String}, Context, TreeWalker) -> Text = unescape_string_literal(String), case call_extension(TreeWalker, translate_ast, [Text, Context, TreeWalker]) of undefined -> case TreeWalker#treewalker.context#dtl_context.trans_fun of none -> runtime_trans_ast(Text, Context, TreeWalker); Fun when is_function(Fun, 2) -> compiletime_trans_ast(Fun, Text, Context, TreeWalker); Fun when is_function(Fun, 1) -> compiletime_trans_ast(fun (T, _) -> Fun(T) end, Text, Context, TreeWalker); Fun -> empty_ast(?ERR({translation_fun, Fun}, TreeWalker)) end; TranslatedAst -> TranslatedAst end; translated_ast(Value, Context, TreeWalker) -> runtime_trans_ast(value_ast(Value, true, false, TreeWalker), Context). runtime_trans_ast(Text, Context, TreeWalker) -> Info = #ast_info{ translatable_strings = [Text] }, runtime_trans_ast({{merl:term(Text), Info}, TreeWalker}, Context). runtime_trans_ast({{ValueAst, AstInfo}, TreeWalker}, undefined) -> {{?Q("erlydtl_runtime:translate(_@ValueAst, _CurrentLocale, _TranslationFun)"), AstInfo}, TreeWalker}; runtime_trans_ast({{ValueAst, AstInfo}, TreeWalker}, Context) -> {{?Q("erlydtl_runtime:translate(_@ValueAst, {_CurrentLocale, _@Context@}, _TranslationFun)"), AstInfo}, TreeWalker}. compiletime_trans_ast(TFun, Text, LContext, #treewalker{ context=#dtl_context{ trans_locales=TLocales }=Context }=TreeWalker) -> ClAst = lists:foldl( fun(Locale, ClausesAcc) -> BodyAst = case TFun(Text, phrase_locale(Locale, LContext)) of default -> string_ast(Text, Context); Translated -> string_ast(Translated, Context) end, Clause = erl_syntax:clause([erl_syntax:abstract(Locale)], none, [BodyAst]), [Clause | ClausesAcc] end, [], TLocales), {{?Q(["case _CurrentLocale of", " _@_ClAst -> _;", " _ -> _@string", "end"], [{string, string_ast(Text, Context)}]), #ast_info{ translatable_strings = [Text] }}, TreeWalker}. %%% end of context being translation context %% Completely unnecessary in ErlyDTL (use {{ "{%" }} etc), but implemented for compatibility. templatetag_ast("openblock", TreeWalker) -> string_ast("{%", TreeWalker); templatetag_ast("closeblock", TreeWalker) -> string_ast("%}", TreeWalker); templatetag_ast("openvariable", TreeWalker) -> string_ast("{{", TreeWalker); templatetag_ast("closevariable", TreeWalker) -> string_ast("}}", TreeWalker); templatetag_ast("openbrace", TreeWalker) -> string_ast("{", TreeWalker); templatetag_ast("closebrace", TreeWalker) -> string_ast("}", TreeWalker); templatetag_ast("opencomment", TreeWalker) -> string_ast("{#", TreeWalker); templatetag_ast("closecomment", TreeWalker) -> string_ast("#}", TreeWalker). widthratio_ast(Numerator, Denominator, Scale, TreeWalker) -> {{NumAst, NumInfo}, TreeWalker1} = value_ast(Numerator, false, true, TreeWalker), {{DenAst, DenInfo}, TreeWalker2} = value_ast(Denominator, false, true, TreeWalker1), {{ScaleAst, ScaleInfo}, TreeWalker3} = value_ast(Scale, false, true, TreeWalker2), {{format_number_ast(?Q("erlydtl_runtime:widthratio(_@NumAst, _@DenAst, _@ScaleAst)")), merge_info(ScaleInfo, merge_info(NumInfo, DenInfo))}, TreeWalker3}. string_ast(Arg, #treewalker{ context=Context }=TreeWalker) -> {{string_ast(Arg, Context), #ast_info{}}, TreeWalker}; string_ast(Arg, Context) -> merl:term(erlydtl_compiler_utils:to_string(Arg, Context)). include_ast(File, ArgList, Scopes, #treewalker{ context=Context }=TreeWalker) -> FilePath = full_path(File, Context#dtl_context.doc_root), ?LOG_TRACE("include file: ~s~n", [FilePath], Context), case parse_file(FilePath, Context) of {ok, InclusionParseTree, CheckSum} -> {NewScope, {ArgInfo, TreeWalker1}} = lists:mapfoldl( fun ({{identifier, _, LocalVarName}, Value}, {AstInfoAcc, TreeWalkerAcc}) -> {{Ast, Info}, TW} = value_ast(Value, false, false, TreeWalkerAcc), {{LocalVarName, Ast}, {merge_info(AstInfoAcc, Info), TW}} end, {#ast_info{}, TreeWalker}, ArgList), C = TreeWalker1#treewalker.context, {{BodyAst, BodyInfo}, TreeWalker2} = with_dependency( {FilePath, CheckSum}, body_ast( InclusionParseTree, TreeWalker1#treewalker{ context=C#dtl_context{ parse_trail = [FilePath | C#dtl_context.parse_trail], local_scopes = [NewScope|Scopes] } })), {{BodyAst, merge_info(BodyInfo, ArgInfo)}, reset_parse_trail(C#dtl_context.parse_trail, TreeWalker2)}; {error, Reason} -> empty_ast(?ERR(Reason, TreeWalker)) end. %% include at run-time ssi_ast(FileName, #treewalker{ context=#dtl_context{ reader = {Mod, Fun}, reader_options = ReaderOptions, doc_root = Dir } }=TreeWalker) -> {{FileAst, Info}, TreeWalker1} = value_ast(FileName, true, true, TreeWalker), {{?Q("erlydtl_runtime:read_file(_@Mod@, _@Fun@, _@Dir@, _@FileAst, _@ReaderOptions@)"), Info}, TreeWalker1}. filter_tag_ast(FilterList, Contents, TreeWalker) -> {{InnerAst, Info}, TreeWalker1} = body_ast(Contents, push_auto_escape(did, TreeWalker)), {{FilteredAst, FilteredInfo}, TreeWalker2} = lists:foldl( fun ({{identifier, _, Name}, []}, {{AstAcc, InfoAcc}, TreeWalkerAcc}) when Name =:= 'escape'; Name =:= 'safe'; Name =:= 'safeseq' -> {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{ safe = true }}; (Filter, {{AstAcc, InfoAcc}, TreeWalkerAcc}) -> {{Ast, AstInfo}, TW} = filter_ast1(Filter, AstAcc, TreeWalkerAcc), {{Ast, merge_info(InfoAcc, AstInfo)}, TW} end, {{?Q("erlang:iolist_to_binary(_@InnerAst)"), Info}, pop_auto_escape(TreeWalker1)}, FilterList), EscapedAst = case search_for_escape_filter( lists:reverse(FilterList), TreeWalker2#treewalker.context) of on -> ?Q("erlydtl_filters:force_escape(_@FilteredAst)"); _ -> FilteredAst end, {{EscapedAst, FilteredInfo}, TreeWalker2}. search_for_escape_filter(FilterList, #dtl_context{auto_escape = [on|_]}) -> search_for_safe_filter(FilterList); search_for_escape_filter(_, #dtl_context{auto_escape = [did|_]}) -> off; search_for_escape_filter([{{identifier, _, 'escape'}, []}|Rest], _Context) -> search_for_safe_filter(Rest); search_for_escape_filter([_|Rest], Context) -> search_for_escape_filter(Rest, Context); search_for_escape_filter([], _Context) -> off. search_for_safe_filter([{{identifier, _, Name}, []}|_]) when Name =:= 'safe'; Name =:= 'safeseq' -> off; search_for_safe_filter([_|Rest]) -> search_for_safe_filter(Rest); search_for_safe_filter([]) -> on. filter_ast(Variable, Filter, TreeWalker) -> %% the escape filter is special; it is always applied last, so we have to go digging for it %% AutoEscape = 'did' means we (will have) decided whether to escape the current variable, %% so don't do any more escaping {{UnescapedAst, Info}, TreeWalker1} = filter_ast_noescape( Variable, Filter, push_auto_escape(did, TreeWalker)), {EscapedAst, TreeWalker2} = case search_for_escape_filter(Variable, Filter, TreeWalker#treewalker.context) of on -> {?Q("erlydtl_filters:force_escape(_@UnescapedAst)"), TreeWalker1#treewalker{ safe = true }}; _ -> {UnescapedAst, TreeWalker1} end, {{EscapedAst, Info}, pop_auto_escape(TreeWalker2)}. filter_ast_noescape(Variable, {{identifier, _, Name}, []}, TreeWalker) when Name =:= 'escape'; Name =:= 'safe'; Name =:= 'safeseq' -> value_ast(Variable, true, false, TreeWalker#treewalker{safe = true}); filter_ast_noescape(Variable, Filter, TreeWalker) -> {{ValueAst, Info1}, TreeWalker2} = value_ast(Variable, true, false, TreeWalker), {{VarValue, Info2}, TreeWalker3} = filter_ast1(Filter, ValueAst, TreeWalker2), {{VarValue, merge_info(Info1, Info2)}, TreeWalker3}. filter_ast1({{identifier, Pos, Name}, Args}, ValueAst, TreeWalker) -> {{ArgsAst, ArgsInfo}, TreeWalker1} = lists:foldr( fun (Arg, {{AccAst, AccInfo}, AccTreeWalker}) -> {{ArgAst, ArgInfo}, ArgTreeWalker} = value_ast(Arg, false, false, AccTreeWalker), {{[ArgAst|AccAst], merge_info(ArgInfo, AccInfo)}, ArgTreeWalker} end, {{[], #ast_info{}}, TreeWalker}, Args), case filter_ast2(Name, [ValueAst|ArgsAst], TreeWalker1#treewalker.context) of {ok, FilterAst} -> {{FilterAst, ArgsInfo}, TreeWalker1}; Error -> empty_ast(?WARN({Pos, Error}, TreeWalker1)) end. % special case for date, which reqires localisation % may be replaced later with a query to a list % of functions which require translation filter_ast2('date' = Name, Args, #dtl_context{ filters = Filters } = Ctx) -> case proplists:get_value(Name, Filters) of {Mod, Fun} -> case erlang:function_exported(Mod, Fun, length(Args) + 2) of true -> {ok, ?Q("'@Mod@':'@Fun@'(_@Args, _TranslationFun, _CurrentLocale )")}; false -> filter_ast3(Name, Args, Ctx) % redefined 'date'? end; % should never happen undefined -> {unknown_filter, Name, length(Args)} end; filter_ast2(Name, Args, Ctx) -> filter_ast3(Name, Args, Ctx). filter_ast3(Name, Args, #dtl_context{ filters = Filters }) -> case proplists:get_value(Name, Filters) of {Mod, Fun}=Filter -> case erlang:function_exported(Mod, Fun, length(Args)) of true -> {ok, ?Q("'@Mod@':'@Fun@'(_@Args)")}; false -> {filter_args, Name, Filter, length(Args)} end; undefined -> {unknown_filter, Name, length(Args)} end. search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = [on|_]}) -> search_for_safe_filter(Variable, Filter); search_for_escape_filter(_, _, #dtl_context{auto_escape = [did|_]}) -> off; search_for_escape_filter(Variable, {{identifier, _, 'escape'}, []} = Filter, _Context) -> search_for_safe_filter(Variable, Filter); search_for_escape_filter({apply_filter, Variable, Filter}, _, Context) -> search_for_escape_filter(Variable, Filter, Context); search_for_escape_filter(_Variable, _Filter, _Context) -> off. search_for_safe_filter(_, {{identifier, _, 'safe'}, []}) -> off; search_for_safe_filter(_, {{identifier, _, 'safeseq'}, []}) -> off; search_for_safe_filter({apply_filter, Variable, Filter}, _) -> search_for_safe_filter(Variable, Filter); search_for_safe_filter(_Variable, _Filter) -> on. finder_function(true) -> {erlydtl_runtime, fetch_value}; finder_function(false) -> {erlydtl_runtime, find_value}. finder_function(EmptyIfUndefined, TreeWalker) -> case call_extension(TreeWalker, finder_function, [EmptyIfUndefined]) of undefined -> finder_function(EmptyIfUndefined); Result -> Result end. resolve_variable_ast({extension, Tag}, _, TreeWalker) -> extension_ast(Tag, TreeWalker); resolve_variable_ast(VarTuple, EmptyIfUndefined, TreeWalker) when is_boolean(EmptyIfUndefined) -> resolve_variable_ast(VarTuple, finder_function(EmptyIfUndefined, TreeWalker), TreeWalker); resolve_variable_ast(VarTuple, FinderFunction, TreeWalker) -> resolve_variable_ast1(VarTuple, FinderFunction, TreeWalker). resolve_variable_ast1({attribute, {{_, Pos, Attr}, Variable}}, {Runtime, Finder}=FinderFunction, TreeWalker) -> {{VarAst, VarInfo}, TreeWalker1} = resolve_variable_ast(Variable, FinderFunction, TreeWalker), #treewalker{ context=#dtl_context{ lists_0_based = Lists0Based, tuples_0_based = Tuples0Based } } = TreeWalker, FileName = get_current_file(TreeWalker1), {{?Q(["'@Runtime@':'@Finder@'(", " _@Attr@, _@VarAst,", " [{lists_0_based, _@Lists0Based@},", " {tuples_0_based, _@Tuples0Based@},", " {render_options, RenderOptions},", " {record_info, _RecordInfo},", " {filename, _@FileName@},", " {pos, _@Pos@}", " ])"]), VarInfo}, TreeWalker1}; resolve_variable_ast1({variable, {identifier, Pos, VarName}}, {Runtime, Finder}, TreeWalker) -> {Source, Value, Filters} = resolve_variable(VarName, TreeWalker), Ast = case {Source, Value} of {_, undefined} -> FileName = get_current_file(TreeWalker), {?Q(["'@Runtime@':'@Finder@'(", " _@VarName@, _Variables,", " [{filename, _@FileName@},", " {pos, _@Pos@},", " {record_info, _RecordInfo},", " {render_options, RenderOptions}])" ]), #ast_info{ var_names=[VarName] }}; {default_vars, Val} -> FileName = get_current_file(TreeWalker), {?Q(["'@Runtime@':fetch_value(", " _@VarName@, _Variables,", " [{filename, _@FileName@},", " {pos, _@Pos@},", " {record_info, _RecordInfo},", " {render_options, RenderOptions}],", " _@val)" ], [{val, merl:term(erlydtl_filters:format_number(Val))}]), #ast_info{ var_names=[VarName], def_names=[VarName] }}; {constant, Val} -> {merl:term(erlydtl_filters:format_number(Val)), #ast_info{ const_names=[VarName] }}; {scope, Val} -> {Val, #ast_info{}} end, lists:foldr( fun ({escape, []}, {{AccAst, AccInfo}, TW}) -> {{?Q("erlydtl_filters:force_escape(_@AccAst)"), AccInfo}, TW#treewalker{ safe = true }}; ({Safe, []}, {Acc, TW}) when Safe == safe; Safe == safeseq -> {Acc, TW#treewalker{ safe = true }}; ({Filter, Args}, {{AccAst, AccInfo}, TW}) when is_atom(Filter), is_list(Args) -> case filter_ast2(Filter, [AccAst|Args], TW#treewalker.context) of {ok, FilteredAst} -> {{FilteredAst, AccInfo}, TW}; Error -> empty_ast(?WARN({Pos, Error}, TW)) end end, {Ast, TreeWalker}, Filters ). resolve_reserved_variable(ReservedName, TreeWalker) -> resolve_reserved_variable(ReservedName, merl:term(undefined), TreeWalker). resolve_reserved_variable(ReservedName, Default, TreeWalker) -> case resolve_variable(ReservedName, Default, TreeWalker) of {Src, Value, []} when Src =:= scope; Value =:= Default -> {Value, TreeWalker}; _ -> {Default, ?ERR({reserved_variable, ReservedName}, TreeWalker)} end. format({{Ast, Info}, TreeWalker}) -> auto_escape({{format_number_ast(Ast), Info}, TreeWalker}). format_number_ast(Ast) -> ?Q("erlydtl_filters:format_number(_@Ast)"). auto_escape({AstInfo, #treewalker{ safe = true }=TW}) -> {AstInfo, TW#treewalker{ safe = false }}; auto_escape({{Value, Info}, #treewalker{ context=#dtl_context{auto_escape=[on|_]} }=TW}) -> {{?Q("erlydtl_filters:force_escape(_@Value)"), Info}, TW}; auto_escape(Value) -> Value. firstof_ast(Vars, TreeWalker) -> body_ast( [lists:foldr( fun ({L, _, _}=Var, []) when L=:=string_literal;L=:=number_literal -> Var; ({L, _, _}, _) when L=:=string_literal;L=:=number_literal -> erlang:error(errbadliteral); (Var, []) -> {'ifelse', Var, [Var], []}; (Var, Acc) -> {'ifelse', Var, [Var], [Acc]} end, [], Vars) ], TreeWalker). ifelse_ast(Expression, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, TreeWalker) -> Info = merge_info(IfContentsInfo, ElseContentsInfo), {{Ast, ExpressionInfo}, TreeWalker1} = value_ast(Expression, false, false, TreeWalker), {{?Q(["case erlydtl_runtime:is_true(_@Ast) of", " true -> _@IfContentsAst;", " _ -> _@ElseContentsAst", "end"]), merge_info(ExpressionInfo, Info)}, TreeWalker1}. with_ast(ArgList, Contents, TreeWalker) -> {ArgAstList, {ArgInfo, TreeWalker1}} = lists:mapfoldl( fun ({{identifier, _, _LocalVarName}, Value}, {AstInfoAcc, TreeWalkerAcc}) -> {{Ast, Info}, TW} = value_ast(Value, false, false, TreeWalkerAcc), {Ast, {merge_info(AstInfoAcc, Info), TW}} end, {#ast_info{}, TreeWalker}, ArgList), NewScope = lists:map( fun ({{identifier, _, LocalVarName}, _Value}) -> {LocalVarName, varname_ast(LocalVarName)} end, ArgList), {{InnerAst, InnerInfo}, TreeWalker2} = body_ast( Contents, push_scope(NewScope, TreeWalker1)), {{?Q("fun (_@args) -> _@InnerAst end (_@ArgAstList)", [{args, element(2, lists:unzip(NewScope))}]), merge_info(ArgInfo, InnerInfo)}, restore_scope(TreeWalker1, TreeWalker2)}. scope_as(VarName, Contents, TreeWalker) -> {{ContentsAst, ContentsInfo}, TreeWalker1} = body_ast(Contents, TreeWalker), VarAst = varname_ast(VarName), {Id, TreeWalker2} = begin_scope( {[{VarName, VarAst}], [?Q("_@VarAst = _@ContentsAst")]}, TreeWalker1), {{Id, ContentsInfo}, TreeWalker2}. regroup_ast(ListVariable, GrouperVariable, LocalVarName, TreeWalker) -> {{ListAst, ListInfo}, TreeWalker1} = value_ast(ListVariable, false, true, TreeWalker), LocalVarAst = varname_ast(LocalVarName), {Id, TreeWalker2} = begin_scope( {[{LocalVarName, LocalVarAst}], [?Q(["_@LocalVarAst = erlydtl_runtime:regroup(", " _@ListAst, _@regroup,", " [{record_info, _RecordInfo}]", ")"], [{regroup, regroup_filter(GrouperVariable, [])}]) ]}, TreeWalker1), {{Id, ListInfo}, TreeWalker2}. regroup_filter({attribute,{{identifier,_,Ident},Next}},Acc) -> regroup_filter(Next,[erl_syntax:atom(Ident)|Acc]); regroup_filter({variable,{identifier,_,Var}},Acc) -> erl_syntax:list([erl_syntax:atom(Var)|Acc]). to_list_ast(Value, IsReversed) -> ?Q("erlydtl_runtime:to_list(_@Value, _@IsReversed)"). to_list_ast(Value, IsReversed, TreeWalker) -> case call_extension(TreeWalker, to_list_ast, [Value, IsReversed, TreeWalker]) of undefined -> to_list_ast(Value, IsReversed); Result -> Result end. for_loop_ast(IteratorList, LoopValue, IsReversed, Contents, {EmptyContentsAst, EmptyContentsInfo}, #treewalker{ context=Context }=TreeWalker) -> %% create unique namespace for this instance Level = length(Context#dtl_context.local_scopes), {Row, Col} = element(2, hd(IteratorList)), ForId = lists:concat(["/", Level, "_", Row, ":", Col]), Counters = merl:var(lists:concat(["Counters", ForId])), Vars = merl:var(lists:concat(["Vars", ForId])), %% setup VarScope = lists:map( fun({identifier, {R, C}, Iterator}) -> {Iterator, varname_ast(lists:concat([ Iterator,"/", Level, "_", R, ":", C]))} end, IteratorList), {Iterators, IteratorVars} = lists:unzip(VarScope), IteratorCount = length(IteratorVars), {{LoopBodyAst, Info}, TreeWalker1} = body_ast( Contents, push_scope([{'forloop', Counters} | VarScope], TreeWalker)), {{LoopValueAst, LoopValueInfo}, TreeWalker2} = value_ast(LoopValue, false, true, restore_scope(TreeWalker, TreeWalker1)), LoopValueAst0 = to_list_ast(LoopValueAst, merl:term(IsReversed), TreeWalker2), {ParentLoop, TreeWalker3} = resolve_reserved_variable('forloop', TreeWalker2), {{?Q(["erlydtl_runtime:forloop(", " fun (_@Vars, _@Counters) ->", " {_@IteratorVars} = if is_tuple(_@Vars), size(_@Vars) == _@IteratorCount@ -> _@Vars;", " _@___ifclauses -> _", " end,", " {_@LoopBodyAst, erlydtl_runtime:increment_counter_stats(_@Counters)}", " end,", " _@LoopValueAst0, _@ParentLoop, _@EmptyContentsAst)"], [{ifclauses, if IteratorCount > 1 -> ?Q(["() when is_list(_@Vars), length(_@Vars) == _@IteratorCount@ ->", " list_to_tuple(_@Vars);", "() when true -> throw({for_loop, _@Iterators@, _@Vars})"]); true -> ?Q("() when true -> {_@Vars}") end}]), merge_info(merge_info(Info, EmptyContentsInfo), LoopValueInfo)}, TreeWalker3}. ifchanged_values_ast(Values, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, TreeWalker) -> Info = merge_info(IfContentsInfo, ElseContentsInfo), ValueAstFun = fun (Expr, {LTreeWalker, LInfo, Acc}) -> {{EAst, EInfo}, ETw} = value_ast(Expr, false, true, LTreeWalker), {ETw, merge_info(LInfo, EInfo), [?Q("{_@hash, _@EAst}", [{hash, merl:term(erlang:phash2(Expr))}]) |Acc]} end, {TreeWalker1, MergedInfo, Changed} = lists:foldl(ValueAstFun, {TreeWalker, Info, []}, Values), {{?Q(["case erlydtl_runtime:ifchanged([_@Changed]) of", " true -> _@IfContentsAst;", " _ -> _@ElseContentsAst", "end"]), MergedInfo}, TreeWalker1}. ifchanged_contents_ast(Contents, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, TreeWalker) -> {{?Q(["case erlydtl_runtime:ifchanged([{_@hash, _@IfContentsAst}]) of", " true -> _@IfContentsAst;", " _ -> _@ElseContentsAst", "end"], [{hash, merl:term(erlang:phash2(Contents))}]), merge_info(IfContentsInfo, ElseContentsInfo)}, TreeWalker}. cycle_ast(Names, undefined, #treewalker{ context=Context }=TreeWalker) -> {NamesTuple, VarNames} = lists:mapfoldl( fun ({string_literal, _, Str}, VarNamesAcc) -> S = string_ast(unescape_string_literal(Str), Context), {S, VarNamesAcc}; ({variable, _}=Var, VarNamesAcc) -> {{V, #ast_info{ var_names=[VarName] }}, _} = resolve_variable_ast(Var, true, TreeWalker), {V, [VarName|VarNamesAcc]}; ({number_literal, _, Num}, VarNamesAcc) -> {format_number_ast(erl_syntax:integer(Num)), VarNamesAcc}; (_, VarNamesAcc) -> {[], VarNamesAcc} end, [], Names), {ForLoop, TreeWalker1} = resolve_reserved_variable('forloop', TreeWalker), {{?Q("erlydtl_runtime:cycle({_@NamesTuple}, _@ForLoop)"), #ast_info{ var_names = VarNames }}, TreeWalker1}; cycle_ast(Names, [{identifier, _, VarName}|Opts], TreeWalker) -> {{VarAst, AstInfo}, TW1} = cycle_ast(Names, undefined, TreeWalker), VarNameAst = varname_ast(VarName), {Scope, TW2} = begin_scope( {[{VarName, VarNameAst}], [?Q("_@VarNameAst = _@VarAst") | case Opts of [silent] -> []; [] -> [VarAst] end ]}, TW1), {{Scope, AstInfo}, TW2}. %% Older Django templates treat cycle with comma-delimited elements as strings cycle_compat_ast(Names, #treewalker{ context=Context }=TreeWalker) -> NamesTuple = lists:map( fun ({identifier, _, X}) -> string_ast(X, Context) end, Names), {ForLoop, TreeWalker1} = resolve_reserved_variable('forloop', TreeWalker), {{?Q("erlydtl_runtime:cycle({_@NamesTuple}, _@ForLoop)"), #ast_info{}}, TreeWalker1}. now_ast(FormatString, TreeWalker) -> %% Note: we can't use unescape_string_literal here %% because we want to allow escaping in the format string. %% We only want to remove the surrounding escapes, %% i.e. \"foo\" becomes "foo" UnescapeOuter = string:strip(FormatString, both, 34), {{StringAst, Info}, TreeWalker1} = string_ast(UnescapeOuter, TreeWalker), {{?Q("erlydtl_dateformat:format(_@StringAst, _TranslationFun, _CurrentLocale)"), Info}, TreeWalker1}. spaceless_ast(Contents, TreeWalker) -> {{Ast, Info}, TreeWalker1} = body_ast(Contents, TreeWalker), {{?Q("erlydtl_runtime:spaceless(_@Ast)"), Info}, TreeWalker1}. load_libs_ast(Libs, TreeWalker) -> TreeWalker1 = lists:foldl( fun ({identifier, Pos, Lib}, TW) -> load_library(Pos, Lib, TW) end, TreeWalker, Libs), empty_ast(TreeWalker1). load_from_lib_ast(What, {identifier, Pos, Lib}, TreeWalker) -> Names = lists:foldl( fun ({identifier, _, Name}, Acc) -> [Name|Acc] end, [], What), empty_ast(load_library(Pos, Lib, Names, TreeWalker)). %%------------------------------------------------------------------- %% Custom tags %%------------------------------------------------------------------- interpret_value({trans, StringLiteral}, TreeWalker) -> translated_ast(StringLiteral, TreeWalker); interpret_value(Value, TreeWalker) -> value_ast(Value, false, false, TreeWalker). interpret_args(Args, TreeWalker) -> lists:foldr( fun ({{identifier, _, Key}, Value}, {{ArgsAcc, AstInfoAcc}, TreeWalkerAcc}) -> {{Ast0, AstInfo0}, TreeWalker0} = interpret_value(Value, TreeWalkerAcc), {{[?Q("{_@Key@, _@Ast0}")|ArgsAcc], merge_info(AstInfo0, AstInfoAcc)}, TreeWalker0}; (Value, {{ArgsAcc, AstInfoAcc}, TreeWalkerAcc}) -> {{Ast0, AstInfo0}, TreeWalker0} = value_ast(Value, false, false, TreeWalkerAcc), {{[Ast0|ArgsAcc], merge_info(AstInfo0, AstInfoAcc)}, TreeWalker0} end, {{[], #ast_info{}}, TreeWalker}, Args). tag_ast(Name, Args, TreeWalker) -> {{InterpretedArgs, AstInfo1}, TreeWalker1} = interpret_args(Args, TreeWalker), {{RenderAst, RenderInfo}, TreeWalker2} = custom_tags_modules_ast(Name, InterpretedArgs, TreeWalker1), {{RenderAst, merge_info(AstInfo1, RenderInfo)}, TreeWalker2}. custom_tags_modules_ast({identifier, Pos, Name}, InterpretedArgs, #treewalker{ context=#dtl_context{ tags = Tags, module = Module, is_compiling_dir=IsCompilingDir } }=TreeWalker) -> case proplists:get_value(Name, Tags) of {Mod, Fun}=Tag -> case lists:max([-1] ++ [I || {N,I} <- Mod:module_info(exports), N =:= Fun]) of 2 -> {{?Q("'@Mod@':'@Fun@'([_@InterpretedArgs], RenderOptions)"), #ast_info{}}, TreeWalker}; 1 -> {{?Q("'@Mod@':'@Fun@'([_@InterpretedArgs])"), #ast_info{}}, TreeWalker}; -1 -> empty_ast(?WARN({Pos, {missing_tag, Name, Tag}}, TreeWalker)); I -> empty_ast(?WARN({Pos, {bad_tag, Name, Tag, I}}, TreeWalker)) end; undefined -> if IsCompilingDir =/= false -> {{?Q("'@Module@':'@Name@'([_@InterpretedArgs], RenderOptions)"), #ast_info{ custom_tags = [Name] }}, TreeWalker}; true -> {{?Q("render_tag(_@Name@, [_@InterpretedArgs], RenderOptions)"), #ast_info{ custom_tags = [Name] }}, TreeWalker} end end. tag_ast(Name, Args, NewTagVar, TreeWalker) -> {{InterpretedArgs, AstInfo1}, TreeWalker1} = interpret_args(Args, TreeWalker), {{RenderAst, RenderInfo}, TreeWalker2} = custom_tags_modules_ast(Name, InterpretedArgs, NewTagVar, TreeWalker1), {{RenderAst, merge_info(AstInfo1, RenderInfo)}, TreeWalker2}. custom_tags_modules_ast({identifier, Pos, Name}, InterpretedArgs, NewTagVar, #treewalker{ context=#dtl_context{ tags = Tags, module = Module, is_compiling_dir=IsCompilingDir } }=TreeWalker) -> LocalVarAst = varname_ast(NewTagVar), case proplists:get_value(Name, Tags) of {Mod, Fun}=Tag -> case lists:max([-1] ++ [I || {N,I} <- Mod:module_info(exports), N =:= Fun]) of 2 -> {Id, TreeWalker1} = begin_scope( {[{NewTagVar, LocalVarAst}], [?Q("_@LocalVarAst = '@Mod@':'@Fun@'([_@InterpretedArgs], RenderOptions)")]}, TreeWalker ), {{Id, #ast_info{}}, TreeWalker1}; 1 -> {Id, TreeWalker1} = begin_scope( {[{NewTagVar, LocalVarAst}], [?Q("_@LocalVarAst = '@Mod@':'@Fun@'([_@InterpretedArgs])")]}, TreeWalker ), {{Id, #ast_info{}}, TreeWalker1}; -1 -> empty_ast(?WARN({Pos, {missing_tag, Name, Tag}}, TreeWalker)); I -> empty_ast(?WARN({Pos, {bad_tag, Name, Tag, I}}, TreeWalker)) end; undefined -> if IsCompilingDir =/= false -> {Id, TreeWalker1} = begin_scope( {[{NewTagVar, LocalVarAst}], [?Q("_@LocalVarAst = '@Module@':'@Name@'([_@InterpretedArgs], RenderOptions)")]}, TreeWalker ), {{Id, #ast_info{ custom_tags = [Name]}}, TreeWalker1}; true -> {Id, TreeWalker1} = begin_scope( {[{NewTagVar, LocalVarAst}], [?Q("_@LocalVarAst = '@Module@':'@Name@'([_@InterpretedArgs], RenderOptions)")]}, TreeWalker ), {{Id, #ast_info{ custom_tags = [Name]}}, TreeWalker1} end end. call_ast(Module, TreeWalker) -> call_ast(Module, merl:var("_Variables"), #ast_info{}, TreeWalker). call_with_ast(Module, Variable, TreeWalker) -> {{VarAst, VarInfo}, TreeWalker2} = resolve_variable_ast(Variable, false, TreeWalker), call_ast(Module, VarAst, VarInfo, TreeWalker2). call_ast(Module, Variable, AstInfo, TreeWalker) -> Ast = ?Q(["case '@Module@':render(_@Variable, RenderOptions) of", " {ok, Rendered} -> Rendered;", " {error, Reason} -> io_lib:format(\"error: ~p\", [Reason])", "end"]), with_dependencies(Module:dependencies(), {{Ast, AstInfo}, TreeWalker}). create_scope(Vars, VarScope) -> {Scope, Values} = lists:foldl( fun (Var, {VarAcc, ValueAcc}) -> {Name, Value, Filters} = case Var of {N, V} -> {N, V, []}; {_, _, _} -> Var end, NameAst = varname_ast(lists:concat(["_", Name, VarScope])), {[{Name, NameAst, Filters}|VarAcc], [?Q("_@NameAst = _@Value")|ValueAcc] } end, empty_scope(), Vars), {Scope, [Values]}. create_scope(Vars, {Row, Col}, FileName, #treewalker{ context=Context }) -> Level = length(Context#dtl_context.local_scopes), create_scope(Vars, lists:concat(["::", FileName, "[", Level, ",", Row, ":", Col, "]"])). varname_ast([$_|VarName]) -> merl:var(lists:concat(["_Var__", VarName])); varname_ast(VarName) -> merl:var(lists:concat(["Var_", VarName])). erlydtl-0.15.0/src/erlydtl_compiler.erl000066400000000000000000000457511504712431400201350ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_compiler.erl %%% @author Roberto Saccon [http://rsaccon.com] %%% @author Evan Miller %%% @author Andreas Stenius %%% @copyright 2008 Roberto Saccon, Evan Miller %%% @copyright 2014 Andreas Stenius %%% @doc %%% ErlyDTL template compiler %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2007 Roberto Saccon, Evan Miller %%% Copyright (c) 2014 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2007-12-16 by Roberto Saccon, Evan Miller %%% @since 2014 by Andreas Stenius %%%------------------------------------------------------------------- -module(erlydtl_compiler). -author('rsaccon@gmail.com'). -author('emmiller@gmail.com'). -author('Andreas Stenius '). %% -------------------------------------------------------------------- %% Definitions %% -------------------------------------------------------------------- -export([compile_file/3, compile_template/3, compile_dir/3, format_error/1, default_options/0]). %% internal use -export([parse_file/2, parse_template/2, do_parse_template/2]). -import(erlydtl_compiler_utils, [add_filters/2, add_tags/2, call_extension/3, load_library/2, shorten_filename/1, get_current_file/1]). -include("erlydtl_ext.hrl"). %% -------------------------------------------------------------------- %% API %% -------------------------------------------------------------------- default_options() -> [verbose, report]. compile_template(Template, Module, Options) -> Context = process_opts(undefined, Module, Options), Bin = iolist_to_binary(Template), ?LOG_INFO("Compile template: ~32s~s~n", [Bin, if size(Bin) > 32 -> "..."; true -> "" end], Context), compile(Context#dtl_context{ bin = Bin }). compile_file(File, Module, Options) -> Context = process_opts(File, Module, Options), ?LOG_INFO("Compile file: ~s~n", [File], Context), compile(Context). compile_dir(Dir, Module, Options) -> Context = process_opts({dir, Dir}, Module, Options), ?LOG_INFO("Compile directory: ~s~n", [Dir], Context), compile(Context). format_error({read_file, Error}) -> io_lib:format( "Failed to read file: ~s", [file:format_error(Error)]); format_error({read_file, File, Error}) -> io_lib:format( "Failed to include file ~s: ~s", [File, file:format_error(Error)]); format_error({deprecated_option, Opt, NewOpt}) -> io_lib:format( "Compile option '~s' has been deprecated. Use '~s' instead.", [Opt, NewOpt]); format_error(Error) -> erlydtl_compiler_utils:format_error(Error). %%==================================================================== %% Internal functions %%==================================================================== process_opts(File, Module, Options0) -> Options1 = proplists:normalize( update_defaults(Options0), [{aliases, deprecated_opts()} ]), Source0 = filename:absname( case File of undefined -> filename:join( [case proplists:get_value(out_dir, Options1, false) of false -> "."; OutDir -> OutDir end, Module]); {dir, Dir} -> Dir; _ -> File end), Source = shorten_filename(Source0), Options = [{compiler_options, [{source, Source}]} |compiler_opts(Options1, [])], Context = case File of {dir, _} -> init_context([], Source, Module, Options); _ -> init_context([Source], filename:dirname(Source), Module, Options) end, %% check original options here now that we have a context to %% process any warnings/errors generated. check_opts(Options0, Context). deprecated_opts() -> [{outdir, out_dir}, {vars, default_vars}, {blocktrans_fun, translation_fun}, {blocktrans_locales, locales}]. check_opts(Options, Context) -> lists:foldl( fun ({Opt, NewOpt}, Ctx) -> case proplists:get_value(Opt, Options) of undefined -> Ctx; _ -> ?WARN({deprecated_option, Opt, NewOpt}, Ctx) end end, Context, deprecated_opts()). compiler_opts([CompilerOption|Os], Acc) when CompilerOption =:= return; CompilerOption =:= return_warnings; CompilerOption =:= return_errors; CompilerOption =:= report; CompilerOption =:= report_warnings; CompilerOption =:= report_errors; CompilerOption =:= warnings_as_errors; CompilerOption =:= verbose; CompilerOption =:= debug_info -> compiler_opts(Os, [CompilerOption, {compiler_options, [CompilerOption]}|Acc]); compiler_opts([O|Os], Acc) -> compiler_opts(Os, [O|Acc]); compiler_opts([], Acc) -> lists:reverse(Acc). update_defaults(Options) -> maybe_add_env_default_opts(Options). maybe_add_env_default_opts(Options) -> case proplists:get_bool(no_env, Options) of true -> Options; _ -> Options ++ env_default_opts() end. %% shamelessly borrowed from: %% https://github.com/erlang/otp/blob/21095e6830f37676dd29c33a590851ba2c76499b/\ %% lib/compiler/src/compile.erl#L128 env_default_opts() -> Key = "ERLYDTL_COMPILER_OPTIONS", case os:getenv(Key) of false -> []; Str when is_list(Str) -> case erl_scan:string(Str) of {ok,Tokens,_} -> case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of {ok,List} when is_list(List) -> List; {ok,Term} -> [Term]; {error,_Reason} -> io:format("Ignoring bad term in ~s\n", [Key]), [] end; {error, {_,_,_Reason}, _} -> io:format("Ignoring bad term in ~s\n", [Key]), [] end end. compile(Context) -> Context1 = do_compile(Context), collect_result(Context1). collect_result(#dtl_context{ module=Module, errors=#error_info{ list=[] }, warnings=Ws }=Context) -> Info = case Ws of #error_info{ return=true, list=Warnings } -> [pack_error_list(Warnings)]; _ -> [] end, Res = case proplists:get_bool(binary, Context#dtl_context.all_options) of true -> [ok, Module, Context#dtl_context.bin | Info]; false -> [ok, Module | Info] end, list_to_tuple(Res); collect_result(#dtl_context{ errors=Es, warnings=Ws }) -> if Es#error_info.return -> {error, pack_error_list(Es#error_info.list), case Ws of #error_info{ list=L } -> pack_error_list(L); _ -> [] end}; true -> error end. init_context(ParseTrail, DefDir, Module, Options) when is_list(Module) -> init_context(ParseTrail, DefDir, list_to_atom(Module), Options); init_context(ParseTrail, DefDir, Module, Options) -> Ctx = #dtl_context{}, Locales = lists:usort( lists:concat( [proplists:get_all_values(locale, Options), proplists:get_value(locales, Options, Ctx#dtl_context.trans_locales)] )), Context0 = #dtl_context{ all_options = Options, auto_escape = case proplists:get_value(auto_escape, Options, true) of true -> [on]; _ -> [off] end, parse_trail = ParseTrail, module = Module, doc_root = proplists:get_value(doc_root, Options, DefDir), libraries = proplists:get_value(libraries, Options, Ctx#dtl_context.libraries), custom_tags_dir = proplists:get_value( custom_tags_dir, Options, filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags"])), trans_fun = erlydtl_runtime:init_translation( proplists:get_value(translation_fun, Options, Ctx#dtl_context.trans_fun)), trans_locales = Locales, vars = proplists:get_value(default_vars, Options, Ctx#dtl_context.vars), const = proplists:get_value(constants, Options, Ctx#dtl_context.const), reader = proplists:get_value(reader, Options, Ctx#dtl_context.reader), reader_options = proplists:get_value(reader_options, Options, Ctx#dtl_context.reader_options), compiler_options = proplists:append_values(compiler_options, Options), binary_strings = proplists:get_value(binary_strings, Options, Ctx#dtl_context.binary_strings), force_recompile = proplists:get_bool(force_recompile, Options), verbose = length(proplists:get_all_values(verbose, Options)), is_compiling_dir = if ParseTrail == [] -> DefDir; true -> false end, extension_module = proplists:get_value(extension_module, Options, Ctx#dtl_context.extension_module), scanner_module = proplists:get_value(scanner_module, Options, Ctx#dtl_context.scanner_module), record_info = [{R, lists:zip(I, lists:seq(2, length(I) + 1))} || {R, I} <- proplists:get_value(record_info, Options, Ctx#dtl_context.record_info)], errors = init_error_info(errors, Ctx#dtl_context.errors, Options), warnings = init_error_info(warnings, Ctx#dtl_context.warnings, Options), lists_0_based = proplists:get_value(lists_0_based, Options, Ctx#dtl_context.lists_0_based), tuples_0_based = proplists:get_value(tuples_0_based, Options, Ctx#dtl_context.tuples_0_based), checks = proplists:substitute_negations( [{no_non_block_tag, non_block_tag}], proplists:get_all_values(w, Options)) }, Context1 = load_libraries(proplists:get_value(default_libraries, Options, []), Context0), Context = default_checks(Ctx#dtl_context.checks, Context1), case call_extension(Context, init_context, [Context]) of {ok, C} when is_record(C, dtl_context) -> C; undefined -> Context end. default_checks([], Context) -> Context; default_checks([C|Cs], #dtl_context{ checks = Checks0 } = Context) -> Checks = case proplists:get_value(C, Checks0) of undefined -> [C|Checks0]; _ -> Checks0 end, default_checks(Cs, Context#dtl_context{ checks = Checks }). init_error_info(warnings, Ei, Options) -> case proplists:get_bool(warnings_as_errors, Options) of true -> warnings_as_errors; false -> init_error_info(get_error_info_opts(warnings, Options), Ei) end; init_error_info(Class, Ei, Options) -> init_error_info(get_error_info_opts(Class, Options), Ei). init_error_info([{return, true}|Flags], #error_info{ return = false }=Ei) -> init_error_info(Flags, Ei#error_info{ return = true }); init_error_info([{report, true}|Flags], #error_info{ report = false }=Ei) -> init_error_info(Flags, Ei#error_info{ report = true }); init_error_info([_|Flags], Ei) -> init_error_info(Flags, Ei); init_error_info([], Ei) -> Ei. get_error_info_opts(Class, Options) -> Flags = case Class of errors -> [return, report, {return_errors, return}, {report_errors, report}]; warnings -> [return, report, {return_warnings, return}, {report_warnings, report}] end, [begin {Key, Value} = if is_atom(Flag) -> {Flag, Flag}; true -> Flag end, {Value, proplists:get_bool(Key, Options)} end || Flag <- Flags]. load_libraries([], #dtl_context{ all_options=Options }=Context) -> %% Load filters and tags passed using the old options Filters = proplists:get_value(custom_filters_modules, Options, []) ++ [erlydtl_filters], Tags = proplists:get_value(custom_tags_modules, Options, []), load_legacy_filters(Filters, load_legacy_tags(Tags, Context)); load_libraries([Lib|Libs], Context) -> load_libraries(Libs, load_library(Lib, Context)). load_legacy_filters([], Context) -> Context; load_legacy_filters([Mod|Mods], Context) -> {Filters, Context1} = read_legacy_library(Mod, Context), load_legacy_filters(Mods, add_filters(Filters, Context1)). load_legacy_tags([], Context) -> Context; load_legacy_tags([Mod|Mods], Context) -> {Tags, Context1} = read_legacy_library(Mod, Context), load_legacy_tags(Mods, add_tags(Tags, Context1)). read_legacy_library(Mod, Context) -> case code:ensure_loaded(Mod) of {module, Mod} -> {[{Name, {Mod, Name}} || {Name, _} <- lists:ukeysort(1, Mod:module_info(exports)), Name =/= module_info ], Context}; {error, Reason} -> {[], ?WARN({load_library, '(custom-legacy)', Mod, Reason}, Context)} end. is_up_to_date(_, #dtl_context{force_recompile = true}) -> false; is_up_to_date(CheckSum, Context) -> erlydtl_beam_compiler:is_up_to_date(CheckSum, Context). parse_file(File, Context) -> {M, F} = Context#dtl_context.reader, ReaderOptions = Context#dtl_context.reader_options, case catch erlydtl_runtime:read_file_internal(M, F, File, ReaderOptions) of {ok, Data} -> parse_template(Data, Context); {error, Reason} -> {error, {read_file, File, Reason}} end. parse_template(Data, Context) -> CheckSum = binary_to_list(erlang:md5(Data)), case is_up_to_date(CheckSum, Context) of true -> up_to_date; false -> case do_parse_template(Data, Context) of {ok, Val} -> {ok, Val, CheckSum}; Err -> Err end end. do_parse_template(Data, #dtl_context{ scanner_module=Scanner }=Context) -> check_scan( apply(Scanner, scan, [Data]), Context). check_scan({ok, Tokens}, Context) -> Tokens1 = case call_extension(Context, post_scan, [Tokens]) of undefined -> Tokens; {ok, T} -> T end, check_parse(erlydtl_parser:parse(Tokens1), [], Context#dtl_context{ scanned_tokens=Tokens1 }); check_scan({error, Err, State}, Context) -> case call_extension(Context, scan, [State]) of undefined -> {error, Err}; {ok, NewState} -> check_scan(apply(Context#dtl_context.scanner_module, resume, [NewState]), Context); ExtRes -> ExtRes end; check_scan({error, _}=Error, _Context) -> Error. check_parse({ok, _}=Ok, [], _Context) -> Ok; check_parse({ok, Parsed}, Acc, _Context) -> {ok, Acc ++ Parsed}; check_parse({error, _}=Err, _, _Context) -> Err; check_parse({error, Err, State}, Acc, Context) -> {State1, Parsed} = reset_parse_state(State, Context), case call_extension(Context, parse, [State1]) of undefined -> {error, Err}; {ok, ExtParsed} -> {ok, Acc ++ Parsed ++ ExtParsed}; {error, ExtErr, ExtState} -> case reset_parse_state(ExtState, Context) of {_, []} -> %% todo: see if this is indeed a sensible ext error, %% or if we should rather present the original Err message {error, ExtErr}; {State2, ExtParsed} -> check_parse(erlydtl_parser:resume(State2), Acc ++ Parsed ++ ExtParsed, Context) end; ExtRes -> ExtRes end. %% backtrack up to the nearest opening tag, and keep the value stack parsed ok so far reset_parse_state([[{Tag, _, _}|_]=Ts, Tzr, _, _, Stack], Context) when Tag==open_tag; Tag==open_var -> %% reached opening tag, so the stack should be sensible here {[reset_token_stream(Ts, Context#dtl_context.scanned_tokens), Tzr, 0, [], []], lists:flatten(Stack)}; reset_parse_state([_, _, 0, [], []]=State, _Context) -> %% top of (empty) stack {State, []}; reset_parse_state([Ts, Tzr, _, [0 | []], [Parsed | []]], Context) when is_list(Parsed) -> %% top of good stack {[reset_token_stream(Ts, Context#dtl_context.scanned_tokens), Tzr, 0, [], []], Parsed}; reset_parse_state([Ts, Tzr, _, [S | Ss], [T | Stack]], Context) -> %% backtrack... reset_parse_state([[T|Ts], Tzr, S, Ss, Stack], Context). reset_token_stream([T|_], [T|Ts]) -> [T|Ts]; reset_token_stream(Ts, [_|S]) -> reset_token_stream(Ts, S). %% we should find the next token in the list of scanned tokens, or something is real fishy pack_error_list(Es) -> collect_error_info([], Es, []). collect_error_info([], [], Acc) -> lists:reverse(Acc); collect_error_info([{File, ErrorInfo}|Es], Rest, [{File, FEs}|Acc]) -> collect_error_info(Es, Rest, [{File, ErrorInfo ++ FEs}|Acc]); collect_error_info([E|Es], Rest, Acc) -> collect_error_info(Es, [E|Rest], Acc); collect_error_info([], Rest, Acc) -> case lists:reverse(Rest) of [E|Es] -> collect_error_info(Es, [], [E|Acc]) end. do_compile(#dtl_context{ is_compiling_dir=false, bin=undefined }=Context) -> compile_output(parse_file(get_current_file(Context), Context), Context); do_compile(#dtl_context{ is_compiling_dir=false, bin=Template }=Context) -> compile_output(parse_template(Template, Context), Context); do_compile(#dtl_context{ is_compiling_dir=Dir }=Context) -> erlydtl_beam_compiler:compile_dir(Dir, Context). compile_output(up_to_date, Context) -> Context; compile_output({ok, DjangoParseTree, CheckSum}, Context) -> erlydtl_beam_compiler:compile(DjangoParseTree, CheckSum, Context#dtl_context{ bin=undefined }); compile_output({error, Reason}, Context) -> ?ERR(Reason, Context). erlydtl-0.15.0/src/erlydtl_compiler_utils.erl000066400000000000000000000473301504712431400213500ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_compiler_utils.erl %%% @author Roberto Saccon [http://rsaccon.com] %%% @author Evan Miller %%% @author Andreas Stenius %%% @copyright 2008 Roberto Saccon, Evan Miller %%% @copyright 2014 Andreas Stenius %%% @doc %%% ErlyDTL template compiler utils. %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2007 Roberto Saccon, Evan Miller %%% Copyright (c) 2014 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2007-12-16 by Roberto Saccon, Evan Miller %%% @since 2014 by Andreas Stenius %%%------------------------------------------------------------------- -module(erlydtl_compiler_utils). -author('rsaccon@gmail.com'). -author('emmiller@gmail.com'). -author('Andreas Stenius '). %% -------------------------------------------------------------------- %% Definitions %% -------------------------------------------------------------------- -define(LIB_VERSION, 1). -export([ add_error/3, add_errors/2, add_filters/2, add_tags/2, add_warning/3, add_warnings/2, begin_scope/1, begin_scope/2, call_extension/3, empty_scope/0, end_scope/4, format_error/1, full_path/2, get_current_file/1, init_treewalker/1, is_stripped_token_empty/1, load_library/2, load_library/3, load_library/4, merge_info/2, print/3, print/4, push_scope/2, reset_block_dict/2, reset_parse_trail/2, resolve_variable/2, resolve_variable/3, restore_scope/2, shorten_filename/1, shorten_filename/2, to_string/2, unescape_string_literal/1, push_auto_escape/2, pop_auto_escape/1, token_pos/1 ]). -include("erlydtl_ext.hrl"). %% -------------------------------------------------------------------- %% API %% -------------------------------------------------------------------- init_treewalker(Context) -> TreeWalker = #treewalker{ context=Context }, case call_extension(Context, init_treewalker, [TreeWalker]) of {ok, TW} when is_record(TW, treewalker) -> TW; undefined -> TreeWalker end. to_string(Arg, #dtl_context{ binary_strings = true }) -> to_binary_string(Arg); to_string(Arg, #dtl_context{ binary_strings = false }) -> to_list_string(Arg). unescape_string_literal(String) -> unescape_string_literal(remove_quotes(String), [], noslash). full_path(File, DocRoot) -> case filename:absname(File) of File -> File; _ -> filename:join([DocRoot, File]) end. print(Fmt, Args, Context) -> print(?V_INFO, Fmt, Args, Context). print(Verbosity, Fmt, Args, #treewalker{ context=Context }) -> print(Verbosity, Fmt, Args, Context); print(Verbosity, Fmt, Args, #dtl_context{ verbose = Verbose }) when Verbosity =< Verbose -> io:format(Fmt, Args); print(_Verbosity, _Fmt, _Args, _Context) -> ok. get_current_file(#treewalker{ context=Context }) -> get_current_file(Context); get_current_file(#dtl_context{ parse_trail=[File|_] }) -> File; get_current_file(#dtl_context{ is_compiling_dir=Dir }) when Dir =/= false -> Dir; get_current_file(#dtl_context{ doc_root=Root }) -> Root. add_error(Module, Error, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=add_error(Module, Error, Context) }; add_error(Module, Error, #dtl_context{ errors=#error_info{ report=Report, list=Es }=Ei }=Context) -> Item = get_error_item( Report, "", get_current_file(Context), Error, Module), Context#dtl_context{ errors=Ei#error_info{ list=[Item|Es] } }. add_errors(Errors, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=add_errors(Errors, Context) }; add_errors(Errors, Context) -> lists:foldl( fun (E, C) -> add_error(?MODULE, E, C) end, Context, Errors). add_warning(Module, Warning, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=add_warning(Module, Warning, Context) }; add_warning(Module, Warning, #dtl_context{ warnings=warnings_as_errors }=Context) -> add_error(Module, Warning, Context); add_warning(Module, Warning, #dtl_context{ warnings=#error_info{ report=Report, list=Ws }=Wi }=Context) -> Item = get_error_item( Report, "Warning: ", get_current_file(Context), Warning, Module), Context#dtl_context{ warnings=Wi#error_info{ list=[Item|Ws] } }. add_warnings(Warnings, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=add_warnings(Warnings, Context) }; add_warnings(Warnings, Context) -> lists:foldl( fun (W, C) -> add_warning(?MODULE, W, C) end, Context, Warnings). call_extension(#treewalker{ context=Context }, Fun, Args) -> call_extension(Context, Fun, Args); call_extension(#dtl_context{ extension_module=undefined }, _Fun, _Args) -> undefined; call_extension(#dtl_context{ extension_module=Mod }, Fun, Args) when is_atom(Mod), is_atom(Fun), is_list(Args) -> M = case code:is_loaded(Mod) of false -> case code:load_file(Mod) of {module, Mod} -> Mod; _ -> undefined end; _ -> Mod end, if M /= undefined -> case erlang:function_exported(M, Fun, length(Args)) of true -> apply(M, Fun, Args); false -> undefined end; true -> undefined end. merge_info(Info1, Info2) when is_record(Info1, ast_info), is_record(Info2, ast_info) -> merge_info1(record_info(size, ast_info), Info1, Info2, #ast_info{}). resolve_variable(VarName, TreeWalker) -> resolve_variable(VarName, undefined, TreeWalker). resolve_variable(VarName, Default, #treewalker{ context=Context }) -> case resolve_variable1(Context#dtl_context.local_scopes, VarName) of undefined -> case proplists:get_value(VarName, Context#dtl_context.const) of undefined -> case proplists:get_value(VarName, Context#dtl_context.vars) of undefined -> {default, Default, []}; Value -> {default_vars, Value, []} end; Value -> {constant, Value, []} end; {Value, Filters} -> {scope, Value, Filters} end. push_scope(Scope, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=push_scope(Scope, Context) }; push_scope(Scope, #dtl_context{ local_scopes=Scopes }=Context) -> Context#dtl_context{ local_scopes=[Scope|Scopes] }. restore_scope(Target, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=restore_scope(Target, Context) }; restore_scope(#treewalker{ context=Target }, Context) -> restore_scope(Target, Context); restore_scope(#dtl_context{ local_scopes=Scopes }, Context) -> Context#dtl_context{ local_scopes=Scopes }. begin_scope(TreeWalker) -> begin_scope(empty_scope(), TreeWalker). begin_scope({Scope, Values}, TreeWalker) -> Id = make_ref(), {Id, push_scope({Id, Scope, Values}, TreeWalker)}. end_scope(Fun, Id, AstList, TreeWalker) -> close_scope(Fun, Id, AstList, TreeWalker). empty_scope() -> {[], []}. reset_block_dict(BlockDict, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=reset_block_dict(BlockDict, Context) }; reset_block_dict(BlockDict, Context) -> Context#dtl_context{ block_dict=BlockDict }. reset_parse_trail(ParseTrail, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=reset_parse_trail(ParseTrail, Context) }; reset_parse_trail(ParseTrail, Context) -> Context#dtl_context{ parse_trail=ParseTrail }. load_library(Lib, Context) -> load_library(none, Lib, [], Context). load_library(Pos, Lib, Context) -> load_library(Pos, Lib, [], Context). load_library(Pos, Lib, Which, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=load_library(Pos, Lib, Which, Context) }; load_library(Pos, Lib, Which, Context) -> case lib_module(Lib, Context) of {ok, Mod} -> ?LOG_DEBUG( "~s: Load library '~s' from ~s ~p~n", [get_current_file(Context), Lib, Mod, Which], Context), Filters = read_library(Mod, filters, Which), Tags = read_library(Mod, tags, Which), Found = Filters ++ Tags, Missing = lists:filter( fun (W) -> lists:keyfind(W, 1, Found) == false end, Which), add_tags( Tags, add_filters( Filters, warn_missing( Missing, {Pos, Lib, Mod}, Context))); Error -> ?WARN({Pos, Error}, Context) end. add_filters(Load, #dtl_context{ filters=Filters }=Context) -> ?LOG_TRACE("Load filters: ~p~n", [Load], Context), Context#dtl_context{ filters=Load ++ Filters }. add_tags(Load, #dtl_context{ tags=Tags }=Context) -> ?LOG_TRACE("Load tags: ~p~n", [Load], Context), Context#dtl_context{ tags=Load ++ Tags }. %% shorten_filename/1 copied from Erlang/OTP lib/compiler/src/compile.erl shorten_filename(Name) -> {ok, Cwd} = file:get_cwd(), shorten_filename(Name, Cwd). shorten_filename(Name, Cwd) -> case lists:prefix(Cwd, Name) of false -> Name; true -> case lists:nthtail(length(Cwd), Name) of "/"++N -> N; N -> N end end. push_auto_escape(State, #treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=push_auto_escape(State, Context) }; push_auto_escape(State, #dtl_context{ auto_escape=AutoEscape }=Context) -> Context#dtl_context{ auto_escape=[State|AutoEscape] }. pop_auto_escape(#treewalker{ context=Context }=TreeWalker) -> TreeWalker#treewalker{ context=pop_auto_escape(Context) }; pop_auto_escape(#dtl_context{ auto_escape=[_|AutoEscape] }=Context) when length(AutoEscape) > 0 -> Context#dtl_context{ auto_escape=AutoEscape }; pop_auto_escape(Context) -> Context. token_pos(Token) when is_tuple(Token) -> token_pos(tuple_to_list(Token)); token_pos([T|Ts]) when is_tuple(T) -> case T of {R, C}=P when is_integer(R), is_integer(C) -> P; _ -> token_pos(tuple_to_list(T) ++ Ts) end; token_pos([T|Ts]) when is_list(T) -> token_pos(T ++ Ts); token_pos([_|Ts]) -> token_pos(Ts); token_pos([]) -> none. is_stripped_token_empty({string, _, S}) -> [] == [C || C <- S, C /= 32, C /= $\r, C /= $\n, C /= $\t]; is_stripped_token_empty({comment, _}) -> true; is_stripped_token_empty({comment_tag, _, _}) -> true; is_stripped_token_empty(_) -> false. format_error({load_library, Name, Mod, Reason}) -> io_lib:format("Failed to load library '~p' (~p): ~p", [Name, Mod, Reason]); format_error({load_from, Name, Mod, Tag}) -> io_lib:format("'~p' not in library '~p' (~p)", [Tag, Name, Mod]); format_error({unknown_extension, Tag}) -> io_lib:format("Unhandled extension: ~p", [Tag]); format_error(Other) -> io_lib:format("## Sorry, error description for ~p not yet implemented.~n" "## Please report this so we can fix it.", [Other]). %%==================================================================== %% Internal functions %%==================================================================== to_binary_string(Arg) when is_binary(Arg) -> Arg; to_binary_string(Arg) when is_list(Arg) -> list_to_binary(Arg); to_binary_string(Arg) when is_integer(Arg) -> case erlang:function_exported(erlang, integer_to_binary, 1) of true -> erlang:integer_to_binary(Arg); false -> list_to_binary(integer_to_list(Arg)) end; to_binary_string(Arg) when is_atom(Arg) -> atom_to_binary(Arg, latin1). to_list_string(Arg) when is_list(Arg) -> Arg; to_list_string(Arg) when is_binary(Arg) -> binary_to_list(Arg); to_list_string(Arg) when is_integer(Arg) -> integer_to_list(Arg); to_list_string(Arg) when is_atom(Arg) -> atom_to_list(Arg). unescape_string_literal([], Acc, noslash) -> lists:reverse(Acc); unescape_string_literal([$\\ | Rest], Acc, noslash) -> unescape_string_literal(Rest, Acc, slash); unescape_string_literal([C | Rest], Acc, noslash) -> unescape_string_literal(Rest, [C | Acc], noslash); unescape_string_literal("n" ++ Rest, Acc, slash) -> unescape_string_literal(Rest, [$\n | Acc], noslash); unescape_string_literal("r" ++ Rest, Acc, slash) -> unescape_string_literal(Rest, [$\r | Acc], noslash); unescape_string_literal("t" ++ Rest, Acc, slash) -> unescape_string_literal(Rest, [$\t | Acc], noslash); unescape_string_literal([C | Rest], Acc, slash) -> unescape_string_literal(Rest, [C | Acc], noslash). get_error_item(Report, Prefix, File, Error, DefaultModule) -> case compose_error_desc(Error, DefaultModule) of {Pos, Module, ErrorDesc} -> new_error_item(Report, Prefix, File, Pos, Module, ErrorDesc); ErrorItem -> ErrorItem end. compose_error_desc({{Line, Col}=Pos, ErrorDesc}, Module) when is_integer(Line), is_integer(Col), is_atom(Module) -> {Pos, Module, ErrorDesc}; compose_error_desc({Line, ErrorDesc}, Module) when is_integer(Line); Line =:= none -> {Line, Module, ErrorDesc}; compose_error_desc({{Line, Col}, Module, _}=ErrorDesc, _) when is_integer(Line), is_integer(Col), is_atom(Module) -> ErrorDesc; compose_error_desc({Line, Module, _}=ErrorDesc, _) when is_integer(Line), is_atom(Module) -> ErrorDesc; compose_error_desc({none, Module, _}=ErrorDesc, _) when is_atom(Module) -> ErrorDesc; compose_error_desc({_, InfoList}=ErrorDesc, _) when is_list(InfoList) -> ErrorDesc; compose_error_desc(ErrorDesc, Module) -> {none, Module, ErrorDesc}. new_error_item(Report, Prefix, File, Pos, Module, ErrorDesc) -> if Report -> io:format("~s:~s~s~s~n", [File, pos_info(Pos), Prefix, Module:format_error(ErrorDesc)]); true -> nop end, {File, [{Pos, Module, ErrorDesc}]}. pos_info(none) -> " "; pos_info(Line) when is_integer(Line) -> io_lib:format("~b: ", [Line]); pos_info({Line, Col}) when is_integer(Line), is_integer(Col) -> io_lib:format("~b:~b ", [Line, Col]). resolve_variable1([], _VarName) -> undefined; resolve_variable1([Scope|Scopes], VarName) -> case lists:keyfind(VarName, 1, get_scope(Scope)) of false -> resolve_variable1(Scopes, VarName); {_, Value} -> {Value, []}; {_, Value, Filters} when is_list(Filters) -> {Value, Filters}; {_, Value, Filter} when is_atom(Filter) -> {Value, [{Filter, []}]}; {_, Value, Filter} -> {Value, [Filter]} end. get_scope({_Id, Scope, _Values}) -> Scope; get_scope(Scope) -> Scope. merge_info1(1, _, _, Info) -> Info; merge_info1(FieldIdx, Info1, Info2, Info) -> Value = lists:umerge( lists:usort(element(FieldIdx, Info1)), lists:usort(element(FieldIdx, Info2))), merge_info1(FieldIdx - 1, Info1, Info2, setelement(FieldIdx, Info, Value)). close_scope(Fun, Id, AstList, TreeWalker) -> case merge_scopes(Id, TreeWalker) of {[], TreeWalker1} -> {AstList, TreeWalker1}; {Values, TreeWalker1} -> {lists:foldl( fun ({ScopeId, ScopeValues}, AstAcc) -> {Pre, Target, Post} = split_ast(ScopeId, AstAcc), Pre ++ Fun(ScopeValues ++ Target) ++ Post end, AstList, Values), TreeWalker1} end. merge_scopes(Id, #treewalker{ context=Context }=TreeWalker) -> {Values, Scopes} = merge_scopes(Id, Context#dtl_context.local_scopes, []), {lists:reverse(Values), TreeWalker#treewalker{ context=Context#dtl_context{ local_scopes = Scopes } }}. merge_scopes(Id, [{Id, _Scope, []}|Scopes], Acc) -> {Acc, Scopes}; merge_scopes(Id, [{Id, _Scope, Values}|Scopes], Acc) -> {[{Id, Values}|Acc], Scopes}; merge_scopes(Id, [{_ScopeId, _Scope, []}|Scopes], Acc) -> merge_scopes(Id, Scopes, Acc); merge_scopes(Id, [{ScopeId, _Scope, Values}|Scopes], Acc) -> merge_scopes(Id, Scopes, [{ScopeId, Values}|Acc]); merge_scopes(Id, [_PlainScope|Scopes], Acc) -> merge_scopes(Id, Scopes, Acc). split_ast(Id, AstList) -> split_ast(Id, AstList, []). split_ast(_Split, [], {Pre, Acc}) -> {Pre, lists:reverse(Acc), []}; split_ast(_Split, [], Acc) -> {[], lists:reverse(Acc), []}; split_ast(Split, [Split|Rest], {Pre, Acc}) -> {Pre, lists:reverse(Acc), Rest}; split_ast(Split, [Split|Rest], Acc) -> split_ast(end_scope, Rest, {lists:reverse(Acc), []}); split_ast(Split, [Ast|Rest], {Pre, Acc}) -> split_ast(Split, Rest, {Pre, [Ast|Acc]}); split_ast(Split, [Ast|Rest], Acc) -> split_ast(Split, Rest, [Ast|Acc]). lib_module(Name, #dtl_context{ libraries=Libs }) -> Mod = proplists:get_value(Name, Libs, Name), case code:ensure_loaded(Mod) of {module, Mod} -> case implements_behaviour(erlydtl_library, Mod) of true -> case Mod:version() of ?LIB_VERSION -> {ok, Mod}; V -> {load_library, Name, Mod, {version, V}} end; false -> {load_library, Name, Mod, behaviour} end; {error, Reason} -> {load_library, Name, Mod, Reason} end. implements_behaviour(Behaviour, Mod) -> Attrs = Mod:module_info(attributes), Found = [B || [B] <- proplists:get_all_values(behaviour, Attrs)] ++ [B || [B] <- proplists:get_all_values(behavior, Attrs)], [] =:= [Behaviour] -- Found. read_library(Mod, Section, Which) -> [{Name, lib_function(Mod, Fun)} || {Name, Fun} <- read_inventory(Mod, Section), Which =:= [] orelse lists:member(Name, Which)]. warn_missing([], _, Context) -> Context; warn_missing([X|Xs], {Pos, Lib, Mod}=Info, Context) -> warn_missing(Xs, Info, ?WARN({Pos, {load_from, Lib, Mod, X}}, Context)). lib_function(_, {Mod, Fun}) -> lib_function(Mod, Fun); lib_function(Mod, Fun) -> %% we could check for lib function availability here.. (sanity check) {Mod, Fun}. read_inventory(Mod, Section) -> [case Item of {_Name, _Fun} -> Item; Fun -> {Fun, Fun} end || Item <- Mod:inventory(Section)]. remove_quotes(String) -> remove_last_quote(remove_first_quote(String)). remove_first_quote([34 | Rest]) -> Rest; remove_first_quote(String) -> String. remove_last_quote(String) -> lists:reverse(remove_first_quote(lists:reverse(String))). erlydtl-0.15.0/src/erlydtl_contrib_humanize.erl000066400000000000000000000010301504712431400216410ustar00rootroot00000000000000-module(erlydtl_contrib_humanize). -export([intcomma/1]). intcomma(Value) when is_integer(Value) -> intcomma(integer_to_list(Value)); intcomma(Value) -> ValueBin = iolist_to_binary(Value), intcomma(ValueBin, size(ValueBin) rem 3, <<>>). intcomma(<<>>, _, Acc) -> Acc; intcomma(<< C, Rest/bits >>, 0, <<>>) -> intcomma(Rest, 2, << C >>); intcomma(<< C, Rest/bits >>, 0, Acc) -> intcomma(Rest, 2, << Acc/binary, $,, C >>); intcomma(<< C, Rest/bits >>, N, Acc) -> intcomma(Rest, N - 1, << Acc/binary, C >>). erlydtl-0.15.0/src/erlydtl_deps.erl000066400000000000000000000047771504712431400172610ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_deps.erl %%% @author Roberto Saccon [http://rsaccon.com] %%% @author Evan Miller %%% @copyright 2008 Roberto Saccon, Evan Miller %%% @doc %%% ErlyDTL helper module %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2007 Roberto Saccon, Evan Miller %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2007-12-16 by Roberto Saccon, Evan Miller %%%------------------------------------------------------------------- -module(erlydtl_deps). -author('rsaccon@gmail.com'). -author('emmiller@gmail.com'). %% API -export([get_base_dir/0, get_base_dir/1]). %%==================================================================== %% API %%==================================================================== %% @spec get_base_dir(Module) -> string() %% @doc Return the application directory for Module. It assumes Module is in %% a standard OTP layout application in the ebin or src directory. get_base_dir(Module) -> {file, Here} = code:is_loaded(Module), filename:dirname(filename:dirname(Here)). %% @spec get_base_dir() -> string() %% @doc Return the application directory for this application. Equivalent to %% get_base_dir(?MODULE). get_base_dir() -> get_base_dir(?MODULE). %%==================================================================== %% Internal functions %%==================================================================== erlydtl-0.15.0/src/erlydtl_filters.erl000066400000000000000000001456731504712431400177770ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_filters.erl %%% @author Roberto Saccon [http://rsaccon.com] %%% @author Evan Miller %%% @copyright 2008 Roberto Saccon, Evan Miller %%% @doc %%% Template filters %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2007 Roberto Saccon, Evan Miller %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2007-11-11 by Roberto Saccon, Evan Miller %%%------------------------------------------------------------------- -module(erlydtl_filters). -author('rsaccon@gmail.com'). -author('emmiller@gmail.com'). -author('drew dot gulino at google dot com'). -ifdef(TEST). -undef(TEST). -endif. -define(TEST,""). %-define(NOTEST,1). -define(NODEBUG,1). -include_lib("eunit/include/eunit.hrl"). -ifdef(TEST). -export([cast_to_float/1,cast_to_integer/1,stringformat_io/7,round/2,unjoin/2,addDefaultURI/1]). -endif. -import(erlydtl_time_compat, [monotonic_time/0, unique_integer/0]). -export([add/2, addslashes/1, capfirst/1, center/2, cut/2, date/1, date/2, date/3, date/4, default/2, default_if_none/2, dictsort/2, dictsortreversed/2, divisibleby/2, %escape/, - implemented in erlydtl_compiler escapejs/1, filesizeformat/1, first/1, fix_ampersands/1, floatformat/1, floatformat/2, force_escape/1, format_integer/1, format_number/1, get_digit/2, iriencode/1, join/2, last/1, length/1, length_is/2, linebreaks/1, linebreaksbr/1, linenumbers/1, ljust/2, lower/1, make_list/1, phone2numeric/1, pluralize/1, pluralize/2, pprint/1, random/1, random_num/1, random_range/1, removetags/2, rjust/2, %safe/, - implemented in erlydtl_compiler %safeseq/, - implemented in erlydtl_compiler slice/2, slugify/1, stringformat/2, striptags/1, time/1, time/2, timesince/1, timesince/2, timeuntil/1, timeuntil/2, title/1, truncatechars/2, truncatewords/2, truncatewords_html/2, unordered_list/1, upper/1, urlencode/1, urlencode/2, urlize/1, urlize/2, urlizetrunc/2, wordcount/1, wordwrap/2, yesno/2]). -define(NO_ENCODE(C), ((C >= $a andalso C =< $z) orelse (C >= $A andalso C =< $Z) orelse (C >= $0 andalso C =< $9) orelse (C =:= $\. orelse C =:= $- orelse C =:= $~ orelse C =:= $_))). -define(NO_IRI_ENCODE(C), (?NO_ENCODE(C) orelse ( C =:= $/ orelse C =:= $# orelse C =:= $[ orelse C =:= $] orelse C =:= $= orelse C =:= $: orelse C =:= $; orelse C =:= $$ orelse C =:= $& orelse C =:= $( orelse C =:= $) orelse C =:= $+ orelse C =:= $, orelse C =:= $! orelse C =:= $? orelse C =:= $* orelse C =:= $@ orelse C =:= $' orelse C =:= $~))). -define(KILOBYTE, 1024). -define(MEGABYTE, (1024 * ?KILOBYTE)). -define(GIGABYTE, (1024 * ?MEGABYTE)). -define(SECONDS_PER_MINUTE, 60). -define(SECONDS_PER_HOUR, (60 * ?SECONDS_PER_MINUTE)). -define(SECONDS_PER_DAY, (24 * ?SECONDS_PER_HOUR)). -define(SECONDS_PER_WEEK, (7 * ?SECONDS_PER_DAY)). -define(SECONDS_PER_MONTH, (30 * ?SECONDS_PER_DAY)). -define(SECONDS_PER_YEAR, (365 * ?SECONDS_PER_DAY)). %% @doc Adds to values add(LHS, RHS) when is_number(LHS), is_number(RHS) -> LHS + RHS; add(LHS, RHS) when is_binary(LHS) -> add(unicode:characters_to_list(LHS), RHS); add(LHS, RHS) when is_binary(RHS) -> add(LHS, unicode:characters_to_list(RHS)); add(LHS, RHS) when is_list(LHS), is_list(RHS) -> case {to_numeric(LHS), to_numeric(RHS)} of {{number, LHSNum}, {number, RHSNum}} -> LHSNum + RHSNum; _ -> LHS ++ RHS end; add(LHS, RHS) when is_list(LHS), is_number(RHS) -> case to_numeric(LHS) of {number, LHSNum} -> LHSNum + RHS; _ -> LHS ++ to_string(RHS) end; add(LHS, RHS) when is_number(LHS), is_list(RHS) -> case to_numeric(RHS) of {number, RHSNum} -> LHS + RHSNum; _ -> to_string(LHS) ++ RHS end. to_string(Num) when is_integer(Num) -> integer_to_list(Num); to_string(Num) when is_float(Num) -> float_to_list(Num). to_numeric(List) -> try {number, list_to_integer(List)} catch error:badarg -> try {number, list_to_float(List)} catch error:badarg -> undefined end end. %% @doc Adds slashes before quotes. addslashes(Input) when is_binary(Input) -> addslashes(unicode:characters_to_list(Input)); addslashes(Input) when is_list(Input) -> addslashes(Input, []). %% @doc Capitalizes the first character of the value. capfirst([H|T]) when H >= $a andalso H =< $z -> [H + $A - $a | T]; capfirst(Other) when is_list(Other) -> Other; capfirst(<>) when Byte >= $a andalso Byte =< $z -> [(Byte + $A - $a)|unicode:characters_to_list(Binary)]; capfirst(Other) when is_binary(Other) -> Other. %% @doc Centers the value in a field of a given width. center(Input, Number) when is_binary(Input) -> unicode:characters_to_binary(center(unicode:characters_to_list(Input), Number)); center(Input, Number) when is_list(Input) -> string:centre(Input, Number). %% @doc Removes all values of arg from the given string. cut(Input, Arg) when is_binary(Arg) -> cut(Input, unicode:characters_to_list(Arg)); cut(Input, Arg) when is_binary(Input) -> cut(unicode:characters_to_list(Input), Arg); cut(Input, [Char]) when is_list(Input) -> cut(Input, Char, []). %% @doc Formats a date according to the default format. date(Input) -> date(Input, "F j, Y"). %% @doc Formats a date according to the given format. date(Input, FormatStr) when is_tuple(Input) andalso (size(Input) == 2 orelse size(Input) == 3) -> erlydtl_dateformat:format(Input, FormatStr); date(Input, _FormatStr) -> io:format("Unexpected date parameter: ~p~n", [Input]), "". %% @doc Formats a date according to the default format %% localizing it with provided translation function. date(Input, TransFun, Locale) -> date(Input, "F j, Y", TransFun, Locale). date(Input, FormatStr, TransFun, Locale) when is_tuple(Input) andalso (size(Input) == 2 orelse size(Input) == 3) -> erlydtl_dateformat:format(Input, FormatStr, TransFun, Locale); date(Input, _FormatStr, _TransFun, _Locale) -> io:format("Unexpected date parameter: ~p~n", [Input]), "". %% @doc If value evaluates to `false', use given default. Otherwise, use the value. default(Input, Default) -> case erlydtl_runtime:is_false(Input) of true -> Default; false -> Input end. %% @doc If (and only if) value is `undefined', use given default. Otherwise, use the value. default_if_none(undefined, Default) -> Default; default_if_none(Input, _) -> Input. %% @doc Takes a list of dictionaries or proplists and returns that list sorted by the key given in the argument. dictsort(DictList, Key) when is_binary(Key) -> dictsort(DictList, [binary_to_atom(B,latin1) || B <- binary:split(Key,<<".">>)]); dictsort(DictList, Key) -> case lists:all( fun(Dict) -> erlydtl_runtime:find_deep_value(Key, Dict) /= undefined end, DictList) of true -> lists:sort( fun(K1,K2) -> erlydtl_runtime:find_deep_value(Key,K1) =< erlydtl_runtime:find_deep_value(Key,K2) end, DictList); false -> error end. %% @doc Same as dictsort, but the list is reversed. dictsortreversed(DictList, Key) -> lists:reverse(dictsort(DictList, Key)). %% @doc Returns `true' if the value is divisible by the argument. divisibleby(Input, Divisor) when is_binary(Input) -> divisibleby(unicode:characters_to_list(Input), Divisor); divisibleby(Input, Divisor) when is_list(Input) -> divisibleby(list_to_integer(Input), Divisor); divisibleby(Input, Divisor) when is_binary(Divisor) -> divisibleby(Input, unicode:characters_to_list(Divisor)); divisibleby(Input, Divisor) when is_list(Divisor) -> divisibleby(Input, list_to_integer(Divisor)); divisibleby(Input, Divisor) when is_integer(Input), is_integer(Divisor) -> Input rem Divisor =:= 0. %% @doc Escapes characters for use in JavaScript strings. escapejs(Input) when is_binary(Input) -> unicode:characters_to_binary(escapejs(unicode:characters_to_list(Input))); escapejs(Input) when is_list(Input) -> escapejs(Input, []). %% @doc Format the value like a human-readable file size. filesizeformat(Input) when is_binary(Input) -> filesizeformat(unicode:characters_to_list(Input)); filesizeformat(Input) when is_list(Input) -> filesizeformat(list_to_integer(Input)); filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?GIGABYTE-> filesizeformat(Bytes / ?GIGABYTE, "GB"); filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?MEGABYTE -> filesizeformat(Bytes / ?MEGABYTE, "MB"); filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?KILOBYTE -> filesizeformat(Bytes / ?KILOBYTE, "KB"); filesizeformat(Bytes) when is_integer(Bytes) -> integer_to_list(Bytes) ++ " bytes". %% @doc Returns the first item in a list. first([First|_Rest]) -> [First]; first(<>) -> <>. %% @doc Replaces ampersands with & entities. fix_ampersands(Input) when is_binary(Input) -> fix_ampersands(Input, 0); fix_ampersands(Input) when is_list(Input) -> fix_ampersands(Input, []). %% @doc When used without an argument, rounds a floating-point number to one decimal place %% -- but only if there's a decimal part to be displayed floatformat(Number) -> floatformat(Number, []). floatformat(Number, Place) when is_number(Number); is_binary(Number); is_list(Number) -> floatformat_io(cast_to_float(Number), cast_to_integer(Place)); floatformat(_, _) -> "". floatformat_io(Number, []) -> floatformat_io(Number, -1); floatformat_io(Number, 0) -> lists:flatten(io_lib:format("~B", [erlang:round(Number)])); floatformat_io(Number, Precision) when Precision > 0 -> lists:flatten(io_lib:format("~.*f",[Precision, Number])); floatformat_io(Number, Precision) when Precision < 0 -> Round = erlang:round(Number), RoundPrecision = round(Number, -Precision), if RoundPrecision == Round -> floatformat_io(Round, 0); true -> floatformat_io(RoundPrecision, -Precision) end. round(Number, Precision) -> P = math:pow(10, Precision), erlang:round(Number * P) / P. %% @doc Applies HTML escaping to a string. force_escape(Input) when is_list(Input) -> escape(Input, []); force_escape(Input) when is_binary(Input) -> escape(Input, 0); force_escape(Input) -> Input. format_integer(Input) when is_integer(Input) -> integer_to_list(Input); format_integer(Input) -> Input. format_number(Input) when is_integer(Input) -> integer_to_list(Input); format_number(Input) when is_float(Input) -> io_lib:format("~p", [Input]); format_number(Input) when is_function(Input, 0) -> format_number(Input()); format_number(Input) -> Input. %% @doc Given a whole number, returns the requested digit, where 1 is the right-most digit. get_digit(Input, Digit) when is_binary(Input) -> get_digit(unicode:characters_to_list(Input), Digit); get_digit(Input, Digit) when is_integer(Input) -> get_digit(integer_to_list(Input), Digit); get_digit(Input, Digit) when is_binary(Digit) -> get_digit(Input, unicode:characters_to_list(Digit)); get_digit(Input, Digit) when is_list(Digit) -> get_digit(Input, list_to_integer(Digit)); get_digit(Input, Digit) when Digit > erlang:length(Input) -> 0; get_digit(Input, Digit) when Digit > 0 -> lists:nth(Digit, lists:reverse(Input)) - $0; get_digit(Input, _) -> Input. iriencode(Input) -> iriencode(unicode:characters_to_list(Input), []). %% @doc Joins a list with a given separator. join(Input, Separator) when is_list(Input) -> join_io(Input, Separator). %% @doc Returns the last item in a list. last(Input) when is_binary(Input) -> case size(Input) of 0 -> Input; N -> Offset = N - 1, <<_:Offset/binary, Byte/binary>> = Input, Byte end; last(Input) when is_list(Input) -> [lists:last(Input)]. %% @doc Returns the length of the value. length(Input) when is_list(Input) -> integer_to_list(erlang:length(Input)); length(Input) when is_binary(Input) -> integer_to_list(size(Input)). %% @doc Returns True iff the value's length is the argument. length_is(Input, Number) when is_list(Input), is_integer(Number) -> length_is(Input, integer_to_list(Number)); length_is(Input, Number) when is_list(Input), is_list(Number) -> ?MODULE:length(Input) =:= Number. %% @doc Replaces line breaks in plain text with appropriate HTML linebreaks(Input) when is_binary(Input) -> linebreaks(unicode:characters_to_list(Input),[]); linebreaks(Input) -> linebreaks(Input,[]). linebreaks([],Acc) -> "

" ++ lists:reverse(Acc) ++ "

"; linebreaks([$\n|T], ">p<"++_ = Acc) -> linebreaks(T, Acc); linebreaks([$\r|T], ">p<"++_ = Acc) -> linebreaks(T, Acc); linebreaks([$\n, $\n|T],Acc) -> linebreaks(T, lists:reverse("

", Acc)); linebreaks([$\r, $\n, $\r, $\n|T],Acc) -> linebreaks(T, lists:reverse("

", Acc)); linebreaks([$\r, $\n|T], Acc) -> linebreaks(T, lists:reverse("
", Acc)); linebreaks([$\n|T], Acc) -> linebreaks(T, lists:reverse("
", Acc)); linebreaks([C|T], Acc) -> linebreaks(T, [C|Acc]). %% @doc Converts all newlines to HTML line breaks. linebreaksbr(Input) when is_binary(Input) -> linebreaksbr(Input, 0); linebreaksbr(Input) -> linebreaksbr(Input, []). %% @doc Displays text with line numbers. linenumbers(Input) when is_binary(Input) -> linenumbers(unicode:characters_to_list(Input)); linenumbers(Input) when is_list(Input) -> linenumbers_io(Input, [], 1). linenumbers_io([], Acc, _) -> lists:reverse(Acc); linenumbers_io(Input, [], LineNumber) -> linenumbers_io(Input, lists:reverse(integer_to_list(LineNumber)++". "), LineNumber + 1); linenumbers_io("\n"++Rest, Acc, LineNumber) -> linenumbers_io(Rest, lists:reverse("\n" ++ integer_to_list(LineNumber) ++ ". ", Acc), LineNumber + 1); linenumbers_io([H|T], Acc, LineNumber) -> linenumbers_io(T, [H|Acc], LineNumber). %% @doc Left-aligns the value in a field of a given width. ljust(Input, Number) when is_binary(Input) -> unicode:characters_to_binary(ljust(unicode:characters_to_list(Input), Number)); ljust(Input, Number) when is_list(Input) -> string:left(Input, Number). %% @doc Converts a string into all lowercase. lower(Input) when is_binary(Input) -> lower(Input, 0); lower(Input) -> string:to_lower(Input). %% @doc Returns the value turned into a list. For an integer, it's a list of digits. %% For a string, it's a list of characters. %% Added this for DTL compatibility, but since strings are lists in Erlang, no need for this. make_list(Input) when is_binary(Input) -> make_list(unicode:characters_to_list(Input)); make_list(Input) -> unjoin(Input,""). %% @doc Converts a phone number (possibly containing letters) to its numerical equivalent. phone2numeric(Input) when is_binary(Input) -> phone2numeric(unicode:characters_to_list(Input)); phone2numeric(Input) when is_list(Input) -> phone2numeric(Input, []). %% @doc Returns a plural suffix if the value is not 1. By default, this suffix is 's'. pluralize(Number, Suffix) when is_binary(Suffix) -> pluralize_io(Number, unicode:characters_to_list(Suffix) ); pluralize(Number, Suffix) when is_list(Suffix) -> pluralize_io(Number, Suffix). pluralize(Number) -> pluralize(Number, "s"). pluralize_io(Number, Suffix) -> [Singular, Plural] = case string:tokens(Suffix,",") of [P] -> ["", P]; [S, P|_] -> [S, P] end, if Number == 1; Number == "1"; Number == <<"1">> -> Singular; true -> Plural end. %% @doc "pretty print" arbitrary data structures. Used for debugging. pprint(Input) -> io_lib:format("~p",[Input]). %% @doc Returns a random item from the given list. random(Input) when is_list(Input) -> lists:nth(uniform(erlang:length(Input)), Input); random(_) -> "". random_num(Value) -> seed(erlang:phash2([node()]), monotonic_time(), unique_integer()), uniform(Value). %% random tags to be used when using erlydtl in testing random_range(Range) -> [Start, End] = string:tokens(Range,","), %?debugFmt("Start, End: ~p,~p~n",[Start,End]), random_range(cast_to_integer(Start),cast_to_integer(End)). random_range(Start, End) when End >= Start -> %?debugFmt("Input, Start, End: ~p,~p,~p~n",[Input,Start,End]), Range = End - Start, Rand = uniform(Range), Num = Rand + Start, lists:flatten(io_lib:format("~B",[Num])). removetags(Input, Tags) when is_binary(Input) -> removetags(unicode:characters_to_list(Input), Tags); removetags(Input, Tags) when is_binary(Tags) -> removetags(Input, unicode:characters_to_list(Tags)); removetags(Input, Tags) -> TagList = string:tokens(Tags," "), TagListString = string:join(TagList,"|"), Regex = lists:flatten(io_lib:format("",[TagListString])), Result = re:replace(Input,Regex,"", [global,{return,list}]), Result. %% @doc Right-aligns the value in a field of a given width. rjust(Input, Number) when is_binary(Input) -> unicode:characters_to_binary(rjust(unicode:characters_to_list(Input), Number)); rjust(Input, Number) -> string:right(Input, Number). %% @doc Returns a slice of the list. slice(Input, Index) when is_binary(Input) -> erlydtl_slice:slice(unicode:characters_to_list(Input), Index); slice(Input, Index) when is_list(Input) -> erlydtl_slice:slice(Input, Index). %% regex " ^([#0-\s+].)([0-9\*]+)(\.[0-9]+)([diouxXeEfFgGcrs]) " matches ALL of "-10.0f" %% ([#0-\s+]?)([0-9\*]+)?(\.?)([0-9]?)([diouxXeEfFgGcrs]) %% @doc Returns a formatted string stringformat(Input, Conversion) when is_binary(Input) -> stringformat(unicode:characters_to_list(Input), Conversion); stringformat(Input, Conversion) when is_binary(Conversion) -> stringformat(Input, unicode:characters_to_list(Conversion)); stringformat(Input, Conversion) -> ParsedConversion = re:replace(Conversion, "([\-#\+ ]?)([0-9\*]+)?(\.?)([0-9]?)([diouxXeEfFgGcrs])", "\\1 ,\\2 ,\\3 ,\\4 ,\\5 ", [{return,list}]), ?debugFmt("ParsedConversion: ~p~n", [ParsedConversion]), ParsedConversion1 = lists:map(fun(X) -> string:strip(X) end, string:tokens(ParsedConversion, ",")), [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType] = ParsedConversion1, ?debugFmt("ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType: ~p, ~p, ~p, ~p, ~p ~n", [ConversionFlag, cast_to_integer(MinFieldWidth), Precision, cast_to_integer(PrecisionLength), ConversionType]), String = stringformat_io(Input, Conversion, ConversionFlag, cast_to_integer(MinFieldWidth), Precision, cast_to_integer(PrecisionLength), ConversionType), lists:flatten(String). %% @doc %% A conversion specifier contains two or more characters and has the following components, which must occur in this order: %% %% 1. The "%" character, which marks the start of the specifier. %% 2. Mapping key (optional), consisting of a parenthesised sequence of characters (for example, (somename)). %% 3. Conversion flags (optional), which affect the result of some conversion types. %% 4. Minimum field width (optional). If specified as an "*" (asterisk), the actual width is read from the next element of the tuple in values, and the object to convert comes after the minimum field width and optional precision. %% 5. Precision (optional), given as a "." (dot) followed by the precision. If specified as "*" (an asterisk), the actual width is read from the next element of the tuple in values, and the value to convert comes after the precision. %% 6. Length modifier (optional). %% 7. Conversion type. stringformat_io(Input, _Conversion, _ConversionFlag, [], _Precision, _PrecisionLength, "s") when is_list(Input) -> Format = lists:flatten(io_lib:format("~~s", [])), io_lib:format(Format, [Input]); stringformat_io(Input, _Conversion, ConversionFlag, MinFieldWidth, _Precision, _PrecisionLength, "s") when is_list(Input) -> %Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType], InputLength = erlang:length(Input), case erlang:abs(MinFieldWidth) < InputLength of true -> MFW = InputLength; false -> MFW = MinFieldWidth end, Format = lists:flatten(io_lib:format("~~~s~ps", [ConversionFlag,MFW])), io_lib:format(Format, [Input]); stringformat_io(Input, _Conversion, _ConversionFlag, MinFieldWidth, Precision, PrecisionLength, "f") when Precision == ".", MinFieldWidth == 0 -> Conversion1 = lists:concat(["","",Precision,PrecisionLength,"f"]), stringformat_io(Input, Conversion1, [], [], Precision, PrecisionLength, "f"); stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth, Precision, "", "f") when Precision == "." -> Format = re:replace(Conversion, "f", "d", [{return, list}] ), stringformat_io(Input, Format, ConversionFlag, MinFieldWidth, Precision, "", "d"); stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth, _Precision, _PrecisionLength, "f")-> %Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType], Format = "~" ++ Conversion, io_lib:format(Format, [cast_to_float(Input)]); stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth, [], [], "d")-> %?debugMsg("plain d"), %Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType], Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ), io_lib:format(Format, [cast_to_integer(Input)]); stringformat_io(Input, _Conversion, "-", MinFieldWidth, _Precision, PrecisionLength, "d") when MinFieldWidth > 0 -> %Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ), DecimalFormat = "~" ++ integer_to_list(PrecisionLength) ++ "..0B", Decimal = lists:flatten( io_lib:format(DecimalFormat, [cast_to_integer(Input)]) ), SpaceFormat = "~" ++ integer_to_list(MinFieldWidth - erlang:length(Decimal)) ++ "s", Spaces = io_lib:format(SpaceFormat,[""]), ?debugFmt("Spaces: |~s|", [Spaces]), ?debugFmt("Decimal: ~s", [Decimal]), [lists:flatten(Decimal ++ Spaces)]; stringformat_io(Input, _Conversion, _ConversionFlag, MinFieldWidth, _Precision, PrecisionLength, "d") when MinFieldWidth > 0 -> %Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ), DecimalFormat = "~" ++ integer_to_list(PrecisionLength) ++ "..0B", Decimal = lists:flatten( io_lib:format(DecimalFormat, [cast_to_integer(Input)]) ), SpaceFormat = "~" ++ integer_to_list(MinFieldWidth - erlang:length(Decimal)) ++ "s", Spaces = io_lib:format(SpaceFormat,[""]), ?debugFmt("Spaces: |~s|", [Spaces]), ?debugFmt("Decimal: ~s", [Decimal]), [lists:flatten(Spaces ++ Decimal)]; stringformat_io(Input, _Conversion, _ConversionFlag, _MinFieldWidth, _Precision, PrecisionLength, "d") -> %Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType], %Format = "~" ++ PrecisionLength ++ "..0" ++ re:replace(Conversion, "d", "B", [{return, list}] ), %?debugFmt("precision d, Conversion: ~p~n", [Conversion]), Format = lists:flatten("~" ++ io_lib:format("~B..0B",[PrecisionLength])), ?debugFmt("Format: ~p~n",[Format]), io_lib:format(Format, [cast_to_integer(Input)]); stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth, _Precision, _PrecisionLength, "i")-> Format = "~" ++ re:replace(Conversion, "i", "B", [{return, list}] ), io_lib:format(Format, [cast_to_integer(Input)]); stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth, _Precision, _PrecisionLength, "X")-> Format = "~" ++ re:replace(Conversion, "X", ".16B", [{return, list}] ), io_lib:format(Format, [cast_to_integer(Input)]); stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth, _Precision, _PrecisionLength, "x")-> Format = "~" ++ re:replace(Conversion, "x", ".16b", [{return, list}] ), io_lib:format(Format, [cast_to_integer(Input)]); stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth, _Precision, _PrecisionLength, "o")-> Format = "~" ++ re:replace(Conversion, "o", ".8b", [{return, list}] ), io_lib:format(Format, [cast_to_integer(Input)]); stringformat_io(Input, _Conversion, _ConversionFlag, _MinFieldWidth, Precision, PrecisionLength, "e") when is_integer(PrecisionLength), PrecisionLength >= 2-> ?debugFmt("PrecisionLength ~p~n", [PrecisionLength]), Conversion1 = lists:concat(["","",Precision,PrecisionLength + 1,"e"]), Format = "~" ++ Conversion1, io_lib:format(Format, [cast_to_float(Input)]); stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth, "", [], "e")-> Format = "~" ++ re:replace(Conversion, "e", ".6e", [{return, list}] ), Raw = lists:flatten(stringformat_io(Input, Format, ConversionFlag, MinFieldWidth, ".", 6, "e") ), %io:format("Raw: ~p~n", [Raw]), Elocate = string:rstr(Raw,"e+"), %io:format("Elocate: ~p~n", [Elocate]), String = [string:substr(Raw,1,Elocate-1) ++ "e+" ++ io_lib:format("~2..0B",[list_to_integer(string:substr(Raw,Elocate+2))])], %works till +99, then outputs "**" %io:format("String: ~p~n", [String]), String; stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth, Precision, PrecisionLength, "E")-> Format = re:replace(Conversion, "E", "e", [{return, list}] ), [Str] = stringformat_io(Input, Format, ConversionFlag, MinFieldWidth, Precision, PrecisionLength, "e"), [string:to_upper(Str)]. %% @doc Strips all [X]HTML tags. striptags(Input) when is_binary(Input) -> striptags(unicode:characters_to_list(Input)); striptags(Input) -> Regex = "(<[^>]+>)", Result = re:replace(Input,Regex,"", [global,{return,list}]), Result. cast_to_float([]) -> []; cast_to_float(Input) when is_float(Input) -> Input; cast_to_float(Input) when is_integer(Input) -> Input + 0.0; cast_to_float(Input) when is_binary(Input) -> %% be compatible with releases prior to R16B case erlang:function_exported(erlang, binary_to_float, 1) of true -> try erlang:binary_to_float(Input) catch error:_Reason -> erlang:binary_to_integer(Input) + 0.0 end; false -> cast_to_float(binary_to_list(Input)) end; cast_to_float(Input) when is_list(Input) -> try list_to_float(Input) catch error:_Reason -> list_to_integer(Input) + 0.0 end. cast_to_integer([]) -> []; cast_to_integer(Input) when is_integer(Input) -> Input; cast_to_integer(Input) when is_float(Input) -> erlang:round(Input); cast_to_integer(Input) when is_binary(Input) -> cast_to_integer(unicode:characters_to_list(Input)); cast_to_integer(Input) when is_list(Input)-> case lists:member($., Input) of true -> erlang:round(erlang:list_to_float(Input)); false -> erlang:list_to_integer(Input) end. cast_to_list(Input) when is_list(Input) -> Input; cast_to_list(Input) when is_binary(Input) -> binary_to_list(Input); cast_to_list(Input) when is_atom(Input) -> atom_to_list(Input); cast_to_list(Input) -> hd(io_lib:format("~p", [Input])). %% @doc Converts to lowercase, removes non-word characters (alphanumerics and underscores) and converts spaces to hyphens. slugify(Input) when is_binary(Input) -> slugify(unicode:characters_to_list(Input)); slugify(Input) when is_list(Input) -> slugify(Input, []). %% @doc Formats a time according to the given format. time(Input) -> date(Input, "f a"). time(Input, FormatStr) -> date(Input, FormatStr). timesince(Date) -> timesince(Date, calendar:local_time()). %%algorithm taken from django code timesince(Date,Comparison) -> Since = calendar:datetime_to_gregorian_seconds(Comparison) - calendar:datetime_to_gregorian_seconds(Date), timesince0(Since, [], 0). timesince0(_, Acc, 2) -> string:join(lists:reverse(Acc), ", "); timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_YEAR -> Years = Seconds div ?SECONDS_PER_YEAR, timesince0(Seconds rem ?SECONDS_PER_YEAR, [io_lib:format("~B ~s~s", [Years, "year", pluralize(Years)])|Acc], Terms+1); timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_MONTH -> Months = Seconds div ?SECONDS_PER_MONTH, timesince0(Seconds rem ?SECONDS_PER_MONTH, [io_lib:format("~B ~s~s", [Months, "month", pluralize(Months)])|Acc], Terms+1); timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_WEEK -> Weeks = Seconds div ?SECONDS_PER_WEEK, timesince0(Seconds rem ?SECONDS_PER_WEEK, [io_lib:format("~B ~s~s", [Weeks, "week", pluralize(Weeks)])|Acc], Terms+1); timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_DAY -> Days = Seconds div ?SECONDS_PER_DAY, timesince0(Seconds rem ?SECONDS_PER_DAY, [io_lib:format("~B ~s~s", [Days, "day", pluralize(Days)])|Acc], Terms+1); timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_HOUR -> Hours = Seconds div ?SECONDS_PER_HOUR, timesince0(Seconds rem ?SECONDS_PER_HOUR, [io_lib:format("~B ~s~s", [Hours, "hour", pluralize(Hours)])|Acc], Terms+1); timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_MINUTE -> Minutes = Seconds div ?SECONDS_PER_MINUTE, timesince0(Seconds rem ?SECONDS_PER_MINUTE,[io_lib:format("~B ~s~s", [Minutes, "minute", pluralize(Minutes)])|Acc], Terms+1); timesince0(Seconds, Acc, Terms) when Seconds >= 1 -> timesince0(0, [io_lib:format("~B ~s~s", [Seconds, "second", pluralize(Seconds)])|Acc], Terms+1); timesince0(Seconds, [], 0) when Seconds =< 0 -> timesince0(0, ["0 minutes"], 1); timesince0(0, Acc, Terms) -> timesince0(0, Acc, Terms+1). timeuntil(Date) -> timesince(calendar:local_time(),Date). timeuntil(Date,Comparison) -> timesince(Comparison,Date). %% @doc Converts a string into titlecase. title(Input) when is_binary(Input) -> title(unicode:characters_to_list(Input)); title(Input) when is_list(Input) -> title(lower(Input), []). %% @doc Truncates a string after a certain number of characters. truncatechars(Input, Max) -> truncatechars_io(cast_to_list(Input), Max, []). %% @doc Truncates a string after a certain number of words. truncatewords(_Input, Max) when Max < 0 -> ""; truncatewords(Input, Max) when is_binary(Input) -> unicode:characters_to_binary(truncatewords(unicode:characters_to_list(Input), Max)); truncatewords(Input, Max) -> truncatewords_io(cast_to_list(Input), Max, []). %% @doc Similar to truncatewords, except that it is aware of HTML tags. truncatewords_html(_Input, Max) when Max < 0 -> ""; truncatewords_html(Input, Max) when is_binary(Input) -> unicode:characters_to_binary(truncatewords_html(unicode:characters_to_list(Input), Max)); truncatewords_html(Input, Max) -> truncatewords_html_io(cast_to_list(Input), Max, [], [], text). %% @doc Recursively takes a self-nested list and returns an HTML unordered list -- WITHOUT opening and closing `

    ' tags. unordered_list(List) -> String = lists:flatten(unordered_list(List, [])), string:substr(String, 5, erlang:length(String) - 9). unordered_list([], Acc) -> ["
      ", lists:reverse(Acc), "
    "]; unordered_list([First|_] = List, []) when is_integer(First) -> "
  • "++List; unordered_list([First|Rest], Acc) when is_list(First), Rest == [] -> unordered_list(Rest, ["
  • "] ++ [unordered_list(First, []) | Acc ]) ; unordered_list([First|Rest], Acc) when is_list(First), is_integer(hd(hd(Rest))) -> unordered_list(Rest, [unordered_list(First, []) ++ "" |Acc]); unordered_list([First|Rest], Acc) when is_list(First) -> unordered_list(Rest, [unordered_list(First, [])|Acc]). %% @doc Converts a string into all uppercase. upper(Input) when is_binary(Input) -> unicode:characters_to_binary(upper(unicode:characters_to_list(Input))); upper(Input) -> string:to_upper(Input). %% @doc Escapes a value for use in a URL. urlencode(Input) -> urlencode(Input, <<"/">>). urlencode(Input, Safe) when is_binary(Input) -> urlencode_io(Input, Safe, 0); urlencode(Input, Safe) when is_list(Input) -> urlencode_io(Input, Safe, []). %% @doc Returns the number of words. wordcount(Input) when is_binary(Input) -> wordcount(unicode:characters_to_list(Input)); wordcount(Input) when is_list(Input) -> wordcount(Input, 0). %% @doc Wraps words at specified line length, uses `
    ' html tag to delimit lines wordwrap(Input, Number) when is_binary(Input) -> wordwrap(unicode:characters_to_list(Input), Number); wordwrap(Input, Number) when is_list(Input) -> wordwrap(Input, [], [], 0, Number). %% @doc Given a string mapping values for true, false and (optionally) undefined, returns one of those strings according to the value. yesno(Bool, Choices) when is_binary(Choices) -> yesno_io(Bool, Choices); yesno(Bool, Choices) when is_list(Choices) -> yesno_io(Bool, unicode:characters_to_binary(Choices)). % internal addslashes([], Acc) -> lists:reverse(Acc); addslashes([H|T], Acc) when H =:= $"; H =:= $' -> addslashes(T, [H, $\\|Acc]); addslashes([H|T], Acc) -> addslashes(T, [H|Acc]). cut([], _, Acc) -> lists:reverse(Acc); cut([H|T], H, Acc) -> cut(T, H, Acc); cut([H|T], Char, Acc) -> cut(T, Char, [H|Acc]). escape(Binary, Index) when is_binary(Binary) -> case Binary of <> -> process_binary_match(Pre, <<"<">>, size(Post), escape(Post, 0)); <, Post/binary>> -> process_binary_match(Pre, <<">">>, size(Post), escape(Post, 0)); <> -> process_binary_match(Pre, <<"&">>, size(Post), escape(Post, 0)); <> -> process_binary_match(Pre, <<""">>, size(Post), escape(Post, 0)); <> -> process_binary_match(Pre, <<"'">>, size(Post), escape(Post, 0)); <<_:Index/binary, _, _/binary>> -> escape(Binary, Index + 1); Binary -> Binary end; escape([], Acc) -> lists:reverse(Acc); escape("<" ++ Rest, Acc) -> escape(Rest, lists:reverse("<", Acc)); escape(">" ++ Rest, Acc) -> escape(Rest, lists:reverse(">", Acc)); escape("&" ++ Rest, Acc) -> escape(Rest, lists:reverse("&", Acc)); escape("\"" ++ Rest, Acc) -> escape(Rest, lists:reverse(""", Acc)); escape("'" ++ Rest, Acc) -> escape(Rest, lists:reverse("'", Acc)); escape([S | Rest], Acc) when is_list(S); is_binary(S)-> escape(Rest, [force_escape(S) | Acc]); escape([C | Rest], Acc) -> escape(Rest, [C | Acc]). escapejs([], Acc) -> lists:reverse(Acc); escapejs([C | Rest], Acc) when C < 32; C =:= $"; C =:= $'; C =:= $\\; C =:= $<; C =:= $>; C =:= $&; C =:= $=; C =:= $-; C =:= $;; C =:= 8232; C =:= 8233 -> % just following django here... escapejs(Rest, lists:reverse(lists:flatten(io_lib:format("\\u~4.16.0B", [C])), Acc)); escapejs([C | Rest], Acc) -> escapejs(Rest, [C | Acc]). filesizeformat(Bytes, UnitStr) -> lists:flatten(io_lib:format("~.1f ~s", [Bytes, UnitStr])). fix_ampersands(Input, Index) when is_binary(Input) -> case Input of <> -> process_binary_match(Pre, <<"&">>, size(Post), Post); <<_:Index/binary, _/binary>> -> fix_ampersands(Input, Index + 1); _ -> Input end; fix_ampersands([], Acc) -> lists:reverse(Acc); fix_ampersands("&" ++ Rest, Acc) -> fix_ampersands(Rest, lists:reverse("&", Acc)); fix_ampersands([C | Rest], Acc) -> fix_ampersands(Rest, [C | Acc]). iriencode([], Acc) -> lists:reverse(Acc); iriencode([C | Rest], Acc) when ?NO_IRI_ENCODE(C) -> iriencode(Rest, [C | Acc]); iriencode([$\s | Rest], Acc) -> iriencode(Rest, [$+ | Acc]); iriencode([C | Rest], Acc) -> <> = <>, iriencode(Rest, [hexdigit(Lo), hexdigit(Hi), $\% | Acc]). join_io([], _Sep) -> []; join_io([X], _Sep) -> [format_number(X)]; join_io([X|T], Sep) -> [format_number(X),Sep] ++ join_io(T, Sep). linebreaksbr(Input, Index) when is_binary(Input) -> Break = <<"
    ">>, case Input of <> -> process_binary_match(Pre, Break, size(Post), linebreaksbr(Post, 0)); <> -> process_binary_match(Pre, Break, size(Post), linebreaksbr(Post, 0)); <<_:Index/binary, _/binary>> -> linebreaksbr(Input, Index + 1); _ -> Input end; linebreaksbr([], Acc) -> lists:reverse(Acc); linebreaksbr("\r\n" ++ Rest, Acc) -> linebreaksbr(Rest, lists:reverse("
    ", Acc)); linebreaksbr("\n" ++ Rest, Acc) -> linebreaksbr(Rest, lists:reverse("
    ", Acc)); linebreaksbr([C | Rest], Acc) -> linebreaksbr(Rest, [C | Acc]). lower(Input, Index) -> case Input of <> when Byte >= $A andalso Byte =< $Z -> process_binary_match(Pre, <<(Byte - $A + $a)>>, size(Post), lower(Post, 0)); <<_:Index/binary, _/binary>> -> lower(Input, Index + 1); _ -> Input end. phone2numeric([], Acc) -> lists:reverse(Acc); phone2numeric([H|T], Acc) when H >= $a, H =< $c; H >= $A, H =< $C -> phone2numeric(T, [$2|Acc]); phone2numeric([H|T], Acc) when H >= $d, H =< $f; H >= $D, H =< $F -> phone2numeric(T, [$3|Acc]); phone2numeric([H|T], Acc) when H >= $g, H =< $i; H >= $G, H =< $I -> phone2numeric(T, [$4|Acc]); phone2numeric([H|T], Acc) when H >= $j, H =< $l; H >= $J, H =< $L -> phone2numeric(T, [$5|Acc]); phone2numeric([H|T], Acc) when H >= $m, H =< $o; H >= $M, H =< $O -> phone2numeric(T, [$6|Acc]); phone2numeric([H|T], Acc) when H >= $p, H =< $s; H >= $P, H =< $S -> phone2numeric(T, [$7|Acc]); phone2numeric([H|T], Acc) when H >= $t, H =< $v; H >= $T, H =< $V -> phone2numeric(T, [$8|Acc]); phone2numeric([H|T], Acc) when H >= $w, H =< $z; H >= $W, H =< $Z -> phone2numeric(T, [$9|Acc]); phone2numeric([H|T], Acc) -> phone2numeric(T, [H|Acc]). slugify([], Acc) -> lists:reverse(Acc); slugify([H|T], Acc) when H >= $A, H =< $Z -> slugify(T, [H-$A+$a|Acc]); slugify([$\ |T], Acc) -> slugify(T, [$-|Acc]); slugify([H|T], Acc) when H >= $a, H =< $z; H >= $0, H =< $9; H =:= $_ -> slugify(T, [H|Acc]); slugify([_|T], Acc) -> slugify(T, Acc). title([], Acc) -> lists:reverse(Acc); title([Char | Rest], [] = Acc) when Char >= $a, Char =< $z -> title(Rest, [Char + ($A - $a) | Acc]); title([Char | Rest], [Sep|[Sep2|_Other]] = Acc) when Char >= $a, Char =< $z, not (Sep >= $a andalso Sep =< $z), not (Sep >= $A andalso Sep =< $Z), not (Sep >= $0 andalso Sep =< $9), not (Sep =:= $' andalso (Sep2 >= $a andalso Sep2 =< $z)) -> title(Rest, [Char + ($A - $a) | Acc]); title([Char | Rest], Acc) -> title(Rest, [Char | Acc]). truncatechars_io([], _CharsLeft, Acc) -> lists:reverse(Acc); truncatechars_io(_Input, 0, Acc) -> lists:reverse("..." ++ drop_chars(Acc, 3)); truncatechars_io([C|Rest], CharsLeft, Acc) when C >= 2#11111100 -> truncatechars_io(Rest, CharsLeft + 4, [C|Acc]); truncatechars_io([C|Rest], CharsLeft, Acc) when C >= 2#11111000 -> truncatechars_io(Rest, CharsLeft + 3, [C|Acc]); truncatechars_io([C|Rest], CharsLeft, Acc) when C >= 2#11110000 -> truncatechars_io(Rest, CharsLeft + 2, [C|Acc]); truncatechars_io([C|Rest], CharsLeft, Acc) when C >= 2#11100000 -> truncatechars_io(Rest, CharsLeft + 1, [C|Acc]); truncatechars_io([C|Rest], CharsLeft, Acc) when C >= 2#11000000 -> truncatechars_io(Rest, CharsLeft, [C|Acc]); truncatechars_io([C|Rest], CharsLeft, Acc) -> truncatechars_io(Rest, CharsLeft - 1, [C|Acc]). drop_chars([], _) -> []; drop_chars(Cs, 0) -> Cs; drop_chars([C|Cs], Count) when C >= 2#11111100 -> drop_chars(Cs, Count + 4); drop_chars([C|Cs], Count) when C >= 2#11111000 -> drop_chars(Cs, Count + 3); drop_chars([C|Cs], Count) when C >= 2#11110000 -> drop_chars(Cs, Count + 2); drop_chars([C|Cs], Count) when C >= 2#11100000 -> drop_chars(Cs, Count + 1); drop_chars([C|Cs], Count) when C >= 2#11000000 -> drop_chars(Cs, Count); drop_chars([_|Cs], Count) -> drop_chars(Cs, Count - 1). truncatewords_io([], _WordsLeft, Acc) -> lists:reverse(Acc); truncatewords_io(_Input, 0, Acc) -> lists:reverse("... " ++ Acc); truncatewords_io([C1, C2|Rest], WordsLeft, Acc) when C1 =/= $\s andalso C2 =:= $\s -> truncatewords_io([C2|Rest], WordsLeft - 1, [C1|Acc]); truncatewords_io([C1|Rest], WordsLeft, Acc) -> truncatewords_io(Rest, WordsLeft, [C1|Acc]). truncatewords_html_io([], _WordsLeft, Acc, [], _) -> lists:reverse(Acc); truncatewords_html_io(_Input, 0, Acc, [], _) -> lists:reverse(Acc); truncatewords_html_io(Input, 0, Acc, [Tag|RestOfTags], done) -> truncatewords_html_io(Input, 0, ">"++Tag++"/<" ++ Acc, RestOfTags, done); truncatewords_html_io(Input, 0, Acc, [Tag|RestOfTags], _) -> truncatewords_html_io(Input, 0, "...>"++Tag++"/<" ++ Acc, RestOfTags, done); truncatewords_html_io([], WordsLeft, Acc, [Tag|RestOfTags], _) -> truncatewords_html_io([], WordsLeft, ">"++Tag++"/<" ++ Acc, RestOfTags, text); truncatewords_html_io([C|Rest], WordsLeft, Acc, Tags, text) when C =:= $< -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], [""|Tags], tag); truncatewords_html_io([C1, C2|Rest], WordsLeft, Acc, Tags, text) when C1 =/= $\ , C2 =:= $\ ; C1 =/= $\ , C2 =:= $< -> truncatewords_html_io([C2|Rest], WordsLeft - 1, [C1|Acc], Tags, text); truncatewords_html_io([C|Rest], WordsLeft, Acc, Tags, text) -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], Tags, text); truncatewords_html_io([C|Rest], WordsLeft, Acc, [""|Tags], tag) when C =:= $/ -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], Tags, close_tag); truncatewords_html_io([C|Rest], WordsLeft, Acc, [Tag|RestOfTags], tag) when C >= $a, C =< $z; C >= $A, C =< $Z -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], [[C|Tag]|RestOfTags], tag); truncatewords_html_io([C|Rest], WordsLeft, Acc, Tags, tag) when C =:= $> -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], Tags, text); truncatewords_html_io([C|Rest], WordsLeft, Acc, Tags, tag) -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], Tags, attrs); truncatewords_html_io([C|Rest], WordsLeft, Acc, Tags, attrs) when C =:= $> -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], Tags, text); truncatewords_html_io([C|Rest], WordsLeft, Acc, [_Tag|RestOfTags], close_tag) when C =:= $> -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], RestOfTags, text); truncatewords_html_io([C|Rest], WordsLeft, Acc, Tags, close_tag) when C =/= $> -> truncatewords_html_io(Rest, WordsLeft, [C|Acc], Tags, close_tag). wordcount([], Count) -> Count; wordcount([C1], Count) when C1 =/= $\ -> Count+1; wordcount([C1, C2|Rest], Count) when C1 =/= $\ andalso C2 =:= $\ -> wordcount([C2|Rest], Count + 1); wordcount([_|Rest], Count) -> wordcount(Rest, Count). % No more input, we're done wordwrap([], Acc, WordAcc, _LineLength, _WrapAt) -> lists:reverse(WordAcc ++ Acc); % Premature newline wordwrap([$\n | Rest], Acc, WordAcc, _LineLength, WrapAt) -> wordwrap(Rest, [$\n | WordAcc ++ Acc], [], 0, WrapAt); % Hit the wrap length at a space character. Add a newline wordwrap([$\ | Rest], Acc, WordAcc, WrapAt, WrapAt) -> wordwrap(Rest, [$\n | WordAcc ++ Acc], [], 0, WrapAt); % Hit a space character before the wrap length. Keep going wordwrap([$\ | Rest], Acc, WordAcc, LineLength, WrapAt) -> wordwrap(Rest, [$\ | WordAcc ++ Acc], [], LineLength + 1 + erlang:length(WordAcc), WrapAt); % Overflowed the current line while building a word wordwrap([C | Rest], Acc, WordAcc, 0, WrapAt) when erlang:length(WordAcc) > WrapAt -> wordwrap(Rest, Acc, [C | WordAcc], 0, WrapAt); wordwrap([C | Rest], Acc, WordAcc, LineLength, WrapAt) when erlang:length(WordAcc) + LineLength > WrapAt -> wordwrap(Rest, [$\n | Acc], [C | WordAcc], 0, WrapAt); % Just building a word... wordwrap([C | Rest], Acc, WordAcc, LineLength, WrapAt) -> wordwrap(Rest, Acc, [C | WordAcc], LineLength, WrapAt). urlencode_io(Input, Safe, Index) when is_binary(Input) -> case Input of <<_:Index/binary, Byte, _/binary>> when ?NO_ENCODE(Byte) -> urlencode_io(Input, Safe, Index + 1); <> -> process_binary_match( Pre, maybe_urlencode_char(C, Safe), size(Post), urlencode_io(Post, Safe, 0)); Input -> Input end; urlencode_io([], _Safe, Acc) -> lists:reverse(Acc); urlencode_io([C | Rest], Safe, Acc) when ?NO_ENCODE(C) -> urlencode_io(Rest, Safe, [C | Acc]); urlencode_io([C | Rest], Safe, Acc) -> urlencode_io(Rest, Safe, [maybe_urlencode_char(<>, Safe) | Acc]). maybe_urlencode_char(C, Safe) -> case binary:match(Safe, C) of nomatch -> <> = C, HiDigit = hexdigit(Hi), LoDigit = hexdigit(Lo), <<$%, HiDigit, LoDigit>>; _ -> C end. %% @doc Converts URLs in text into clickable links. %%TODO: Autoescape not yet implemented urlize(Input) when is_binary(Input) -> urlize(unicode:characters_to_list(Input),0); urlize(Input) -> urlize(Input,0). urlize(Input, Trunc) when is_binary(Input) -> urlize(unicode:characters_to_list(Input),Trunc); urlize(Input, Trunc) -> {ok,RE} = re:compile("(([[:alpha:]]+://|www\.)[^<>[:space:]]+[[:alnum:]/])"), RegexResult = re:run(Input,RE,[global]), case RegexResult of {match, Matches} -> Indexes = lists:map(fun(Match) -> lists:nth(2,Match) end, Matches), Domains = lists:map(fun({Start, Length}) -> lists:sublist(Input, Start+1, Length) end, Indexes), URIDomains = lists:map(fun(Domain) -> addDefaultURI(Domain) end, Domains), case Trunc == 0 of true -> DomainsTrunc = Domains; false -> DomainsTrunc = lists:map(fun(Domain) -> string:concat( string:substr(Domain,1,Trunc-3), "...") end, Domains) end, ReplaceList = lists:zip(URIDomains,DomainsTrunc), ReplaceStrings = lists:map(fun({URIDomain,Domain}) -> lists:flatten(io_lib:format("~s",[URIDomain,Domain])) end, ReplaceList), Template = re:replace(Input,"(([[:alpha:]]+://|www\.)[^<>[:space:]]+[[:alnum:]/])", "~s", [global,{return,list}]), Result = lists:flatten(io_lib:format(Template,ReplaceStrings)), Result; nomatch -> Input end. %% @doc Converts URLs into clickable links just like urlize, but truncates URLs longer than the given character limit. urlizetrunc(Input, Trunc) -> urlize(Input, Trunc). addDefaultURI(Domain) -> case string:str(Domain,"://") of 0 -> Domain1 = string:concat("http://",Domain); _ -> Domain1 = Domain end, Domain1. hexdigit(C) when C < 10 -> $0 + C; hexdigit(C) when C < 16 -> $A + (C - 10). process_binary_match(Pre, Insertion, SizePost, Post) -> case {size(Pre), SizePost} of {0, 0} -> Insertion; {0, _} -> [Insertion, Post]; {_, 0} -> [Pre, Insertion]; _ -> [Pre, Insertion, Post] end. yesno_io(Val, Choices) -> {True, False, Undefined} = case binary:split(Choices, <<",">>, [global]) of [T, F, U] -> {T, F, U}; [T, F] -> {T, F, F}; _ -> throw({yesno, choices}) end, if Val =:= false -> False; Val =:= undefined -> Undefined; is_list(Val); is_binary(Val) -> case iolist_size(Val) of 0 -> False; _ -> True end; true -> True end. %% unjoin == split in other languages; inverse of join %%FROM: http://www.erlang.org/pipermail/erlang-questions/2008-October/038896.html unjoin(String, []) -> unjoin0(String); unjoin(String, [Sep]) when is_integer(Sep) -> unjoin1(String, Sep); unjoin(String, [C1,C2|L]) when is_integer(C1), is_integer(C2) -> unjoin2(String, C1, C2, L). %% Split a string at "", which is deemed to occur _between_ %% adjacent characters, but queerly, not at the beginning %% or the end. unjoin0([C|Cs]) -> [[C] | unjoin0(Cs)]; unjoin0([]) -> []. %% Split a string at a single character separator. unjoin1(String, Sep) -> unjoin1_loop(String, Sep, ""). unjoin1_loop([Sep|String], Sep, Rev) -> [lists:reverse(Rev) | unjoin1(String, Sep)]; unjoin1_loop([Chr|String], Sep, Rev) -> unjoin1_loop(String, Sep, [Chr|Rev]); unjoin1_loop([], _, Rev) -> [lists:reverse(Rev)]. %% Split a string at a multi-character separator %% [C1,C2|L]. These components are split out for %% a fast match. unjoin2(String, C1, C2, L) -> unjoin2_loop(String, C1, C2, L, ""). unjoin2_loop([C1|S = [C2|String]], C1, C2, L, Rev) -> case unjoin_prefix(L, String) of no -> unjoin2_loop(S, C1, C2, L, [C1|Rev]) ; Rest -> [lists:reverse(Rev) | unjoin2(Rest, C1, C2, L)] end; unjoin2_loop([Chr|String], C1, C2, L, Rev) -> unjoin2_loop(String, C1, C2, L, [Chr|Rev]); unjoin2_loop([], _, _, _, Rev) -> [lists:reverse(Rev)]. unjoin_prefix([C|L], [C|S]) -> unjoin_prefix(L, S); unjoin_prefix([], S) -> S; unjoin_prefix(_, _) -> no. %% random compatibility %% Credits: https://github.com/benoitc/hackney/blob/master/src/hackney_util.erl uniform(N) -> case have_rand() of true -> rand:uniform(N); false -> (fun random:uniform/1)(N) end. seed(A0, A1, A2) -> case have_rand() of true -> rand:seed(exsplus, {A0, A1, A2}); false -> (fun random:seed/3)(A0, A1, A2) end. have_rand() -> (code:which(rand) /= non_existing). erlydtl-0.15.0/src/erlydtl_library.erl000066400000000000000000000040211504712431400177500ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_library.erl %%% @author Andreas Stenius %%% @copyright 2014 Andreas Stenius %%% @doc %%% ErlyDTL library behaviour. %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2014 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2014 by Andreas Stenius %%%------------------------------------------------------------------- -module(erlydtl_library). -author('Andreas Stenius '). %% -------------------------------------------------------------------- %% Definitions %% -------------------------------------------------------------------- -type exported_fun() :: Name::atom(). -type external_fun() :: {Module::atom(), exported_fun()}. -type library_function() :: exported_fun() | external_fun(). -type inventory_item() :: exported_fun() | {Name::atom(), library_function()}. -callback inventory(filters|tags) -> list(inventory_item()). -callback version() -> 1. erlydtl-0.15.0/src/erlydtl_parser.yrl000066400000000000000000000400441504712431400176310ustar00rootroot00000000000000%%% -*- mode: erlang -*- ------------------------------------------------------------------ %%% File: erlydtl_parser.erl %%% @author Roberto Saccon [http://rsaccon.com] %%% @author Evan Miller %%% @copyright 2008 Roberto Saccon, Evan Miller %%% @doc Template language grammar %%% @reference See http://erlydtl.googlecode.com for more information %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2007 Roberto Saccon, Evan Miller %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2007-11-11 by Roberto Saccon, Evan Miller %%%------------------------------------------------------------------- Nonterminals Elements Literal ValueBraced Value Values Variable Filter FilterArg AutoEscapeBlock AutoEscapeBraced EndAutoEscapeBraced BlockBlock BlockBraced EndBlockBraced CommentTag CommentBlock CommentBraced EndCommentBraced CycleAs CycleTag CycleNames CycleNamesCompat ExtendsTag IncludeTag NowTag LanguageBlock LanguageBraced EndLanguageBraced FirstofTag FilterBlock FilterBraced EndFilterBraced Filters ForBlock ForBraced EmptyBraced EndForBraced ForExpression ForGroup IfBlock IfBraced ElifBlock ElifBraced IfExpression ElseBraced EndIfBraced IfChangedBlock IfChangedBraced EndIfChangedBraced IfEqualBlock IfEqualBraced IfEqualExpression EndIfEqualBraced IfNotEqualBlock IfNotEqualBraced IfNotEqualExpression EndIfNotEqualBraced CustomTag CustomArgs Arg Args RegroupTag SpacelessBlock SSITag BlockTransBlock BlockTransBraced EndBlockTransBraced BlockTransArgs BlockTransContents PluralTag TransTag TransArgs TransText TransValue TemplatetagTag Templatetag WidthRatioTag WithBlock WithBraced EndWithBraced CallTag CallWithTag LoadTag LoadArgs Unot. Terminals and_keyword as_keyword autoescape_keyword block_keyword blocktrans_keyword by_keyword call_keyword close_tag close_var comment_tag comment_keyword context_keyword count_keyword cycle_keyword elif_keyword else_keyword empty_keyword endautoescape_keyword endblock_keyword endblocktrans_keyword endcomment_keyword endfilter_keyword endfor_keyword endif_keyword endifchanged_keyword endifequal_keyword endifnotequal_keyword endlanguage_keyword endregroup_keyword endspaceless_keyword endwith_keyword extends_keyword filter_keyword firstof_keyword for_keyword from_keyword identifier if_keyword ifchanged_keyword ifequal_keyword ifnotequal_keyword in_keyword include_keyword language_keyword load_keyword noop_keyword not_keyword now_keyword number_literal only_keyword or_keyword open_tag open_var parsed_keyword plural_keyword regroup_keyword reversed_keyword silent_keyword spaceless_keyword ssi_keyword string_literal string templatetag_keyword trimmed_keyword openblock_keyword closeblock_keyword openvariable_keyword closevariable_keyword openbrace_keyword closebrace_keyword opencomment_keyword closecomment_keyword trans_keyword widthratio_keyword with_keyword ',' '|' '=' ':' '.' '==' '!=' '>=' '<=' '>' '<' '(' ')' '_'. Rootsymbol Elements. %% Operator precedences for the E non terminal Left 100 or_keyword. Left 110 and_keyword. Nonassoc 300 '==' '!=' '>=' '<=' '>' '<'. Unary 600 Unot. Elements -> '$empty' : []. Elements -> Elements string : '$1' ++ ['$2']. Elements -> Elements AutoEscapeBlock : '$1' ++ ['$2']. Elements -> Elements BlockBlock : '$1' ++ ['$2']. Elements -> Elements BlockTransBlock : '$1' ++ ['$2']. Elements -> Elements CallTag : '$1' ++ ['$2']. Elements -> Elements CallWithTag : '$1' ++ ['$2']. Elements -> Elements CommentBlock : '$1' ++ ['$2']. Elements -> Elements CommentTag : '$1' ++ ['$2']. Elements -> Elements CustomTag : '$1' ++ ['$2']. Elements -> Elements CycleTag : '$1' ++ ['$2']. Elements -> Elements ExtendsTag : '$1' ++ ['$2']. Elements -> Elements FilterBlock : '$1' ++ ['$2']. Elements -> Elements FirstofTag : '$1' ++ ['$2']. Elements -> Elements ForBlock : '$1' ++ ['$2']. Elements -> Elements IfBlock : '$1' ++ ['$2']. Elements -> Elements IfEqualBlock : '$1' ++ ['$2']. Elements -> Elements IfNotEqualBlock : '$1' ++ ['$2']. Elements -> Elements IfChangedBlock : '$1' ++ ['$2']. Elements -> Elements IncludeTag : '$1' ++ ['$2']. Elements -> Elements LanguageBlock : '$1' ++ ['$2']. Elements -> Elements LoadTag : '$1' ++ ['$2']. Elements -> Elements NowTag : '$1' ++ ['$2']. Elements -> Elements RegroupTag : '$1' ++ ['$2']. Elements -> Elements SpacelessBlock : '$1' ++ ['$2']. Elements -> Elements SSITag : '$1' ++ ['$2']. Elements -> Elements TemplatetagTag : '$1' ++ ['$2']. Elements -> Elements TransTag : '$1' ++ ['$2']. Elements -> Elements ValueBraced : '$1' ++ ['$2']. Elements -> Elements WidthRatioTag : '$1' ++ ['$2']. Elements -> Elements WithBlock : '$1' ++ ['$2']. ValueBraced -> open_var Value close_var : '$2'. Value -> Value '|' Filter : {apply_filter, '$1', '$3'}. Value -> '_' '(' Value ')' : {trans, '$3'}. Value -> Variable : '$1'. Value -> Literal : '$1'. Values -> Value : ['$1']. Values -> Value Values : ['$1'|'$2']. Filter -> identifier FilterArg : {'$1', '$2'}. FilterArg -> '$empty' : []. FilterArg -> ':' Variable : ['$2']. FilterArg -> ':' Literal : ['$2']. Variable -> identifier : {variable, '$1'}. Variable -> Variable '.' identifier : {attribute, {'$3', '$1'}}. Variable -> Variable '.' Literal : {attribute, {'$3', '$1'}}. Literal -> string_literal : '$1'. Literal -> number_literal : '$1'. AutoEscapeBlock -> AutoEscapeBraced Elements EndAutoEscapeBraced : {autoescape, '$1', '$2'}. AutoEscapeBraced -> open_tag autoescape_keyword identifier close_tag : '$3'. EndAutoEscapeBraced -> open_tag endautoescape_keyword close_tag. BlockBlock -> BlockBraced Elements EndBlockBraced : {block, '$1', '$2'}. BlockBraced -> open_tag block_keyword identifier close_tag : '$3'. EndBlockBraced -> open_tag endblock_keyword close_tag. LanguageBlock -> LanguageBraced Elements EndLanguageBraced : {language, '$1', '$2'}. LanguageBraced -> open_tag language_keyword Value close_tag : '$3'. EndLanguageBraced -> open_tag endlanguage_keyword close_tag. ExtendsTag -> open_tag extends_keyword string_literal close_tag : {extends, '$3'}. IncludeTag -> open_tag include_keyword string_literal close_tag : {include, '$3', []}. IncludeTag -> open_tag include_keyword string_literal with_keyword Args close_tag : {include, '$3', '$5'}. IncludeTag -> open_tag include_keyword string_literal only_keyword close_tag : {include_only, '$3', []}. IncludeTag -> open_tag include_keyword string_literal with_keyword Args only_keyword close_tag : {include_only, '$3', '$5'}. LoadTag -> open_tag load_keyword LoadArgs close_tag : {load_libs, '$3'}. LoadTag -> open_tag load_keyword LoadArgs from_keyword identifier close_tag : {load_from_lib, '$3', '$5'}. LoadArgs -> '$empty' : []. LoadArgs -> identifier LoadArgs : ['$1'|'$2']. NowTag -> open_tag now_keyword string_literal close_tag : {date, now, '$3'}. CommentBlock -> CommentBraced Elements EndCommentBraced : {comment, '$2'}. CommentBraced -> open_tag comment_keyword close_tag. EndCommentBraced -> open_tag endcomment_keyword close_tag. CommentTag -> comment_tag : '$1'. CycleTag -> open_tag cycle_keyword CycleNamesCompat close_tag : {cycle_compat, '$3'}. CycleTag -> open_tag cycle_keyword CycleNames CycleAs close_tag : {cycle, '$3', '$4'}. CycleNames -> Value : ['$1']. CycleNames -> CycleNames Value : '$1' ++ ['$2']. CycleAs -> '$empty' : undefined. CycleAs -> as_keyword identifier : ['$2']. CycleAs -> as_keyword identifier silent_keyword : ['$2', silent]. CycleNamesCompat -> identifier ',' : ['$1']. CycleNamesCompat -> CycleNamesCompat identifier ',' : '$1' ++ ['$2']. CycleNamesCompat -> CycleNamesCompat identifier : '$1' ++ ['$2']. FilterBlock -> FilterBraced Elements EndFilterBraced : {filter, '$1', '$2'}. FilterBraced -> open_tag filter_keyword Filters close_tag : '$3'. EndFilterBraced -> open_tag endfilter_keyword close_tag. Filters -> Filter : ['$1']. Filters -> Filter '|' Filters : ['$1'|'$3']. FirstofTag -> open_tag firstof_keyword Values close_tag : {firstof, '$3'}. ForBlock -> ForBraced Elements EndForBraced : {for, '$1', '$2'}. ForBlock -> ForBraced Elements EmptyBraced Elements EndForBraced : {for, '$1', '$2', '$4'}. EmptyBraced -> open_tag empty_keyword close_tag. ForBraced -> open_tag for_keyword ForExpression close_tag : '$3'. EndForBraced -> open_tag endfor_keyword close_tag. ForExpression -> ForGroup in_keyword Value : {'in', '$1', '$3', false}. ForExpression -> ForGroup in_keyword Value reversed_keyword : {'in', '$1', '$3', true}. ForGroup -> identifier : ['$1']. ForGroup -> ForGroup ',' identifier : '$1' ++ ['$3']. IfBlock -> IfBraced Elements ElseBraced Elements EndIfBraced : {'ifelse', '$1', '$2', '$4'}. IfBlock -> IfBraced Elements EndIfBraced : {'if', '$1', '$2'}. IfBlock -> IfBraced Elements ElifBlock : {'if', '$1', '$2', ['$3']}. ElifBlock -> ElifBraced Elements ElseBraced Elements EndIfBraced : {'ifelse', '$1', '$2', '$4'}. ElifBlock -> ElifBraced Elements EndIfBraced : {'if', '$1', '$2'}. ElifBlock -> ElifBraced Elements ElifBlock : {'if', '$1', '$2', ['$3']}. IfBraced -> open_tag if_keyword IfExpression close_tag : '$3'. ElifBraced -> open_tag elif_keyword IfExpression close_tag : '$3'. IfExpression -> Value in_keyword Value : {'expr', "in", '$1', '$3'}. IfExpression -> Value not_keyword in_keyword Value : {'expr', "not", {'expr', "in", '$1', '$4'}}. IfExpression -> Value '==' Value : {'expr', "eq", '$1', '$3'}. IfExpression -> Value '!=' Value : {'expr', "ne", '$1', '$3'}. IfExpression -> Value '>=' Value : {'expr', "ge", '$1', '$3'}. IfExpression -> Value '<=' Value : {'expr', "le", '$1', '$3'}. IfExpression -> Value '>' Value : {'expr', "gt", '$1', '$3'}. IfExpression -> Value '<' Value : {'expr', "lt", '$1', '$3'}. IfExpression -> '(' IfExpression ')' : '$2'. IfExpression -> Unot : '$1'. IfExpression -> IfExpression or_keyword IfExpression : {'expr', "or", '$1', '$3'}. IfExpression -> IfExpression and_keyword IfExpression : {'expr', "and", '$1', '$3'}. IfExpression -> Value : '$1'. Unot -> not_keyword IfExpression : {expr, "not", '$2'}. ElseBraced -> open_tag else_keyword close_tag. EndIfBraced -> open_tag endif_keyword close_tag. IfChangedBlock -> IfChangedBraced Elements ElseBraced Elements EndIfChangedBraced : {ifchangedelse, '$1', '$2', '$4'}. IfChangedBlock -> IfChangedBraced Elements EndIfChangedBraced : {ifchanged, '$1', '$2'}. IfChangedBraced -> open_tag ifchanged_keyword close_tag. IfChangedBraced -> open_tag ifchanged_keyword Values close_tag : '$3'. EndIfChangedBraced -> open_tag endifchanged_keyword close_tag. IfEqualBlock -> IfEqualBraced Elements ElseBraced Elements EndIfEqualBraced : {ifequalelse, '$1', '$2', '$4'}. IfEqualBlock -> IfEqualBraced Elements EndIfEqualBraced : {ifequal, '$1', '$2'}. IfEqualBraced -> open_tag ifequal_keyword IfEqualExpression Value close_tag : ['$3', '$4']. IfEqualExpression -> Value : '$1'. EndIfEqualBraced -> open_tag endifequal_keyword close_tag. IfNotEqualBlock -> IfNotEqualBraced Elements ElseBraced Elements EndIfNotEqualBraced : {ifnotequalelse, '$1', '$2', '$4'}. IfNotEqualBlock -> IfNotEqualBraced Elements EndIfNotEqualBraced : {ifnotequal, '$1', '$2'}. IfNotEqualBraced -> open_tag ifnotequal_keyword IfNotEqualExpression Value close_tag : ['$3', '$4']. IfNotEqualExpression -> Value : '$1'. EndIfNotEqualBraced -> open_tag endifnotequal_keyword close_tag. RegroupTag -> open_tag regroup_keyword Value by_keyword Value as_keyword identifier close_tag : {regroup, {'$3', '$5', '$7'}}. RegroupTag -> open_tag endregroup_keyword close_tag : end_regroup. SpacelessBlock -> open_tag spaceless_keyword close_tag Elements open_tag endspaceless_keyword close_tag : {spaceless, '$4'}. SSITag -> open_tag ssi_keyword Value close_tag : {ssi, '$3'}. SSITag -> open_tag ssi_keyword string_literal parsed_keyword close_tag : {ssi_parsed, '$3'}. BlockTransBlock -> BlockTransBraced BlockTransContents EndBlockTransBraced : {blocktrans, '$1', '$2', undefined}. BlockTransBlock -> BlockTransBraced BlockTransContents PluralTag BlockTransContents EndBlockTransBraced : {blocktrans, '$1', '$2', '$4'}. BlockTransBraced -> open_tag blocktrans_keyword BlockTransArgs close_tag : '$3'. EndBlockTransBraced -> open_tag endblocktrans_keyword close_tag. BlockTransArgs -> '$empty' : []. BlockTransArgs -> count_keyword Arg BlockTransArgs : [{count, '$2'}|'$3']. BlockTransArgs -> with_keyword Args BlockTransArgs : [{args, '$2'}|'$3']. BlockTransArgs -> context_keyword string_literal BlockTransArgs : [{context, '$2'}|'$3']. BlockTransArgs -> trimmed_keyword BlockTransArgs : [trimmed|'$2']. BlockTransContents -> '$empty' : []. BlockTransContents -> open_var identifier close_var BlockTransContents : [{variable, '$2'}|'$4']. BlockTransContents -> string BlockTransContents : ['$1'|'$2']. PluralTag -> open_tag plural_keyword close_tag. TemplatetagTag -> open_tag templatetag_keyword Templatetag close_tag : {templatetag, '$3'}. Templatetag -> openblock_keyword : '$1'. Templatetag -> closeblock_keyword : '$1'. Templatetag -> openvariable_keyword : '$1'. Templatetag -> closevariable_keyword : '$1'. Templatetag -> openbrace_keyword : '$1'. Templatetag -> closebrace_keyword : '$1'. Templatetag -> opencomment_keyword : '$1'. Templatetag -> closecomment_keyword : '$1'. TransTag -> open_tag trans_keyword TransArgs close_tag : '$3'. TransTag -> open_tag trans_keyword TransArgs as_keyword identifier close_tag : {scope_as, '$5', ['$3']}. TransArgs -> TransText : {trans, '$1'}. TransArgs -> TransText context_keyword string_literal: {trans, '$1', '$3'}. TransText -> TransValue : '$1'. TransText -> TransValue noop_keyword : {noop, '$1'}. TransValue -> string_literal : '$1'. TransValue -> Variable : '$1'. WidthRatioTag -> open_tag widthratio_keyword Value Value number_literal close_tag : {widthratio, '$3', '$4', '$5'}. WithBlock -> WithBraced Elements EndWithBraced : {with, '$1', '$2'}. WithBraced -> open_tag with_keyword Args close_tag : '$3'. EndWithBraced -> open_tag endwith_keyword close_tag. CustomTag -> open_tag identifier CustomArgs close_tag : {tag, '$2', '$3'}. CustomTag -> open_tag identifier CustomArgs as_keyword identifier close_tag : {tag, '$2', '$3', '$5'}. CustomArgs -> '$empty' : []. CustomArgs -> identifier '=' Value CustomArgs : [{'$1', '$3'}|'$4']. CustomArgs -> Value CustomArgs : ['$1'|'$2']. Args -> '$empty' : []. Args -> Arg Args : ['$1'|'$2']. Arg -> identifier '=' Value : {'$1', '$3'}. %% Arg -> identifier : {'$1', true}. CallTag -> open_tag call_keyword identifier close_tag : {call, '$3'}. CallWithTag -> open_tag call_keyword identifier with_keyword Value close_tag : {call, '$3', '$5'}. Erlang code. %% vim: syntax=erlang erlydtl-0.15.0/src/erlydtl_runtime.erl000066400000000000000000000423421504712431400177770ustar00rootroot00000000000000-module(erlydtl_runtime). -compile([export_all, nowarn_export_all]). -type text() :: string() | binary(). -type phrase() :: text() | {text(), {PluralPhrase::text(), non_neg_integer()}}. -type locale() :: term() | {Locale::term(), Context::binary()}. -type old_translate_fun() :: fun((text()) -> iodata() | default). -type new_translate_fun() :: fun((phrase(), locale()) -> iodata() | default). -type translate_fun() :: new_translate_fun() | old_translate_fun(). -type init_translation() :: none | fun (() -> init_translation()) | {M::atom(), F::atom()} | {M::atom(), F::atom(), A::list()} | translate_fun(). -define(IFCHANGED_CONTEXT_VARIABLE, erlydtl_ifchanged_context). find_value(Key, Data, Options) when is_atom(Key), is_tuple(Data) -> Rec = element(1, Data), Info = proplists:get_value(record_info, Options, []), case proplists:get_value(Rec, Info) of Fields when is_list(Fields), length(Fields) == size(Data) - 1 -> case proplists:get_value(Key, Fields) of Idx when is_integer(Idx) -> element(Idx, Data); _ -> undefined end; _ -> find_value(Key, Data) end; find_value(Key, Data, Options) when is_integer(Key), is_list(Data) -> find_value(adjust_index(Key, 1, lists_0_based, Options), Data); find_value(Key, Data, Options) when is_integer(Key), is_tuple(Data) -> find_value(adjust_index(Key, 1, tuples_0_based, Options), Data); find_value(Key, Data, _Options) -> find_value(Key, Data). adjust_index(Key, Off, Opt, Options) when is_list(Options) -> case proplists:get_value(Opt, Options) of defer -> adjust_index( Key, Off, Opt, proplists:get_value(render_options, Options)); true -> Key + Off; _ -> Key end; adjust_index(Key, _Off, _Opt, _Options) -> Key. find_value(_, undefined) -> undefined; find_value(Key, Fun) when is_function(Fun, 1) -> Fun(Key); find_value(Key, L) when is_atom(Key), is_list(L) -> case lists:keyfind(Key, 1, L) of false -> find_value(atom_to_list(Key), L); {Key, Value} -> Value end; find_value(Key, L) when is_list(Key), is_list(L) -> case lists:keyfind(Key, 1, L) of false -> find_value(list_to_binary(Key), L); {Key, Value} -> Value end; find_value(Key, L) when is_binary(Key), is_list(L) -> case lists:keyfind(Key, 1, L) of false -> undefined; {Key, Value} -> Value end; find_value(Key, L) when is_integer(Key), is_list(L) -> if Key =< length(L) -> lists:nth(Key, L); true -> undefined end; find_value(_, {0, nil}) -> undefined; find_value(Key, {GBSize, {_, _, _, _}=GBData}) when is_integer(GBSize) -> case gb_trees:lookup(Key, {GBSize, GBData}) of {value, Val} -> Val; _ -> undefined end; find_value(Key, Tuple) when is_tuple(Tuple) -> case element(1, Tuple) of L when is_list(L) andalso size(Tuple) =:= 1 -> find_value(Key, L); dict -> case dict:find(Key, Tuple) of {ok, Val} -> Val; _ -> undefined end; _ when is_integer(Key) -> if Key =< size(Tuple) -> element(Key, Tuple); true -> undefined end; Module -> case lists:member({Key, 1}, Module:module_info(exports)) of true -> case Tuple:Key() of Val when is_tuple(Val) -> case element(1, Val) of 'Elixir.Ecto.Associations.BelongsTo' -> Val:get(); 'Elixir.Ecto.Associations.HasOne' -> Val:get(); _ -> Val end; Val -> Val end; _ -> undefined end end; find_value(Key, Map) -> case erlang:is_builtin(erlang, is_map, 1) andalso erlang:is_map(Map) of true -> find_map_value(Key, Map); false -> undefined end. find_map_value(Key, Map) when is_atom(Key) -> case maps:find(Key, Map) of error -> find_map_value(atom_to_list(Key), Map); {ok, Value} -> Value end; find_map_value(Key, Map) when is_list(Key) -> case maps:find(Key, Map) of error -> find_map_value(list_to_binary(Key), Map); {ok, Value} -> Value end; find_map_value(Key, Map) when is_binary(Key) -> case maps:find(Key, Map) of error -> undefined; {ok, Value} -> Value end; find_map_value(Key, Map) -> case maps:find(Key, Map) of error -> undefined; {ok, Value} -> Value end. fetch_value(Key, Data, Options) -> fetch_value(Key, Data, Options, []). fetch_value(Key, Data, Options, Default) -> case find_value(Key, Data, Options) of undefined -> Default; Val -> Val end. find_deep_value(Key, Data) -> find_deep_value(Key, Data, []). find_deep_value([Key|Rest], Item, Opts) -> case find_value(Key, Item, Opts) of undefined -> undefined; NewItem -> find_deep_value(Rest, NewItem, Opts) end; find_deep_value([], Item, _Opts) -> Item. regroup(List, Attribute) -> do_regroup(List, Attribute, [], []). regroup(List, Attribute, Options) -> do_regroup(List, Attribute, Options, []). do_regroup([], _, _, []) -> []; do_regroup([], _, _, [[{grouper, LastGrouper}, {list, LastList}]|Acc]) -> lists:reverse([[{grouper, LastGrouper}, {list, lists:reverse(LastList)}]|Acc]); do_regroup([Item|Rest], Attribute, Options, []) -> do_regroup(Rest, Attribute, Options, [[{grouper, find_deep_value(Attribute, Item, Options)}, {list, [Item]}]]); do_regroup([Item|Rest], Attribute, Options, [[{grouper, PrevGrouper}, {list, PrevList}]|Acc]) -> case find_deep_value(Attribute, Item, Options) of Value when Value =:= PrevGrouper -> do_regroup(Rest, Attribute, Options, [[{grouper, PrevGrouper}, {list, [Item|PrevList]}]|Acc]); Value -> do_regroup(Rest, Attribute, Options, [[{grouper, Value}, {list, [Item]}], [{grouper, PrevGrouper}, {list, lists:reverse(PrevList)}]|Acc]) end. -spec init_translation(init_translation()) -> none | translate_fun(). init_translation(none) -> none; init_translation(Fun) when is_function(Fun, 0) -> init_translation(Fun()); init_translation({M, F}) -> init_translation({M, F, []}); init_translation({M, F, A}) -> init_translation(apply(M, F, A)); init_translation(Fun) when is_function(Fun, 1); is_function(Fun, 2) -> Fun; init_translation(Other) -> throw({translation_fun, Other}). -spec translate(Phrase, Locale, Fun) -> iodata() | default when Phrase :: phrase(), Locale :: locale(), Fun :: none | translate_fun(). translate(Phrase, Locale, TranslationFun) -> translate(Phrase, Locale, TranslationFun, trans_text(Phrase)). translate(_Phrase, _Locale, none, Default) -> Default; translate(Phrase, Locale, TranslationFun, Default) -> case do_translate(Phrase, Locale, TranslationFun) of default -> Default; <<"">> -> Default; "" -> Default; Translated -> Translated end. trans_text({Text, _}) -> Text; trans_text(Text) -> Text. do_translate(Phrase, _Locale, TranslationFun) when is_function(TranslationFun, 1) -> TranslationFun(trans_text(Phrase)); do_translate(Phrase, Locale, TranslationFun) when is_function(TranslationFun, 2) -> TranslationFun(Phrase, Locale). %% @doc Translate and interpolate 'blocktrans' content. %% Pre-requisites: %% * `Variables' should be sorted %% * Each interpolation variable should exist %% (String="{{a}}", Variables=[{"b", "b-val"}] will fall) %% * Orddict keys should be string(), not binary() -spec translate_block(phrase(), locale(), atom(), orddict:orddict(), none | translate_fun()) -> iodata(). translate_block(Phrase, Locale, AutoEscape, Variables, TranslationFun) -> case translate(Phrase, Locale, TranslationFun, default) of default -> default; Translated -> try interpolate_variables(Translated, Variables, AutoEscape) catch {no_close_var, T} -> io:format(standard_error, "Warning: template translation: variable not closed: \"~s\"~n", [T]), default; _:_ -> default end end. interpolate_variables(Tpl, [], _) -> Tpl; interpolate_variables(Tpl, Variables, AutoEscape) -> BTpl = iolist_to_binary(Tpl), interpolate_variables1(BTpl, Variables, AutoEscape). interpolate_variables1(Tpl, Vars, AutoEscape) -> %% pre-compile binary patterns? case binary:split(Tpl, <<"{{">>) of [Tpl]=NoVars -> NoVars; %% need to enclose in list due to list tail call below.. [Pre, Post] -> case binary:split(Post, <<"}}">>) of [_] -> throw({no_close_var, Tpl}); [Var, Post1] -> Var1 = string:strip(binary_to_list(Var)), Value = cast(orddict:fetch(Var1, Vars), AutoEscape), [Pre, Value | interpolate_variables1(Post1, Vars, AutoEscape)] end end. cast(V, _) when is_integer(V); is_float(V) -> erlydtl_filters:format_number(V); cast(V, true) when is_binary(V); is_list(V) -> erlydtl_filters:force_escape(V); cast(V, false) when is_binary(V); is_list(V) -> V; cast(V, AutoEscape) -> cast(io_lib:format("~p", [V]), AutoEscape). are_equal(Arg1, Arg2) when Arg1 =:= Arg2 -> true; are_equal(Arg1, Arg2) when is_binary(Arg1) -> are_equal(binary_to_list(Arg1), Arg2); are_equal(Arg1, Arg2) when is_binary(Arg2) -> are_equal(Arg1, binary_to_list(Arg2)); are_equal(Arg1, Arg2) when is_integer(Arg1) -> are_equal(integer_to_list(Arg1), Arg2); are_equal(Arg1, Arg2) when is_integer(Arg2) -> are_equal(Arg1, integer_to_list(Arg2)); are_equal(Arg1, Arg2) when is_atom(Arg1), is_list(Arg2) -> are_equal(atom_to_list(Arg1), Arg2); are_equal(Arg1, Arg2) when is_list(Arg1), is_atom(Arg2) -> are_equal(Arg1, atom_to_list(Arg2)); are_equal(_, _) -> false. is_false("") -> true; is_false(false) -> true; is_false(undefined) -> true; is_false(null) -> true; is_false(0) -> true; is_false("0") -> true; is_false(<<"0">>) -> true; is_false(<<>>) -> true; is_false(_) -> false. is_true(V) -> not is_false(V). 'in'(Sublist, [Sublist|_]) -> true; 'in'(Sublist, List) when is_atom(List) -> 'in'(Sublist, atom_to_list(List)); 'in'(Sublist, List) when is_binary(Sublist) -> 'in'(binary_to_list(Sublist), List); 'in'(Sublist, List) when is_binary(List) -> 'in'(Sublist, binary_to_list(List)); 'in'(Sublist, [C|Rest]) when is_list(Sublist) andalso is_binary(C) -> 'in'(Sublist, [binary_to_list(C)|Rest]); 'in'(Sublist, [C|Rest]) when is_list(Sublist) andalso is_list(C) -> 'in'(Sublist, Rest); 'in'(Sublist, List) when is_list(Sublist) andalso is_list(List) -> string:str(List, Sublist) > 0; 'in'(Element, List) when is_list(List) -> lists:member(Element, List); 'in'(_, _) -> false. 'not'(Value) -> not is_true(Value). 'or'(Value1, Value2) -> is_true(Value1) or is_true(Value2). 'and'(Value1, Value2) -> is_true(Value1) and is_true(Value2). 'eq'(Value1, Value2) -> are_equal(Value1, Value2). 'ne'(Value1, Value2) -> not are_equal(Value1, Value2). 'le'(Value1, Value2) -> not 'gt'(Value1, Value2). 'ge'(Value1, Value2) -> not 'lt'(Value1, Value2). 'gt'(Value1, Value2) when is_list(Value1) -> 'gt'(list_to_integer(Value1), Value2); 'gt'(Value1, Value2) when is_list(Value2) -> 'gt'(Value1, list_to_integer(Value2)); 'gt'(Value1, Value2) when Value1 > Value2 -> true; 'gt'(_, _) -> false. 'lt'(Value1, Value2) when is_list(Value1) -> 'lt'(list_to_integer(Value1), Value2); 'lt'(Value1, Value2) when is_list(Value2) -> 'lt'(Value1, list_to_integer(Value2)); 'lt'(Value1, Value2) when Value1 < Value2 -> true; 'lt'(_, _) -> false. stringify_final(In, BinaryStrings) -> stringify_final(In, [], BinaryStrings). stringify_final([], Out, _) -> lists:reverse(Out); stringify_final([El | Rest], Out, false = BinaryStrings) when is_atom(El) -> stringify_final(Rest, [atom_to_list(El) | Out], BinaryStrings); stringify_final([El | Rest], Out, true = BinaryStrings) when is_atom(El) -> stringify_final(Rest, [atom_to_binary(El, latin1) | Out], BinaryStrings); stringify_final([El | Rest], Out, BinaryStrings) when is_list(El) -> stringify_final(Rest, [stringify_final(El, BinaryStrings) | Out], BinaryStrings); stringify_final([El | Rest], Out, false = BinaryStrings) when is_tuple(El) -> stringify_final(Rest, [io_lib:print(El) | Out], BinaryStrings); stringify_final([El | Rest], Out, true = BinaryStrings) when is_tuple(El) -> stringify_final(Rest, [list_to_binary(io_lib:print(El)) | Out], BinaryStrings); stringify_final([El | Rest], Out, BinaryStrings) -> stringify_final(Rest, [El | Out], BinaryStrings). to_list(Value, true) -> lists:reverse(to_list(Value, false)); to_list(Value, false) when is_list(Value) -> Value; to_list(Value, false) when is_tuple(Value) -> case element(1, Value) of 'Elixir.Ecto.Associations.HasMany' -> Value:to_list(); _ -> tuple_to_list(Value) end. init_counter_stats(List) -> init_counter_stats(List, undefined). init_counter_stats(List, Parent) when is_list(List) -> ListLen = length(List), [{counter, 1}, {counter0, 0}, {revcounter, ListLen}, {revcounter0, ListLen - 1}, {first, true}, {last, ListLen =:= 1}, {parentloop, Parent}]. increment_counter_stats([{counter, Counter}, {counter0, Counter0}, {revcounter, RevCounter}, {revcounter0, RevCounter0}, {first, _}, {last, _}, {parentloop, Parent}]) -> [{counter, Counter + 1}, {counter0, Counter0 + 1}, {revcounter, RevCounter - 1}, {revcounter0, RevCounter0 - 1}, {first, false}, {last, RevCounter0 =:= 1}, {parentloop, Parent}]. forloop(_Fun, [], _Parent, Default) -> Default; forloop(Fun, Values, Parent, _Default) -> push_ifchanged_context(), {Result, _Acc} = lists:mapfoldl(Fun, init_counter_stats(Values, Parent), Values), pop_ifchanged_context(), Result. %% keep old version for backwards compatibility.. forloop(_Fun, [], _Parent) -> empty; forloop(Fun, Values, Parent) -> {forloop(Fun, Values, Parent, undefined), undefined}. push_ifchanged_context() -> IfChangedContextStack = case get(?IFCHANGED_CONTEXT_VARIABLE) of undefined -> []; Stack -> Stack end, put(?IFCHANGED_CONTEXT_VARIABLE, [[]|IfChangedContextStack]). pop_ifchanged_context() -> [_|Rest] = get(?IFCHANGED_CONTEXT_VARIABLE), put(?IFCHANGED_CONTEXT_VARIABLE, Rest). ifchanged(Expressions) -> [IfChangedContext|Rest] = get(?IFCHANGED_CONTEXT_VARIABLE), {Result, NewContext} = lists:foldl(fun (Expr, {ProvResult, Context}) when ProvResult == true -> {_, NContext} = ifchanged2(Expr, Context), {true, NContext}; (Expr, {_ProvResult, Context}) -> ifchanged2(Expr, Context) end, {false, IfChangedContext}, Expressions), put(?IFCHANGED_CONTEXT_VARIABLE, [NewContext|Rest]), Result. ifchanged2({Key, Value}, IfChangedContext) -> PreviousValue = proplists:get_value(Key, IfChangedContext), if PreviousValue =:= Value -> {false, IfChangedContext}; true -> NewContext = [{Key, Value}|proplists:delete(Key, IfChangedContext)], {true, NewContext} end. cycle(NamesTuple, Counters) when is_tuple(NamesTuple) -> element(find_value(counter0, Counters) rem size(NamesTuple) + 1, NamesTuple). widthratio(Numerator, Denominator, Scale) -> round(Numerator / Denominator * Scale). spaceless(Contents) -> Contents1 = lists:flatten(Contents), Contents2 = re:replace(Contents1, "^\\s+<", "<", [{return,list}]), Contents3 = re:replace(Contents2, ">\\s+$", ">", [{return,list}]), Contents4 = re:replace(Contents3, ">\\s+<", "><", [global, {return,list}]), Contents4. read_file(Module, Function, DocRoot, FileName, ReaderOptions) -> AbsName = case filename:absname(FileName) of FileName -> FileName; _ -> filename:join([DocRoot, FileName]) end, case read_file_internal(Module, Function, AbsName, ReaderOptions) of {ok, Data} -> Data; {error, Reason} -> throw({read_file, AbsName, Reason}) end. read_file_internal(Module, Function, FileName, ReaderOptions) -> _ = code:ensure_loaded(Module), case erlang:function_exported(Module, Function,1) of true -> Module:Function(FileName); false -> case erlang:function_exported(Module, Function,2) of true -> Module:Function(FileName, ReaderOptions); false -> {error, "Empty reader"} end end. erlydtl-0.15.0/src/erlydtl_scanner.erl000066400000000000000000000460171504712431400177500ustar00rootroot00000000000000%%%%% THIS IS A SLEX GENERATED FILE %%%%% %%%------------------------------------------------------------------- %%% File: erlydtl_scanner.slex %%% @author Andreas Stenius %%% @copyright 2013 Andreas Stenius %%% @doc %%% erlydtl scanner %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2013 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2013-11-05 by Andreas Stenius %%% %%% Rules based on the original erlydtl_scanner by Robert Saccon and Evan Miller. %%%------------------------------------------------------------------- -module(erlydtl_scanner). %% This file was generated 2016-05-28 20:22:41 UTC by slex 0.2.1-2-g7814678. %% http://github.com/erlydtl/slex -slex_source(["src/erlydtl_scanner.slex"]). -export([scan/1, scan/4]). -compile(nowarn_unused_vars). -export([resume/1, format_error/1]). -record(scanner_state, {template = [], scanned = [], pos = {1, 1}, state = in_text}). resume(#scanner_state{template = Template, scanned = Scanned, pos = Pos, state = State}) -> scan(Template, Scanned, Pos, State). return_error(Error, P, T, S, St) -> {error, {P, erlydtl_scanner, Error}, #scanner_state{template = T, scanned = post_process(S, err), pos = P, state = St}}. return_error(Error, P) -> {error, {P, erlydtl_scanner, Error}}. to_atom(L) when is_list(L) -> list_to_atom(L). to_keyword(L, P) -> {to_atom(L ++ "_keyword"), P, L}. atomize(L, T) -> setelement(3, T, to_atom(L)). is_keyword(Class, {_, _, L} = T) -> L1 = lists:reverse(L), case is_keyword(Class, L1) of true -> to_keyword(L1, element(2, T)); false -> atomize(L1, T) end; is_keyword([C | Cs], L) -> is_keyword(C, L) orelse is_keyword(Cs, L); is_keyword(all, L) -> is_keyword([any, open, close], L); is_keyword(open_tag, L) -> is_keyword([any, open], L); is_keyword(close_tag, L) -> is_keyword([any, close], L); is_keyword(any, "in") -> true; is_keyword(any, "not") -> true; is_keyword(any, "or") -> true; is_keyword(any, "and") -> true; is_keyword(any, "as") -> true; is_keyword(any, "by") -> true; is_keyword(any, "with") -> true; is_keyword(any, "from") -> true; is_keyword(any, "count") -> true; is_keyword(any, "context") -> true; is_keyword(any, "noop") -> true; is_keyword(any, "trimmed") -> true; is_keyword(close, "only") -> true; is_keyword(close, "parsed") -> true; is_keyword(close, "silent") -> true; is_keyword(close, "reversed") -> true; is_keyword(close, "openblock") -> true; is_keyword(close, "closeblock") -> true; is_keyword(close, "openvariable") -> true; is_keyword(close, "closevariable") -> true; is_keyword(close, "openbrace") -> true; is_keyword(close, "closebrace") -> true; is_keyword(close, "opencomment") -> true; is_keyword(close, "closecomment") -> true; is_keyword(open, "autoescape") -> true; is_keyword(open, "endautoescape") -> true; is_keyword(open, "block") -> true; is_keyword(open, "endblock") -> true; is_keyword(open, "language") -> true; is_keyword(open, "endlanguage") -> true; is_keyword(open, "comment") -> true; is_keyword(open, "endcomment") -> true; is_keyword(open, "cycle") -> true; is_keyword(open, "extends") -> true; is_keyword(open, "filter") -> true; is_keyword(open, "endfilter") -> true; is_keyword(open, "firstof") -> true; is_keyword(open, "for") -> true; is_keyword(open, "empty") -> true; is_keyword(open, "endfor") -> true; is_keyword(open, "if") -> true; is_keyword(open, "elif") -> true; is_keyword(open, "else") -> true; is_keyword(open, "endif") -> true; is_keyword(open, "ifchanged") -> true; is_keyword(open, "endifchanged") -> true; is_keyword(open, "ifequal") -> true; is_keyword(open, "endifequal") -> true; is_keyword(open, "ifnotequal") -> true; is_keyword(open, "endifnotequal") -> true; is_keyword(open, "include") -> true; is_keyword(open, "now") -> true; is_keyword(open, "regroup") -> true; is_keyword(open, "endregroup") -> true; is_keyword(open, "spaceless") -> true; is_keyword(open, "endspaceless") -> true; is_keyword(open, "ssi") -> true; is_keyword(open, "templatetag") -> true; is_keyword(open, "widthratio") -> true; is_keyword(open, "call") -> true; is_keyword(open, "endwith") -> true; is_keyword(open, "trans") -> true; is_keyword(open, "blocktrans") -> true; is_keyword(open, "endblocktrans") -> true; is_keyword(open, "load") -> true; is_keyword(open, "plural") -> true; is_keyword(_, _) -> false. format_error({illegal_char, C}) -> io_lib:format("Illegal character '~s'", [[C]]); format_error({eof, Where}) -> io_lib:format("Unexpected end of file ~s", [format_where(Where)]). format_where(in_comment) -> "in comment"; format_where(in_code) -> "in code block". scan(Template) when is_list(Template) -> scan(Template, [], {1, 1}, in_text); scan(Template) when is_binary(Template) -> scan(binary_to_list(Template)). scan("{{" ++ T, S, {R, C} = P, in_text) -> scan(T, [{open_var, P, "{{"} | post_process(S, open_var)], {R, C + 2}, {in_code, "}}"}); scan("{%" ++ T, S, {R, C} = P, in_text) -> scan(T, [{open_tag, P, "{%"} | post_process(S, open_tag)], {R, C + 2}, {in_code, "%}"}); scan(""}); scan(""}); scan("{#" ++ T, S, {R, C}, in_text) -> scan(T, S, {R, C + 2}, {in_comment, "#}"}); scan(""}); scan("#}-->" ++ T, S, {R, C}, {_, "#}-->"}) -> scan(T, S, {R, C + 5}, in_text); scan("#}" ++ T, S, {R, C}, {_, "#}"}) -> scan(T, S, {R, C + 2}, in_text); scan([H | T], S, {R, C} = P, {in_comment, E} = St) -> scan(T, case S of [{comment_tag, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{comment_tag, P, [H]} | post_process(S, comment_tag)] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, St); scan([H | T], S, {R, C} = P, in_text = St) -> scan(T, case S of [{string, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{string, P, [H]} | post_process(S, string)] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, St); scan("\"" ++ T, S, {R, C} = P, {in_code, E}) -> scan(T, [{string_literal, P, "\""} | post_process(S, string_literal)], {R, C + 1}, {in_double_quote, E}); scan("'" ++ T, S, {R, C} = P, {in_code, E}) -> scan(T, [{string_literal, P, "\""} | post_process(S, string_literal)], {R, C + 1}, {in_single_quote, E}); scan("\"" ++ T, S, {R, C} = P, {in_double_quote, E}) -> scan(T, case S of [{string_literal, _, L} = M | Ss] -> [setelement(3, M, "\"" ++ L) | Ss]; _ -> [{string_literal, P, "\""} | post_process(S, string_literal)] end, {R, C + 1}, {in_code, E}); scan("'" ++ T, S, {R, C} = P, {in_single_quote, E}) -> scan(T, case S of [{string_literal, _, L} = M | Ss] -> [setelement(3, M, "\"" ++ L) | Ss]; _ -> [{string_literal, P, "\""} | post_process(S, string_literal)] end, {R, C + 1}, {in_code, E}); scan("\\" ++ T, S, {R, C} = P, {in_double_quote, E}) -> scan(T, case S of [{string_literal, _, L} = M | Ss] -> [setelement(3, M, "\\" ++ L) | Ss]; _ -> [{string_literal, P, "\\"} | post_process(S, string_literal)] end, {R, C + 1}, {in_double_quote_escape, E}); scan("\\" ++ T, S, {R, C} = P, {in_single_quote, E}) -> scan(T, case S of [{string_literal, _, L} = M | Ss] -> [setelement(3, M, "\\" ++ L) | Ss]; _ -> [{string_literal, P, "\\"} | post_process(S, string_literal)] end, {R, C + 1}, {in_single_quote_escape, E}); scan([H | T], S, {R, C} = P, {in_double_quote, E} = St) -> scan(T, case S of [{string_literal, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{string_literal, P, [H]} | post_process(S, string_literal)] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, St); scan([H | T], S, {R, C} = P, {in_single_quote, E} = St) -> scan(T, case S of [{string_literal, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{string_literal, P, [H]} | post_process(S, string_literal)] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, St); scan([H | T], S, {R, C} = P, {in_double_quote_escape, E}) -> scan(T, case S of [{string_literal, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{string_literal, P, [H]} | post_process(S, string_literal)] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, {in_double_quote, E}); scan([H | T], S, {R, C} = P, {in_single_quote_escape, E}) -> scan(T, case S of [{string_literal, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{string_literal, P, [H]} | post_process(S, string_literal)] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, {in_single_quote, E}); scan("}}-->" ++ T, S, {R, C} = P, {_, "}}-->"}) -> scan(T, [{close_var, P, "}}-->"} | post_process(S, close_var)], {R, C + 5}, in_text); scan("%}-->" ++ T, S, {R, C} = P, {_, "%}-->"}) -> scan(T, [{close_tag, P, "%}-->"} | post_process(S, close_tag)], {R, C + 5}, in_text); scan("}}" ++ T, S, {R, C} = P, {_, "}}"}) -> scan(T, [{close_var, P, "}}"} | post_process(S, close_var)], {R, C + 2}, in_text); scan("%}" ++ T, S, {R, C} = P, {_, "%}"} = St) -> case S of [{identifier, _, "mitabrev"}, {open_tag, _, '{%'} | Ss] -> scan(T, [{string, {R, C + 2}, ""} | Ss], {R, C + 2}, {in_verbatim, undefined}); [{identifier, _, Tag}, {identifier, _, verbatim}, {open_tag, _, '{%'} | Ss] -> scan(T, [{string, {R, C + 2}, ""} | Ss], {R, C + 2}, {in_verbatim, Tag}); _ -> scan(T, [{close_tag, P, "%}"} | post_process(S, close_tag)], {R, C + 2}, in_text) end; scan("{%" ++ T, S, {R, C} = P, {in_verbatim, E} = St) -> scan(T, S, {R, C + 2}, {in_verbatim_code, {E, "%{"}}); scan(" " ++ T, S, {R, C} = P, {in_verbatim_code, E} = St) -> {Tag, Backtrack} = E, scan(T, S, {R, C + 1}, {in_verbatim_code, {Tag, [$\s | Backtrack]}}); scan("endverbatim%}" ++ T, S, {R, C} = P, {in_verbatim_code, E} = St) when element(1, E) =:= undefined -> scan(T, S, {R, C + 13}, in_text); scan("endverbatim " ++ T, S, {R, C} = P, {in_verbatim_code, E} = St) -> {Tag, Backtrack} = E, scan(T, S, {R, C + 12}, {in_endverbatim_code, {Tag, lists:reverse("endverbatim ", Backtrack), ""}}); scan(" " ++ T, S, {R, C} = P, {in_endverbatim_code, E} = St) when element(3, E) =:= "" -> {Tag, Backtrack, EndTag} = E, scan(T, S, {R, C + 1}, {in_endverbatim_code, {Tag, [$\s | Backtrack], EndTag}}); scan([H | T], S, {R, C} = P, {in_endverbatim_code, E} = St) when H >= $a andalso H =< $z orelse H >= $0 andalso H =< $9 orelse H =:= $_ -> {Tag, Backtrack, EndTag} = E, scan(T, S, {R, C + 1}, {in_endverbatim_code, {Tag, [H | Backtrack], [H | EndTag]}}); scan(" " ++ T, S, {R, C} = P, {in_endverbatim_code, E} = St) when element(1, E) =:= element(3, E) -> {Tag, Backtrack, Tag} = E, scan(T, S, {R, C + 1}, {in_endverbatim_code, {Tag, [$\s | Backtrack], Tag}}); scan("%}" ++ T, S, {R, C} = P, {in_endverbatim_code, E} = St) when element(1, E) =:= element(3, E) -> scan(T, S, {R, C + 2}, in_text); scan("%}" ++ T, S, {R, C} = P, {in_endverbatim_code, E} = St) when element(1, E) =:= undefined andalso element(3, E) =:= "" -> scan(T, S, {R, C + 2}, in_text); scan([H | T], S, {R, C} = P, {in_endverbatim_code, E} = St) -> {Tag, Backtrack, _} = E, scan(T, case S of [{string, _, L} = M | Ss] -> [setelement(3, M, [H | Backtrack] ++ L) | Ss]; _ -> [{string, P, [H | Backtrack]} | S] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, {in_verbatim, Tag}); scan([H | T], S, {R, C} = P, {in_verbatim_code, E} = St) -> {Tag, Backtrack} = E, scan(T, case S of [{string, _, L} = M | Ss] -> [setelement(3, M, [H | Backtrack] ++ L) | Ss]; _ -> [{string, P, [H | Backtrack]} | S] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, {in_verbatim, Tag}); scan([H | T], S, {R, C} = P, {in_verbatim, E} = St) -> scan(T, case S of [{string, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{string, P, [H]} | S] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, {in_verbatim, E}); scan("==" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'==', P} | post_process(S, '==')], {R, C + 2}, {in_code, E}); scan("!=" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'!=', P} | post_process(S, '!=')], {R, C + 2}, {in_code, E}); scan(">=" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'>=', P} | post_process(S, '>=')], {R, C + 2}, {in_code, E}); scan("<=" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'<=', P} | post_process(S, '<=')], {R, C + 2}, {in_code, E}); scan(">" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'>', P} | post_process(S, '>')], {R, C + 1}, {in_code, E}); scan("<" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'<', P} | post_process(S, '<')], {R, C + 1}, {in_code, E}); scan("(" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'(', P} | post_process(S, '(')], {R, C + 1}, {in_code, E}); scan(")" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{')', P} | post_process(S, ')')], {R, C + 1}, {in_code, E}); scan("," ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{',', P} | post_process(S, ',')], {R, C + 1}, {in_code, E}); scan("|" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'|', P} | post_process(S, '|')], {R, C + 1}, {in_code, E}); scan("=" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'=', P} | post_process(S, '=')], {R, C + 1}, {in_code, E}); scan(":" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{':', P} | post_process(S, ':')], {R, C + 1}, {in_code, E}); scan("." ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'.', P} | post_process(S, '.')], {R, C + 1}, {in_code, E}); scan("_(" ++ T, S, {R, C} = P, {_, E}) -> scan(T, [{'(', P}, {'_', P} | post_process(S, '_')], {R, C + 2}, {in_code, E}); scan(" " ++ T, S, {R, C}, {_, E}) -> scan(T, S, {R, C + 1}, {in_code, E}); scan("\r" ++ T, S, {R, C}, {_, E}) -> scan(T, S, {R, C + 1}, {in_code, E}); scan("\n" ++ T, S, {R, C}, {_, E}) -> scan(T, S, {R + 1, 1}, {in_code, E}); scan("\t" ++ T, S, {R, C}, {_, E}) -> scan(T, S, {R, C + 1}, {in_code, E}); scan([H | T], S, {R, C} = P, {in_code, E}) when H >= $a andalso H =< $z orelse H >= $A andalso H =< $Z orelse H == $_ -> scan(T, [{identifier, P, [H]} | post_process(S, identifier)], case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, {in_identifier, E}); scan([H | T], S, {R, C} = P, {in_code, E}) when H >= $0 andalso H =< $9 orelse H == $- -> scan(T, [{number_literal, P, [H]} | post_process(S, number_literal)], case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, {in_number, E}); scan([H | T], S, {R, C} = P, {in_code, E} = St) -> return_error({illegal_char, H}, P, [H | T], S, St); scan([H | T], S, {R, C} = P, {in_number, E} = St) when H >= $0 andalso H =< $9 -> scan(T, case S of [{number_literal, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{number_literal, P, [H]} | post_process(S, number_literal)] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, St); scan([H | T], S, {R, C} = P, {in_number, E} = St) -> return_error({illegal_char, H}, P, [H | T], S, St); scan([H | T], S, {R, C} = P, {in_identifier, E}) when H >= $a andalso H =< $z orelse H >= $A andalso H =< $Z orelse H >= $0 andalso H =< $9 orelse H == $_ -> scan(T, case S of [{identifier, _, L} = M | Ss] -> [setelement(3, M, [H | L]) | Ss]; _ -> [{identifier, P, [H]} | post_process(S, identifier)] end, case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, {in_identifier, E}); scan([H | T], S, {R, C} = P, {in_identifier, E} = St) -> return_error({illegal_char, H}, P, [H | T], S, St); scan([], S, {R, C} = P, in_text = St) -> {ok, lists:reverse(post_process(S, eof))}; scan([], S, {R, C} = P, {in_comment, E} = St) -> return_error({eof, in_comment}, P); scan([], S, {R, C} = P, {_, E} = St) -> return_error({eof, in_code}, P). post_process(_, {string, _, L} = T, _) -> setelement(3, T, begin L1 = lists:reverse(L), L1 end); post_process(_, {string_literal, _, L} = T, _) -> setelement(3, T, begin L1 = lists:reverse(L), L1 end); post_process(_, {comment_tag, _, L} = T, _) -> setelement(3, T, begin L1 = lists:reverse(L), L1 end); post_process(_, {number_literal, _, L} = T, _) -> setelement(3, T, begin L1 = lists:reverse(L), L2 = list_to_integer(L1), L2 end); post_process(_, {open_var, _, L} = T, _) -> setelement(3, T, begin L1 = to_atom(L), L1 end); post_process(_, {close_var, _, L} = T, _) -> setelement(3, T, begin L1 = to_atom(L), L1 end); post_process(_, {open_tag, _, L} = T, _) -> setelement(3, T, begin L1 = to_atom(L), L1 end); post_process(_, {close_tag, _, L} = T, _) -> setelement(3, T, begin L1 = to_atom(L), L1 end); post_process([{open_tag, _, _} | _], {identifier, _, L} = T, close_tag) -> is_keyword(all, T); post_process([{open_tag, _, _} | _], {identifier, _, L} = T, _) -> is_keyword(open_tag, T); post_process([{open_var, _, _} | _], {identifier, _, L} = T, _) -> setelement(3, T, begin L1 = lists:reverse(L), L2 = to_atom(L1), L2 end); post_process([{'.', _} | _], {identifier, _, L} = T, _) -> setelement(3, T, begin L1 = lists:reverse(L), L2 = to_atom(L1), L2 end); post_process(_, {identifier, _, L} = T, close_tag) -> is_keyword(close_tag, T); post_process(_, {identifier, _, L} = T, _) -> is_keyword(any, T); post_process(_, T, _) -> T. post_process([S | Ss], N) -> [post_process(Ss, S, N) | Ss]; post_process(T, N) -> post_process(undefined, T, N). erlydtl-0.15.0/src/erlydtl_scanner.slex000066400000000000000000000311521504712431400201330ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_scanner.slex %%% @author Andreas Stenius %%% @copyright 2013 Andreas Stenius %%% @doc %%% erlydtl scanner %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2013 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2013-11-05 by Andreas Stenius %%% %%% Rules based on the original erlydtl_scanner by Robert Saccon and Evan Miller. %%%------------------------------------------------------------------- -module erlydtl_scanner. -function scan. -init_state in_text. form -compile(nowarn_unused_vars) end. form -export([resume/1, format_error/1]) end. form \ -record(scanner_state, { \ template=[], \ scanned=[], \ pos={1,1}, \ state=in_text \ }) \ end. form \ resume(#scanner_state{ template=Template, scanned=Scanned, \ pos=Pos, state=State }) -> \ scan(Template, Scanned, Pos, State) \ end. %% Rule syntax: Prio Prefix|any|- InState[-]|any[+|-] [, Guard] : {: Body}|{[Action...,] NewState [until Closer]}. %% `state-' means a state without a closer state. %% Where Guard and Body are one erlang expression (see it as a begin ... end block): expr end %% Open tags 10 {{ in_text-: open_var, in_code until }}. 10 {% in_text-: open_tag, in_code until %}. 10 . 10 . %% Comments 20 {# in_text-: in_comment until #}. 20 . %% `any+' will match the closer with the prefix.. 30 #}--> any+: skip, in_text-. 30 #} any+: skip, in_text-. %% must come before the `space any' rule 40 any in_comment: +comment_tag. %% end comment rules %% The rest is "just" text.. 50 any in_text-: +string. %% Quoted strings 60 \" in_code: string_literal, in_double_quote. 62 \" in_double_quote: +string_literal, in_code. 64 \\ in_double_quote: +string_literal, in_double_quote_escape. 66 any in_double_quote: +string_literal. 68 any in_double_quote_escape: +string_literal, in_double_quote. 60 \' in_code: string_literal-\", in_single_quote. 62 \' in_single_quote: +string_literal-\", in_code. 64 \\ in_single_quote: +string_literal, in_single_quote_escape. 66 any in_single_quote: +string_literal. 68 any in_single_quote_escape: +string_literal, in_single_quote. %% Close tags 70 }}--> any+: close_var, in_text-. 70 %}--> any+: close_tag, in_text-. 72 }} any+: close_var, in_text-. 72 %} any+: expr \ case S of \ [{identifier,_,"mitabrev"}, {open_tag,_,'{%'}|Ss] -> \ scan(T, [{string, {R, C + 2}, ""} | Ss], \ {R, C + 2}, {in_verbatim, undefined}); \ [{identifier,_,Tag}, {identifier,_,verbatim}, {open_tag,_,'{%'}|Ss] -> \ scan(T, [{string, {R, C + 2}, ""} | Ss], \ {R, C + 2}, {in_verbatim, Tag}); \ _ -> scan(T, [{close_tag, P, "%}"} | post_process(S, close_tag)], \ {R, C + 2}, in_text) \ end \ end. %% verbatim stuff 80 {% in_verbatim: expr scan(T, S, {R, C + 2}, {in_verbatim_code, {E, "%{"}}) end. 82 \s in_verbatim_code: expr \ {Tag, Backtrack} = E, \ scan(T, S, {R, C + 1}, {in_verbatim_code, {Tag, [$\ |Backtrack]}}) \ end. 84 'endverbatim%}' in_verbatim_code, expr element(1, E) =:= undefined end: expr scan(T, S, {R, C + 13}, in_text) end. 86 'endverbatim ' in_verbatim_code: expr \ {Tag, Backtrack} = E, \ scan(T, S, {R, C + 12}, \ {in_endverbatim_code, \ {Tag, lists:reverse("endverbatim ", Backtrack), ""}}) \ end. 88 \s in_endverbatim_code, expr element(3, E) =:= "" end: expr \ {Tag, Backtrack, EndTag} = E, \ scan(T, S, {R, C + 1}, \ {in_endverbatim_code, \ {Tag, [$\ |Backtrack], EndTag}}) \ end. 90 any in_endverbatim_code, expr \ H >= $a andalso H =< $z orelse \ H >= $0 andalso H =< $9 orelse H =:= $_ end: expr \ {Tag, Backtrack, EndTag} = E, \ scan(T, S, {R, C + 1}, \ {in_endverbatim_code, \ {Tag, [H|Backtrack], [H|EndTag]}}) \ end. 92 \s in_endverbatim_code, expr element(1, E) =:= element(3, E) end: expr \ {Tag, Backtrack, Tag} = E, \ scan(T, S, {R, C + 1}, \ {in_endverbatim_code, \ {Tag, [$\ |Backtrack], Tag}}) \ end. 94 %} in_endverbatim_code, expr element(1, E) =:= element(3, E) end: expr scan(T, S, {R, C + 2}, (in_text)) end. 96 %} in_endverbatim_code, expr element(1, E) =:= undefined andalso \ element(3, E) =:= "" end: expr scan(T, S, {R, C + 2}, in_text) end. 98 any in_endverbatim_code: expr \ {Tag, Backtrack, _} = E, \ scan(T, \ case S of \ [{string,_, L}=M|Ss] -> \ [setelement(3, M, [H|Backtrack] ++ L)|Ss]; \ _ -> [{string, P, [H|Backtrack]}|S] \ end, \ case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, \ {in_verbatim, Tag}) \ end. 100 any in_verbatim_code: expr \ {Tag, Backtrack} = E, \ scan(T, \ case S of \ [{string,_, L}=M|Ss] -> \ [setelement(3, M, [H|Backtrack] ++ L)|Ss]; \ _ -> [{string, P, [H|Backtrack]}|S] \ end, \ case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, \ {in_verbatim, Tag}) \ end. 102 any in_verbatim: expr \ scan(T, \ case S of \ [{string,_, L}=M|Ss] -> \ [setelement(3, M, [H|L])|Ss]; \ _ -> [{string, P, [H]}|S] \ end, \ case H of $\n -> {R + 1, 1}; _ -> {R, C + 1} end, \ {in_verbatim, E}) \ end. %% Get back to `in_code' on these tokens: 110 == any: ==, in_code. 110 != any: !=, in_code. 110 >= any: >=, in_code. 110 <= any: <=, in_code. 110 > any: >, in_code. 110 < any: <, in_code. 110 ( any: (, in_code. 110 ) any: ), in_code. 110 \, any: \,, in_code. 110 | any: |, in_code. 110 = any: =, in_code. 110 \: any: \:, in_code. 110 \. any: \., in_code. 110 \_( any: \_ \(, in_code. %% Eat space (and get back to `in_code') %% note that `any' here will match states *with* a closer, i.e. not `in_text'. %% (`any-' would match any stateless state.) 110 \s any: skip, in_code. 111 \r any: skip, in_code. 112 \n any: skip, in_code. 113 \t any: skip, in_code. 120 any in_code, expr \ (H >= $a andalso H =< $z) orelse \ (H >= $A andalso H =< $Z) orelse \ H == $_ \ end: identifier, in_identifier. 122 any in_code, expr \ (H >= $0 andalso H =< $9) orelse H == $- \ end: number_literal, in_number. 124 any in_code: expr return_error({illegal_char, H}, P, [H|T], S, St) end. 130 any in_number, expr H >= $0 andalso H =< $9 end: +number_literal. 132 any in_number: expr return_error({illegal_char, H}, P, [H|T], S, St) end. 140 any in_identifier, expr \ (H >= $a andalso H =< $z) orelse \ (H >= $A andalso H =< $Z) orelse \ (H >= $0 andalso H =< $9) orelse \ H == $_ \ end: +identifier, in_identifier. 142 any in_identifier: expr return_error({illegal_char, H}, P, [H|T], S, St) end. 200 : in_text- : expr \ {ok, lists:reverse(post_process(S,eof))} \ end. 202 : in_comment : expr return_error({eof, in_comment}, P) end. 204 : any : expr return_error({eof, in_code}, P) end. %% Process tokens as we parse them string: lists reverse. string_literal: lists reverse. comment_tag: lists reverse. number_literal: lists reverse, list_to_integer. open_var: to_atom. close_var: to_atom. open_tag: to_atom. close_tag: to_atom. open_tag identifier, close_tag: expr is_keyword(all, T) end. open_tag identifier: expr is_keyword(open_tag, T) end. open_var identifier: lists reverse, to_atom. \. - identifier: lists reverse, to_atom. identifier, close_tag: expr is_keyword(close_tag, T) end. identifier: expr is_keyword(any, T) end. %% Utility functions form return_error(Error, P, T, S, St) -> \ {error, \ {P, erlydtl_scanner, Error}, \ #scanner_state{ template=T, \ scanned=post_process(S, err), \ pos=P, state=St } \ } \ end. form return_error(Error, P) -> {error, {P, erlydtl_scanner, Error}} end. form to_atom(L) when is_list(L) -> list_to_atom(L) end. form to_keyword(L, P) -> {to_atom(L ++ "_keyword"), P, L} end. form atomize(L, T) -> setelement(3, T, to_atom(L)) end. form \ is_keyword(Class, {_, _, L} = T) -> \ L1 = lists:reverse(L), \ case is_keyword(Class, L1) of \ true -> to_keyword(L1, element(2, T)); \ false -> atomize(L1, T) \ end; \ is_keyword([C|Cs], L) -> \ is_keyword(C, L) orelse \ is_keyword(Cs, L); \ is_keyword(all, L) -> is_keyword([any, open, close], L); \ is_keyword(open_tag, L) -> is_keyword([any, open], L); \ is_keyword(close_tag, L) -> is_keyword([any, close], L); \ \ is_keyword(any, "in") -> true; \ is_keyword(any, "not") -> true; \ is_keyword(any, "or") -> true; \ is_keyword(any, "and") -> true; \ is_keyword(any, "as") -> true; \ is_keyword(any, "by") -> true; \ is_keyword(any, "with") -> true; \ is_keyword(any, "from") -> true; \ is_keyword(any, "count") -> true; \ is_keyword(any, "context") -> true; \ is_keyword(any, "noop") -> true; \ is_keyword(any, "trimmed") -> true; \ \ is_keyword(close, "only") -> true; \ is_keyword(close, "parsed") -> true; \ is_keyword(close, "silent") -> true; \ is_keyword(close, "reversed") -> true; \ is_keyword(close, "openblock") -> true; \ is_keyword(close, "closeblock") -> true; \ is_keyword(close, "openvariable") -> true; \ is_keyword(close, "closevariable") -> true; \ is_keyword(close, "openbrace") -> true; \ is_keyword(close, "closebrace") -> true; \ is_keyword(close, "opencomment") -> true; \ is_keyword(close, "closecomment") -> true; \ \ is_keyword(open, "autoescape") -> true; \ is_keyword(open, "endautoescape") -> true; \ is_keyword(open, "block") -> true; \ is_keyword(open, "endblock") -> true; \ is_keyword(open, "language") -> true; \ is_keyword(open, "endlanguage") -> true; \ is_keyword(open, "comment") -> true; \ is_keyword(open, "endcomment") -> true; \ is_keyword(open, "cycle") -> true; \ is_keyword(open, "extends") -> true; \ is_keyword(open, "filter") -> true; \ is_keyword(open, "endfilter") -> true; \ is_keyword(open, "firstof") -> true; \ is_keyword(open, "for") -> true; \ is_keyword(open, "empty") -> true; \ is_keyword(open, "endfor") -> true; \ is_keyword(open, "if") -> true; \ is_keyword(open, "elif") -> true; \ is_keyword(open, "else") -> true; \ is_keyword(open, "endif") -> true; \ is_keyword(open, "ifchanged") -> true; \ is_keyword(open, "endifchanged") -> true; \ is_keyword(open, "ifequal") -> true; \ is_keyword(open, "endifequal") -> true; \ is_keyword(open, "ifnotequal") -> true; \ is_keyword(open, "endifnotequal") -> true; \ is_keyword(open, "include") -> true; \ is_keyword(open, "now") -> true; \ is_keyword(open, "regroup") -> true; \ is_keyword(open, "endregroup") -> true; \ is_keyword(open, "spaceless") -> true; \ is_keyword(open, "endspaceless") -> true; \ is_keyword(open, "ssi") -> true; \ is_keyword(open, "templatetag") -> true; \ is_keyword(open, "widthratio") -> true; \ is_keyword(open, "call") -> true; \ is_keyword(open, "endwith") -> true; \ is_keyword(open, "trans") -> true; \ is_keyword(open, "blocktrans") -> true; \ is_keyword(open, "endblocktrans") -> true; \ is_keyword(open, "load") -> true; \ is_keyword(open, "plural") -> true; \ is_keyword(_, _) -> false \ end. form format_error({illegal_char, C}) -> \ io_lib:format("Illegal character '~s'", [[C]]); \ format_error({eof, Where}) -> \ io_lib:format("Unexpected end of file ~s", [format_where(Where)]) \ end. form format_where(in_comment) -> "in comment"; \ format_where(in_code) -> "in code block" \ end. erlydtl-0.15.0/src/erlydtl_time_compat.erl000066400000000000000000000162211504712431400206120ustar00rootroot00000000000000%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2014-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% %% If your code need to be able to execute on ERTS versions both %% earlier and later than 7.0, the best approach is to use the new %% time API introduced in ERTS 7.0 and implement a fallback %% solution using the old primitives to be used on old ERTS %% versions. This way your code can automatically take advantage %% of the improvements in the API when available. This is an %% example of how to implement such an API, but it can be used %% as is if you want to. Just add (a preferably renamed version of) %% this module to your project, and call the API via this module %% instead of calling the BIFs directly. %% %% use own name to avoid conflicts.. -module(erlydtl_time_compat). %% We don't want warnings about the use of erlang:now/0 in %% this module. -compile(nowarn_deprecated_function). %% %% We don't use %% -compile({nowarn_deprecated_function, [{erlang, now, 0}]}). %% since this will produce warnings when compiled on systems %% where it has not yet been deprecated. %% -export([monotonic_time/0, monotonic_time/1, erlang_system_time/0, erlang_system_time/1, os_system_time/0, os_system_time/1, time_offset/0, time_offset/1, convert_time_unit/3, timestamp/0, unique_integer/0, unique_integer/1, monitor/2, system_info/1, system_flag/2]). monotonic_time() -> try erlang:monotonic_time() catch error:undef -> %% Use Erlang system time as monotonic time erlang_system_time_fallback() end. monotonic_time(Unit) -> try erlang:monotonic_time(Unit) catch error:badarg -> erlang:error(badarg, [Unit]); error:undef -> %% Use Erlang system time as monotonic time STime = erlang_system_time_fallback(), try convert_time_unit_fallback(STime, native, Unit) catch error:bad_time_unit -> erlang:error(badarg, [Unit]) end end. erlang_system_time() -> try erlang:system_time() catch error:undef -> erlang_system_time_fallback() end. erlang_system_time(Unit) -> try erlang:system_time(Unit) catch error:badarg -> erlang:error(badarg, [Unit]); error:undef -> STime = erlang_system_time_fallback(), try convert_time_unit_fallback(STime, native, Unit) catch error:bad_time_unit -> erlang:error(badarg, [Unit]) end end. os_system_time() -> try os:system_time() catch error:undef -> os_system_time_fallback() end. os_system_time(Unit) -> try os:system_time(Unit) catch error:badarg -> erlang:error(badarg, [Unit]); error:undef -> STime = os_system_time_fallback(), try convert_time_unit_fallback(STime, native, Unit) catch error:bad_time_unit -> erlang:error(badarg, [Unit]) end end. time_offset() -> try erlang:time_offset() catch error:undef -> %% Erlang system time and Erlang monotonic %% time are always aligned 0 end. time_offset(Unit) -> try erlang:time_offset(Unit) catch error:badarg -> erlang:error(badarg, [Unit]); error:undef -> try _ = integer_time_unit(Unit) catch error:bad_time_unit -> erlang:error(badarg, [Unit]) end, %% Erlang system time and Erlang monotonic %% time are always aligned 0 end. convert_time_unit(Time, FromUnit, ToUnit) -> try erlang:convert_time_unit(Time, FromUnit, ToUnit) catch error:undef -> try convert_time_unit_fallback(Time, FromUnit, ToUnit) catch _:_ -> erlang:error(badarg, [Time, FromUnit, ToUnit]) end; error:Error -> erlang:error(Error, [Time, FromUnit, ToUnit]) end. timestamp() -> try erlang:timestamp() catch error:undef -> erlang:now() end. unique_integer() -> try erlang:unique_integer() catch error:undef -> {MS, S, US} = erlang:now(), (MS*1000000+S)*1000000+US end. unique_integer(Modifiers) -> try erlang:unique_integer(Modifiers) catch error:badarg -> erlang:error(badarg, [Modifiers]); error:undef -> case is_valid_modifier_list(Modifiers) of true -> %% now() converted to an integer %% fulfill the requirements of %% all modifiers: unique, positive, %% and monotonic... {MS, S, US} = erlang:now(), (MS*1000000+S)*1000000+US; false -> erlang:error(badarg, [Modifiers]) end end. monitor(Type, Item) -> try erlang:monitor(Type, Item) catch error:Error -> case {Error, Type, Item} of {badarg, time_offset, clock_service} -> %% Time offset is final and will never change. %% Return a dummy reference, there will never %% be any need for 'CHANGE' messages... make_ref(); _ -> erlang:error(Error, [Type, Item]) end end. system_info(Item) -> try erlang:system_info(Item) catch error:badarg -> case Item of time_correction -> case erlang:system_info(tolerant_timeofday) of enabled -> true; disabled -> false end; time_warp_mode -> no_time_warp; time_offset -> final; NotSupArg when NotSupArg == os_monotonic_time_source; NotSupArg == os_system_time_source; NotSupArg == start_time; NotSupArg == end_time -> %% Cannot emulate this... erlang:error(notsup, [NotSupArg]); _ -> erlang:error(badarg, [Item]) end; error:Error -> erlang:error(Error, [Item]) end. system_flag(Flag, Value) -> try erlang:system_flag(Flag, Value) catch error:Error -> case {Error, Flag, Value} of {badarg, time_offset, finalize} -> %% Time offset is final final; _ -> erlang:error(Error, [Flag, Value]) end end. %% %% Internal functions %% integer_time_unit(native) -> 1000*1000; integer_time_unit(nano_seconds) -> 1000*1000*1000; integer_time_unit(micro_seconds) -> 1000*1000; integer_time_unit(milli_seconds) -> 1000; integer_time_unit(seconds) -> 1; integer_time_unit(I) when is_integer(I), I > 0 -> I; integer_time_unit(BadRes) -> erlang:error(bad_time_unit, [BadRes]). erlang_system_time_fallback() -> {MS, S, US} = erlang:now(), (MS*1000000+S)*1000000+US. os_system_time_fallback() -> {MS, S, US} = os:timestamp(), (MS*1000000+S)*1000000+US. convert_time_unit_fallback(Time, FromUnit, ToUnit) -> FU = integer_time_unit(FromUnit), TU = integer_time_unit(ToUnit), case Time < 0 of true -> TU*Time - (FU - 1); false -> TU*Time end div FU. is_valid_modifier_list([positive|Ms]) -> is_valid_modifier_list(Ms); is_valid_modifier_list([monotonic|Ms]) -> is_valid_modifier_list(Ms); is_valid_modifier_list([]) -> true; is_valid_modifier_list(_) -> false. erlydtl-0.15.0/src/erlydtl_unparser.erl000066400000000000000000000275751504712431400201660ustar00rootroot00000000000000-module(erlydtl_unparser). -export([unparse/1, unparse/2]). unparse(DjangoParseTree, undefined) -> unparse(DjangoParseTree); unparse(DjangoParseTree, true) -> Text = unparse(DjangoParseTree), Trimmed = re:replace(Text, <<"(^\\s+)|(\\s+$)|\n">>, <<"">>, [global, multiline]), Joined = join_iolist(Trimmed, " "), binary_to_list(iolist_to_binary(Joined)). join_iolist(IOList, Sep) -> join_iolist(IOList, Sep, []). join_iolist([[]|IOList], Sep, Acc) -> join_iolist(IOList, Sep, Acc); join_iolist([Data|IOList], Sep, Acc) -> join_iolist(IOList, Sep, [Sep, Data|Acc]); join_iolist([], _, [_|Acc]) -> lists:reverse(Acc); join_iolist(IOList, _, Acc) -> lists:reverse([IOList|Acc]). unparse(DjangoParseTree) -> do_unparse(DjangoParseTree). do_unparse(DjangoParseTree) -> do_unparse(DjangoParseTree, []). do_unparse([], Acc) -> lists:flatten(lists:reverse(Acc)); do_unparse([{'extends', Value}|Rest], Acc) -> do_unparse(Rest, [["{% extends ", unparse_value(Value), " %}"]|Acc]); do_unparse([{'autoescape', OnOrOff, Contents}|Rest], Acc) -> do_unparse(Rest, [["{% autoescape ", unparse_identifier(OnOrOff), " %}", do_unparse(Contents), "{% endautoescape %}"]|Acc]); do_unparse([{'block', Identifier, Contents}|Rest], Acc) -> do_unparse(Rest, [["{% block ", unparse_identifier(Identifier), " %}", do_unparse(Contents), "{% endblock %}"]|Acc]); do_unparse([{'blocktrans', Args, Contents, undefined}|Rest], Acc) -> do_unparse(Rest, [["{% blocktrans ", unparse_blocktrans_args(Args), " %}", do_unparse(Contents), "{% endblocktrans %}"]|Acc]); do_unparse([{'blocktrans', Args, Contents, PluralContents}|Rest], Acc) -> do_unparse(Rest, [["{% blocktrans ", unparse_blocktrans_args(Args), " %}", do_unparse(Contents), "{% plural %}", do_unparse(PluralContents), "{% endblocktrans %}"]|Acc]); do_unparse([{'call', Identifier}|Rest], Acc) -> do_unparse(Rest, [["{% call ", unparse_identifier(Identifier), " %}"]|Acc]); do_unparse([{'call', Identifier, With}|Rest], Acc) -> do_unparse(Rest, [["{% call ", unparse_identifier(Identifier), " with ", unparse_args(With), " %}"]|Acc]); do_unparse([{'comment', Contents}|Rest], Acc) -> do_unparse(Rest, [["{% comment %}", do_unparse(Contents), "{% endcomment %}"]|Acc]); do_unparse([{'comment_tag', _Pos, Text}|Rest], Acc) -> do_unparse(Rest, [["{#", Text, "#}"]|Acc]); do_unparse([{'cycle', Names}|Rest], Acc) -> do_unparse(Rest, [["{% cycle ", do_unparse(Names), " %}"]|Acc]); do_unparse([{'cycle_compat', Names}|Rest], Acc) -> do_unparse(Rest, [["{% cycle ", unparse_cycle_compat_names(Names), " %}"]|Acc]); do_unparse([{'date', 'now', Value}|Rest], Acc) -> do_unparse(Rest, [["{% now ", unparse_value(Value), " %}"]|Acc]); do_unparse([{'filter', FilterList, Contents}|Rest], Acc) -> do_unparse(Rest, [["{% filter ", unparse_filters(FilterList), " %}", do_unparse(Contents), "{% endfilter %}"]|Acc]); do_unparse([{'firstof', Vars}|Rest], Acc) -> do_unparse(Rest, [["{% firstof ", do_unparse(Vars), " %}"]|Acc]); do_unparse([{'for', {'in', IteratorList, Identifier}, Contents}|Rest], Acc) -> do_unparse(Rest, [["{% for ", unparse_identifier(Identifier), " in ", do_unparse(IteratorList), " %}", do_unparse(Contents), "{% endfor %}"]|Acc]); do_unparse([{'for', {'in', IteratorList, Identifier}, Contents, EmptyPartsContents}|Rest], Acc) -> do_unparse(Rest, [["{% for ", unparse_identifier(Identifier), " in ", do_unparse(IteratorList), " %}", do_unparse(Contents), "{% empty %}", do_unparse(EmptyPartsContents), "{% endfor %}"]|Acc]); do_unparse([{'if', Expression, Contents}|Rest], Acc) -> do_unparse(Rest, [["{% if ", unparse_expression(Expression), " %}", do_unparse(Contents), "{% endif %}"]|Acc]); do_unparse([{'ifchanged', Expression, IfContents}|Rest], Acc) -> do_unparse(Rest, [["{% ifchanged ", unparse_expression(Expression), " %}", do_unparse(IfContents), "{% endifchanged %}"]|Acc]); do_unparse([{'ifchangedelse', Expression, IfContents, ElseContents}|Rest], Acc) -> do_unparse(Rest, [["{% ifchanged ", unparse_expression(Expression), " %}", do_unparse(IfContents), "{% else %}", do_unparse(ElseContents), "{% endifchanged %}"]|Acc]); do_unparse([{'ifelse', Expression, IfContents, ElseContents}|Rest], Acc) -> do_unparse(Rest, [["{% if ", unparse_expression(Expression), " %}", do_unparse(IfContents), "{% else %}", do_unparse(ElseContents), "{% endif %}"]|Acc]); do_unparse([{'ifequal', [Arg1, Arg2], Contents}|Rest], Acc) -> do_unparse(Rest, [["{% ifequal ", unparse_value(Arg1), " ", unparse_value(Arg2), " %}", do_unparse(Contents), "{% endifequal %}"]|Acc]); do_unparse([{'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}|Rest], Acc) -> do_unparse(Rest, [["{% ifequal ", unparse_value(Arg1), " ", unparse_value(Arg2), " %}", do_unparse(IfContents), "{% else %}", do_unparse(ElseContents), "{% endifequal %}"]|Acc]); do_unparse([{'ifnotequal', [Arg1, Arg2], Contents}|Rest], Acc) -> do_unparse(Rest, [["{% ifnotequal ", unparse_value(Arg1), " ", unparse_value(Arg2), " %}", do_unparse(Contents), "{% endifnotequal %}"]|Acc]); do_unparse([{'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}|Rest], Acc) -> do_unparse(Rest, [["{% ifnotequal ", unparse_value(Arg1), " ", unparse_value(Arg2), " %}", do_unparse(IfContents), "{% else %}", do_unparse(ElseContents), "{% endifnotequal %}"]|Acc]); do_unparse([{'include', Value, []}|Rest], Acc) -> do_unparse(Rest, [["{% include ", unparse_value(Value), " %}"]|Acc]); do_unparse([{'include', Value, Args}|Rest], Acc) -> do_unparse(Rest, [["{% include ", unparse_value(Value), " with ", unparse_args(Args)]|Acc]); do_unparse([{'include_only', Value, []}|Rest], Acc) -> do_unparse(Rest, [["{% include ", unparse_value(Value), " only %}"]|Acc]); do_unparse([{'include_only', Value, Args}|Rest], Acc) -> do_unparse(Rest, [["{% include ", unparse_value(Value), " with ", unparse_args(Args), " only %}"]|Acc]); do_unparse([{'regroup', {Variable, Identifier1, Identifier2}, Contents}|Rest], Acc) -> do_unparse(Rest, [["{% regroup ", unparse_value(Variable), " by ", unparse_identifier(Identifier1), " as ", unparse_identifier(Identifier2), " %}", do_unparse(Contents), "{% endregroup %}"]|Acc]); do_unparse([{'spaceless', Contents}|Rest], Acc) -> do_unparse(Rest, [["{% spaceless %}", do_unparse(Contents), "{% endspaceless %}"]|Acc]); do_unparse([{'ssi', Arg}|Rest], Acc) -> do_unparse(Rest, [["{% ssi ", unparse_value(Arg), " %}"]|Acc]); do_unparse([{'ssi_parsed', Arg}|Rest], Acc) -> do_unparse(Rest, [["{% ssi ", unparse_value(Arg), " parsed %}"]|Acc]); do_unparse([{'string', _, String}|Rest], Acc) -> do_unparse(Rest, [[String]|Acc]); do_unparse([{'tag', Identifier, []}|Rest], Acc) -> do_unparse(Rest, [["{% ", unparse_identifier(Identifier), " %}"]|Acc]); do_unparse([{'tag', Identifier, Args}|Rest], Acc) -> do_unparse(Rest, [["{% ", unparse_identifier(Identifier), " ", unparse_args(Args), " %}"]|Acc]); do_unparse([{'templatetag', Identifier}|Rest], Acc) -> do_unparse(Rest, [["{% templatetag ", unparse_identifier(Identifier), " %}"]|Acc]); do_unparse([{'trans', Value}|Rest], Acc) -> do_unparse(Rest, [["{% trans ", unparse_value(Value), " %}"]|Acc]); do_unparse([{'widthratio', Numerator, Denominator, Scale}|Rest], Acc) -> do_unparse(Rest, [["{% widthratio ", unparse_value(Numerator), " ", unparse_value(Denominator), " ", unparse_value(Scale), " %}"]|Acc]); do_unparse([{'with', Args, Contents}|Rest], Acc) -> do_unparse(Rest, [["{% with ", unparse_args(Args), " %}", do_unparse(Contents), "{% endwidth %}"]|Acc]); do_unparse([ValueToken|Rest], Acc) -> do_unparse(Rest, [["{{ ", unparse_value(ValueToken), " }}"]|Acc]). unparse_identifier({identifier, _, Name}) -> atom_to_list(Name). unparse_filters(FilterList) -> unparse_filters(FilterList, []). unparse_filters([], Acc) -> lists:reverse(Acc); unparse_filters([Filter], Acc) -> unparse_filters([], [unparse_filter(Filter)|Acc]); unparse_filters([Filter|Rest], Acc) -> unparse_filters(Rest, lists:reverse([unparse_filter(Filter), "|"], Acc)). unparse_filter([Identifier]) -> unparse_identifier(Identifier); unparse_filter([Identifier, Arg]) -> [unparse_identifier(Identifier), ":", unparse_value(Arg)]. unparse_expression({'expr', "in", Arg1, Arg2}) -> [unparse_value(Arg1), " in ", unparse_value(Arg2)]; unparse_expression({'expr', "not", {'expr', "in", Arg1, Arg2}}) -> [unparse_value(Arg1), " not in ", unparse_value(Arg2)]; unparse_expression({'expr', "not", Expr}) -> ["not ", unparse_expression(Expr)]; unparse_expression({'expr', "eq", Arg1, Arg2}) -> [unparse_value(Arg1), " == ", unparse_value(Arg2)]; unparse_expression({'expr', "ne", Arg1, Arg2}) -> [unparse_value(Arg1), " != ", unparse_value(Arg2)]; unparse_expression({'expr', "ge", Arg1, Arg2}) -> [unparse_value(Arg1), " >= ", unparse_value(Arg2)]; unparse_expression({'expr', "le", Arg1, Arg2}) -> [unparse_value(Arg1), " <= ", unparse_value(Arg2)]; unparse_expression({'expr', "gt", Arg1, Arg2}) -> [unparse_value(Arg1), " > ", unparse_value(Arg2)]; unparse_expression({'expr', "lt", Arg1, Arg2}) -> [unparse_value(Arg1), " < ", unparse_value(Arg2)]; unparse_expression({'expr', "or", Arg1, Arg2}) -> [unparse_expression(Arg1), " or ", unparse_expression(Arg2)]; unparse_expression({'expr', "and", Arg1, Arg2}) -> [unparse_expression(Arg1), " and ", unparse_expression(Arg2)]; unparse_expression(Other) -> unparse_value(Other). unparse_value({'string_literal', _, Value}) -> Value; unparse_value({'number_literal', _, Value}) -> Value; unparse_value({'apply_filter', Variable, Filter}) -> [unparse_value(Variable), "|", unparse_filter(Filter)]; unparse_value({'attribute', {Variable, Identifier}}) -> [unparse_value(Variable), ".", unparse_identifier(Identifier)]; unparse_value({'variable', Identifier}) -> unparse_identifier(Identifier). unparse_args(Args) -> unparse_args(Args, []). unparse_args([], Acc) -> collect_args_acc(Acc); unparse_args([{{identifier, _, Name}, Value}|Args], Acc) -> unparse_args(Args, [[atom_to_list(Name), "=", unparse_value(Value)]|Acc]). unparse_cycle_compat_names(Names) -> unparse_cycle_compat_names(Names, []). unparse_cycle_compat_names([], Acc) -> lists:reverse(Acc); unparse_cycle_compat_names([{identifier, _, Name}], Acc) -> unparse_cycle_compat_names([], [atom_to_list(Name)|Acc]); unparse_cycle_compat_names([{identifier, _, Name}|Rest], Acc) -> unparse_cycle_compat_names(Rest, lists:reverse([atom_to_list(Name), ", "], Acc)). unparse_blocktrans_args(Args) -> unparse_blocktrans_args(Args, []). unparse_blocktrans_args([], Acc) -> collect_args_acc(Acc); unparse_blocktrans_args([{args, WithArgs}|Args], Acc) -> unparse_blocktrans_args( Args, [["with ", unparse_args(WithArgs)]|Acc]); unparse_blocktrans_args([{count, Count}|Args], Acc) -> unparse_blocktrans_args( Args, [["count ", unparse_args([Count])]|Acc]); unparse_blocktrans_args([{context, Context}|Args], Acc) -> unparse_blocktrans_args( Args, [["context ", unparse_value(Context)]|Acc]); unparse_blocktrans_args([trimmed|Args], Acc) -> unparse_blocktrans_args( Args, ["trimmed"|Acc]). collect_args_acc(Acc) -> lists:flatten(string:join(lists:reverse(Acc), " ")). erlydtl-0.15.0/src/filter_lib/000077500000000000000000000000001504712431400161575ustar00rootroot00000000000000erlydtl-0.15.0/src/filter_lib/erlydtl_dateformat.erl000066400000000000000000000370471504712431400225630ustar00rootroot00000000000000-module(erlydtl_dateformat). -export([format/1, format/2, format/3, format/4]). -define(TAG_SUPPORTED(C), C =:= $a orelse C =:= $A orelse C =:= $b orelse C =:= $B orelse C =:= $c orelse C =:= $d orelse C =:= $D orelse C =:= $E orelse C =:= $f orelse C =:= $F orelse C =:= $g orelse C =:= $G orelse C =:= $h orelse C =:= $H orelse C =:= $i orelse C =:= $I orelse C =:= $j orelse C =:= $l orelse C =:= $L orelse C =:= $m orelse C =:= $M orelse C =:= $n orelse C =:= $N orelse C =:= $O orelse C =:= $P orelse C =:= $r orelse C =:= $s orelse C =:= $S orelse C =:= $t orelse C =:= $T orelse C =:= $U orelse C =:= $w orelse C =:= $W orelse C =:= $y orelse C =:= $Y orelse C =:= $z orelse C =:= $Z orelse C =:= $o ). %% %% Format the current date/time %% format(FormatString) when is_binary(FormatString) -> format(binary_to_list(FormatString), fun stub_tran/2, <<>>); format(FormatString) -> {Date, Time} = erlang:localtime(), replace_tags(Date, Time, FormatString, fun stub_tran/2, <<>>). %% %% Format a tuple of the form {{Y,M,D},{H,M,S}} %% This is the format returned by erlang:localtime() %% and other standard date/time BIFs %% format(DateTime, FormatString) when is_binary(FormatString) -> format(DateTime, binary_to_list(FormatString),fun stub_tran/2, <<>>); format({{_,_,_} = Date,{_,_,_} = Time}, FormatString) -> replace_tags(Date, Time, FormatString, fun stub_tran/2, <<>> ); %% %% Format a tuple of the form {Y,M,D} %% format({_,_,_} = Date, FormatString) -> replace_tags(Date, {0,0,0}, FormatString, fun stub_tran/2, <<>> ); format(DateTime, FormatString) -> io:format("Unrecognised date parameter : ~p~n", [DateTime]), FormatString. %% The same set of functions with TranslationFunction and Locale args %% Translation function may be 'none' atom - handle this separately %% replacing atom with a stub function (it's easier to do it this way) format(FormatString, TransFun, Locale) when is_binary(FormatString) -> format(binary_to_list(FormatString), TransFun, Locale); format(FormatString, none, _Locale) -> format(FormatString, fun stub_tran/2, <<>>); format(FormatString, TransFun, Locale) -> {Date, Time} = erlang:localtime(), replace_tags(Date, Time, FormatString, TransFun, Locale). format(DateTime, FormatString, TransFun, Locale) when is_binary(FormatString) -> format(DateTime, binary_to_list(FormatString), TransFun, Locale); format(DateTime, FormatString, none, _Locale) -> format(DateTime, FormatString, fun stub_tran/2, <<>>); format({{_,_,_} = Date,{_,_,_} = Time}, FormatString, TransFun, Locale) -> replace_tags(Date, Time, FormatString, TransFun, Locale ); format({_,_,_} = Date, FormatString, none, _Locale) -> replace_tags(Date, {0,0,0}, FormatString, fun stub_tran/2, <<>>); format({_,_,_} = Date, FormatString, TransFun, Locale) -> replace_tags(Date, {0,0,0}, FormatString, TransFun, Locale); format(DateTime, FormatString, _TransFun, _Locale) -> io:format("Unrecognised date parameter : ~p~n", [DateTime]), FormatString. replace_tags(Date, Time, Input, TransFun, Locale) -> replace_tags(Date, Time, Input, [], noslash, TransFun, Locale). replace_tags(_Date, _Time, [], Out, _State, _TransFun, _Locale) -> lists:reverse(Out); replace_tags(Date, Time, [C|Rest], Out, noslash, TransFun, Locale) when ?TAG_SUPPORTED(C) -> case tag_to_value(C, Date, Time, TransFun, Locale) of V when is_binary(V) -> replace_tags(Date, Time, Rest, [V] ++ Out, noslash, TransFun, Locale); V when is_list(V) -> replace_tags(Date, Time, Rest, lists:reverse(V) ++ Out, noslash, TransFun, Locale) end; replace_tags(Date, Time, [$\\|Rest], Out, noslash, TransFun, Locale) -> replace_tags(Date, Time, Rest, Out, slash, TransFun, Locale); replace_tags(Date, Time, [C|Rest], Out, slash, TransFun, Locale) -> replace_tags(Date, Time, Rest, [C|Out], noslash, TransFun, Locale); replace_tags(Date, Time, [C|Rest], Out, _State, TransFun, Locale) -> replace_tags(Date, Time, Rest, [C|Out], noslash, TransFun, Locale). %%----------------------------------------------------------- %% Time formatting %%----------------------------------------------------------- %% 'a.m.' or 'p.m.' tag_to_value($a, _, {H, _, _}, TransFun, Locale) when H > 11 -> TransFun("p.m.", Locale); tag_to_value($a, _, _, TransFun, Locale) -> TransFun("a.m.", Locale); %% 'AM' or 'PM' tag_to_value($A, _, {H, _, _}, TransFun, Locale) when H > 11 -> TransFun("PM", Locale); tag_to_value($A, _, _, TransFun, Locale) -> TransFun("AM", Locale); %% Swatch Internet time tag_to_value($B, _, _, _TransFun, _Locale) -> ""; %% NotImplementedError %% ISO 8601 Format. tag_to_value($c, Date, Time, TransFun, Locale) -> tag_to_value($Y, Date, Time, TransFun, Locale) ++ "-" ++ tag_to_value($m, Date, Time, TransFun, Locale) ++ "-" ++ tag_to_value($d, Date, Time, TransFun, Locale) ++ "T" ++ tag_to_value($H, Date, Time, TransFun, Locale) ++ ":" ++ tag_to_value($i, Date, Time, TransFun, Locale) ++ ":" ++ tag_to_value($s, Date, Time, TransFun, Locale); %% %% Time, in 12-hour hours and minutes, with minutes %% left off if they're zero. %% %% Examples: '1', '1:30', '2:05', '2' %% %% Proprietary extension. %% tag_to_value($f, Date, {H, 0, S}, TransFun, Locale) -> %% If min is zero then return the hour only tag_to_value($g, Date, {H, 0, S}, TransFun, Locale); tag_to_value($f, Date, Time, TransFun, Locale) -> %% Otherwise return hours and mins tag_to_value($g, Date, Time, TransFun, Locale) ++ ":" ++ tag_to_value($i, Date, Time, TransFun, Locale); %% Hour, 12-hour format without leading zeros; i.e. '1' to '12' tag_to_value($g, _, {H,_,_}, _TransFun, _Locale) -> integer_to_list(hour_24to12(H)); %% Hour, 24-hour format without leading zeros; i.e. '0' to '23' tag_to_value($G, _, {H,_,_}, _TransFun, _Locale) -> integer_to_list(H); %% Hour, 12-hour format; i.e. '01' to '12' tag_to_value($h, _, {H,_,_}, _TransFun, _Locale) -> integer_to_list_zerofill(hour_24to12(H)); %% Hour, 24-hour format; i.e. '00' to '23' tag_to_value($H, _, {H,_,_}, _TransFun, _Locale) -> integer_to_list_zerofill(H); %% Minutes; i.e. '00' to '59' tag_to_value($i, _, {_,M,_}, _TransFun, _Locale) -> integer_to_list_zerofill(M); %% Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off %% if they're zero and the strings 'midnight' and 'noon' if appropriate. %% Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.' %% Proprietary extension. tag_to_value($P, _, {0, 0, _}, TransFun, Locale) -> TransFun("midnight", Locale); tag_to_value($P, _, {12, 0, _}, TransFun, Locale) -> TransFun("noon", Locale); tag_to_value($P, Date, Time, TransFun, Locale) -> tag_to_value($f, Date, Time, TransFun, Locale) ++ " " ++ tag_to_value($a, Date, Time, TransFun, Locale); %% Seconds; i.e. '00' to '59' tag_to_value($s, _, {_,_,S}, _TransFun, _Locale) -> integer_to_list_zerofill(S); %%----------------------------------------------------------- %% Date formatting %%----------------------------------------------------------- %% Month, textual, 3 letters, lowercase; e.g. 'jan' tag_to_value($b, {_,M,_}, _, TransFun, Locale) -> TransFun(string:sub_string(monthname(M), 1, 3), Locale); %% Day of the month, 2 digits with leading zeros; i.e. '01' to '31' tag_to_value($d, {_, _, D}, _, _TransFun, _Locale) -> integer_to_list_zerofill(D); %% Day of the week, textual, 3 letters; e.g. 'Fri' tag_to_value($D, Date, _, TransFun, Locale) -> Dow = calendar:day_of_the_week(Date), TransFun(ucfirst(string:sub_string(dayname(Dow), 1, 3)), Locale); %% Month, textual, long, alternative; e.g. 'Listopada' tag_to_value($E, {_,M,_}, _, TransFun, Locale) -> TransFun(ucfirst(monthname(M)), {Locale, <<"alt. month">>}); %% Month, textual, long; e.g. 'January' tag_to_value($F, {_,M,_}, _, TransFun, Locale) -> TransFun(ucfirst(monthname(M)), Locale); %% '1' if Daylight Savings Time, '0' otherwise. tag_to_value($I, _, _, _TransFun, _Locale) -> "TODO"; %% Day of the month without leading zeros; i.e. '1' to '31' tag_to_value($j, {_, _, D}, _, _TransFun, _Locale) -> integer_to_list(D); %% Day of the week, textual, long; e.g. 'Friday' tag_to_value($l, Date, _, TransFun, Locale) -> TransFun(ucfirst(dayname(calendar:day_of_the_week(Date))), Locale); %% Boolean for whether it is a leap year; i.e. True or False tag_to_value($L, {Y,_,_}, _, _TransFun, _Locale) -> case calendar:is_leap_year(Y) of true -> "True"; _ -> "False" end; %% Month; i.e. '01' to '12' tag_to_value($m, {_, M, _}, _, _TransFun, _Locale) -> integer_to_list_zerofill(M); %% Month, textual, 3 letters; e.g. 'Jan' tag_to_value($M, {_,M,_}, _, TransFun, Locale) -> TransFun(ucfirst(string:sub_string(monthname(M), 1, 3)), Locale); %% Month without leading zeros; i.e. '1' to '12' tag_to_value($n, {_, M, _}, _, _TransFun, _Locale) -> integer_to_list(M); %% Month abbreviation in Associated Press style. Proprietary extension. tag_to_value($N, {_,M,_}, _, TransFun, Locale) when M =:= 9 -> %% Special case - "Sept." TransFun(ucfirst(string:sub_string(monthname(M), 1, 4)) ++ ".", {Locale, <<"abbrev. month">>}); tag_to_value($N, {_,M,_}, _, TransFun, Locale) when M < 3 orelse M > 7 -> %% Jan, Feb, Aug, Oct, Nov, Dec are all %% abbreviated with a full-stop appended. TransFun(ucfirst(string:sub_string(monthname(M), 1, 3)) ++ ".", {Locale, <<"abbrev. month">>}); tag_to_value($N, {_,M,_}, _, TransFun, Locale) -> %% The rest are the fullname. TransFun(ucfirst(monthname(M)), {Locale, <<"abbrev. month">>}); %% Difference to Greenwich time in hours; e.g. '+0200' tag_to_value($O, Date, Time, _TransFun, _Locale) -> Diff = utc_diff(Date, Time), Offset = if Diff < 0 -> io_lib:format("-~4..0w", [abs(Diff)]); true -> io_lib:format("+~4..0w", [Diff]) end, lists:flatten(Offset); %% RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' tag_to_value($r, Date, Time, _TransFun, _Locale) -> % afaik, date should not be translated in case RFC format is specified. replace_tags(Date, Time, "D, j M Y H:i:s O", fun stub_tran/2, <<>> ); %% English ordinal suffix for the day of the month, 2 characters; %% i.e. 'st', 'nd', 'rd' or 'th' tag_to_value($S, {_, _, D}, _, _TransFun, _Locale) when D rem 100 =:= 11 orelse D rem 100 =:= 12 orelse D rem 100 =:= 13 -> "th"; tag_to_value($S, {_, _, D}, _, _TransFun, _Locale) when D rem 10 =:= 1 -> "st"; tag_to_value($S, {_, _, D}, _, _TransFun, _Locale) when D rem 10 =:= 2 -> "nd"; tag_to_value($S, {_, _, D}, _, _TransFun, _Locale) when D rem 10 =:= 3 -> "rd"; tag_to_value($S, _, _, _TransFun, _Locale) -> "th"; %% Number of days in the given month; i.e. '28' to '31' tag_to_value($t, {Y,M,_}, _, _TransFun, _Locale) -> integer_to_list(calendar:last_day_of_the_month(Y,M)); %% Time zone of this machine; e.g. 'EST' or 'MDT' tag_to_value($T, _, _, _TransFun, _Locale) -> "TODO"; %% Seconds since the Unix epoch (January 1 1970 00:00:00 GMT) tag_to_value($U, Date, Time, _TransFun, _Locale) -> EpochSecs = calendar:datetime_to_gregorian_seconds({Date, Time}) - calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}), integer_to_list(EpochSecs); %% Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday) tag_to_value($w, Date, _, _TransFun, _Locale) -> %% Note: calendar:day_of_the_week returns %% 1 | .. | 7. Monday = 1, Tuesday = 2, ..., Sunday = 7 integer_to_list(calendar:day_of_the_week(Date) rem 7); %% ISO-8601 week number of year, weeks starting on Monday tag_to_value($W, {Y,M,D}, _, _TransFun, _Locale) -> integer_to_list(year_weeknum(Y,M,D)); %% Year, 2 digits; e.g. '99' tag_to_value($y, {Y, _, _}, _, _TransFun, _Locale) -> string:sub_string(integer_to_list(Y), 3); %% Year, 4 digits; e.g. '1999' tag_to_value($Y, {Y, _, _}, _, _TransFun, _Locale) -> integer_to_list(Y); %% Day of the year; i.e. '0' to '365' tag_to_value($z, {Y,M,D}, _, _TransFun, _Locale) -> integer_to_list(day_of_year(Y,M,D)); %% Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for %% timezones west of UTC is always negative, and for those east of UTC is %% always positive. tag_to_value($Z, _, _, _TransFun, _Locale) -> "TODO"; %% o – the ISO 8601 year number tag_to_value($o, {Y,M,D}, _, _TransFun, _Locale) -> integer_to_list(weeknum_year(Y,M,D)); tag_to_value(C, Date, Time, _TransFun, _Locale) -> io:format("Unimplemented tag : ~p [Date : ~p] [Time : ~p]", [C, Date, Time]), "". %% Date helper functions day_of_year(Y,M,D) -> day_of_year(Y,M,D,0). day_of_year(_Y,M,D,Count) when M =< 1 -> D + Count; day_of_year(Y,M,D,Count) when M =< 12 -> day_of_year(Y, M - 1, D, Count + calendar:last_day_of_the_month(Y,M)); day_of_year(Y,_M,D,_Count) -> day_of_year(Y, 12, D, 0). hour_24to12(0) -> 12; hour_24to12(H) when H < 13 -> H; hour_24to12(H) when H < 24 -> H - 12; hour_24to12(H) -> H. year_weeknum(Y,M,D) -> First = (calendar:day_of_the_week(Y, 1, 1) rem 7) - 1, Wk = ((((calendar:date_to_gregorian_days(Y, M, D) - calendar:date_to_gregorian_days(Y, 1, 1)) + First) div 7) + (case First < 4 of true -> 1; _ -> 0 end)), case Wk of 0 -> weeks_in_year(Y - 1); _ -> case weeks_in_year(Y) of WksInThisYear when Wk > WksInThisYear -> 1; _ -> Wk end end. weeknum_year(Y,M,D) -> WeekNum = year_weeknum(Y,M,D), case {M, WeekNum} of {1, 53} -> Y - 1; {1, 52} -> Y - 1; {12, 1} -> Y + 1; {12, 2} -> Y + 1; _ -> Y end. weeks_in_year(Y) -> D1 = calendar:day_of_the_week(Y, 1, 1), D2 = calendar:day_of_the_week(Y, 12, 31), if (D1 =:= 4 orelse D2 =:= 4) -> 53; true -> 52 end. utc_diff({Y, M, D}, Time) when Y < 1970-> utc_diff({1970, M, D}, Time); utc_diff(Date, Time) -> LTime = {Date, Time}, UTime = erlang:localtime_to_universaltime(LTime), DiffSecs = calendar:datetime_to_gregorian_seconds(LTime) - calendar:datetime_to_gregorian_seconds(UTime), trunc((DiffSecs / 3600) * 100). dayname(1) -> "monday"; dayname(2) -> "tuesday"; dayname(3) -> "wednesday"; dayname(4) -> "thursday"; dayname(5) -> "friday"; dayname(6) -> "saturday"; dayname(7) -> "sunday". monthname(1) -> "january"; monthname(2) -> "february"; monthname(3) -> "march"; monthname(4) -> "april"; monthname(5) -> "may"; monthname(6) -> "june"; monthname(7) -> "july"; monthname(8) -> "august"; monthname(9) -> "september"; monthname(10) -> "october"; monthname(11) -> "november"; monthname(12) -> "december". %% Utility functions integer_to_list_zerofill(N) when is_float(N) -> integer_to_list_zerofill(erlang:round(N)); integer_to_list_zerofill(N) when N < 10 -> lists:flatten(io_lib:format("~2..0B", [N])); integer_to_list_zerofill(N) when is_integer(N) -> integer_to_list(N). ucfirst([First | Rest]) when First >= $a, First =< $z -> [First-($a-$A) | Rest]; ucfirst(Other) -> Other. stub_tran(A,_) -> % useful for test debuggging % io:format("calling stub translation!!!",[]), A. erlydtl-0.15.0/src/filter_lib/erlydtl_slice.erl000066400000000000000000000172121504712431400215240ustar00rootroot00000000000000-module(erlydtl_slice). -export([slice/2,slice_input_cases/7]). -ifdef(TEST). -undef(TEST). -endif. -define(TEST,""). -define(NOTEST,1). %% remark out NODEBUG when running tests; unremark when debugging individual use cases -define(NODEBUG,1). -include_lib("eunit/include/eunit.hrl"). slice(List,":") -> List; slice(List,Index) -> ListLength = erlang:length(List), {Start,End,C1,C2,Step} = parse_index(Index), try slice_input_cases(List,ListLength,Start,C1,End,C2,Step) catch throw:outOfBounds -> []; throw:indexError -> indexError end. slice_input_cases(_List,ListLength,Start,false,[],false,[]) when Start > 0, Start >= ListLength -> throw(indexError); slice_input_cases(_List,ListLength,Start,false,[],false,[]) when Start < 0, Start < -ListLength -> throw(indexError); %%[-1] slice_input_cases(List,ListLength,Start,false,[],false,[]) when Start<0 -> S = start_transform(ListLength,Start+ListLength+1), LowerBound = single_index_bounds(S), ?debugFmt("slice_transform exit: ~p, ~p, ~p~n",[List,S,LowerBound]), %%[Result] = lists:sublist(List,LowerBound,Step), lists:nth(LowerBound,List); %%[1] slice_input_cases(List,ListLength,Start,false,[],false,[]) -> %%S = start_transform(ListLength,Start+1), %%E = end_transform(ListLength,Start+2), Step = 1, End = Start + 1, {Start1,End1,Step1} = index_defaults(ListLength,Start,End,Step), S = start_transform(ListLength,Start1), E = end_transform(ListLength,End1), ?debugFmt("slice_transform: S,E,Step1: ~p,~p,~p~n",[S,E,Step1]), [Result] = slice_list(List,ListLength,S,false,E,false,Step1), Result; %%slice_transform(List, ListLength, Start, C1, End, C2, Step) when End < 0, Step > 0 -> %% []; %%slice_transform(List, ListLength, Start, C1, End, C2, Step) when End > 0, Step < 0 -> %% []; %%[::-1] slice_input_cases(List,ListLength,[],true,[],true,Step) when Step < 0 -> ?debugMsg("here"), slice_transform(List,ListLength,ListLength,true,-2*(ListLength+1),true,Step); %%[::1] slice_input_cases(List,ListLength,[],true,[],true,Step) when Step > 0 -> slice_transform(List,ListLength,0,true,ListLength,true,Step); slice_input_cases(List,ListLength,Start,C1,End,C2,Step) -> slice_transform(List,ListLength,Start,C1,End,C2,Step). %%[N:N:N] slice_transform(List,ListLength,Start,C1,End,C2,Step) -> {Start1,End1,Step1} = index_defaults(ListLength,Start,End,Step), S = start_transform(ListLength,Start1), E = end_transform(ListLength,End1), ?debugFmt("slice_transform: S,C1,E,C2,Step1: ~p,~p,~p,~p,~p~n",[S,C1,E,C2,Step1]), slice_list(List,ListLength,S,C1,E,C2,Step1). %%[N:N:N] slice_list(_List,_ListLength,Start,_C1,End,_C2,Step) when Start > End, Step > 0 -> throw(outOfBounds); slice_list(_List,_ListLength,Start,_C1,End,_C2,Step) when Start < End andalso Step < 0 -> throw(outOfBounds); slice_list(_List,_ListLength,Start,_C1,End,_C2,_Step) when Start < 0 andalso End < 0 -> throw(outOfBounds); slice_list(_List,ListLength,Start,_C1,End,_C2,_Step) when Start > ListLength andalso End > ListLength-1 -> throw(outOfBounds); slice_list(List,ListLength,Start,_C1,End,_C2,Step) when Step > 0 -> {LowerBound,UpperBound} = index_bounds(Step,ListLength,Start,End), ?debugFmt("LowerBound+1, UpperBound+1, UpperBound - LowerBound + 1: ~p, ~p, ~p~n",[LowerBound+1,UpperBound,UpperBound-LowerBound]), BoundList = lists:sublist(List,LowerBound+1,UpperBound-LowerBound), SequenceList = lists:seq(1,erlang:length(BoundList),Step), lists:map(fun (N) -> lists:nth(N,BoundList) end,SequenceList); slice_list(List,ListLength,Start,_C1,End,_C2,Step) when Step < 0 -> {LowerBound,UpperBound} = index_bounds(Step,ListLength,Start,End), %%S1 = S0 - 1, ?debugFmt("Start,End: ~p, ~p~n",[Start,End]), case erlang:abs(End) > ListLength of true -> ?debugFmt("LowerBound, UpperBound, UpperBound - LowerBound + 1: ~p, ~p, ~p~n",[LowerBound+1,UpperBound,UpperBound-LowerBound+1]), BoundList = lists:sublist(List, LowerBound+1, UpperBound - LowerBound + 1); false -> ?debugFmt("LowerBound+2, UpperBound, UpperBound - LowerBound: ~p, ~p, ~p~n",[LowerBound+2,UpperBound,UpperBound-LowerBound]), BoundList = lists:sublist(List, LowerBound+2, UpperBound - LowerBound) end, ?debugFmt("BoundList: ~p~n",[BoundList]), SequenceList = lists:seq(erlang:length(BoundList),1,Step), ?debugFmt("SequenceList: ~p~n",[SequenceList]), lists:map(fun (N) -> lists:nth(N,BoundList) end,SequenceList). index_defaults(ListLength, Start, End, Step) -> case Start==[] of true -> Start1 = 0; false -> Start1 = Start end, case End==[] of true -> End1 = ListLength; false -> End1 = End end, case Step==[] of true -> Step1 = 1; false -> Step1 = Step end, {Start1, End1, Step1}. single_index_bounds(S) -> if S >= 0 -> LowerBound = S; S < 0 -> LowerBound = 0 end, LowerBound. index_bounds(Step1, ListLength, S, E) -> AbsListLength = erlang:abs(ListLength), case Step1 < 0 of true -> ?debugMsg("true"), if S > AbsListLength -> UpperBound = ListLength; S =< AbsListLength -> UpperBound = S end, if E >= 0 -> LowerBound = E; %%List1 = tl(List); E < 0 -> LowerBound = 0 %%List1 = List end; false -> ?debugMsg("false"), if S >= 0 -> LowerBound = S; S < 0 -> LowerBound = 0 end, if E > AbsListLength -> UpperBound = ListLength; E =< AbsListLength -> UpperBound = E end end, ?debugFmt("index_bounds: LowerBound,UpperBound: ~p,~p~n",[LowerBound,UpperBound]), {LowerBound, UpperBound}. parse_index(Index) -> ParsedIndex = re:replace(Index, "([0-9\-]+)?(:)?([0-9\-]+)?(:)?([0-9\-]+)?", "\\1 ,\\2 ,\\3 ,\\4 ,\\5 ", [{return,list}]), ParsedIndex1 = lists:map(fun(X) -> string:strip(X) end, string:tokens(ParsedIndex, ",")), [Start, D1, End, D2, Step] = ParsedIndex1, Start1 = cast_to_integer(Start), End1 = cast_to_integer(End), C1 = parse_colon(D1), C2 = parse_colon(D2), Step1 = cast_to_integer(Step), ?debugFmt("Parsed: Start1, End1, C1, C2, Step1: ~p, ~p, ~p, ~p, ~p~n",[Start1, End1, C1, C2, Step1]), {Start1, End1, C1, C2, Step1}. start_transform(_ListLength, []) -> ?debugFmt("start_transform: ~p~n", [0]), 0; start_transform(ListLength, Start) -> case Start >= 0 orelse erlang:abs(Start) > ListLength of true -> ?debugFmt("start_transform: ~p~n", [Start]), Start; false -> ?debugFmt("start_transform: ~p~n", [ListLength + Start]), ListLength + Start end. end_transform(ListLength, []) -> ?debugFmt("end_transform: ~p~n", [ListLength]), ListLength; end_transform(ListLength, End) -> case End >= 0 orelse erlang:abs(End) > ListLength of true -> ?debugFmt("end_transform: ~p~n", [End]), End; false -> ?debugFmt("end_transform: ~p~n", [ListLength + End]), ListLength + End end. cast_to_integer([]) -> []; cast_to_integer(Input) when is_list(Input)-> case lists:member($., Input) of true -> erlang:round(erlang:list_to_float(Input)); false -> erlang:list_to_integer(Input) end. parse_colon([]) -> false; parse_colon(Colon) -> case Colon of ":" -> true; _ -> false end. erlydtl-0.15.0/src/i18n/000077500000000000000000000000001504712431400146235ustar00rootroot00000000000000erlydtl-0.15.0/src/i18n/i18n_manager.erl000066400000000000000000000107101504712431400175770ustar00rootroot00000000000000%% Author: dave %% Created: Feb 26, 2010 %% Description: TODO: Add description to dets_generator -module(i18n_manager). %% %% Include files %% %% Exported Functions %% -export([generate_pos/1]). -define(EPOT_TABLE,epos). -define(EPOT_TABLE_FUZZY,epos_fuzzy). %% %% API Functions %% generate_pos([Lang,Files])-> io:format("~s -> ~s ~n",[Lang,Files]), SplittedLocales = string:tokens(Lang,","), SplittedFiles = string:tokens(Files, ","), ProcessedFiles = sources_parser:parse(SplittedFiles), io:format("Parsed tokens are ~p~n",[ProcessedFiles]), BaseDir = "lang/default/", PopulateTable = fun(Language) -> io:format("-------------------------Generating po file for ~s-------------------------~n",[Language]), ok = open_table(Language), put(locale, Language), insert_tokens(ProcessedFiles), %%Recover already present translations TranslationsForLanguage = po_scanner:scan(BaseDir ++ Language ++ "/gettext.po"), io:format("Updating translations~n"), insert_translations(TranslationsForLanguage), Data = dets_data(), io:format("Generating po file ~n"), Fuzzy = dets_fuzzy(), po_generator:generate_file(Language, Data, Fuzzy), io:format("Closing files ~n"), ok = close_tables(Language), io:format("All files closed ~n") end, lists:foreach(PopulateTable, SplittedLocales), init:stop(). %% %% Local Functions %% %% Open a temporal table for a given locale open_table(Locale)-> Dir = "./lang/tmp/" ++ Locale, io:format("Creating dir ~s~n",[Dir]), ok = file:del_dir(Dir), ok = file:make_dir(Dir), OpenTable = fun({TableName, TableFile}, ok) -> File = Dir ++ TableFile, case dets:open_file(TableName, [{file, File}]) of {ok,Ref} -> io:format("Opened DETS ~p ~p~n",[TableName,Ref]); _Error -> io:format("Error opening DETS~p~n",[_Error]) end end, lists:foldl(OpenTable, ok, [{?EPOT_TABLE,"/epot.dets"},{?EPOT_TABLE_FUZZY,"/epot_fuzzy.dets"}]). %%TODO better way to do cleanup close_tables(Locale) -> %%dets:delete_all_objects(?EPOT_TABLE), ok = dets:close(?EPOT_TABLE), ok = dets:close(?EPOT_TABLE_FUZZY), ok = file:delete("./lang/tmp/" ++ Locale ++ "/epot.dets"), file:delete("./lang/tmp/" ++ Locale ++ "/epot_fuzzy.dets"). %%Get all data from dets table dets_data() -> dets:foldl(fun(E, Acc) -> [E|Acc] end, [], ?EPOT_TABLE). dets_fuzzy() -> dets:foldl(fun(E, Acc) -> [E|Acc] end, [], ?EPOT_TABLE_FUZZY). insert_tokens([]) -> noop; insert_tokens([{Id,{Fname,Line,_Col}}|Tail]) -> ok = insert_token(Id, Id, Fname, Line), insert_tokens(Tail). insert_token(Id, Translation,Fname,Line)-> FileInfo = get_file_info(Id), %%File info are all files where this string is present AllFileReferences = lists:sort( [{Fname,Line} | FileInfo] ), dets:insert(?EPOT_TABLE, {Id, Translation,AllFileReferences}). insert_translations([]) -> noop; insert_translations(L = [H|T]) -> %%io:format("Remaining ~p~n",[L]), case H of {comment, _} -> %%Comments are skipped insert_translations(T); _Other -> [{id,Id}, {str,Str}|Tail] = L, ok = insert_translation(Id,Str), insert_translations(Tail) end. insert_translation(Id, Translation) -> io:format("Updating translation for ~p to ~p ~n",[Id,Translation]), case Id of [] -> noop; Id -> case dets:lookup(?EPOT_TABLE,Id) of [] -> %%Fuzzy translation! dets:insert(?EPOT_TABLE_FUZZY, {Id, Translation,fuzzy}); [{Id, _StoredTranslation,FileInfo}] -> %%TODO check for translation unicity io:format("Recovered translation for ~p ~p ~n",[Id,_StoredTranslation]), dets:insert(?EPOT_TABLE, {Id, Translation,FileInfo}) end end. get_file_info(Key) -> case dets:lookup(?EPOT_TABLE, Key) of [] -> []; [{_,_,Finfo}|_] -> Finfo end. erlydtl-0.15.0/src/i18n/po_generator.erl000066400000000000000000000041101504712431400200070ustar00rootroot00000000000000%% Author: dave %% Created: Mar 1, 2010 %% Description: Generates po files from dets tables, based on erlang gettext impl -module(po_generator). -define(ENDCOL, 72). %% %% Include files %% %% %% Exported Functions %% -export([generate_file/3]). %% %% API Functions %% -define(LANG_DIR, "lang"). -define(POFILE, "gettext.po"). generate_file(Lang,Items, Fuzzy) -> Gettext_App_Name = "tmp", GtxtDir = ".", io:format("Opening po file"), DefDir = filename:join([GtxtDir, ?LANG_DIR, Gettext_App_Name, Lang]), Fname = filename:join([DefDir, ?POFILE]), filelib:ensure_dir(Fname), {ok,Fd} = file:open(Fname, [write]), put(fd,Fd), gettext_compile:write_header(), io:format("Writing entries~n"), write_entries(Items), io:format("Writing fuzzy entries~n"), write_fuzzy_entries(Fuzzy), file:close(Fd). %% %% Local Functions %% to_list(A) when is_atom(A) -> atom_to_list(A); to_list(I) when is_integer(I) -> integer_to_list(I); to_list(B) when is_binary(B) -> binary_to_list(B); to_list(L) when is_list(L) -> L. write_entries(Items)-> Fd = get(fd), F = fun({Id,Translation,Finfo}) -> Fun = fun({Fname,LineNo}, Acc) -> Fname ++ ":" ++ to_list(LineNo) ++ [$\s|Acc] end, Fi = lists:foldr(Fun,[],Finfo), io:format(Fd, "~n#: ~s~n", [Fi]), ok = file:write(Fd, "msgid \"\"\n"), gettext_compile:write_pretty(Id, Fd), ok = file:write(Fd, "msgstr \"\"\n"), gettext_compile:write_pretty(Translation, Fd) end, lists:foreach(F, Items). write_fuzzy_entries(Items) -> Fd = get(fd), ok = file:write(Fd, "\n"), F = fun({Id,Translation,_}) -> ok = file:write(Fd, "#, fuzzy\n"), ok = file:write(Fd, "msgid \"\"\n"), gettext_compile:write_pretty(Id, Fd), ok = file:write(Fd, "msgstr \"\"\n"), gettext_compile:write_pretty(Translation, Fd), ok = file:write(Fd, "\n") end, lists:foreach(F, Items). erlydtl-0.15.0/src/i18n/po_scanner.erl000066400000000000000000000066311504712431400174640ustar00rootroot00000000000000%% Author: dave %% Created: Mar 1, 2010 %% Description: TODO: Add description to po_scanner -module(po_scanner). %% %% Include files %% %% %% Exported Functions %% -export([scan/1]). %% %% API Functions %% scan(Path) -> case file:read_file(Path) of {ok,File} -> Str = re:replace(File, "\\\\n", "\\\n", [global, {return,list}]), scan(Str, [], {1, 1}, [in_text]); _Error -> io:format("No po file found at path ~p~n",[Path]), [] end. scan("#" ++ T, Scanned, {Row, Column}, Status = [in_text]) -> scan(T, Scanned, {Row, Column + 1}, lists:append([{in_comment, []}],Status)); scan("\n" ++ T, Scanned, {Row, _Column}, [{in_comment, Comment}|Status]) -> scan(T, lists:append(Scanned, [{comment, Comment}]), {Row +1 , 1}, Status); scan([Head | T], Scanned, {Row, Column}, _Status = [{in_comment, Comment}|Stack]) -> NewStatus = lists:append([{in_comment, lists:append(Comment,[Head])}],Stack), scan(T, Scanned, {Row, Column + 1}, NewStatus); %%Msg id scan("msgid" ++ T, Scanned, {Row, Column}, Status = [in_text]) -> scan(T, Scanned, {Row, Column + 5}, lists:append([{in_message_id, []}],Status)); %%scan("msgid" ++ T, Scanned, {Row, Column}, [{in_message_str, Body}|Stack]) -> %% scan(T, lists:append(Scanned , [{str, Body}]), {Row, Column + 5}, lists:append([{in_message_id, []}],Stack)); scan("\n\n" ++ T, Scanned, {Row, _Column}, [{in_message_str, Body}|Stack]) -> scan(T, lists:append(Scanned , [{str, Body}]), {Row + 2, 1}, Stack); scan("\n", Scanned, {Row, _Column}, [{in_message_str, Body}|Stack]) -> scan([], lists:append(Scanned , [{str, Body}]), {Row + 2, 1}, Stack); %%Msg str scan("msgstr" ++ T, Scanned, {Row, Column}, [{in_message_id, Body} | Stack]) -> %%io:format("Id is ~s~n",[Body]), scan(T, lists:append(Scanned ,[{id, Body}]), {Row, Column + 6}, lists:append([{in_message_str, []}],Stack)); scan([$\\, C|T], Scanned, {Row, Column}, [{in_string_body, Body}|Stack]) -> scan(T, Scanned, {Row, Column + 2}, [{in_string_body, lists:append(Body, [C])} | Stack]); %%Start and end for a message body scan("\"" ++ T, Scanned, {Row, Column}, [{in_string_body, Body}|Stack]) -> %%io:format("Ending string ~s ~p~n",[Body, Stack]), end_of_string(Body, Stack, T, Scanned, Row, Column); scan("\"" ++ T, Scanned, {Row, Column}, Stack) -> scan(T, Scanned, {Row, Column + 1}, lists:append([{in_string_body, []}], Stack)); %%Carriage return are ignored %% scan("\n" ++ T, Scanned, {Row, _Column}, Status) -> %% scan(T, Scanned, {Row + 1, 1}, Status); %%Concat string body to already parsed scan([H | T] , Scanned, {Row, Column}, [{in_string_body, Body} | Stack]) -> scan(T, Scanned, {Row, Column + 1}, [{in_string_body, lists:append(Body, [H])} | Stack]); %%Others characters are ignored scan([_H | T] , Scanned, {Row, Column}, Status) -> scan(T, Scanned, {Row, Column + 1}, Status); %%EOF scan([], Scanned, {_Row, _Column}, _Stack) ->Scanned; scan(In, Scanned, {_Row, _Column}, _Status) -> io:format("Cannot process ~p, scanned ~p ~n",[In, Scanned]). end_of_string(String, [{in_message_id, Body}|Stack] ,T, Scanned, Row, Column) -> scan(T, Scanned, {Row, Column}, [{in_message_id, lists:append(Body ,String)} | Stack ]); end_of_string(String, [{in_message_str, Body}|Stack] , T, Scanned, Row, Column) -> scan(T, Scanned, {Row, Column }, [{in_message_str, lists:append(Body,String)} |Stack ]). %% %% Local Functions %% erlydtl-0.15.0/src/i18n/sources_parser.erl000066400000000000000000000205061504712431400203710ustar00rootroot00000000000000%% Author: dave %% Author: Sergey Prokhorov (new/ext API) %% Created: Mar 1, 2010 %% @doc: %% Parses source files and extracts translation directives on templates %% Examples: %%
    %% Tpl = <<"111"
    %%         "{#Translators: btrans comment #}{%blocktrans%}btrns{%endblocktrans%}"
    %%         "{%comment%}  TRANSLATORS: trans comment {%endcomment%}222{%trans 'trns'%}"
    %%         "333">>,
    %% Phrases = sources_parser:parse_content("filename.dtl", Tpl),
    %% Msgids = [sources_parser:phrase_info(msgid, P) || P <- Phrases].
    %% %% -> ["btrns", "trns"]
    %% InOldFormat = [begin
    %%                  [Str, File, Line, Col] = sources_parser:phrase_info([msgid, file, line, col], P),
    %%                  {Str, {File, Line, Col}}
    %%                end || P <- Phrases].
    %% %% -> [{"btrns", {"filename.dtl", 1, 47}}, {"trns", {"filename.dtl", 1, 135}]
    %% 
    -module(sources_parser). %% %% Exported Functions %% %% New API -export([parse_pattern/1, parse_file/1, parse_content/2, phrase_info/2]). %% Deprecated API -export([parse/0, parse/1, process_content/2]). -deprecated([{parse, '_'}, {process_content, 2}]). %% Type exports -export_type([phrase/0, compat_phrase/0, field/0]). %% %% Include files %% -include("erlydtl_ext.hrl"). -record(phrase, {msgid :: string(), msgid_plural :: string() | undefined, context :: string() | undefined, comment :: string() | undefined, file :: string(), line :: non_neg_integer(), col :: non_neg_integer()}). -record(state, {acc=[], translators_comment}). -opaque phrase() :: #phrase{}. -type compat_phrase() :: {string(), {string(), non_neg_integer(), non_neg_integer()}}. -type field() :: msgid | msgid_plural | context | comment | file | line | col. -define(bail(Fmt, Args), throw(lists:flatten(io_lib:format(Fmt, Args)))). -define(GET_FIELD(Key), phrase_info(Key, #phrase{ Key = Value }) -> Value). %% %% API Functions %% %% Old API parse() -> Parsed_Files = parse(["./views/*/*.html"]), io:format("Parsed files are ~p~n",[Parsed_Files]). parse(Pattern) -> to_compat(parse_pattern(Pattern)). process_content(Path, Content) -> to_compat(parse_content(Path, Content)). %% @doc convert new API output to old one. -spec to_compat([phrase()]) -> [compat_phrase()]. to_compat(Phrases) -> [{Str, {File, Line, Col}} || #phrase{msgid=Str, file=File, line=Line, col=Col} <- Phrases]. %% New API %% @doc extract info about phrase. %% See `field()' type for list of available info field names. -spec phrase_info([field()] | field(), phrase()) -> [Info] | Info when Info :: non_neg_integer() | string() | undefined. ?GET_FIELD(msgid); ?GET_FIELD(msgid_plural); ?GET_FIELD(context); ?GET_FIELD(comment); ?GET_FIELD(file); ?GET_FIELD(line); ?GET_FIELD(col); phrase_info(Fields, Phrase) when is_list(Fields) -> %% you may pass list of fields [phrase_info(Field, Phrase) || Field <- Fields]. %% @doc list files, using wildcard and extract phrases from them -spec parse_pattern([string()]) -> [phrase()]. parse_pattern(Pattern) -> %%We assume a basedir GetFiles = fun(Path,Acc) -> Acc ++ [F || F <- filelib:wildcard(Path), filelib:is_regular(F)] end, Files = lists:foldl(GetFiles,[],Pattern), io:format("Parsing files ~p~n",[Files]), ParsedFiles = [parse_file(File) || File <- Files], lists:flatten(ParsedFiles). %% @doc extract phrases from single file parse_file(Path) -> case file:read_file((Path)) of {ok, Content} -> parse_content(Path, Content); Error -> ?bail("Cannot read file ~s problem ~p~n", [Path, Error]) end. %% @doc extract phrases from string / binary -spec parse_content(string(), binary()) -> [phrase()]. parse_content(Path,Content)-> case erlydtl_compiler:do_parse_template(Content, #dtl_context{}) of {ok, Data} -> try process_ast(Path, Data) of {ok, Result} -> Result catch ?WITH_STACKTRACE(Error, Reason, Stacktrace) io:format("~s: Template processing failed~nData: ~p~n", [Path, Data]), erlang:raise(Error, Reason, Stacktrace) end; Error -> ?bail("Template parsing failed for template ~s, cause ~p~n", [Path, Error]) end. %% %% Local Functions %% process_ast(Fname, Tokens) -> State = process_ast(Fname, Tokens, #state{}), {ok, State#state.acc}. process_ast(Fname, Tokens, State) when is_list(Tokens) -> lists:foldl( fun (Token, St) -> process_token(Fname, Token, St) end, State, Tokens); process_ast(Fname, Token, State) -> process_token(Fname, Token, State). %%Block are recursively processed, trans are accumulated and other tags are ignored process_token(Fname, {block,{identifier,{_Line,_Col},_Identifier},Children}, St) -> process_ast(Fname, Children, St); process_token(Fname, {trans,Text}, #state{acc=Acc, translators_comment=Comment}=St) -> {{Line, Col}, String} = trans(Text), Phrase = #phrase{msgid=unescape(String), comment=Comment, file=Fname, line=Line, col=Col}, St#state{acc=[Phrase | Acc], translators_comment=undefined}; process_token(Fname, {trans,Text,{string_literal, _, Context}}, #state{acc=Acc, translators_comment=Comment}=St) -> {{Line, Col}, String} = trans(Text), Phrase = #phrase{msgid=unescape(String), context=unescape(Context), comment=Comment, file=Fname, line=Line, col=Col}, St#state{acc=[Phrase | Acc], translators_comment=undefined}; process_token(Fname, {blocktrans, Args, Contents, PluralContents}, #state{acc=Acc, translators_comment=Comment}=St) -> {Fname, Line, Col} = guess_blocktrans_lc(Fname, Args, Contents), Trim = proplists:get_value(trimmed, Args), Phrase = #phrase{msgid=unparse(Contents, Trim), msgid_plural=unparse(PluralContents, Trim), context=case proplists:get_value(context, Args) of {string_literal, _, String} -> erlydtl_compiler_utils:unescape_string_literal(String); undefined -> undefined end, comment=Comment, file=Fname, line=Line, col=Col}, St#state{acc=[Phrase | Acc], translators_comment=undefined}; process_token(_, {comment, Comment}, St) -> St#state{translators_comment=maybe_translators_comment(Comment)}; process_token(_Fname, {comment_tag, _Pos, Comment}, St) -> St#state{translators_comment=translators_comment_text(Comment)}; process_token(Fname, {_Instr, _Cond, Children}, St) -> process_ast(Fname, Children, St); process_token(Fname, {_Instr, _Cond, Children, Children2}, St) -> StModified = process_ast(Fname, Children, St), process_ast(Fname, Children2, StModified); process_token(_,_AST,St) -> St. trans({noop, Value}) -> trans(Value); trans({string_literal,Pos,String}) -> {Pos, String}. unescape(String) -> string:sub_string(String, 2, string:len(String) -1). unparse(undefined, _) -> undefined; unparse(Contents, Trim) -> erlydtl_unparser:unparse(Contents, Trim). %% hack to guess ~position of blocktrans guess_blocktrans_lc(Fname, [{{identifier, {L, C}, _}, _} | _], _) -> %% guess by 1'st with {Fname, L, C - length("blocktrans with ")}; guess_blocktrans_lc(Fname, _, [{string, {L, C}, _} | _]) -> %% guess by 1'st string {Fname, L, C - length("blocktrans %}")}; guess_blocktrans_lc(Fname, _, [{variable, {identifier, {L, C}, _}} | _]) -> %% guess by 1'st {{...}} {Fname, L, C - length("blocktrans %}")}; guess_blocktrans_lc(Fname, _, _) -> {Fname, -1, -1}. maybe_translators_comment([{string, _Pos, S}]) -> translators_comment_text(S); maybe_translators_comment(Other) -> %% smth like "{%comment%}Translators: Hey, {{var}} is variable substitution{%endcomment%}" Unparsed = erlydtl_unparser:unparse(Other), translators_comment_text(Unparsed). translators_comment_text(S) -> Stripped = string:strip(S, left), case "translators:" == string:to_lower(string:substr(Stripped, 1, 12)) of true -> S; false -> undefined end. erlydtl-0.15.0/test/000077500000000000000000000000001504712431400142345ustar00rootroot00000000000000erlydtl-0.15.0/test/.gitignore000066400000000000000000000000411504712431400162170ustar00rootroot00000000000000erlydtl_extension_testparser.erl erlydtl-0.15.0/test/erlydtl_custom_tags.erl000066400000000000000000000006131504712431400210270ustar00rootroot00000000000000-module(erlydtl_custom_tags). -export([custom1/1, custom2/2, custom3/2, custom4/1, custom1_var/2]). custom1(_TagVars = []) -> <<"b1">>. custom2([], _RenderOptions = [{locale, ru}, {foo, bar}]) -> <<"b2">>. custom3([], _RenderOptions = [{locale, ru}]) -> <<"b3">>. custom4(_TagVars = [<<"a">>]) -> <<"a">>. custom1_var(_TagVars = [], _O) -> [{name, <<"b1">>}, {count, 11}]. erlydtl-0.15.0/test/erlydtl_custom_tags_lib.erl000066400000000000000000000004141504712431400216540ustar00rootroot00000000000000-module(erlydtl_custom_tags_lib). -behaviour(erlydtl_library). -export([version/0, inventory/1, customtag2_var/2]). version() -> 1. inventory(filters) -> []; inventory(tags) -> [customtag2_var]. customtag2_var(_V,_R) -> [{name, <<"b1">>}, {count, 11}]. erlydtl-0.15.0/test/erlydtl_dateformat_tests.erl000066400000000000000000000231551504712431400220550ustar00rootroot00000000000000-module(erlydtl_dateformat_tests). -include_lib("eunit/include/eunit.hrl"). all_dateformats_test_() -> [{Title, [test_fun(Param, Test) || Test <- Tests]} || {Title, Param, Tests} <- test_defs()]. test_fun(Param, {Input, Expect}) -> ?_assertMatch(Expect, erlydtl_dateformat:format(Param, Input)). test_defs() -> [{"date 1", {1979, 7, 8}, % just a date [{"a", "a.m."}, {"A", "AM"}, {"c", "1979-07-08T00:00:00"}, {"d", "08"}, {"D", "Sun"}, {"f", "12"}, {"F", "July"}, {"g", "12"}, {"G", "0"}, {"h", "12"}, {"H", "00"}, {"i", "00"}, {"j", "8"}, {"l", "Sunday"}, {"L", "False"}, {"m", "07"}, {"M", "Jul"}, {"b", "jul"}, {"n", "7"}, {"N", "July"}, {"P", "midnight"}, {"s", "00"}, {"S", "th"}, {"t", "31"}, {"w", "0"}, {"W", "27"}, {"y", "79"}, {"Y", "1979"}, {"z", "189"}, {"jS F Y H:i", "8th July 1979 00:00"}, {"jS \\o\\f F", "8th of July"}, %% We expect these to come back verbatim {"x", "x"}, {"C", "C"}, {";", ";"}, {"%%", "%%"} %% TODO : timezone related tests. %%{"r", "Sun, 8 Jul 1979 00:00:00 +0000"}, %%{"O", "0000"}, %%{"T", "CET"}, %%{"U", "300531600"}, %%{"Z", "3600"} ] }, {"datetime 1", {{1979, 7, 8}, {22, 7, 12}}, %% date/time tuple [{"a", "p.m."}, {"A", "PM"}, {"c", "1979-07-08T22:07:12"}, {"d", "08"}, {"D", "Sun"}, {"f", "10:07"}, {"F", "July"}, {"g", "10"}, {"G", "22"}, {"h", "10"}, {"H", "22"}, {"i", "07"}, {"j", "8"}, {"l", "Sunday"}, {"L", "False"}, {"m", "07"}, {"M", "Jul"}, {"b", "jul"}, {"n", "7"}, {"N", "July"}, {"P", "10:07 p.m."}, {"s", "12"}, {"S", "th"}, {"t", "31"}, {"w", "0"}, {"W", "27"}, {"y", "79"}, {"Y", "1979"}, {"z", "189"}, {"jS F Y H:i", "8th July 1979 22:07"}, {"jS \\o\\f F", "8th of July"}, %% We expect these to come back verbatim {"x", "x"}, {"C", "C"}, {";", ";"}, {"%%", "%%"} %% TODO : timezone related tests. %% {"r", "Sun, 8 Jul 1979 22:07:12 +0000"}, %% {"O", "0000"}, %% {"T", "CET"}, %% {"U", "300531600"}, %% {"Z", "3600"} ] }, {"datetime 2", {{2008, 12, 25}, {7, 0, 9}}, %% date/time tuple [{"a", "a.m."}, {"A", "AM"}, {"c", "2008-12-25T07:00:09"}, {"d", "25"}, {"D", "Thu"}, {"f", "7"}, {"F", "December"}, {"g", "7"}, {"G", "7"}, {"h", "07"}, {"H", "07"}, {"i", "00"}, {"j", "25"}, {"l", "Thursday"}, {"L", "True"}, {"m", "12"}, {"M", "Dec"}, {"b", "dec"}, {"n", "12"}, {"N", "Dec."}, {"P", "7 a.m."}, {"s", "09"}, {"S", "th"}, {"t", "31"}, {"w", "4"}, {"W", "52"}, {"y", "08"}, {"Y", "2008"}, {"z", "360"}, {"jS F Y H:i", "25th December 2008 07:00"}, {"jS \\o\\f F", "25th of December"}, %% We expect these to come back verbatim {"x", "x"}, {"C", "C"}, {";", ";"}, {"%%", "%%"} %% TODO : timezone related tests. %% {"r", "Thu, 25 Dec 2008 07:00:09 +0000"}, %% {"O", "0000"}, %% {"T", "CET"}, %% {"U", "300531600"}, %% {"Z", "3600"} ] }, {"datetime 3", {{2004, 2, 29}, {12, 0, 59}}, %% date/time tuple [{"a", "p.m."}, {"A", "PM"}, {"c", "2004-02-29T12:00:59"}, {"d", "29"}, {"D", "Sun"}, {"f", "12"}, {"F", "February"}, {"g", "12"}, {"G", "12"}, {"h", "12"}, {"H", "12"}, {"i", "00"}, {"j", "29"}, {"l", "Sunday"}, {"L", "True"}, {"m", "02"}, {"M", "Feb"}, {"b", "feb"}, {"n", "2"}, {"N", "Feb."}, {"P", "noon"}, {"s", "59"}, {"S", "th"}, {"t", "29"}, {"w", "0"}, {"W", "9"}, {"y", "04"}, {"Y", "2004"}, {"z", "58"}, {"jS F Y H:i", "29th February 2004 12:00"}, {"jS \\o\\f F", "29th of February"}, %% We expect these to come back verbatim {"x", "x"}, {"C", "C"}, {";", ";"}, {"%%", "%%"} %% TODO : timezone related tests. %% {"r", "Sun, 29 Feb 2004 12:00:59 +0000"}, %% {"O", "0000"}, %% {"T", "CET"}, %% {"U", "300531600"}, %% {"Z", "3600"} ] }, {"datetime 4", {{2004, 2, 29}, {12, 0, 09.256687}}, %% date/time tuple [{"a", "p.m."}, {"A", "PM"}, {"c", "2004-02-29T12:00:09"}, {"d", "29"}, {"D", "Sun"}, {"f", "12"}, {"F", "February"}, {"g", "12"}, {"G", "12"}, {"h", "12"}, {"H", "12"}, {"i", "00"}, {"j", "29"}, {"l", "Sunday"}, {"L", "True"}, {"m", "02"}, {"M", "Feb"}, {"b", "feb"}, {"n", "2"}, {"N", "Feb."}, {"P", "noon"}, {"s", "09"}, {"S", "th"}, {"t", "29"}, {"w", "0"}, {"W", "9"}, {"y", "04"}, {"Y", "2004"}, {"z", "58"}, {"jS F Y H:i", "29th February 2004 12:00"}, {"jS \\o\\f F", "29th of February"}, %% We expect these to come back verbatim {"x", "x"}, {"C", "C"}, {";", ";"}, {"%%", "%%"} %% TODO : timezone related tests. %% {"r", "Sun, 29 Feb 2004 12:00:59 +0000"}, %% {"O", "0000"}, %% {"T", "CET"}, %% {"U", "300531600"}, %% {"Z", "3600"} ] }, %% Weeknum tests. Largely based on examples from : %% http://en.wikipedia.org/wiki/ISO_week_date { "weeknum 1.1", {2005, 1, 1}, [{"W", "53"}] }, { "weeknum 1.2", {2005, 1, 2}, [{"W", "53"}] }, { "weeknum 1.3", {2005, 12, 31}, [{"W", "52"}] }, { "weeknum 1.4", {2007, 1, 1}, [{"W", "1"}] }, { "weeknum 1.5", {2007, 12, 30}, [{"W", "52"}] }, { "weeknum 1.6", {2007, 12, 31}, [{"W", "1"}] }, { "weeknum 1.6", {2008, 1, 1}, [{"W", "1"}] }, { "weeknum 1.7", {2008, 12, 29}, [{"W", "1"}] }, { "weeknum 1.8", {2008, 12, 31}, [{"W", "1"}] }, { "weeknum 1.9", {2009, 1, 1}, [{"W", "1"}] }, { "weeknum 1.10", {2009, 12, 31}, [{"W", "53"}] }, { "weeknum 1.11", {2010, 1, 3}, [{"W", "53"}] }, %% Examples where the ISO year is three days into %% the next Gregorian year { "weeknum 2.1", {2009, 12, 31}, [{"W", "53"}] }, { "weeknum 2.2", {2010, 1, 1}, [{"W", "53"}] }, { "weeknum 2.3", {2010, 1, 2}, [{"W", "53"}] }, { "weeknum 2.4", {2010, 1, 3}, [{"W", "53"}] }, { "weeknum 2.5", {2010, 1, 5}, [{"W", "1"}] }, %% Example where the ISO year is three days into %% the previous Gregorian year { "weeknum 3.1", {2008, 12, 28}, [{"W", "52"}] }, { "weeknum 3.2", {2008, 12, 29}, [{"W", "1"}] }, { "weeknum 3.3", {2008, 12, 30}, [{"W", "1"}] }, { "weeknum 3.4", {2008, 12, 31}, [{"W", "1"}] }, { "weeknum 3.5", {2009, 1, 1}, [{"W", "1"}] }, %% freeform tests { "weeknum 4.1", {2008, 2, 28}, [{"W", "9"}] }, { "weeknum 4.2", {1975, 7, 24}, [{"W","30"}] }, %% Yearweek tests. Largely based on examples from : %% http://en.wikipedia.org/wiki/ISO_week_date { "weeknum_year 1.1", {2005, 1, 1}, [{"o", "2004"}] }, { "weeknum_year 1.2", {2005, 1, 2}, [{"o", "2004"}] }, { "weeknum_year 1.3", {2005, 12, 31}, [{"o", "2005"}] }, { "weeknum_year 1.4", {2007, 1, 1}, [{"o", "2007"}] }, { "weeknum_year 1.5", {2007, 12, 30}, [{"o", "2007"}] }, { "weeknum_year 1.6", {2007, 12, 31}, [{"o", "2008"}] }, { "weeknum_year 1.6", {2008, 1, 1}, [{"o", "2008"}] }, { "weeknum_year 1.7", {2008, 12, 29}, [{"o", "2009"}] }, { "weeknum_year 1.8", {2008, 12, 31}, [{"o", "2009"}] }, { "weeknum_year 1.9", {2009, 1, 1}, [{"o", "2009"}] }, { "weeknum_year 1.10", {2009, 12, 31}, [{"o", "2009"}] }, { "weeknum_year 1.11", {2010, 1, 3}, [{"o", "2009"}] }, %% Examples where the ISO year is three days into %% the next Gregorian year { "weeknum_year 2.1", {2009, 12, 31}, [{"o", "2009"}] }, { "weeknum_year 2.2", {2010, 1, 1}, [{"o", "2009"}] }, { "weeknum_year 2.3", {2010, 1, 2}, [{"o", "2009"}] }, { "weeknum_year 2.4", {2010, 1, 3}, [{"o", "2009"}] }, { "weeknum_year 2.5", {2010, 1, 5}, [{"o", "2010"}] }, %% Example where the ISO year is three days into %% the previous Gregorian year { "weeknum_year 3.1", {2008, 12, 28}, [{"o", "2008"}] }, { "weeknum_year 3.2", {2008, 12, 29}, [{"o", "2009"}] }, { "weeknum_year 3.3", {2008, 12, 30}, [{"o", "2009"}] }, { "weeknum_year 3.4", {2008, 12, 31}, [{"o", "2009"}] }, { "weeknum_year 3.5", {2009, 1, 1}, [{"o", "2009"}] }, %% freeform tests { "weeknum_year 4.1", {2008, 2, 28}, [{"o", "2008"}] }, { "weeknum_year 4.2", {1975, 7, 24}, [{"o", "1975"}] }, %% Ordinal suffix tests. { "Ordinal suffix 1", {1984,1,1}, [{"S", "st"}] }, { "Ordinal suffix 2", {1984,2,2}, [{"S", "nd"}] }, { "Ordinal suffix 3", {1984,3,3}, [{"S", "rd"}] }, { "Ordinal suffix 4", {1984,4,4}, [{"S", "th"}] }, { "Ordinal suffix 5", {1984,6,5}, [{"S", "th"}] }, { "Ordinal suffix 7", {1984,2,9}, [{"S", "th"}] }, { "Ordinal suffix 8", {1984,9,9}, [{"S", "th"}] }, { "Ordinal suffix 9", {1984,11,10}, [{"S", "th"}] }, { "Ordinal suffix 10", {1984,12,11}, [{"S", "th"}] }, { "Ordinal suffix 11", {1984,8,12}, [{"S", "th"}] }, { "Ordinal suffix 12", {1984,1,19}, [{"S", "th"}] }, { "Ordinal suffix 13", {1984,2,20}, [{"S", "th"}] }, { "Ordinal suffix 14", {1984,2,21}, [{"S", "st"}] }, { "Ordinal suffix 15", {1984,7,22}, [{"S", "nd"}] }, { "Ordinal suffix 16", {1984,6,23}, [{"S", "rd"}] }, { "Ordinal suffix 17", {1984,5,24}, [{"S", "th"}] }, { "Ordinal suffix 18", {1984,1,29}, [{"S", "th"}] }, { "Ordinal suffix 19", {1984,3,30}, [{"S", "th"}] }, { "Ordinal suffix 20", {1984,1,31}, [{"S", "st"}] }, { "Ordinal suffix 21", {1984,1,310}, [{"S", "th"}] }, { "Ordinal suffix 22", {1984,1,121}, [{"S", "st"}] } ]. erlydtl-0.15.0/test/erlydtl_eunit_testrunner.erl000066400000000000000000000064401504712431400221200ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_eunit_testrunner.erl %%% @author Andreas Stenius %%% @copyright 2014 Andreas Stenius %%% @doc %%% Test suite runner for erlydtl %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2014 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2014 by Andreas Stenius %%%------------------------------------------------------------------- -module(erlydtl_eunit_testrunner). -author('Andreas Stenius '). -export([run_test/1, run_compile/1, run_render/1]). -include_lib("eunit/include/eunit.hrl"). -define(noimport,). -include("testrunner.hrl"). run_test(T) -> case run_compile(T) of ok -> run_render(T); error_ok -> ok end. compile_opts(#test{ compile_vars = undefined, compile_opts = Opts }) -> Opts; compile_opts(#test{ compile_vars = Vars, compile_opts = Opts }) -> [{default_vars, Vars}|Opts]. run_compile(T) -> case erlydtl:compile( T#test.source, T#test.module, compile_opts(T)) of {ok, M, W0} -> ?assertEqual(T#test.module, M), %% ignore useless_building warnings W = lists:flatten( [case W1 of {_, [{_, sys_core_fold, useless_building}]} -> []; _ -> W1 end || W1 <- W0]), ?assertEqual(T#test.warnings, W); {error, E, W} -> ?assertEqual(T#test.errors, E), ?assertEqual(T#test.warnings, W), error_ok end. run_render(#test{ renderer=Renderer }=T) -> Output = if is_atom(Renderer) -> (T#test.module):Renderer(T#test.render_vars, T#test.render_opts); is_function(Renderer) -> Renderer(T) end, case Output of {ok, O} -> B = iolist_to_binary(O), case T#test.output of O -> ok; B -> ok; F when is_function(F) -> F(B); _ -> ?assertEqual(T#test.output, B) end; RenderOutput -> ?assertEqual(T#test.output, RenderOutput), error_ok end. erlydtl-0.15.0/test/erlydtl_extension_testparser.yrl000066400000000000000000000102311504712431400230100ustar00rootroot00000000000000%%% -*- mode: erlang -*- ------------------------------------------------------------------ %%% File: erlydtl_parser.erl %%% @author Andreas Stenius %%% @copyright 2013 Andreas Stenius %%% @doc Sample extension grammar %%% @reference See http://erlydtl.googlecode.com for more information %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2013 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2013-06-20 by Andreas Stenius %%%------------------------------------------------------------------- Nonterminals Extensions Literal ValueExpressionBraced ValueExpression Value Variable . Terminals %% "new" terminals that are partially parsed tokens from the erlydtl parser: variable %% standard scanner tokens: %% and_keyword %% as_keyword %% autoescape_keyword %% block_keyword %% blocktrans_keyword %% by_keyword %% call_keyword %% close_tag close_var %% comment_keyword %% cycle_keyword %% elif_keyword %% else_keyword %% empty_keyword %% endautoescape_keyword %% endblock_keyword %% endblocktrans_keyword %% endcomment_keyword %% endfilter_keyword %% endfor_keyword %% endif_keyword %% endifchanged_keyword %% endifequal_keyword %% endifnotequal_keyword %% endregroup_keyword %% endspaceless_keyword %% endwith_keyword %% extends_keyword %% filter_keyword %% firstof_keyword %% for_keyword identifier %% if_keyword %% ifchanged_keyword %% ifequal_keyword %% ifnotequal_keyword %% in_keyword %% include_keyword %% noop_keyword %% not_keyword %% now_keyword number_literal %% only_keyword or_keyword %% open_tag open_var %% parsed_keyword %% regroup_keyword %% reversed_keyword %% spaceless_keyword %% ssi_keyword string_literal %% string %% templatetag_keyword %% openblock_keyword %% closeblock_keyword %% openvariable_keyword %% closevariable_keyword %% openbrace_keyword %% closebrace_keyword %% opencomment_keyword %% closecomment_keyword %% trans_keyword %% widthratio_keyword %% with_keyword %% ',' '|' '=' ':' '.' %% '==' '!=' %% '>=' '<=' %% '>' '<' %% '(' ')' %% '_' . Rootsymbol Extensions. %% Operator precedences for the E non terminal Left 100 or_keyword. %Left 110 and_keyword. %Nonassoc 300 '==' '!=' '>=' '<=' '>' '<'. %Unary 600 Unot. Extensions -> ValueExpressionBraced : ['$1']. ValueExpressionBraced -> open_var ValueExpression close_var : '$2'. ValueExpression -> Value or_keyword Value : {extension, {value_or, {'$1', '$3'}}}. %Value -> Value '|' Filter : {apply_filter, '$1', '$3'}. %Value -> '_' '(' Value ')' : {trans, '$3'}. Value -> Variable : '$1'. Value -> Literal : '$1'. Variable -> identifier : {variable, '$1'}. Variable -> variable : '$1'. Variable -> Variable '.' identifier : {attribute, {'$3', '$1'}}. Literal -> string_literal : '$1'. Literal -> number_literal : '$1'. %% vim: syntax=erlang erlydtl-0.15.0/test/erlydtl_lib_test1.erl000066400000000000000000000006671504712431400203760ustar00rootroot00000000000000-module(erlydtl_lib_test1). -behaviour(erlydtl_library). -export([version/0, inventory/1, reverse/1]). %% dummy behaviour for lib_test2 -export([behaviour_info/1]). behaviour_info(callbacks) -> []. %% end behaviour version() -> 1. inventory(filters) -> [reverse]; inventory(tags) -> []. reverse(String) when is_list(String) -> lists:reverse(String); reverse(String) when is_binary(String) -> reverse(binary_to_list(String)). erlydtl-0.15.0/test/erlydtl_lib_test2.erl000066400000000000000000000006001504712431400203620ustar00rootroot00000000000000-module(erlydtl_lib_test2). %% test multiple behaviours -behaviour(erlydtl_lib_test1). -behaviour(erlydtl_library). -export([version/0, inventory/1, reverse/1]). version() -> 1. inventory(filters) -> [reverse]; inventory(tags) -> []. reverse(String) when is_list(String) -> lists:reverse(String); reverse(String) when is_binary(String) -> reverse(binary_to_list(String)). erlydtl-0.15.0/test/erlydtl_lib_test2a.erl000066400000000000000000000006251504712431400205320ustar00rootroot00000000000000-module(erlydtl_lib_test2a). %% test multiple behaviors (alternative spelling) -behavior(erlydtl_lib_test1). -behavior(erlydtl_library). -export([version/0, inventory/1, reverse/1]). version() -> 1. inventory(filters) -> [reverse]; inventory(tags) -> []. reverse(String) when is_list(String) -> lists:reverse(String); reverse(String) when is_binary(String) -> reverse(binary_to_list(String)). erlydtl-0.15.0/test/erlydtl_lib_testversion.erl000066400000000000000000000002541504712431400217130ustar00rootroot00000000000000-module(erlydtl_lib_testversion). -behaviour(erlydtl_library). -export([version/0, inventory/1]). version() -> invalid. inventory(filters) -> []; inventory(tags) -> []. erlydtl-0.15.0/test/erlydtl_test_defs.erl000066400000000000000000003063001504712431400204610ustar00rootroot00000000000000%% -*- coding: utf-8 -*- -module(erlydtl_test_defs). -export([tests/0, extra_reader/2]). -include("testrunner.hrl"). -record(testrec, {foo, bar, baz}). -record(person, {first_name, gender}). %% {Name, DTL, Vars, Output} %% {Name, DTL, Vars, RenderOpts, Output} %% {Name, DTL, Vars, RenderOpts, CompilerOpts, Output} %% {Name, DTL, Vars, RenderOpts, CompilerOpts, Output, Warnings} tests() -> [def_to_test(G, D) || {G, Ds} <- all_test_defs(), D <- Ds]. all_test_defs() -> [{"vars", [{"string", <<"String value is: {{ var1 }}">>, [{var1, "foo"}], <<"String value is: foo">>}, {"int", <<"The magic number is: {{ var1 }}">>, [{var1, 42}], <<"The magic number is: 42">>}, {"float", <<"The price of milk is: {{ var1 }}">>, [{var1, 0.42}], <<"The price of milk is: 0.42">>}, {"No spaces", <<"{{var1}}">>, [{var1, "foo"}], <<"foo">>}, {"Variable name is a tag name", <<"{{ comment }}">>, [{comment, "Nice work!"}], <<"Nice work!">>}, #test{ title = "reserved name ok as variable name", source = <<"{{ from }}">>, render_vars = [{from, "test"}], output = <<"test">> } ]}, {"maps", case erlang:is_builtin(erlang, is_map, 1) of false -> []; true -> [#test{ title = "simple test", source = <<"{{ msg.hello }}">>, render_vars = [{msg, maps:put(hello, "world", maps:new())}], output = <<"world">> }, #test{ title = "various key types", source = <<"{{ msg.key1 }},{{ msg.key2 }},{{ msg.key3 }},{{ msg.4 }}">>, render_vars = [{msg, maps:from_list([{key1, 1}, {"key2", 2}, {<<"key3">>, 3}, {4, "value4"}])}], output = <<"1,2,3,value4">> } ] end}, {"comment", [{"comment block is excised", <<"bob {% comment %}(moron){% endcomment %} loblaw">>, [], <<"bob loblaw">>}, {"inline comment is excised", <<"you're {# not #} a very nice person">>, [], <<"you're a very nice person">>} ]}, {"autoescape", [{"Autoescape works", <<"{% autoescape on %}{{ var1 }}{% endautoescape %}">>, [{var1, "bold"}], <<"<b>bold</b>">>}, {"Nested autoescape", <<"{% autoescape on %}{{ var1 }}{% autoescape off %}{{ var1 }}{% endautoescape %}{% endautoescape %}">>, [{var1, ""}], <<"<b>">>}, {"default auto escape", <<"{{ var1 }}">>, [{var1, "&"}], [], [auto_escape], <<"&">>}, {"intermixed autoescape", <<"{% autoescape on %}1:{{ var1 }}{% endautoescape %} 2:{{ var1 }}{% autoescape on %} 3:{{ var1 }}{% endautoescape %}">>, [{var1, "&"}], <<"1:& 2:& 3:&">>} ]}, {"string literal", [{"Render literal", <<"{{ \"foo\" }} is my name">>, [], <<"foo is my name">>}, {"Newlines are escaped", <<"{{ \"foo\\n\" }}">>, [], <<"foo\n">>}, {"strip quotes", <<"{{ \"foo\"|add:\"\\\"\" }}">>, [], <<"foo\"">>} ]}, {"cycle", [#test{ title = "deprecated cycle syntax", source = <<"{% for i in test %}{% cycle a,b %}{{ i }},{% endfor %}">>, render_vars = [{test, [0,1,2,3,4]}], output = <<"a0,b1,a2,b3,a4,">> }, {"Cycling through quoted strings", <<"{% for i in test %}{% cycle 'a' 'b' %}{{ i }},{% endfor %}">>, [{test, ["0", "1", "2", "3", "4"]}], <<"a0,b1,a2,b3,a4,">>}, {"Cycling through normal variables", <<"{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}">>, [{test, ["0", "1", "2", "3", "4"]}, {aye, "a"}, {bee, "b"}], <<"a0,b1,a2,b3,a4,">>}, #test{ title = "mix strings and variables", source = <<"{% for i in test %}{% cycle 'a' b 'c' %}{{ i }},{% endfor %}">>, render_vars = [{test, [0,1,2,3,4]}, {b, 'B'}], output = <<"a0,B1,c2,a3,B4,">> }, #test{ title = "keep current value in local variable", source = <<"{% for i in test %}{% cycle 'a' 'b' as c %}{{ i }}{{ c }},{% endfor %}">>, render_vars = [{test, [0,1,2,3,4]}], output = <<"a0a,b1b,a2a,b3b,a4a,">> }, #test{ title = "keep current value silently in local variable", source = <<"{% for i in test %}{% cycle 'a' 'b' as c silent %}{{ i }}{{ c }},{% endfor %}">>, render_vars = [{test, [0,1,2,3,4]}], output = <<"0a,1b,2a,3b,4a,">> } ]}, {"number literal", [{"Render integer", <<"{{ 5 }}">>, [], <<"5">>} ]}, {"variable", [{"Render variable", <<"{{ var1 }} is my game">>, [{var1, "bar"}], <<"bar is my game">>}, {"Render variable with attribute", <<"I enjoy {{ var1.game }}">>, [{var1, [{game, "Othello"}]}], <<"I enjoy Othello">>}, {"Render variable with string-key attribute", <<"I also enjoy {{ var1.game }}">>, [{var1, [{"game", "Parcheesi"}]}], <<"I also enjoy Parcheesi">>}, {"Render variable with binary-key attribute", <<"I also enjoy {{ var1.game }}">>, [{var1, [{<<"game">>, "Parcheesi"}]}], <<"I also enjoy Parcheesi">>}, {"Render variable with tuple wrapped proplist", <<"I also enjoy {{ var1.game }}">>, [{var1, {[{<<"game">>, "Parcheesi"}]}}], <<"I also enjoy Parcheesi">>}, {"Render variable in dict", <<"{{ var1 }}">>, dict:store(var1, "bar", dict:new()), <<"bar">>}, {"Render variable with missing attribute in dict", <<"{{ var1.foo }}">>, [{var1, dict:store(bar, "Othello", dict:new())}], <<"">>}, {"Render variable in a two elements tuple", <<"{{ var1.2 }}">>, [{var1,{12,[bar]}}], <<"bar">>}, {"Render variable in gb_tree", <<"{{ var1 }}">>, gb_trees:insert(var1, "bar", gb_trees:empty()), <<"bar">>}, {"Render variable in arity-1 func", <<"I enjoy {{ var1 }}">>, fun (var1) -> "Othello" end, <<"I enjoy Othello">>}, {"Render variable with attribute in dict", <<"{{ var1.attr }}">>, [{var1, dict:store(attr, "Othello", dict:new())}], <<"Othello">>}, {"Render variable with attribute in gb_tree", <<"{{ var1.attr }}">>, [{var1, gb_trees:insert(attr, "Othello", gb_trees:empty())}], <<"Othello">>}, {"Render variable with attribute in arity-1 func", <<"I enjoy {{ var1.game }}">>, [{var1, fun (game) -> "Othello" end}], <<"I enjoy Othello">>}, %% {"Render variable in parameterized module", %% <<"{{ var1.some_var }}">>, [{var1, erlydtl_example_variable_storage:new("foo")}], <<"foo">>}, {"Nested attributes", <<"{{ person.city.state.country }}">>, [{person, [{city, [{state, [{country, "Italy"}]}]}]}], <<"Italy">>}, {"Index list variable", <<"{{ var1.2 }}">>, [{var1, [a, b, c]}], <<"b">>}, {"Index tuple variable", <<"{{ var1.2 }}">>, [{var1, {a, b, c}}], <<"b">>}, {"Index all elements of list (default, 1-based)", <<"{{ var1.1 }},{{ var1.2 }},{{ var1.3 }}.">>, [{var1, [a, b, c]}], <<"a,b,c.">>}, {"Index all list elements 0-based (selected at compile time)", <<"{{ var1.0 }},{{ var1.1 }},{{ var1.2 }}.">>, [{var1, [a, b, c]}], [], [lists_0_based], <<"a,b,c.">>}, {"Index all list elements 0-based (selected at render time)", <<"{{ var1.0 }},{{ var1.1 }},{{ var1.2 }}.">>, [{var1, [a, b, c]}], [lists_0_based], [{lists_0_based, defer}], <<"a,b,c.">>}, {"Index all list elements 1-based (selected at render time)", <<"{{ var1.1 }},{{ var1.2 }},{{ var1.3 }}.">>, [{var1, [a, b, c]}], [], [{lists_0_based, defer}], <<"a,b,c.">>}, {"Index all elements of tuple (default, 1-based)", <<"{{ var1.1 }},{{ var1.2 }},{{ var1.3 }}.">>, [{var1, {a, b, c}}], <<"a,b,c.">>}, {"Index all tuple elements 0-based (selected at compile time)", <<"{{ var1.0 }},{{ var1.1 }},{{ var1.2 }}.">>, [{var1, {a, b, c}}], [], [tuples_0_based], <<"a,b,c.">>}, {"Index all tuple elements 0-based (selected at render time)", <<"{{ var1.0 }},{{ var1.1 }},{{ var1.2 }}.">>, [{var1, {a, b, c}}], [tuples_0_based], [{tuples_0_based, defer}], <<"a,b,c.">>}, {"Index all tuple elements 1-based (selected at render time)", <<"{{ var1.1 }},{{ var1.2 }},{{ var1.3 }}.">>, [{var1, {a, b, c}}], [], [{tuples_0_based, defer}], <<"a,b,c.">>}, {"Index tuple using a \"reserved\" keyword", <<"{{ list.count }}">>, [{list, [{count, 123}]}], <<"123">>}, {"Index list value", <<"{{ content.description }}">>, [{content, "test"}], <<"">>}, {"Index binary value", <<"{{ content.description }}">>, [{content, <<"test">>}], <<"">>} ]}, {"now", [{"now functional", <<"It is the {% now \"jS \\o\\f F Y\" %}.">>, [{var1, ""}], generate_test_date()} ]}, {"now", [{"now function with translation", % notice, that only date output is translated. While you might want to transle the whole format string ('F'->'E') <<"It is the {% now \"jS \\o\\f F Y\" %}.">>, [{var1, ""}], [{locale, <<"ru">>}, {translation_fun, fun date_translation/2}], generate_test_date(russian)} ]}, {"if", [{"If/else", <<"{% if var1 %}boo{% else %}yay{% endif %}">>, [{var1, ""}], <<"yay">>}, {"If elif", <<"{% if var1 %}boo{% elif var2 %}yay{% endif %}">>, [{var1, ""}, {var2, "happy"}], <<"yay">>}, {"If elif/else", <<"{% if var1 %}boo{% elif var2 %}sad{% else %}yay{% endif %}">>, [{var1, ""}, {var2, ""}], <<"yay">>}, {"If elif/elif/else", <<"{% if var1 %}boo{% elif var2 %}yay{% elif var3 %}sad{% else %}noo{% endif %}">>, [{var1, ""}, {var2, "happy"}, {var3, "not_taken"}], <<"yay">>}, {"If", <<"{% if var1 %}boo{% endif %}">>, [{var1, ""}], <<>>}, {"If not", <<"{% if not var1 %}yay{% endif %}">>, [{var1, ""}], <<"yay">>}, {"If \"0\"", <<"{% if var1 %}boo{% endif %}">>, [{var1, "0"}], <<>>}, {"If 0", <<"{% if var1 %}boo{% endif %}">>, [{var1, 0}], <<>>}, {"If false", <<"{% if var1 %}boo{% endif %}">>, [{var1, false}], <<>>}, {"If false string", <<"{% if var1 %}boo{% endif %}">>, [{var1, "false"}], <<"boo">>}, {"If undefined", <<"{% if var1 %}boo{% endif %}">>, [{var1, undefined}], <<>>}, {"If other atom", <<"{% if var1 %}yay{% endif %}">>, [{var1, foobar}], <<"yay">>}, {"If non-empty string", <<"{% if var1 %}yay{% endif %}">>, [{var1, "hello"}], <<"yay">>}, {"If proplist", <<"{% if var1 %}yay{% endif %}">>, [{var1, [{foo, "bar"}]}], <<"yay">>}, {"If complex", <<"{% if foo.bar.baz %}omgwtfbbq{% endif %}">>, [], <<"">>} ]}, {"if .. in ..", [{"If substring in string", <<"{% if var1 in var2 %}yay{% endif %}">>, [{var1, "rook"}, {var2, "Crooks"}], <<"yay">>}, {"If substring in string (false)", <<"{% if var1 in var2 %}boo{% endif %}">>, [{var1, "Cook"}, {var2, "Crooks"}], <<>>}, {"If substring not in string", <<"{% if var1 not in var2 %}yay{% endif %}">>, [{var1, "Cook"}, {var2, "Crooks"}], <<"yay">>}, {"If substring not in string (false)", <<"{% if var1 not in var2 %}boo{% endif %}">>, [{var1, "rook"}, {var2, "Crooks"}], <<>>}, {"If literal substring in string", <<"{% if \"man\" in \"Ottoman\" %}yay{% endif %}">>, [], <<"yay">>}, {"If literal substring in string (false)", <<"{% if \"woman\" in \"Ottoman\" %}boo{% endif %}">>, [], <<>>}, {"If element in list", <<"{% if var1 in var2 %}yay{% endif %}">>, [{var1, "foo"}, {var2, ["bar", "foo", "baz"]}], <<"yay">>}, {"If element in list (false)", <<"{% if var1 in var2 %}boo{% endif %}">>, [{var1, "FOO"}, {var2, ["bar", "foo", "baz"]}], <<>>} ]}, {"if .. and ..", [{"If true and true", <<"{% if var1 and var2 %}yay{% endif %}">>, [{var1, true}, {var2, true}], <<"yay">>}, {"If true and false", <<"{% if var1 and var2 %}yay{% endif %}">>, [{var1, true}, {var2, false}], <<"">>}, {"If false and true", <<"{% if var1 and var2 %}yay{% endif %}">>, [{var1, false}, {var2, true}], <<"">>}, {"If false and false ", <<"{% if var1 and var2 %}yay{% endif %}">>, [{var1, false}, {var2, false}], <<"">>} ]}, {"if .. or ..", [{"If true or true", <<"{% if var1 or var2 %}yay{% endif %}">>, [{var1, true}, {var2, true}], <<"yay">>}, {"If true or false", <<"{% if var1 or var2 %}yay{% endif %}">>, [{var1, true}, {var2, false}], <<"yay">>}, {"If false or true", <<"{% if var1 or var2 %}yay{% endif %}">>, [{var1, false}, {var2, true}], <<"yay">>}, {"If false or false ", <<"{% if var1 or var2 %}yay{% endif %}">>, [{var1, false}, {var2, false}], <<"">>} ]}, {"if equality", [{"If int equals number literal", <<"{% if var1 == 2 %}yay{% endif %}">>, [{var1, 2}], <<"yay">>}, {"If int equals number literal (false)", <<"{% if var1 == 2 %}yay{% endif %}">>, [{var1, 3}], <<"">>}, {"If string equals string literal", <<"{% if var1 == \"2\" %}yay{% endif %}">>, [{var1, "2"}], <<"yay">>}, {"If string equals string literal (false)", <<"{% if var1 == \"2\" %}yay{% endif %}">>, [{var1, "3"}], <<"">>}, {"If int not equals number literal", <<"{% if var1 != 2 %}yay{% endif %}">>, [{var1, 3}], <<"yay">>}, {"If string not equals string literal", <<"{% if var1 != \"2\" %}yay{% endif %}">>, [{var1, "3"}], <<"yay">>}, {"If filter result equals number literal", <<"{% if var1|length == 2 %}yay{% endif %}">>, [{var1, ["fo", "bo"]}], <<"yay">>}, {"If filter result equals string literal", <<"{% if var1|capfirst == \"Foo\" %}yay{% endif %}">>, [{var1, "foo"}], <<"yay">>} ]}, {"if size comparison", [{"If int greater than number literal", <<"{% if var1 > 2 %}yay{% endif %}">>, [{var1, 3}], <<"yay">>}, {"If int greater than negative number literal", <<"{% if var1 > -2 %}yay{% endif %}">>, [{var1, -1}], <<"yay">>}, {"If int greater than number literal (false)", <<"{% if var1 > 2 %}yay{% endif %}">>, [{var1, 2}], <<"">>}, {"If int greater than or equal to number literal", <<"{% if var1 >= 2 %}yay{% endif %}">>, [{var1, 3}], <<"yay">>}, {"If int greater than or equal to number literal (2)", <<"{% if var1 >= 2 %}yay{% endif %}">>, [{var1, 2}], <<"yay">>}, {"If int greater than or equal to number literal (false)", <<"{% if var1 >= 2 %}yay{% endif %}">>, [{var1, 1}], <<"">>}, {"If int less than number literal", <<"{% if var1 < 2 %}yay{% endif %}">>, [{var1, 1}], <<"yay">>}, {"If int less than number literal (false)", <<"{% if var1 < 2 %}yay{% endif %}">>, [{var1, 2}], <<"">>}, {"If int less than or equal to number literal", <<"{% if var1 <= 2 %}yay{% endif %}">>, [{var1, 1}], <<"yay">>}, {"If int less than or equal to number literal", <<"{% if var1 <= 2 %}yay{% endif %}">>, [{var1, 2}], <<"yay">>}, {"If int less than or equal to number literal (false)", <<"{% if var1 <= 2 %}yay{% endif %}">>, [{var1, 3}], <<"">>} ]}, {"if complex bool", [{"If (true or false) and true", <<"{% if (var1 or var2) and var3 %}yay{% endif %}">>, [{var1, true}, {var2, false}, {var3, true}], <<"yay">>}, {"If true or (false and true)", <<"{% if var1 or (var2 and var3) %}yay{% endif %}">>, [{var1, true}, {var2, false}, {var3, true}], <<"yay">>} ]}, {"for", [{"Simple loop", <<"{% for x in list %}{{ x }}{% endfor %}">>, [{'list', ["1", "2", "3"]}], <<"123">>}, {"Reversed loop", <<"{% for x in list reversed %}{{ x }}{% endfor %}">>, [{'list', ["1", "2", "3"]}], <<"321">>}, {"Expand list", <<"{% for x, y in list %}{{ x }},{{ y }}\n{% endfor %}">>, [{'list', [["X", "1"], ["X", "2"]]}], <<"X,1\nX,2\n">>}, {"Expand tuple", <<"{% for x, y in list %}{{ x }},{{ y }}\n{% endfor %}">>, [{'list', [{"X", "1"}, {"X", "2"}]}], <<"X,1\nX,2\n">>}, {"Resolve variable attribute", <<"{% for number in person.numbers %}{{ number }}\n{% endfor %}">>, [{person, [{numbers, ["411", "911"]}]}], <<"411\n911\n">>}, {"Resolve nested variable attribute", <<"{% for number in person.home.numbers %}{{ number }}\n{% endfor %}">>, [{person, [{home, [{numbers, ["411", "911"]}]}]}], <<"411\n911\n">>}, {"Counter0", <<"{% for number in numbers %}{{ forloop.counter0 }}. {{ number }}\n{% endfor %}">>, [{numbers, ["Zero", "One", "Two"]}], <<"0. Zero\n1. One\n2. Two\n">>}, {"Counter", <<"{% for number in numbers %}{{ forloop.counter }}. {{ number }}\n{% endfor %}">>, [{numbers, ["One", "Two", "Three"]}], <<"1. One\n2. Two\n3. Three\n">>}, {"Reverse Counter0", <<"{% for number in numbers %}{{ forloop.revcounter0 }}. {{ number }}\n{% endfor %}">>, [{numbers, ["Two", "One", "Zero"]}], <<"2. Two\n1. One\n0. Zero\n">>}, {"Reverse Counter", <<"{% for number in numbers %}{{ forloop.revcounter }}. {{ number }}\n{% endfor %}">>, [{numbers, ["Three", "Two", "One"]}], <<"3. Three\n2. Two\n1. One\n">>}, {"Counter \"first\"", <<"{% for number in numbers %}{% if forloop.first %}{{ number }}{% endif %}{% endfor %}">>, [{numbers, ["One", "Two", "Three"]}], <<"One">>}, {"Counter \"last\"", <<"{% for number in numbers %}{% if forloop.last %}{{ number }}{% endif %}{% endfor %}">>, [{numbers, ["One", "Two", "Three"]}], <<"Three">>}, {"Nested for loop", <<"{% for outer in list %}{% for inner in outer %}{{ inner }}\n{% endfor %}{% endfor %}">>, [{'list', [["Al", "Albert"], ["Jo", "Joseph"]]}], <<"Al\nAlbert\nJo\nJoseph\n">>}, {"Unused variable in foreach proplist", <<"{% for k,v in plist %}{{v}}{% endfor %}">>, [{'plist',[{1,"one"},{2,"two"}]}], [], [], <<"onetwo">>, [error_info([{0, erl_lint, {unused_var, 'Var_k/1_1:8'}}])]}, {"Unused variable in foreach proplist, prefixed with underscore", <<"{% for _k,v in plist %}{{v}}{% endfor %}">>, [{'plist',[{1,"one"},{2,"two"}]}], [], [], <<"onetwo">>}, {"Access parent loop counters", <<"{% for outer in list %}{% for inner in outer %}({{ forloop.parentloop.counter0 }}, {{ forloop.counter0 }})\n{% endfor %}{% endfor %}">>, [{'list', [["One", "two"], ["One", "two"]]}], [], [], <<"(0, 0)\n(0, 1)\n(1, 0)\n(1, 1)\n">>, %% the warnings we get from the erlang compiler still needs some care.. [error_info([{0, erl_lint, {unused_var, 'Var_inner/3_1:31'}}])]}, {"If changed", <<"{% for x in list %}{% ifchanged %}{{ x }}\n{% endifchanged %}{% endfor %}">>, [{'list', ["one", "two", "two", "three", "three", "three"]}], <<"one\ntwo\nthree\n">>}, {"If changed/2", <<"{% for x, y in list %}{% ifchanged %}{{ x|upper }}{% endifchanged %}{% ifchanged %}{{ y|lower }}{% endifchanged %}\n{% endfor %}">>, [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "c"], ["Three", "b"]]}], <<"ONEa\nTWO\nb\nTHREE\nc\nb\n">>}, {"If changed/else", <<"{% for x in list %}{% ifchanged %}{{ x }}\n{% else %}foo\n{% endifchanged %}{% endfor %}">>, [{'list', ["one", "two", "two", "three", "three", "three"]}], <<"one\ntwo\nfoo\nthree\nfoo\nfoo\n">>}, {"If changed/param", <<"{% for date in list %}{% ifchanged date.month %} {{ date.month }}:{{ date.day }}{% else %},{{ date.day }}{% endifchanged %}{% endfor %}\n">>, [{'list', [[{month,"Jan"},{day,1}],[{month,"Jan"},{day,2}],[{month,"Apr"},{day,10}], [{month,"Apr"},{day,11}],[{month,"May"},{day,4}]]}], <<" Jan:1,2 Apr:10,11 May:4\n">>}, {"If changed/param2", <<"{% for x, y in list %}{% ifchanged y|upper %}{{ x|upper }}{% endifchanged %}\n{% endfor %}">>, [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "c"], ["Three", "b"]]}], <<"ONE\n\nTWO\n\nTHREE\nTHREE\n">>}, {"If changed/param2 combined", <<"{% for x, y in list %}{% ifchanged x y|upper %}{{ x }}{% endifchanged %}\n{% endfor %}">>, [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "B"], ["three", "c"]]}], <<"one\ntwo\ntwo\nthree\n\nthree\n">>}, {"If changed/resolve", <<"{% for x in list %}{% ifchanged x.name|first %}{{ x.value }}{% endifchanged %}\n{% endfor %}">>, [{'list', [[{"name", ["nA","nB"]},{"value","1"}],[{"name", ["nA","nC"]},{"value","2"}], [{"name", ["nB","nC"]},{"value","3"}],[{"name", ["nB","nA"]},{"value","4"}]]}], <<"1\n\n3\n\n">>}, {"Loop undefined var", <<"{% for i in undef %}i = {{ i }}.\n{% endfor %}">>, [], <<"">>}, {"Loop filtered value rather than variable", <<"{% for x in 123|make_list %}{% if not forloop.first %}, {% endif %}{{ x }}{% endfor %}">>, [], <<"1, 2, 3">>} ]}, {"for/empty", [{"Simple loop", <<"{% for x in list %}{{ x }}{% empty %}shucks{% endfor %}">>, [{'list', ["1", "2", "3"]}], <<"123">>}, {"Simple loop (empty)", <<"{% for x in list %}{{ x }}{% empty %}shucks{% endfor %}">>, [{'list', []}], <<"shucks">>} ]}, {"ifequal", [{"Compare variable to literal", <<"{% ifequal var1 \"foo\" %}yay{% endifequal %}">>, [{var1, "foo"}], <<"yay">>}, {"Compare variable to unequal literal", <<"{% ifequal var1 \"foo\" %}boo{% endifequal %}">>, [{var1, "bar"}], <<>>}, {"Compare literal to variable", <<"{% ifequal \"foo\" var1 %}yay{% endifequal %}">>, [{var1, "foo"}], <<"yay">>}, {"Compare literal to unequal variable", <<"{% ifequal \"foo\" var1 %}boo{% endifequal %}">>, [{var1, "bar"}], <<>>}, {"Compare variable to literal (int string)", <<"{% ifequal var1 \"2\" %}yay{% else %}boo{% endifequal %}">>, [{var1, "2"}], <<"yay">>}, {"Compare variable to literal (int)", <<"{% ifequal var1 2 %}yay{% else %}boo{% endifequal %}">>, [{var1, 2}], <<"yay">>}, {"Compare variable to unequal literal (int)", <<"{% ifequal var1 2 %}boo{% else %}yay{% endifequal %}">>, [{var1, 3}], <<"yay">>}, {"Compare variable to equal literal (atom)", <<"{% ifequal var1 \"foo\"%}yay{% endifequal %}">>, [{var1, foo}], <<"yay">>}, {"Compare variable to unequal literal (atom)", <<"{% ifequal var1 \"foo\"%}yay{% else %}boo{% endifequal %}">>, [{var1, bar}], <<"boo">>} ]}, {"ifequal/else", [{"Compare variable to literal", <<"{% ifequal var1 \"foo\" %}yay{% else %}boo{% endifequal %}">>, [{var1, "foo"}], <<"yay">>}, {"Compare variable to unequal literal", <<"{% ifequal var1 \"foo\" %}boo{% else %}yay{% endifequal %}">>, [{var1, "bar"}], <<"yay">>}, {"Compare literal to variable", <<"{% ifequal \"foo\" var1 %}yay{% else %}boo{% endifequal %}">>, [{var1, "foo"}], <<"yay">>}, {"Compare literal to unequal variable", <<"{% ifequal \"foo\" var1 %}boo{% else %}yay{% endifequal %}">>, [{var1, "bar"}], <<"yay">>} ]}, {"ifnotequal", [{"Compare variable to literal", <<"{% ifnotequal var1 \"foo\" %}boo{% endifnotequal %}">>, [{var1, "foo"}], <<>>}, {"Compare variable to unequal literal", <<"{% ifnotequal var1 \"foo\" %}yay{% endifnotequal %}">>, [{var1, "bar"}], <<"yay">>}, {"Compare literal to variable", <<"{% ifnotequal \"foo\" var1 %}boo{% endifnotequal %}">>, [{var1, "foo"}], <<>>}, {"Compare literal to unequal variable", <<"{% ifnotequal \"foo\" var1 %}yay{% endifnotequal %}">>, [{var1, "bar"}], <<"yay">>} ]}, {"ifnotequal/else", [{"Compare variable to literal", <<"{% ifnotequal var1 \"foo\" %}boo{% else %}yay{% endifnotequal %}">>, [{var1, "foo"}], <<"yay">>}, {"Compare variable to unequal literal", <<"{% ifnotequal var1 \"foo\" %}yay{% else %}boo{% endifnotequal %}">>, [{var1, "bar"}], <<"yay">>}, {"Compare literal to variable", <<"{% ifnotequal \"foo\" var1 %}boo{% else %}yay{% endifnotequal %}">>, [{var1, "foo"}], <<"yay">>}, {"Compare literal to unequal variable", <<"{% ifnotequal \"foo\" var1 %}yay{% else %}boo{% endifnotequal %}">>, [{var1, "bar"}], <<"yay">>} ]}, {"filter tag", [{"Apply a filter", <<"{% filter escape %}&{% endfilter %}">>, [], <<"&">>}, {"Chained filters", <<"{% filter linebreaksbr|escape %}\n{% endfilter %}">>, [], <<"<br />">>} ]}, {"filters", [{"Filter a literal", <<"{{ \"pop\"|capfirst }}">>, [], <<"Pop">>}, {"Filters applied in order", <<"{{ var1|force_escape|length }}">>, [{var1, <<"&">>}], <<"5">>}, {"Escape is applied last", <<"{{ var1|escape|linebreaksbr }}">>, [{var1, <<"\n">>}], <<"<br />">>}, {"add; lhs number, rhs number", <<"{{ one|add:4}}">>, [{one, 1}], <<"5">>}, {"add; lhs numeric string, rhs number", <<"{{ one|add:4}}">>, [{one, "1"}], <<"5">>}, {"add; lhs number, rhs numeric string", <<"{{ one|add:'4'}}">>, [{one, 1}], <<"5">>}, {"add; lhs non-numeric string, rhs number", <<"{{ one|add:4}}">>, [{one, "foo"}], <<"foo4">>}, {"add; lhs number, rhs non-numeric string", <<"{{ one|add:'foo'}}">>, [{one, 1}], <<"1foo">>}, {"add; lhs non-numeric string, rhs non-numeric string", <<"{{ one|add:'bar'}}">>, [{one, "foo"}], <<"foobar">>}, {"add; lhs numeric string, rhs numeric string", <<"{{ one|add:'4'}}">>, [{one, "1"}], <<"5">>}, {"|addslashes", <<"{{ var1|addslashes }}">>, [{var1, "Jimmy's \"great\" meats'n'things"}], <<"Jimmy\\'s \\\"great\\\" meats\\'n\\'things">>}, {"|capfirst", <<"{{ var1|capfirst }}">>, [{var1, "dana boyd"}], <<"Dana boyd">>}, {"|center:10", <<"{{ var1|center:10 }}">>, [{var1, "MB"}], <<" MB ">>}, {"|center:1", <<"{{ var1|center:1 }}">>, [{var1, "KBR"}], <<"B">>}, {"|cut:\" \"", <<"{{ var1|cut:\" \" }}">>, [{var1, "String with spaces"}], <<"Stringwithspaces">>}, {"|date 1", <<"{{ var1|date:\"jS F Y H:i\" }}">>, [{var1, {1975,7,24}}], <<"24th July 1975 00:00">>}, {"|date 2", <<"{{ var1|date:\"jS F Y H:i\" }}">>, [{var1, {{1975,7,24}, {7,13,1}}}], <<"24th July 1975 07:13">>}, {"|date 3", <<"{{ var1|date }}">>, [{var1, {{1975,7,24}, {7,13,1}}}], <<"July 24, 1975">>}, % I doubt someone need first two, but test we support it {"|date a translation", <<"{{ var1|date:\"a\" }}">>, [{var1, {{1975,7,24},{12,00,00}}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"п.п."/utf8>>}, {"|date A translation", <<"{{ var1|date:\"A\" }}">>, [{var1, {{1975,7,24},{12,00,00}}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"ПП"/utf8>>}, {"|date b translation", <<"{{ var1|date:\"b\" }}">>, [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"июл"/utf8>>}, {"|date D translation", <<"{{ var1|date:\"D\" }}">>, [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"Чтв"/utf8>>}, {"|date E translation", <<"{{ var1|date:\"E\" }}">>, [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"ИюлÑ"/utf8>>}, {"|date F translation", <<"{{ var1|date:\"F\" }}">>, [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"Июль"/utf8>>}, {"|date l translation", <<"{{ var1|date:\"l\" }}">>, [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"Четверг"/utf8>>}, {"|date M translation", <<"{{ var1|date:\"M\" }}">>, [{var1, {1986,9,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"Сен"/utf8>>}, {"|date N translation", <<"{{ var1|date:\"N\" }}">>, [{var1, {1986,9,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"Сен."/utf8>>}, {"|date P translation", <<"{{ var1|date:\"P\" }}">>, [{var1, {{1986,9,24},{12,0,0}}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}], <<"полдень"/utf8>>}, {"|default:\"foo\" 1", <<"{{ var1|default:\"foo\" }}">>, [], <<"foo">>}, {"|default:\"foo\" 2", <<"{{ var1|default:\"foo\" }}">>, [{var1, "bar"}], <<"bar">>}, {"|default:\"foo\" 3", <<"{{ var1|default:\"foo\" }}">>, [{var1, "0"}], <<"foo">>}, {"|default_if_none:\"foo\"", <<"{{ var1|default_if_none:\"foo\" }}">>, [], <<"foo">>}, {"|default_if_none:\"foo\" 2", <<"{{ var1|default_if_none:\"foo\" }}">>, [{var1, "bar"}], <<"bar">>}, {"|dictsort 1", <<"{{ var1|dictsort:\"foo\" }}">>, [{var1,[[{foo,2}],[{foo,1}]]}], <<"{foo,1}{foo,2}">>}, {"|dictsort 2", <<"{{ var1|dictsort:\"foo.bar\" }}">>, [{var1,[[{foo,[{bar,2}]}],[{foo,[{bar,1}]}]]}], <<"{foo,[{bar,1}]}{foo,[{bar,2}]}">>}, {"|divisibleby:\"3\"", <<"{% if var1|divisibleby:\"3\" %}yay{% endif %}">>, [{var1, 21}], <<"yay">>}, {"|divisibleby:\"3\"", <<"{% if var1|divisibleby:\"3\" %}yay{% endif %}">>, [{var1, 22}], <<"">>}, {"|escape", <<"{% autoescape on %}{{ var1|escape|escape|escape }}{% endautoescape %}">>, [{var1, ">&1"}], <<">&1">>}, {"|escapejs", <<"{{ var1|escapejs }}">>, [{var1, "testing\r\njavascript 'string\" escaping"}], <<"testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E">>}, {"|filesizeformat (bytes)", <<"{{ var1|filesizeformat }}">>, [{var1, 1023}], <<"1023 bytes">>}, {"|filesizeformat (KB)", <<"{{ var1|filesizeformat }}">>, [{var1, 3487}], <<"3.4 KB">>}, {"|filesizeformat (MB)", <<"{{ var1|filesizeformat }}">>, [{var1, 6277098}], <<"6.0 MB">>}, {"|filesizeformat (GB)", <<"{{ var1|filesizeformat }}">>, [{var1, 1024 * 1024 * 1024}], <<"1.0 GB">>}, {"|first", <<"{{ var1|first }}">>, [{var1, "James"}], <<"J">>}, {"|fix_ampersands", <<"{{ var1|fix_ampersands }}">>, [{var1, "Ben & Jerry's"}], <<"Ben & Jerry's">>}, {"|floatformat:\"-1\"", <<"{{ var1|floatformat:\"-1\" }}">>, [{var1, 34.23234}], <<"34.2">>}, {"int |floatformat", <<"{{ var1|floatformat:\"-1\" }}">>, [{var1, 123}], <<"123">>}, {"string |floatformat", <<"{{ var1|floatformat:\"-1\" }}">>, [{var1, "123.321"}], <<"123.3">>}, {"binary |floatformat", <<"{{ var1|floatformat:\"-1\" }}">>, [{var1, <<"123.321">>}], <<"123.3">>}, %% from: https://docs.djangoproject.com/en/1.6/ref/templates/builtins/#floatformat {"1.a) |floatformat", <<"{{ var1|floatformat }}">>, [{var1, 34.23234}], <<"34.2">>}, {"1.b) |floatformat", <<"{{ var1|floatformat }}">>, [{var1, 34.00000}], <<"34">>}, {"1.c) |floatformat", <<"{{ var1|floatformat }}">>, [{var1, 34.26000}], <<"34.3">>}, {"2.a) |floatformat:\"3\"", <<"{{ var1|floatformat:\"3\" }}">>, [{var1, 34.23234}], <<"34.232">>}, {"2.b) |floatformat:\"3\"", <<"{{ var1|floatformat:\"3\" }}">>, [{var1, 34.00000}], <<"34.000">>}, {"2.c) |floatformat:\"3\"", <<"{{ var1|floatformat:\"3\" }}">>, [{var1, 34.26000}], <<"34.260">>}, {"3.a) |floatformat:\"0\"", <<"{{ var1|floatformat:\"0\" }}">>, [{var1, 34.23234}], <<"34">>}, {"3.b) |floatformat:\"0\"", <<"{{ var1|floatformat:\"0\" }}">>, [{var1, 34.00000}], <<"34">>}, {"3.c) |floatformat:\"0\"", <<"{{ var1|floatformat:\"0\" }}">>, [{var1, 39.56000}], <<"40">>}, {"4.a) |floatformat:\"-3\"", <<"{{ var1|floatformat:\"-3\" }}">>, [{var1, 34.23234}], <<"34.232">>}, {"4.b) |floatformat:\"-3\"", <<"{{ var1|floatformat:\"-3\" }}">>, [{var1, 34.00000}], <<"34">>}, {"4.c) |floatformat:\"-3\"", <<"{{ var1|floatformat:\"-3\" }}">>, [{var1, 34.26000}], <<"34.260">>}, {"|force_escape", <<"{{ var1|force_escape }}">>, [{var1, "Ben & Jerry's <=> \"The World's Best Ice Cream\""}], <<"Ben & Jerry's <=> "The World's Best Ice Cream"">>}, {"iolist |force_escape", <<"{{ var1|force_escape }}">>, [{var1, ["'a'"]}], <<"'a'">>}, {"nested iolist |force_escape", <<"{{ var1|force_escape }}">>, [{var1, ["a'", <<"b">>, [<<">, "d", ["e>"]]]}], <<"a'b<cde>">>}, {"|format_integer", <<"{{ var1|format_integer }}">>, [{var1, 28}], <<"28">>}, {"|format_number 1", <<"{{ var1|format_number }}">>, [{var1, 28}], <<"28">>}, {"|format_number 2", <<"{{ var1|format_number }}">>, [{var1, 23.77}], <<"23.77">>}, {"|format_number 3", <<"{{ var1|format_number }}">>, [{var1, "28.77"}], <<"28.77">>}, {"|format_number 4", <<"{{ var1|format_number }}">>, [{var1, "23.77"}], <<"23.77">>}, {"|format_number 5", <<"{{ var1|format_number }}">>, [{var1, fun() -> 29 end}], <<"29">>}, {"|format_number 6", <<"{{ var1|format_number }}">>, [{var1, fun() -> fun() -> 31 end end}], <<"31">>}, {"|get_digit:\"2\"", <<"{{ var1|get_digit:\"2\" }}">>, [{var1, 42}], <<"4">>}, {"|iriencode", <<"{{ url|iriencode }}">>, [{url, "You #$*@!!"}], <<"You+#$*@!!">>}, {"|join:\", \" (list)", <<"{{ var1|join:\", \" }}">>, [{var1, ["Liberte", "Egalite", "Fraternite"]}], <<"Liberte, Egalite, Fraternite">>}, {"|join:\", \" (binary)", <<"{{ var1|join:\", \" }}">>, [{var1, [<<"Liberte">>, "Egalite", <<"Fraternite">>]}], <<"Liberte, Egalite, Fraternite">>}, {"|join:\", \" (numbers)", <<"{{ var1|join:\", \" }}">>, [{var1, [1, 2, 3]}], <<"1, 2, 3">>}, {"|last", <<"{{ var1|last }}">>, [{var1, "XYZ"}], <<"Z">>}, {"|length", <<"{{ var1|length }}">>, [{var1, "antidisestablishmentarianism"}], <<"28">>}, {"|linebreaks", <<"{{ var1|linebreaks }}">>, [{var1, "Joel\nis a slug"}], <<"

    Joel
    is a slug

    ">>}, {"|linebreaks", <<"{{ var1|linebreaks }}">>, [{var1, "Joel\n\n\n\nis a slug"}], <<"

    Joel

    is a slug

    ">>}, {"|linebreaks", <<"{{ var1|linebreaks }}">>, [{var1, "Joel\n\nis a \nslug"}], <<"

    Joel

    is a
    slug

    ">>}, {"|linebreaksbr", <<"{{ var1|linebreaksbr }}">>, [{var1, "One\nTwo\n\nThree\n\n\n"}], <<"One
    Two

    Three


    ">>}, {"|linebreaksbr", <<"{{ \"One\\nTwo\\n\\nThree\\n\\n\\n\"|linebreaksbr }}">>, [], <<"One
    Two

    Three


    ">>}, {"|linenumbers", <<"{{ var1|linenumbers }}">>, [{var1, "a\nb\nc"}], <<"1. a\n2. b\n3. c">>}, {"|linenumbers", <<"{{ var1|linenumbers }}">>, [{var1, "a"}], <<"1. a">>}, {"|linenumbers", <<"{{ var1|linenumbers }}">>, [{var1, "a\n"}], <<"1. a\n2. ">>}, {"|ljust:10", <<"{{ var1|ljust:10 }}">>, [{var1, "Gore"}], <<"Gore ">>}, {"|lower", <<"{{ var1|lower }}">>, [{var1, "E. E. Cummings"}], <<"e. e. cummings">>}, {"|makelist", <<"{{ list|make_list }}">>, [{list, "Joel"}], <<"J","o","e","l">>}, {"|pluralize", <<"{{ num|pluralize }}">>, [{num, 1}], <<"">>}, {"|pluralize", <<"{{ num|pluralize }}">>, [{num, 2}], <<"s">>}, {"|pluralize:\"s\"", <<"{{ num|pluralize }}">>, [{num, 1}], <<"">>}, {"|pluralize:\"s\"", <<"{{ num|pluralize }}">>, [{num, 2}], <<"s">>}, {"|pluralize:\"y,es\" (list)", <<"{{ num|pluralize:\"y,es\" }}">>, [{num, 1}], <<"y">>}, {"|pluralize:\"y,es\" (list)", <<"{{ num|pluralize:\"y,es\" }}">>, [{num, 2}], <<"es">>}, {"|length|pluralize", <<"{{ list|length|pluralize:\"plural\" }}">>, [{list, [foo, bar]}], <<"plural">>}, {"|length|pluralize", <<"{{ list|length|pluralize:\"plural\" }}">>, [{list, [foo]}], <<"">>}, {"|random", <<"{{ var1|random }}">>, [{var1, ["foo", "foo", "foo"]}], <<"foo">>}, {"|removetags:\"b span\"", <<"{{ var1|removetags:\"b span\" }}">>, [{var1, "Joel a slug"}], <<"Joel a slug">>}, {"|rjust:10", <<"{{ var1|rjust:10 }}">>, [{var1, "Bush"}], <<" Bush">>}, {"|safe", <<"{% autoescape on %}{{ var1|safe|escape }}{% endautoescape %}">>, [{var1, "&"}], <<"&">>}, {"|safe is local", <<"{{ var1 }}{{ var1|safe }}{{ var1 }}">>, [{var1, "&"}], [], [auto_escape], <<"&&&">>}, %%python/django slice is zero based, erlang lists are 1 based %%first number included, second number not %%negative numbers are allowed %%regex to convert from erlydtl_filters_tests: % for slice: \?assert.*\( \[(.*)\], erlydtl_filters:(.*)\((.*),"(.*)"\)\), % {"|slice:\"$4\"", <<"{{ var|$2:\"$4\" }}">>, [{var, $3}],<<$1>>}, % \t\t{"|slice:\"$4\"",\n\t\t\t\t\t <<"{{ var|$2:\"$4\" }}">>, [{var, $3}],\n\t\t\t\t\t<<$1>>}, % % for stringformat: % \?assert.*\( (.*), erlydtl_filters:(.*)\((.*), "(.*)"\) \) % \t\t{"|stringformat:\"$4\"",\n\t\t\t\t\t <<"{{ var|$2:\"$4\" }}">>, [{var, $3}],\n\t\t\t\t\t<<$1>>} {"|slice:\":\"", <<"{{ var|slice:\":\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1,2,3,4,5,6,7,8,9>>}, {"|slice:\"1\"", <<"{{ var|slice:\"1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<"2">>}, {"|slice:\"100\"", <<"{{ var|slice:\"100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<"indexError">>}, {"|slice:\"-1\"", <<"{{ var|slice:\"-1\" }}">>, [{var, ["a","b","c","d","e","f","g","h","i"]}], <<"i">>}, {"|slice:\"-1\"", <<"{{ var|slice:\"-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<"9">>}, {"|slice:\"-100\"", <<"{{ var|slice:\"-100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<"indexError">>}, {"|slice:\"1:\"", <<"{{ var|slice:\"1:\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<2,3,4,5,6,7,8,9>>}, {"|slice:\"100:\"", <<"{{ var|slice:\"100:\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"-1:\"", <<"{{ var|slice:\"-1:\" }}">>, [{var, ["a","b","c","d","e","f","h","i","j"]}], <<"j">>}, {"|slice:\"-1:\"", <<"{{ var|slice:\"-1:\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<9>>}, {"|slice:\"-100:\"", <<"{{ var|slice:\"-100:\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1,2,3,4,5,6,7,8,9>>}, {"|slice:\":1\"", <<"{{ var|slice:\":1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1>>}, {"|slice:\":100\"", <<"{{ var|slice:\":100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1,2,3,4,5,6,7,8,9>>}, {"|slice:\":-1\"", <<"{{ var|slice:\":-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1,2,3,4,5,6,7,8>>}, {"|slice:\":-100\"", <<"{{ var|slice:\":-100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"-1:-1\"", <<"{{ var|slice:\"-1:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"1:1\"", <<"{{ var|slice:\"1:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"1:-1\"", <<"{{ var|slice:\"1:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<2,3,4,5,6,7,8>>}, {"|slice:\"-1:1\"", <<"{{ var|slice:\"-1:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"-100:-100\"", <<"{{ var|slice:\"-100:-100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"100:100\"", <<"{{ var|slice:\"100:100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"100:-100\"", <<"{{ var|slice:\"100:-100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"-100:100\"", <<"{{ var|slice:\"-100:100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1,2,3,4,5,6,7,8,9>>}, {"|slice:\"1:3\"", <<"{{ var|slice:\"1:3\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<2,3>>}, {"|slice:\"::\"", <<"{{ var|slice:\"::\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1,2,3,4,5,6,7,8,9>>}, {"|slice:\"1:9:1\"", <<"{{ var|slice:\"1:9:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<2,3,4,5,6,7,8,9>>}, {"|slice:\"10:1:-1\"", <<"{{ var|slice:\"10:1:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<9,8,7,6,5,4,3>>}, {"|slice:\"-111:-1:1\"", <<"{{ var|slice:\"-111:-1:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1,2,3,4,5,6,7,8>>}, {"|slice:\"-111:-111:1\"", <<"{{ var|slice:\"-111:-111:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"111:111:1\"", <<"{{ var|slice:\"111:111:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"-111:111:1\"", <<"{{ var|slice:\"-111:111:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<1,2,3,4,5,6,7,8,9>>}, {"|slice:\"111:-111:1\"", <<"{{ var|slice:\"111:-111:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"-111:-111:-1\"", <<"{{ var|slice:\"-111:-111:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"111:111:-1\"", <<"{{ var|slice:\"111:111:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"-111:111:-1\"", <<"{{ var|slice:\"-111:111:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<>>}, {"|slice:\"111:-111:-1\"", <<"{{ var|slice:\"111:-111:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], <<9,8,7,6,5,4,3,2,1>>}, {"|phone2numeric", <<"{{ var1|phone2numeric }}">>, [{var1, "1-800-COLLECT"}], <<"1-800-2655328">>}, {"|slugify", <<"{{ var1|slugify }}">>, [{var1, "What The $#_! Was He Thinking?"}], <<"what-the-_-was-he-thinking">>}, {"|slice:\"s\"", <<"{{ var|stringformat:\"s\" }}">>, [{var, "test"}], <<"test">>}, {"|stringformat:\"s\"", <<"{{ var|stringformat:\"s\" }}">>, [{var, "test"}], <<"test">>}, {"|stringformat:\"s\"", <<"{{ var|stringformat:\"s\" }}">>, [{var, "1"}], <<"1">>}, {"|stringformat:\"s\"", <<"{{ var|stringformat:\"s\" }}">>, [{var, "test"}], <<"test">>}, {"|stringformat:\"10s\"", <<"{{ var|stringformat:\"10s\" }}">>, [{var, "test"}], <<" test">>}, {"|stringformat:\"-10s\"", <<"{{ var|stringformat:\"-10s\" }}">>, [{var, "test"}], <<"test ">>}, {"|stringformat:\"d\"", <<"{{ var|stringformat:\"d\" }}">>, [{var, "90"}], <<"90">>}, {"|stringformat:\"10d\"", <<"{{ var|stringformat:\"10d\" }}">>, [{var, "90"}], <<" 90">>}, {"|stringformat:\"-10d\"", <<"{{ var|stringformat:\"-10d\" }}">>, [{var, "90"}], <<"90 ">>}, {"|stringformat:\"i\"", <<"{{ var|stringformat:\"i\" }}">>, [{var, "90"}], <<"90">>}, {"|stringformat:\"10i\"", <<"{{ var|stringformat:\"10i\" }}">>, [{var, "90"}], <<" 90">>}, {"|stringformat:\"-10i\"", <<"{{ var|stringformat:\"-10i\" }}">>, [{var, "90"}], <<"90 ">>}, {"|stringformat:\"0.2d\"", <<"{{ var|stringformat:\"0.2d\" }}">>, [{var, "9"}], <<"09">>}, {"|stringformat:\"10.4d\"", <<"{{ var|stringformat:\"10.4d\" }}">>, [{var, "9"}], <<" 0009">>}, {"|stringformat:\"-10.4d\"", <<"{{ var|stringformat:\"-10.4d\" }}">>, [{var, "9"}], <<"0009 ">>}, {"|stringformat:\"f\"", <<"{{ var|stringformat:\"f\" }}">>, [{var, "1"}], <<"1.000000">>}, {"|stringformat:\".2f\"", <<"{{ var|stringformat:\".2f\" }}">>, [{var, "1"}], <<"1.00">>}, {"|stringformat:\"0.2f\"", <<"{{ var|stringformat:\"0.2f\" }}">>, [{var, "1"}], <<"1.00">>}, {"|stringformat:\"-0.2f\"", <<"{{ var|stringformat:\"-0.2f\" }}">>, [{var, "1"}], <<"1.00">>}, {"|stringformat:\"10.2f\"", <<"{{ var|stringformat:\"10.2f\" }}">>, [{var, "1"}], <<" 1.00">>}, {"|stringformat:\"-10.2f\"", <<"{{ var|stringformat:\"-10.2f\" }}">>, [{var, "1"}], <<"1.00 ">>}, {"|stringformat:\".2f\"", <<"{{ var|stringformat:\".2f\" }}">>, [{var, "1"}], <<"1.00">>}, {"|stringformat:\"x\"", <<"{{ var|stringformat:\"x\" }}">>, [{var, "90"}], <<"5a">>}, {"|stringformat:\"X\"", <<"{{ var|stringformat:\"X\" }}">>, [{var, "90"}], <<"5A">>}, {"|stringformat:\"o\"", <<"{{ var|stringformat:\"o\" }}">>, [{var, "90"}], <<"132">>}, {"|stringformat:\"e\"", <<"{{ var|stringformat:\"e\" }}">>, [{var, "90"}], <<"9.000000e+01">>}, {"|stringformat:\"e\"", <<"{{ var|stringformat:\"e\" }}">>, [{var, "90000000000"}], <<"9.000000e+10">>}, {"|stringformat:\"E\"", <<"{{ var|stringformat:\"E\" }}">>, [{var, "90"}], <<"9.000000E+01">>}, {"|striptags", <<"{{ var|striptags }}">>, [{var, "Joel a slug"}], <<"Joel is a slug">>}, {"|striptags", <<"{{ var|striptags }}">>, [{var, "Joel a slug"}], <<"Joel is a slug">>}, {"|striptags", <<"{{ var|striptags }}">>, [{var, "Check out http://www.djangoproject.com"}], <<"Check out http://www.djangoproject.com">>}, {"|time:\"H:i\"", <<"{{ var|time:\"H:i\" }}">>, [{var, {{2010,12,1}, {10,11,12}} }], <<"10:11">>}, {"|time", <<"{{ var|time }}">>, [{var, {{2010,12,1}, {10,11,12}} }], <<"10:11 a.m.">>}, {"|timesince:from_date", <<"{{ from_date|timesince:conference_date }}">>, [{conference_date, {{2006,6,1},{8,0,0}} }, {from_date, {{2006,6,1},{0,0,0}} }], <<"8 hours">>}, {"|timesince:from_date", <<"{{ from_date|timesince:conference_date }}">>, [{conference_date, {{2010,6,1},{8,0,0}} },{from_date, {{2006,6,1},{0,0,0}} }], <<"4 years, 1 day">>}, % leap year {"|timesince:from_date", <<"{{ from_date|timesince:conference_date }}">>, [{conference_date, {{2006,7,15},{8,0,0}} },{from_date, {{2006,6,1},{0,0,0}} }], <<"1 month, 2 weeks">>}, {"|timeuntil:from_date", <<"{{ conference_date|timeuntil:from_date }}">>, [{conference_date, {{2006,6,1},{8,0,0}} }, {from_date, {{2006,6,1},{0,0,0}} }], <<"8 hours">>}, {"|timeuntil:from_date", <<"{{ conference_date|timeuntil:from_date }}">>, [{conference_date, {{2010,6,1},{8,0,0}} },{from_date, {{2006,6,1},{0,0,0}} }], <<"4 years, 1 day">>}, {"|timeuntil:from_date", <<"{{ conference_date|timeuntil:from_date }}">>, [{conference_date, {{2006,7,15},{8,0,0}} },{from_date, {{2006,6,1},{0,0,0}} }], <<"1 month, 2 weeks">>}, {"|title", <<"{{ \"my title case\"|title }}">>, [], <<"My Title Case">>}, {"|title (pre-formatted)", <<"{{ \"My Title Case\"|title }}">>, [], <<"My Title Case">>}, {"|title (wacky separators)", <<"{{ \"my-title!case\"|title }}">>, [], <<"My-Title!Case">>}, {"|title (numbers)", <<"{{ \"my-title123CaSe\"|title }}">>, [], <<"My-Title123case">>}, {"|title (Irish names)", <<"{{ \"who's o'malley?\"|title }}">>, [], <<"Who's O'Malley?">>}, {"|truncatechars:0", <<"{{ var1|truncatechars:0 }}">>, [{var1, "Empty Me"}], <<"...">>}, {"|truncatechars:14", <<"{{ var1|truncatechars:14 }}">>, [{var1, "Truncate Me Please"}], <<"Truncate Me...">>}, {"|truncatechars:17", <<"{{ var1|truncatechars:17 }}">>, [{var1, "Don't Truncate Me"}], <<"Don't Truncate Me">>}, {"|truncatechars:4 (UTF-8)", <<"{{ var1|truncatechars:4 }}">>, [{var1, "\x{E2}\x{82}\x{AC}1.99"}], <<"\x{E2}\x{82}\x{AC}...">>}, {"|truncatechars:5 (UTF-8)", <<"{{ var1|truncatechars:5 }}">>, [{var1, "\x{E2}\x{82}\x{AC} 1.99"}], <<"\x{E2}\x{82}\x{AC} ...">>}, {"|truncatewords:0", <<"{{ var1|truncatewords:0 }}">>, [{var1, "Empty Me"}], <<" ...">>}, {"|truncatewords:2", <<"{{ var1|truncatewords:2 }}">>, [{var1, "Truncate Me Please"}], <<"Truncate Me ...">>}, {"|truncatewords:3", <<"{{ var1|truncatewords:3 }}">>, [{var1, "Don't Truncate Me"}], <<"Don't Truncate Me">>}, {"|truncatewords_html:4", <<"{{ var1|truncatewords_html:4 }}">>, [{var1, "

    The Long and Winding Road is too long

    "}], <<"

    The Long and Winding...

    ">>}, {"|truncatewords_html:50", <<"{{ var1|truncatewords_html:50 }}">>, [{var1, "

    The Long and Winding Road is too long

    "}], <<"

    The Long and Winding Road is too long

    ">>}, {"|unordered_list", <<"{{ var1|unordered_list }}">>, [{var1, ["States", ["Kansas", ["Lawrence", "Topeka"], "Illinois"]]}], <<"
  • States
    • Kansas
      • Lawrence
      • Topeka
    • Illinois
  • ">>}, {"|upper", <<"{{ message|upper }}">>, [{message, "That man has a gun."}], <<"THAT MAN HAS A GUN.">>}, {"|urlencode", <<"{{ url|urlencode }}">>, [{url, "You #$*@!!"}], <<"You%20%23%24%2A%40%21%21">>}, {"|urlencode", <<"{{ url|urlencode }}">>, [{url, "http://www.example.org/foo?a=b&c=d"}], <<"http%3A//www.example.org/foo%3Fa%3Db%26c%3Dd">>}, {"|urlencode", <<"{{ url|urlencode:\"\" }}">>, [{url, "http://www.example.org/foo?a=b&c=d"}], <<"http%3A%2F%2Fwww.example.org%2Ffoo%3Fa%3Db%26c%3Dd">>}, {"|urlencode", <<"{{ url|urlencode:\":/?=&\" }}">>, [{url, "http://www.example.org/foo?a=b&c=d"}], <<"http://www.example.org/foo?a=b&c=d">>}, {"|urlize", <<"{{ var|urlize }}">>, [{var, "Check out www.djangoproject.com"}], <<"Check out www.djangoproject.com">>}, {"|urlize", <<"{{ var|urlize }}">>, [{var, "Check out http://www.djangoproject.com"}], <<"Check out http://www.djangoproject.com">>}, {"|urlize", <<"{{ var|urlize }}">>, [{var, "Check out \"http://www.djangoproject.com\""}], <<"Check out \"http://www.djangoproject.com\"">>}, {"|urlizetrunc:15", <<"{{ var|urlizetrunc:15 }}">>, [{var, "Check out www.djangoproject.com"}], <<"Check out www.djangopr...">>}, {"|wordcount", <<"{{ words|wordcount }}">>, [{words, "Why Hello There!"}], <<"3">>}, {"|wordwrap:2", <<"{{ words|wordwrap:2 }}">>, [{words, "this is"}], <<"this \nis">>}, {"|wordwrap:100", <<"{{ words|wordwrap:100 }}">>, [{words, "testing testing"}], <<"testing testing">>}, {"|wordwrap:10", <<"{{ words|wordwrap:10 }}">>, [{words, ""}], <<"">>}, {"|wordwrap:1", <<"{{ words|wordwrap:1 }}">>, [{words, "two"}], <<"two">>}, %% yesno match: \?assert.*\( (.*), erlydtl_filters:(.*)\((.*), "(.*)"\)\) %% yesno replace: \t\t{"|$2:\"$4\"",\n\t\t\t\t\t <<"{{ var|$2:\"$4\" }}">>, [{var, $3}],\n\t\t\t\t\t<<$1>>} {"|yesno:\"yeah,no,maybe\"", <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, true}], <<"yeah">>}, {"|yesno:\"yeah,no,maybe\"", <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, false}], <<"no">>}, {"|yesno:\"yeah,no\"", <<"{{ var|yesno:\"yeah,no\" }}">>, [{var, undefined}], <<"no">>}, {"|yesno:\"yeah,no,maybe\"", <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, undefined}], <<"maybe">>}, {"string |yesno:\"yeah,no,maybe\"", <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, "non-empty string"}], <<"yeah">>}, {"binary |yesno:\"yeah,no,maybe\"", <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, <<"non-empty binary">>}], <<"yeah">>}, {"empty string |yesno:\"yeah,no,maybe\"", <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, ""}], <<"no">>}, {"empty binary |yesno:\"yeah,no\"", <<"{{ var|yesno:\",no\" }}">>, [{var, <<"">>}], <<"no">>}, {"term |yesno:\"yeah,,maybe\"", <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, {my, [term, "test"]}}], <<"yeah">>}, {"|yesno:\"yeah,\"", <<"{{ var|yesno:\"yeah,\" }}">>, [{var, false}], <<"">>}, {"|yesno:\"yeah,,maybe\"", <<"{{ var|yesno:\"yeah,,maybe\" }}">>, [{var, false}], <<"">>}, #test{ title = "|yesno:\"missing_false_choice\"", source = <<"{{ var|yesno:\"missing_false_choice\" }}">>, render_vars = [{var, true}], output = {error, {yesno, choices}} }, {"escape only once (#150) - no auto escape", %% note that auto_escape is off by default in the test suite %% due to how the tests have been written (and it's too much %% work for me to rewrite them) <<"{{ foo }}{{ foo|add:'bar' }}">>, [{foo, "foo&"}], <<"foo&foo&bar">>}, {"escape only once (#150) - auto escape block", <<"{% autoescape on %}{{ foo }}{{ foo|add:'bar' }}{% endautoescape %}">>, [{foo, "foo&"}], <<"foo&foo&bar">>}, {"escape only once (#150) - auto escape", <<"{{ foo }}{{ foo|add:'bar' }}">>, [{foo, "foo&"}], [], [auto_escape], <<"foo&foo&bar">>}, {"escape only once (#150) - auto escape, safe", <<"{{ foo|safe }}{{ foo|add:'bar'|safe }}&{{ foo|safe|add:'bar' }}">>, [{foo, "foo&"}], [], [auto_escape], <<"foo&foo&bar&foo&bar">>}, {"escape only once (#150) - escape filter", <<"{{ foo|escape }}{{ foo|add:'bar'|escape }}&{{ foo|escape|add:'bar' }}">>, [{foo, "foo&"}], <<"foo&foo&bar&foo&bar">>}, {"escape only once (#150) - auto escape + escape filter", <<"{{ foo|escape }}{{ foo|add:'bar'|escape }}&{{ foo|escape|add:'bar' }}">>, [{foo, "foo&"}], [], [auto_escape], <<"foo&foo&bar&foo&bar">>} ]}, {"filters_if", [{"Filter if 1.1", <<"{% if var1|length_is:0 %}Y{% else %}N{% endif %}">>, [{var1, []}], <<"Y">>}, {"Filter if 1.2", <<"{% if var1|length_is:1 %}Y{% else %}N{% endif %}">>, [{var1, []}], <<"N">>}, {"Filter if 1.3", <<"{% if var1|length_is:7 %}Y{% else %}N{% endif %}">>, [{var1, []}], <<"N">>}, {"Filter if 2.1", <<"{% if var1|length_is:0 %}Y{% else %}N{% endif %}">>, [{var1, ["foo"]}], <<"N">>}, {"Filter if 2.2", <<"{% if var1|length_is:1 %}Y{% else %}N{% endif %}">>, [{var1, ["foo"]}], <<"Y">>}, {"Filter if 2.3", <<"{% if var1|length_is:7 %}Y{% else %}N{% endif %}">>, [{var1, ["foo"]}], <<"N">>}, {"Filter if 3.1", <<"{% ifequal var1|length 0 %}Y{% else %}N{% endifequal %}">>, [{var1, []}], <<"Y">>}, {"Filter if 3.2", <<"{% ifequal var1|length 1 %}Y{% else %}N{% endifequal %}">>, [{var1, []}], <<"N">>}, {"Filter if 4.1", <<"{% ifequal var1|length 3 %}Y{% else %}N{% endifequal %}">>, [{var1, ["foo", "bar", "baz"]}], <<"Y">>}, {"Filter if 4.2", <<"{% ifequal var1|length 0 %}Y{% else %}N{% endifequal %}">>, [{var1, ["foo", "bar", "baz"]}], <<"N">>}, {"Filter if 4.3", <<"{% ifequal var1|length 1 %}Y{% else %}N{% endifequal %}">>, [{var1, ["foo", "bar", "baz"]}], <<"N">>} ]}, {"firstof", [{"Firstof first", <<"{% firstof foo bar baz %}">>, [{foo, "1"},{bar, "2"}], <<"1">>}, {"Firstof second", <<"{% firstof foo bar baz %}">>, [{bar, "2"}], <<"2">>}, {"Firstof none", <<"{% firstof foo bar baz %}">>, [], <<"">>}, {"Firstof complex", <<"{% firstof foo.bar.baz bar %}">>, [{foo, [{bar, [{baz, "quux"}]}]}], <<"quux">>}, {"Firstof undefined complex", <<"{% firstof foo.bar.baz bar %}">>, [{bar, "bar"}], <<"bar">>}, {"Firstof literal", <<"{% firstof foo bar \"baz\" %}">>, [], <<"baz">>} ]}, {"regroup .. endregroup", [{"Ordered", <<"{% regroup people by gender as gender_list %}{% for gender in gender_list %}{{ gender.grouper }}\n{% for item in gender.list %}{{ item.first_name }}\n{% endfor %}{% endfor %}{% endregroup %}">>, [{people, [[{first_name, "George"}, {gender, "Male"}], [{first_name, "Bill"}, {gender, "Male"}], [{first_name, "Margaret"}, {gender, "Female"}], [{first_name, "Condi"}, {gender, "Female"}]]}], <<"Male\nGeorge\nBill\nFemale\nMargaret\nCondi\n">>}, {"Unordered", <<"{% regroup people by gender as gender_list %}{% for gender in gender_list %}{{ gender.grouper }}\n{% for item in gender.list %}{{ item.first_name }}\n{% endfor %}{% endfor %}{% endregroup %}">>, [{people, [[{first_name, "George"}, {gender, "Male"}], [{first_name, "Margaret"}, {gender, "Female"}], [{first_name, "Condi"}, {gender, "Female"}], [{first_name, "Bill"}, {gender, "Male"}] ]}], <<"Male\nGeorge\nFemale\nMargaret\nCondi\nMale\nBill\n">>}, {"NestedOrdered", <<"{% regroup people by name.last as lastname_list %}{% for lastname in lastname_list %}{{ lastname.grouper }}\n{% for item in lastname.list %}{{ item.name.first }}\n{% endfor %}{% endfor %}{% endregroup %}">>, [{people, [[{name, [{first,"George"},{last,"Costanza"}]}], [{name, [{first,"Margaret"},{last,"Costanza"}]}], [{name, [{first,"Bill"},{last,"Buffalo"}]}], [{name, [{first,"Condi"},{last,"Buffalo"}]}]]}], <<"Costanza\nGeorge\nMargaret\nBuffalo\nBill\nCondi\n">>}, {"NestedUnordered", <<"{% regroup people by name.last as lastname_list %}{% for lastname in lastname_list %}{{ lastname.grouper }}\n{% for item in lastname.list %}{{ item.name.first }}\n{% endfor %}{% endfor %}{% endregroup %}">>, [{people, [[{name, [{first,"George"},{last,"Costanza"}]}], [{name, [{first,"Bill"},{last,"Buffalo"}]}], [{name, [{first,"Margaret"},{last,"Costanza"}]}], [{name, [{first,"Condi"},{last,"Buffalo"}]}]]}], <<"Costanza\nGeorge\nBuffalo\nBill\nCostanza\nMargaret\nBuffalo\nCondi\n">>}, {"Filter", <<"{% regroup people|dictsort:\"name.last\" by name.last as lastname_list %}{% for lastname in lastname_list %}{{ lastname.grouper }}\n{% for item in lastname.list %}{{ item.name.first }}\n{% endfor %}{% endfor %}{% endregroup %}">>, [{people, [[{name, [{first,"George"},{last,"Costanza"}]}], [{name, [{first,"Bill"},{last,"Buffalo"}]}], [{name, [{first,"Margaret"},{last,"Costanza"}]}], [{name, [{first,"Condi"},{last,"Buffalo"}]}]]}], <<"Buffalo\nBill\nCondi\nCostanza\nGeorge\nMargaret\n">>} ]}, {"regroup", [{"Ordered", <<"{% regroup people by gender as gender_list %}{% for gender in gender_list %}{{ gender.grouper }}\n{% for item in gender.list %}{{ item.first_name }}\n{% endfor %}{% endfor %}">>, [{people, [[{first_name, "George"}, {gender, "Male"}], [{first_name, "Bill"}, {gender, "Male"}], [{first_name, "Margaret"}, {gender, "Female"}], [{first_name, "Condi"}, {gender, "Female"}]]}], <<"Male\nGeorge\nBill\nFemale\nMargaret\nCondi\n">>}, {"Unordered", <<"{% regroup people by gender as gender_list %}{% for gender in gender_list %}{{ gender.grouper }}\n{% for item in gender.list %}{{ item.first_name }}\n{% endfor %}{% endfor %}">>, [{people, [[{first_name, "George"}, {gender, "Male"}], [{first_name, "Margaret"}, {gender, "Female"}], [{first_name, "Condi"}, {gender, "Female"}], [{first_name, "Bill"}, {gender, "Male"}] ]}], <<"Male\nGeorge\nFemale\nMargaret\nCondi\nMale\nBill\n">>}, {"NestedOrdered", <<"{% regroup people by name.last as lastname_list %}{% for lastname in lastname_list %}{{ lastname.grouper }}\n{% for item in lastname.list %}{{ item.name.first }}\n{% endfor %}{% endfor %}">>, [{people, [[{name, [{first,"George"},{last,"Costanza"}]}], [{name, [{first,"Margaret"},{last,"Costanza"}]}], [{name, [{first,"Bill"},{last,"Buffalo"}]}], [{name, [{first,"Condi"},{last,"Buffalo"}]}]]}], <<"Costanza\nGeorge\nMargaret\nBuffalo\nBill\nCondi\n">>}, {"NestedUnordered", <<"{% regroup people by name.last as lastname_list %}{% for lastname in lastname_list %}{{ lastname.grouper }}\n{% for item in lastname.list %}{{ item.name.first }}\n{% endfor %}{% endfor %}">>, [{people, [[{name, [{first,"George"},{last,"Costanza"}]}], [{name, [{first,"Bill"},{last,"Buffalo"}]}], [{name, [{first,"Margaret"},{last,"Costanza"}]}], [{name, [{first,"Condi"},{last,"Buffalo"}]}]]}], <<"Costanza\nGeorge\nBuffalo\nBill\nCostanza\nMargaret\nBuffalo\nCondi\n">>}, {"Filter", <<"{% regroup people|dictsort:\"name.last\" by name.last as lastname_list %}{% for lastname in lastname_list %}{{ lastname.grouper }}\n{% for item in lastname.list %}{{ item.name.first }}\n{% endfor %}{% endfor %}">>, [{people, [[{name, [{first,"George"},{last,"Costanza"}]}], [{name, [{first,"Bill"},{last,"Buffalo"}]}], [{name, [{first,"Margaret"},{last,"Costanza"}]}], [{name, [{first,"Condi"},{last,"Buffalo"}]}]]}], <<"Buffalo\nBill\nCondi\nCostanza\nGeorge\nMargaret\n">>}, {"With surrounding context", <<"People: {% regroup people by gender as gender_list %}{% for gender in gender_list %}{{ gender.grouper }}\n{% for item in gender.list %}{{ item.first_name }}\n{% endfor %}{% endfor %}Done.">>, [{people, [[{first_name, "George"}, {gender, "Male"}], [{first_name, "Bill"}, {gender, "Male"}], [{first_name, "Margaret"}, {gender, "Female"}], [{first_name, "Condi"}, {gender, "Female"}]]}], <<"People: Male\nGeorge\nBill\nFemale\nMargaret\nCondi\nDone.">>}, #test{ title = "regroup record", source = <<"{% regroup people by gender as gender_list %}{% for gender in gender_list %}{{ gender.grouper }}:\n{% for person in gender.list %} - {{ person.first_name }}\n{% endfor %}{% endfor %}">>, compile_opts = [{record_info, [{person, record_info(fields, person)}]} | (#test{})#test.compile_opts], render_vars = [{people, [#person{ first_name = "George", gender = "Male" }, #person{ first_name = "Bill", gender = "Male" }, #person{ first_name = "Margaret", gender = "Female" }, #person{ first_name = "Condi", gender = "Female" } ]} ], output = <<"Male:\n - George\n - Bill\nFemale:\n - Margaret\n - Condi\n">> } ]}, {"spaceless", [{"Beginning", <<"{% spaceless %} foo{% endspaceless %}">>, [], <<"foo">>}, {"Middle", <<"{% spaceless %}foo bar{% endspaceless %}">>, [], <<"foobar">>}, {"End", <<"{% spaceless %}foo {% endspaceless %}">>, [], <<"foo">>}, {"NewLine", <<"{% spaceless %}\n
    \n foo \n
    \n {% endspaceless %}">>, [], <<"
    foo
    ">>} ]}, {"templatetag", [{"openblock", <<"{% templatetag openblock %}">>, [], <<"{%">>}, {"closeblock", <<"{% templatetag closeblock %}">>, [], <<"%}">>}, {"openvariable", <<"{% templatetag openvariable %}">>, [], <<"{{">>}, {"closevariable", <<"{% templatetag closevariable %}">>, [], <<"}}">>}, {"openbrace", <<"{% templatetag openbrace %}">>, [], <<"{">>}, {"closebrace", <<"{% templatetag closebrace %}">>, [], <<"}">>}, {"opencomment", <<"{% templatetag opencomment %}">>, [], <<"{#">>}, {"closecomment", <<"{% templatetag closecomment %}">>, [], <<"#}">>} ]}, {"trans", [{"trans functional default locale", <<"Hello {% trans \"Hi\" %}">>, [], <<"Hello Hi">> }, {"trans functional reverse locale", <<"Hello {% trans \"Hi\" %}">>, [], [{locale, "reverse"}], [{locales, ["reverse"]}, {translation_fun, fun("Hi"=Key, "reverse") -> list_to_binary(lists:reverse(Key)) end}], <<"Hello iH">> }, {"trans literal at run-time", <<"Hello {% trans \"Hi\" %}">>, [], [{translation_fun, fun("Hi") -> "Konichiwa" end}], [], <<"Hello Konichiwa">>}, {"trans variable at run-time", <<"Hello {% trans var1 %}">>, [{var1, <<"Hi">>}], [{translation_fun, fun(<<"Hi">>) -> <<"Konichiwa">> end}], [], <<"Hello Konichiwa">>}, {"trans literal at run-time: No-op", <<"Hello {% trans \"Hi\" noop %}">>, [], [{translation_fun, fun("Hi") -> <<"Konichiwa">> end}], [], <<"Hello Hi">>}, {"trans variable at run-time: No-op", <<"Hello {% trans var1 noop %}">>, [{var1, <<"Hi">>}], [{translation_fun, fun(<<"Hi">>) -> <<"Konichiwa">> end}], [], <<"Hello Hi">>}, {"trans as", <<"{% trans 'Hans' as name %}Hello {{ name }}">>, [], <<"Hello Hans">>}, {"trans value", <<"{{ _('foo') }}">>, [], [], [{locale, default}, {translation_fun, fun ("foo") -> "bar" end}], <<"bar">>}, {"filtered value", <<"{{ _('foo')|reverse }}">>, [], [], [{locale, default}, {translation_fun, fun ("foo") -> "bar" end}, {default_libraries, [test1]}, {libraries, [{test1, erlydtl_lib_test1}]}], <<"rab">>} ]}, {"blocktrans", [{"blocktrans default locale", <<"{% blocktrans %}Hello{% endblocktrans %}">>, [], <<"Hello">>}, {"blocktrans choose locale", <<"{% blocktrans %}Hello, {{ name }}{% endblocktrans %}">>, [{name, "Mr. President"}], [{locale, "de"}], [{locales, ["de"]}, {translation_fun, fun("Hello, {{ name }}", "de") -> <<"Guten tag, {{ name }}">> end}], <<"Guten tag, Mr. President">>}, {"blocktrans with args", <<"{% blocktrans with var1=foo %}{{ var1 }}{% endblocktrans %}">>, [{foo, "Hello"}], <<"Hello">>}, #test{ title = "blocktrans blocks in content not allowed", source = <<"{% blocktrans %}Hello{%if name%}, {{ name }}{%endif%}!{% endblocktrans %}">>, errors = [error_info([{{1, 24}, erlydtl_parser, ["syntax error before: ",["\"if\""]]}])] }, #test{ title = "blocktrans nested variables not allowed", source = <<"{% blocktrans %}Hello, {{ user.name }}!{% endblocktrans %}">>, errors = [error_info([{{1,31}, erlydtl_parser, ["syntax error before: ","'.'"]}])] }, {"blocktrans runtime", <<"{%blocktrans with v1=foo%}Hello, {{ name }}! See {{v1}}.{%endblocktrans%}">>, [{name, "Mr. President"}, {foo, <<"rubber-duck">>}], [{translation_fun, fun("Hello, {{ name }}! See {{ v1 }}.") -> <<"Guten tag, {{name}}! Sehen {{ v1 }}.">> end}], [], <<"Guten tag, Mr. President! Sehen rubber-duck.">>}, {"trimmed", <<"{% blocktrans trimmed %}\n foo \n bar here .\n \n \n baz{% endblocktrans %}">>, [], [{translation_fun, fun ("foo bar here . baz") -> "ok" end}], <<"ok">>} ]}, {"extended translation features (#131)", [{"trans default locale", <<"test {% trans 'message' %}">>, [], [{translation_fun, fun ("message", default) -> "ok" end}], <<"test ok">>}, {"trans foo locale", <<"test {% trans 'message' %}">>, [], [{locale, "foo"}, {translation_fun, fun ("message", "foo") -> "ok" end}], <<"test ok">>}, {"trans context (run-time)", <<"test {% trans 'message' context 'foo' %}">>, [], [{translation_fun, fun ("message", {default, "foo"}) -> "ok" end}], <<"test ok">>}, {"trans context (compile-time)", <<"test {% trans 'message' context 'foo' %}">>, [], [{locale, "baz"}], [{locales, ["bar", "baz"]}, {translation_fun, fun ("message", {L, "foo"}) -> case L of "bar" -> "rab"; "baz" -> "ok" end end}], <<"test ok">>}, {"trans context noop", <<"{% trans 'message' noop context 'foo' %}">>, [], [], <<"message">>}, {"blocktrans context (run-time)", <<"{% blocktrans context 'bar' %}translate this{% endblocktrans %}">>, [], [{locale, "foo"}, {translation_fun, fun ("translate this", {"foo", "bar"}) -> "got it" end}], <<"got it">>}, {"blocktrans context (compile-time)", <<"{% blocktrans context 'bar' %}translate this{% endblocktrans %}">>, [], [{locale, "foo"}], [{locale, "foo"}, {translation_fun, fun ("translate this", {"foo", "bar"}) -> "got it" end}], <<"got it">>}, {"blocktrans plural", <<"{% blocktrans count foo=bar %}", "There is just one foo..", "{% plural %}", "There are many foo's..", "{% endblocktrans %}">>, [{bar, 2}], [{locale, "baz"}, {translation_fun, fun ({"There is just one foo..", {"There are many foo's..", 2}}, "baz") -> "ok" end}], <<"ok">>}, {"blocktrans a lot of stuff", <<"{% blocktrans with foo=a.b count c=a|length context 'quux' %}" "foo={{ foo }};bar={{ bar }};c={{ c }}:" "{% plural %}" "FOO:{{ foo }},BAR:{{ bar }},C:{{ c }}." "{% endblocktrans %}">>, [{a, [{b, "B"}]}, {bar, "BAR"}], [{locale, "rub"}, {translation_fun, fun ({Single, {Plural, "1"=_Count}}, {Locale, Context}) -> [Single, Plural, Locale, Context] end}], <<"foo=B;bar=BAR;c=1:" "FOO:B,BAR:BAR,C:1." "rub" "quux">>}, {"new translation options", <<"{% trans foo %}{% blocktrans %}abc{% endblocktrans %}">>, [{foo, "1234"}], [{locale, "test"}, {translation_fun, fun (Msg) -> lists:reverse(Msg) end}], [{locale, "foo"}, {locale, "test"}, {locales, ["bar", "baz"]}, {translation_fun, fun (Msg, _) -> [Msg, lists:reverse(Msg)] end}], <<"4321" "abccba">>} %% This does work, but always prints a warning to std err.. :/ %% Warning: template translation: variable not closed: "bar {{ 123" %% {"variable error", %% <<"{% blocktrans %}foo{{ bar }}{% endblocktrans %}">>, %% [], [{translation_fun, fun (_) -> "bar {{ 123" end}], %% <<"foo">>} ]}, {"i18n", [{"setup translation context, using fun, at render time", <<"{% trans 'foo' %}">>, [], [{translation_fun, fun () -> fun (Msg) -> string:to_upper(Msg) end end}], <<"FOO">>}, {"setup translation context, using fun, at compile time", <<"{% trans 'foo' %}">>, [], [], [{locale, default}, {translation_fun, fun () -> fun lists:reverse/1 end}], <<"oof">>} ]}, {"language", [{"override locale", <<"{% trans 'foo' %}{% language 'other' %}{% trans 'foo' %}{% endlanguage %}">>, [], [{locale, <<"default">>}, {translation_fun, fun ("foo", <<"default">>) -> "1"; ("foo", <<"other">>) -> "2"; (A, B) -> [A, B] end}], <<"12">>} ]}, {"verbatim", [{"Plain verbatim", <<"{% verbatim %}{{ oh no{% foobar %}{% endverbatim %}">>, [], <<"{{ oh no{% foobar %}">>}, {"Named verbatim", <<"{% verbatim foobar %}{% verbatim %}{% endverbatim foobar2 %}{% endverbatim foobar %}">>, [], <<"{% verbatim %}{% endverbatim foobar2 %}">>} ]}, {"widthratio", [{"Literals", <<"{% widthratio 5 10 100 %}">>, [], <<"50">>}, {"Rounds up", <<"{% widthratio a b 100 %}">>, [{a, 175}, {b, 200}], <<"88">>} ]}, {"with", [{"Cache literal", <<"{% with a=1 %}{{ a }}{% endwith %}">>, [], <<"1">>}, {"Cache variable", <<"{% with a=b %}{{ a }}{% endwith %}">>, [{b, "foo"}], <<"foo">>}, {"Cache variable with attribute", <<"I enjoy {% with a = var1 %}{{ a.game }}{% endwith %}">>, [{var1, [{game, "Othello"}]}], <<"I enjoy Othello">>}, {"Cache variable attribute", <<"I enjoy {% with a = var1.game %}{{ a }}{% endwith %}">>, [{var1, [{game, "Othello"}]}], <<"I enjoy Othello">>}, {"Cache multiple", <<"{% with alpha=1 beta=b %}{{ alpha }}/{{ beta }}{% endwith %}">>, [{b, 2}], <<"1/2">>} ]}, {"unicode", [{"(tm) somewhere", <<"â„¢">>, [], <<"â„¢">>} ]}, {"contrib_humanize", [{"intcomma", <<"{{ a|intcomma }} {{ b|intcomma }} {{ c|intcomma }} {{ d|intcomma }}">>, [{a, 999}, {b, 123456789}, {c, 12345}, {d, 1234567890}], [], [{custom_filters_modules, [erlydtl_contrib_humanize]}], <<"999 123,456,789 12,345 1,234,567,890">>} ]}, %% custom syntax stuff {"extension_module", [ %% the erlydtl_test_extension module replaces a foo identifier with bar when hitting a # following foo. {"replace parsed token", <<"{{ foo # }}">>, [{bar, "ok"}], [], [{extension_module, erlydtl_test_extension}], <<"ok">>}, #test{ title = "proper error message", source = <<"{{ bar # }}">>, render_vars = [{bar, "ok"}], compile_opts = [{extension_module, erlydtl_test_extension}, report, return, force_recompile, {out_dir, false}], errors = [error_info([{{1,8},erlydtl_scanner,{illegal_char, $#}}])] }, %% accept identifiers as expressions (this is a dummy functionality to test the parser extensibility) {"identifiers as expressions", <<"{{ foo.bar or baz }}">>, [{baz, "ok"}], [], [{extension_module, erlydtl_test_extension}], <<"ok">>} ]}, {"records", [{"field access", <<"{{ r.baz }}">>, [{r, #testrec{ foo="Foo", bar="Bar", baz="Baz" }}], [], [{record_info, [{testrec, record_info(fields, testrec)}]}], <<"Baz">>} ]}, {"error reporting", [#test{ title = "no out dir warning", source = <<"foo bar">>, compile_opts = [report, return, force_recompile], output = <<"foo bar">>, warnings = [error_info([no_out_dir])] }, #test{ title = "warnings as errors", source = <<"foo bar">>, compile_opts = [report, return, warnings_as_errors, force_recompile], errors = [error_info([no_out_dir])] }, #test{ title = "illegal character", source = <<"{{{">>, errors = [error_info([{{1,3},erlydtl_scanner,{illegal_char, ${}}])] }, #test{ title = "unexpected end of file - in code", source = <<"{{">>, errors = [error_info([{{1,3},erlydtl_scanner,{eof, in_code}}])] }, #test{ title = "unexpected end of file - in comment", source = <<"{#">>, errors = [error_info([{{1,3},erlydtl_scanner,{eof, in_comment}}])] }, {"unknown library", <<"{% load foo %}">>, [], [], [], <<>>, [error_info( [{{1,9},erlydtl_compiler_utils,{load_library,foo,foo,nofile}} ])] }, {"not a library", <<"{% load foo %}">>, [], [], [{libraries, [{foo, ?MODULE}]}], <<>>, [error_info( [{{1,9},erlydtl_compiler_utils,{load_library,foo,?MODULE,behaviour}} ])] }, {"library version", <<"{% load foo %}">>, [], [], [{libraries, [{foo, erlydtl_lib_testversion}]}], <<>>, [error_info( [{{1,9},erlydtl_compiler_utils,{load_library,foo,erlydtl_lib_testversion,{version,invalid}}} ])] }, {"not in library", <<"{% load foo bar from test1 %}\n{{ \"w00t\"|reverse }}">>, [], [], [{libraries, [{test1, erlydtl_lib_test1}]}], <<"\n">>, [error_info( [{{2,11},erlydtl_beam_compiler,{unknown_filter,reverse,1}}, {{1,22},erlydtl_compiler_utils,{load_from,test1,erlydtl_lib_test1,foo}}, {{1,22},erlydtl_compiler_utils,{load_from,test1,erlydtl_lib_test1,bar}} ])] }, {"pre load unknown library", <<"{{ '123'|reverse }}">>, [], [], [{default_libraries, [test1]}], <<"">>, [error_info( [{{1,10},erlydtl_beam_compiler,{unknown_filter,reverse,1}}, {none,erlydtl_compiler_utils,{load_library,test1,test1,nofile}} ])] }, {"pre load unknown legacy library", <<"{% foo %}">>, [], [], [{custom_tags_modules, [foo]}], <<"">>, [error_info( [{none,erlydtl_beam_compiler,{unknown_tag, foo}}, {none,erlydtl_compiler,{load_library,'(custom-legacy)',foo,nofile}} ])] }, {"unknown filter", <<"{{ '123'|foo }}">>, [], [], [], <<"">>, [error_info([{{1,10},erlydtl_beam_compiler,{unknown_filter,foo,1}}])] }, {"unknown tag", <<"a{% b %}c">>, [], [], [], <<"ac">>, [error_info([{none,erlydtl_beam_compiler,{unknown_tag, b}}])] }, {"ssi file not found", <<"{% ssi 'foo' %}">>, [], {error, {read_file, <<"./foo">>, enoent}} }, {"deprecated compile options", <<"">>, [], [], [{blocktrans_locales, []}, {blocktrans_fun, fun (_) -> [] end}], <<"">>, [error_info([{deprecated_option, O, N} || {O, N} <- [{blocktrans_locales, locales}, {blocktrans_fun, translation_fun}]], erlydtl_compiler)] } ]}, {"load", [{"filter", <<"{% load test1 %}{{ \"1234\"|reverse }}">>, [], [], [{libraries, [{test1, erlydtl_lib_test1}]}], <<"4321">> }, {"named", <<"{% load reverse from test1 %}{{ \"abcd\"|reverse }}">>, [], [], [{libraries, [{test1, erlydtl_lib_test1}]}], <<"dcba">> }, {"pre loaded", <<"{{ QWER|reverse }}">>, [{'QWER', "Qwerty"}], [], [{default_libraries, [test1]}, {libraries, [{test1, erlydtl_lib_test1}]}], <<"ytrewQ">> }, {"lib with multiple behaviours", <<"{{ QWER|reverse }}">>, [{'QWER', "Qwerty"}], [], [{default_libraries, [test2]}, {libraries, [{test2, erlydtl_lib_test2}]}], <<"ytrewQ">> }, {"lib with multiple behaviors (alternative spelling)", <<"{{ QWER|reverse }}">>, [{'QWER', "Qwerty"}], [], [{default_libraries, [test2]}, {libraries, [{test2, erlydtl_lib_test2a}]}], <<"ytrewQ">> } ]}, {"compile time default vars/constants", begin Tpl = <<"Test {{ var1 }}:{{ var2 }}.">>, Txt = <<"Test 123:abc.">>, Fun = fun (F) -> fun (#test{ module=M }) -> M:F() end end, [{"default vars", Tpl, [], [], [{default_vars, [{var1, 123}, {var2, abc}]}], Txt}, {"default vars (using fun)", Tpl, [], [], [{default_vars, [{var1, 123}, {var2, fun () -> abc end}]}], Txt}, {"override default vars", Tpl, [{var2, abc}], [], [{default_vars, [{var1, 123}, {var2, 456}]}], Txt}, {"constants", Tpl, [], [], [{constants, [{var1, 123}, {var2, abc}]}], Txt}, {"constants (using fun)", Tpl, [], [], [{constants, [{var1, 123}, {var2, fun () -> abc end}]}], Txt}, {"constants non-overridable", Tpl, [{var1, ohno}, {var2, noway}], [], [{constants, [{var1, 123}, {var2, "abc"}]}], Txt} |[#test{ title = T, source = Tpl, compile_vars = undefined, compile_opts = CO ++ (#test{})#test.compile_opts, renderer = Fun(F), output = O } || {T, F, O, CO} <- [{"variables/0", variables, [var1, var2], []}, {"variables/0 w. defaults", variables, [var1, var2], [{default_vars, [{var1, aaa}]}]}, {"variables/0 w. constants", variables, [var2], [{constants, [{var1, bbb}]}]}, {"default_variables/0", default_variables, [], []}, {"default_variables/0 w. defaults", default_variables, [var1], [{default_vars, [{var1, aaa}]}]}, {"default_variables/0 w. constants", default_variables, [], [{constants, [{var1, bbb}]}]}, {"constants/0", constants, [], []}, {"constants/0 w. defaults", constants, [], [{default_vars, [{var1, aaa}]}]}, {"constants/0 w. constants", constants, [var1], [{constants, [{var1, bbb}]}]} ] ]] end}, {"functional", [functional_test(F) %% order is important for a few of these tests, unfortunately. || F <- ["autoescape", "comment", "extends", "filters", "for", "for_list", "for_tuple", "for_list_preset", "for_preset", "for_records", "for_records_preset", "include", "if", "if_preset", "ifequal", "ifequal_preset", "ifnotequal", "ifnotequal_preset", "now", "var", "var_preset", "cycle", "custom_tag", "custom_tag1", "custom_tag2", "custom_tag3", "custom_tag4", "custom_tag_var", "custom_tag_lib_var", "custom_call", "include_template", "include_path", "ssi", "extends_path", "extends_path2", "trans", "extends_for", "extends2", "extends3", "recursive_block", "extend_recursive_block", "missing", "block_super", "wrapper", "extends4", "super_escaped", "extends_chain", "reader_options", "ssi_reader_options", "extend_doubleblock"] ]}, {"compile_dir", [setup_compile(T) || T <- [#test{ title = "non-existing dir", source = {dir, "non-existing-made-up-dir"}, renderer = fun(#test{ source={dir, Dir} }) -> Dir end, output = "non-existing-made-up-dir" }, #test{ title = "path1", source = {dir, template_file(input, "path1")}, renderer = fun(#test{ module=M, render_vars=V, render_opts=O }) -> M:render(base1, V, O) end } ] ]} ]. %% {Name, DTL, Vars, Output} %% {Name, DTL, Vars, RenderOpts, Output} %% {Name, DTL, Vars, RenderOpts, CompilerOpts, Output} %% {Name, DTL, Vars, RenderOpts, CompilerOpts, Output, Warnings} def_to_test(Group, #test{ title=Name }=T) -> T#test{ title = lists:concat([Group, ": ", Name]) }; def_to_test(Group, {Name, DTL, Vars, Output}) -> def_to_test(Group, {Name, DTL, Vars, [], [], Output, default_warnings()}); def_to_test(Group, {Name, DTL, Vars, RenderOpts, Output}) -> def_to_test(Group, {Name, DTL, Vars, RenderOpts, [], Output, default_warnings()}); def_to_test(Group, {Name, DTL, Vars, RenderOpts, CompilerOpts, Output}) -> def_to_test(Group, {Name, DTL, Vars, RenderOpts, CompilerOpts, Output, default_warnings()}); def_to_test(Group, {Name, DTL, Vars, RenderOpts, CompilerOpts, Output, Warnings}) -> #test{ title = lists:concat([Group, ": ", Name]), source = {template, DTL}, render_vars = Vars, render_opts = RenderOpts, compile_vars = undefined, compile_opts = CompilerOpts ++ (#test{})#test.compile_opts, output = Output, warnings = Warnings }. date_translation(Val, LC) when is_list(Val) -> io:format("Translating ~p~n", [Val]), date_translation(list_to_binary(Val),LC); % date a date_translation(<<"p.m.">>, <<"ru">>) -> <<"п.п."/utf8>>; % date A date_translation(<<"PM">>, <<"ru">>) -> <<"ПП"/utf8>>; % date b date_translation(<<"jul">>, <<"ru">>) -> <<"июл"/utf8>>; % date D date_translation(<<"Thu">>, <<"ru">>) -> <<"Чтв"/utf8>>; % date E date_translation(<<"July">>, {<<"ru">>, <<"alt. month">>}) -> <<"ИюлÑ"/utf8>>; % date F date_translation(<<"July">>, <<"ru">>) -> <<"Июль"/utf8>>; % date l date_translation(<<"Thursday">>, <<"ru">>) -> <<"Четверг"/utf8>>; % date M date_translation(<<"Sep">>, <<"ru">>) -> <<"Сен"/utf8>>; % date N date_translation(<<"Sept.">>, {<<"ru">>, <<"abbrev. month">>}) -> <<"Сен."/utf8>>; % date P date_translation(<<"noon">>, <<"ru">>) -> <<"полдень"/utf8>>; date_translation(Text, <<"ru">>) -> proplists:get_value(Text, lists:zip( lists:map(fun list_to_binary/1, en_months()), ru_months()), Text); date_translation(Text, _) -> Text. ru_months() -> [ <<"Январь"/utf8>>, <<"Февраль"/utf8>>, <<"Март"/utf8>>, <<"Ðпрель"/utf8>>, <<"Май"/utf8>>, <<"Июнь"/utf8>>, <<"Июль"/utf8>>, <<"ÐвгуÑÑ‚"/utf8>>, <<"СентÑбрь"/utf8>>, <<"ОктÑбрь"/utf8>>, <<"ÐоÑбрь"/utf8>>, <<"Декабрь"/utf8>>]. en_months() -> ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]. generate_test_date() -> generate_test_date(false). generate_test_date(Translation) -> {{Y,M,D}, _} = erlang:localtime(), MonthName = case Translation of russian -> ru_months(); _ -> en_months() end, OrdinalSuffix = [ "st","nd","rd","th","th","th","th","th","th","th", % 1-10 "th","th","th","th","th","th","th","th","th","th", % 10-20 "st","nd","rd","th","th","th","th","th","th","th", % 20-30 "st" ], list_to_binary([ "It is the ", integer_to_list(D), lists:nth(D, OrdinalSuffix), " of ", lists:nth(M, MonthName), " ", integer_to_list(Y), "." ]). default_warnings() -> []. error_info(File, Ws, Mod) -> {File, [error_info(W, Mod) || W <- Ws]}. error_info({Line, ErrorDesc}, Mod) when is_integer(Line); Line =:= none -> {Line, Mod, ErrorDesc}; error_info({Line, Module, _}=ErrorDesc, _Mod) when is_integer(Line), is_atom(Module) -> ErrorDesc; error_info({none, Module, _}=ErrorDesc, _Mod) when is_atom(Module) -> ErrorDesc; error_info({{Line, Col}, Module, _}=ErrorDesc, _Mod) when is_integer(Line), is_integer(Col), is_atom(Module) -> ErrorDesc; error_info(Ws, Mod) when is_list(Ws) -> error_info("erly_test", Ws, Mod); error_info(ErrorDesc, Mod) -> {none, Mod, ErrorDesc}. error_info(Ei) -> error_info(Ei, erlydtl_beam_compiler). template_file(Dir, Name) -> %% {ok, CWD} = file:get_cwd(), %% io:format(user, "~s~n", [CWD]), filename:join(["test/files", Dir, Name]). functional_test(F) -> setup_compile(#test{ title = F, module = list_to_atom("functional_test_" ++ F), source = {file, template_file(input, F)} }). setup_compile(#test{ title=F, compile_opts=Opts }=T) -> CompileOpts = [{doc_root, "test/files/input"}|Opts], case setup_compile(F) of {ok, [CV|Other]} -> CO = proplists:get_value(compile_opts, Other, []), Ws = proplists:get_value(warnings, Other, []), setup(T#test{ compile_vars = CV, compile_opts = CO ++ CompileOpts, warnings = Ws }); {error, Es, Ws} -> T#test{ errors = Es, warnings = Ws, compile_opts = CompileOpts } end; setup_compile("for_list_preset") -> CompileVars = [{fruit_list, [["apple", "apples"], ["banana", "bananas"], ["coconut", "coconuts"]]}], {ok, [CompileVars]}; setup_compile("for_preset") -> CompileVars = [{fruit_list, ["preset-apple", "preset-banana", "preset-coconut"]}], {ok, [CompileVars]}; setup_compile("for_records_preset") -> Link1a = [{name, "Amazon (preset)"}, {url, "http://amazon.com"}], Link2a = [{name, "Google (preset)"}, {url, "http://google.com"}], Link3a = [{name, "Microsoft (preset)"}, {url, "http://microsoft.com"}], CompileVars = [{software_links, [Link1a, Link2a, Link3a]}], {ok, [CompileVars]}; setup_compile("if_preset") -> CompileVars = [{var1, "something"}], {ok, [CompileVars]}; setup_compile("ifequal_preset") -> CompileVars = [{var1, "foo"}, {var2, "foo"}], {ok, [CompileVars]}; setup_compile("ifnotequal_preset") -> CompileVars = [{var1, "foo"}, {var2, "foo"}], {ok, [CompileVars]}; setup_compile("var_preset") -> CompileVars = [{preset_var1, "preset-var1"}, {preset_var2, "preset-var2"}], {ok, [CompileVars]}; setup_compile("extends_for") -> CompileVars = [{veggie_list, ["broccoli", "beans", "peas", "carrots"]}], {ok, [CompileVars]}; setup_compile("extends2") -> File = template_file(input, "extends2"), Error = {none, erlydtl_beam_compiler, unexpected_extends_tag}, {error, [{File, [Error]}], []}; setup_compile("extends3") -> File = template_file(input, "extends3"), Include = template_file(input, "imaginary"), Error = {none, erlydtl_beam_compiler, {read_file, Include, enoent}}, {error, [{File, [Error]}], []}; setup_compile("extends4") -> File = template_file(input, "extends4"), Warning = {{1,21}, erlydtl_beam_compiler, non_block_tag}, {ok, [[]|[{warnings, [{File, [Warning]}]}]]}; setup_compile("missing") -> File = template_file(input, "missing"), Error = {none, erlydtl_compiler, {read_file, File, enoent}}, {error, [{File, [Error]}], []}; setup_compile("custom_tag") -> {ok, [[]|[{compile_opts, [{custom_tags_modules, [erlydtl_custom_tags]}]}]]}; setup_compile("custom_tag1") -> setup_compile("custom_tag"); setup_compile("custom_tag2") -> setup_compile("custom_tag"); setup_compile("custom_tag3") -> setup_compile("custom_tag"); setup_compile("custom_tag4") -> setup_compile("custom_tag"); setup_compile("custom_tag_var") -> setup_compile("custom_tag"); setup_compile("custom_tag_lib_var") -> {ok, [[]|[{compile_opts, [{libraries, [{custom_tag_lib,erlydtl_custom_tags_lib}]}, {default_libraries, [custom_tag_lib]}]}]]}; setup_compile("super_escaped") -> {ok, [[]|[{compile_opts, [auto_escape]}]]}; setup_compile("reader_options") -> {ok, [[]|[{compile_opts, [{reader, {?MODULE, extra_reader}}, {reader_options, [{user_id, <<"007">>}, {user_name, <<"Agent">>}]}]}]]}; setup_compile("ssi_reader_options") -> {ok, [[]|[{compile_opts, [{reader, {?MODULE, extra_reader}}, {reader_options, [{user_id, <<"007">>}, {user_name, <<"Agent">>}]}]}]]}; %%setup_compile("path1") -> %% {ok, [[]|[{compile_opts, [debug_compiler]}]]}; setup_compile(_) -> {ok, [[]]}. extra_reader(FileName, ReaderOptions) -> UserID = proplists:get_value(user_id, ReaderOptions, <<"IDUnknown">>), UserName = proplists:get_value(user_name, ReaderOptions, <<"NameUnknown">>), case file:read_file(FileName) of {ok, Data} when UserID == <<"007">>, UserName == <<"Agent">> -> {ok, Data}; {ok, _Data} -> {error, "Not Found"}; Err -> Err end. expected(File) -> Filename = template_file(expect, File), case file:read_file(Filename) of {ok, Data} -> Data; _ -> fun (Data) -> ok = file:write_file(Filename, Data), io:format( user, "## Saved expected output for test ~p to ~p.~n" " Verify the contents, as it is used to pass the test on subsequent test runs.~n" "~n", [File, Filename]), throw({verify_new_expected_output, Filename}) end end. setup(#test{ title = F, output=undefined }=T) -> {Vars, Opts, Result} = case setup(F) of {ok, V} -> {V, [], expected(F)}; {ok, V, O} -> {V, O, expected(F)}; {ok, V, O, skip_check} -> {V, O, fun (_) -> ok end}; {ok, V, O, R} -> {V, O, R} end, T#test{ render_vars = Vars, render_opts = Opts, output = Result }; setup(#test{}=T) -> T; setup("autoescape") -> RenderVars = [{var1, "bold"}], {ok, RenderVars}; setup("extends") -> RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}], {ok, RenderVars}; setup("include_template") -> setup("extends"); setup("include_path") -> setup("extends"); setup("extends_path") -> setup("extends"); setup("extends_path2") -> setup("extends"); setup("block_super") -> setup("extends"); setup("filters") -> RenderVars = [ {date_var1, {1975,7,24}}, {datetime_var1, {{1975,7,24}, {7,13,1}}}, {'list', ["eins", "zwei", "drei"]} ], {ok, RenderVars}; setup("for") -> RenderVars = [{fruit_list, ["apple", "banana", "coconut"]}], {ok, RenderVars}; setup("for_list") -> RenderVars = [{fruit_list, [["apple", "apples", "$1"], ["banana", "bananas", "$2"], ["coconut", "coconuts", "$500"]]}], {ok, RenderVars}; setup("for_tuple") -> RenderVars = [{fruit_list, [{"apple", "apples"}, {"banana", "bananas"}, {"coconut", "coconuts"}]}], {ok, RenderVars}; setup("for_records") -> Link1 = [{name, "Amazon"}, {url, "http://amazon.com"}], Link2 = [{name, "Google"}, {url, "http://google.com"}], Link3 = [{name, "Microsoft"}, {url, "http://microsoft.com"}], RenderVars = [{link_list, [Link1, Link2, Link3]}], {ok, RenderVars}; setup("for_records_preset") -> Link1b = [{name, "Canon"}, {url, "http://canon.com"}], Link2b = [{name, "Leica"}, {url, "http://leica.com"}], Link3b = [{name, "Nikon"}, {url, "http://nikon.com"}], RenderVars = [{photo_links, [Link1b, Link2b, Link3b]}], {ok, RenderVars}; setup("include") -> RenderVars = [{var1, "foostring1"}, {var2, "foostring2"}], {ok, RenderVars}; setup("if") -> RenderVars = [{var1, "something"}], {ok, RenderVars}; setup("ifequal") -> RenderVars = [{var1, "foo"}, {var2, "foo"}, {var3, "bar"}], {ok, RenderVars}; setup("ifequal_preset") -> RenderVars = [{var3, "bar"}], {ok, RenderVars}; setup("ifnotequal") -> RenderVars = [{var1, "foo"}, {var2, "foo"}, {var3, "bar"}], {ok, RenderVars}; setup("now") -> {ok, [], [], skip_check}; setup("var") -> RenderVars = [{var1, "foostring1"}, {var2, "foostring2"}, {var_not_used, "foostring3"}], {ok, RenderVars}; setup("var_preset") -> RenderVars = [{var1, "foostring1"}, {var2, "foostring2"}], {ok, RenderVars}; setup("cycle") -> RenderVars = [{test, [integer_to_list(X) || X <- lists:seq(1, 20)]}, {a, "Apple"}, {b, "Banana"}, {c, "Cherry"}], {ok, RenderVars}; setup("trans") -> RenderOpts = [{translation_fun, fun lists:reverse/1}], {ok, [], RenderOpts}; setup("locale") -> {ok, _RenderVars = [{locale, "ru"}]}; setup("custom_tag1") -> {ok, [{a, <<"a1">>}], [{locale, ru}], <<"b1\n">>}; setup("custom_tag2") -> {ok, [{a, <<"a1">>}], [{locale, ru}, {foo, bar}], <<"b2\n">>}; setup("custom_tag3") -> {ok, [{a, <<"a1">>}], [{locale, ru}], <<"b3\n">>}; setup("custom_tag4") -> {ok, [], [], <<"a\n">>}; setup("custom_tag_var") -> {ok, [{a, <<"a1">>}], [{locale, ru}], <<"\nb1\n11\n">>}; setup("custom_tag_lib_var") -> {ok, [{a, <<"a1">>}], [{locale, ru}], <<"\nb1\n11\n">>}; setup("ssi") -> RenderVars = [{path, "ssi_include.html"}], {ok, RenderVars}; setup("wrapper") -> RenderVars = [{types, ["b", "a", "c"]}], {ok, RenderVars}; setup("reader_options") -> RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}], % Options = [],%[{compile_opts, [{reader, {?MODULE, extra_reader}}, {reader_options, [{user_id, <<"007">>}, {user_name, <<"Agent">>}]}]}], {ok, RenderVars}; setup("ssi_reader_options") -> RenderVars = [{path, "ssi_include.html"}], {ok, RenderVars}; %%-------------------------------------------------------------------- %% Custom tags %%-------------------------------------------------------------------- setup("custom_call") -> RenderVars = [{var1, "something"}], {ok, RenderVars}; setup(_) -> {ok, []}. erlydtl-0.15.0/test/erlydtl_test_extension.erl000066400000000000000000000026331504712431400215560ustar00rootroot00000000000000-module(erlydtl_test_extension). -export([scan/1, parse/1, compile_ast/2]). -include("erlydtl_ext.hrl"). %% look for a foo identifier followed by a # scan(#scanner_state{ template="#" ++ T, scanned=[{identifier, Loc, foo}|Scanned], pos={L,C} }=S) -> %% return new state with the hash dropped, and the foo identifier replaced with bar {ok, S#scanner_state{ template=T, scanned=[{identifier, Loc, "rab"}|Scanned], pos={L, C+1} }}; scan(#scanner_state{ template="#" ++ _T, pos=Pos }) -> %% give error when # not follows foo {error, {Pos,erlydtl_scanner,{illegal_char, $#}}}; scan(_) -> %% for anything else, fallback to the error message from erlydtl_scanner.. undefined. parse(State) -> erlydtl_extension_testparser:resume(State). %% {{ varA or varB }} is equivalent to {% if varA %}{{ varA }}{% else %}{{ varB }}{% endif %} compile_ast({value_or, {Value1, Value2}}, TreeWalker) -> {{V1_Ast, V1_Info}, TW1} = erlydtl_beam_compiler:value_ast(Value1, false, false, TreeWalker), {{V2_Ast, V2_Info}, TW2} = erlydtl_beam_compiler:value_ast(Value2, false, false, TW1), {{erl_syntax:case_expr(V1_Ast, [erl_syntax:clause([erl_syntax:atom(undefined)], none, [V2_Ast]), erl_syntax:clause([erl_syntax:underscore()], none, [V1_Ast]) ]), erlydtl_compiler_utils:merge_info(V1_Info, V2_Info)}, TW2}. erlydtl-0.15.0/test/erlydtl_tests.erl000066400000000000000000000033421504712431400176430ustar00rootroot00000000000000%%%------------------------------------------------------------------- %%% File: erlydtl_tests.erl %%% @author Andreas Stenius %%% @copyright 2014 Andreas Stenius %%% @doc %%% Test suite for erlydtl %%% @end %%% %%% The MIT License %%% %%% Copyright (c) 2014 Andreas Stenius %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%% @since 2014 by Andreas Stenius %%%------------------------------------------------------------------- -module(erlydtl_tests). -author('Andreas Stenius '). -include_lib("eunit/include/eunit.hrl"). -include("testrunner.hrl"). all_defs_test_() -> [{T#test.title, fun () -> run_test(T) end} || T <- erlydtl_test_defs:tests() ]. erlydtl-0.15.0/test/erlydtl_translation_tests.erl000066400000000000000000000102341504712431400222570ustar00rootroot00000000000000-module(erlydtl_translation_tests). -include_lib("eunit/include/eunit.hrl"). all_sources_parser_test_() -> [{Title, [test_fun(Test) || Test <- Tests]} || {Title, Tests} <- test_defs()]. test_fun({Name, Template, Variables, Options, Output}) -> {Name, fun () -> Tokens = (catch compile_and_render(Template, Variables, Options)), ?assertMatch(Output, Tokens) end}. compile_and_render(Template, Variables, Options) -> {ok, test} = erlydtl:compile_template(Template, test), {ok, R} = test:render(Variables, Options), iolist_to_binary(R). test_defs() -> [ {"trans", [ {"simple", "{% trans \"hello\" %}", [], [], <<"hello">>}, {"with_fun", "{% trans \"text\" %}", [], [{translation_fun, fun(_ID, _L) -> "hola" end}], <<"hola">>}, {"with_fun_utf8", "{% trans \"text\" %}", [], [{translation_fun, fun(_ID, _L) -> <<"привет"/utf8>> end}], <<"привет"/utf8>>} ]}, {"blocktrans", [ {"simple", "{% blocktrans %} hello {% endblocktrans %}", [], [], <<" hello ">>}, {"with_fun", "{% blocktrans %} hello {% endblocktrans %}", [], [{translation_fun, fun(_ID, _L) -> "hola" end}], <<"hola">>}, {"s_param_no_fun", "{% blocktrans %} hello {{ p }} {% endblocktrans %}", [{p, "mundo"}], [], <<" hello mundo ">>}, {"s_param", "{% blocktrans %} hello {{ p }} {% endblocktrans %}", [{p, "mundo"}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola mundo">>}, {"b_param", "{% blocktrans %} hello {{ p }} {% endblocktrans %}", [{p, <<"mundo">>}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola mundo">>}, {"i_param", "{% blocktrans %} hello {{ p }} {% endblocktrans %}", [{p, 1}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola 1">>}, {"f_param", "{% blocktrans %} hello {{ p }} {% endblocktrans %}", [{p, 3.1415}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola 3.1415">>}, {"b_xss", "{% blocktrans %} hello {{ p }} {% endblocktrans %}", [{p, <<"">>}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola <script>alert('pwnd');</script>">>}, {"s_xss", "{% blocktrans %} hello {{ p }} {% endblocktrans %}", [{p, ""}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola <script>alert('pwnd');</script>">>}, {"b_autoecape_off", "{% autoescape off %}{% blocktrans %} hello {{ p }} {% endblocktrans %}{% endautoescape %}", [{p, <<"">>}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola ">>}, {"b_autoecape_nested", "{% autoescape off %}{% autoescape on %}{% blocktrans %} hello {{ p }} {% endblocktrans %}{% endautoescape %}{% endautoescape %}", [{p, <<"">>}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola <script>alert('pwnd');</script>">>}, {"term_hack_", "{% blocktrans %} hello {{ p }} {% endblocktrans %}", [{p, {""}}], [{translation_fun, fun(_ID, _L) -> "hola {{ p }}" end}], <<"hola {"<script>alert('pwnd');</script>"}">>}, {"plural_2", "{% blocktrans count counter=p %} hello world {% plural %} hello {{ p }} worlds {% endblocktrans %}", [{p, 2}], [{translation_fun, fun({" hello world ", {" hello {{ p }} worlds ", 2}}, _L) -> "hola {{ p }} mundos" end}], <<"hola 2 mundos">>} ]} ]. erlydtl-0.15.0/test/files/000077500000000000000000000000001504712431400153365ustar00rootroot00000000000000erlydtl-0.15.0/test/files/expect/000077500000000000000000000000001504712431400166265ustar00rootroot00000000000000erlydtl-0.15.0/test/files/expect/autoescape000066400000000000000000000002141504712431400206770ustar00rootroot00000000000000 This is escaped: <b>bold</b> This is not escaped: bold This is escaped: <b>bold</b> erlydtl-0.15.0/test/files/expect/block_super000066400000000000000000000003031504712431400210550ustar00rootroot00000000000000base-barstring base template extending title: "base title" more of base template replacing the base content - variable: test-barstring after variable. Was: base content end of base template erlydtl-0.15.0/test/files/expect/comment000066400000000000000000000004561504712431400202200ustar00rootroot00000000000000 Test Comment bla blue black erlydtl-0.15.0/test/files/expect/custom_call000066400000000000000000000004571504712431400210640ustar00rootroot00000000000000>>>> before custom call tag 'comment' before
    • preset-apple
    • preset-banana
    • preset-coconut
    after >>>> after custom call tag 'comment' >>>> before custom call tag 'if' One but not two: two Two but not one: one One: None: >>>> after custom call tag 'if' erlydtl-0.15.0/test/files/expect/custom_tag000066400000000000000000000022501504712431400207150ustar00rootroot00000000000000 Test variable before

    To view the Video:

    Get Adobe Flash player

    after erlydtl-0.15.0/test/files/expect/cycle000066400000000000000000000010121504712431400176420ustar00rootroot00000000000000before
    • 1. 1 - Apple
    • 2. 2 - Banana
    • 3. 3 - Cherry
    • 4. 4 - Apple
    • 5. 5 - Banana
    • 6. 6 - Cherry
    • 7. 7 - Apple
    • 8. 8 - Banana
    • 9. 9 - Cherry
    • 10. 10 - Apple
    • 11. 11 - Banana
    • 12. 12 - Cherry
    • 13. 13 - Apple
    • 14. 14 - Banana
    • 15. 15 - Cherry
    • 16. 16 - Apple
    • 17. 17 - Banana
    • 18. 18 - Cherry
    • 19. 19 - Apple
    • 20. 20 - Banana
    after erlydtl-0.15.0/test/files/expect/extend_doubleblock000066400000000000000000000001761504712431400224110ustar00rootroot00000000000000 Chicago Boss Admin - General Info

    Chicago Boss Admin - General Info

    erlydtl-0.15.0/test/files/expect/extend_recursive_block000066400000000000000000000000271504712431400233000ustar00rootroot00000000000000 extended content erlydtl-0.15.0/test/files/expect/extends000066400000000000000000000002541504712431400202240ustar00rootroot00000000000000base-barstring base template replacing the base title more of base template replacing the base content - variable: test-barstring after variable end of base template erlydtl-0.15.0/test/files/expect/extends4000066400000000000000000000001271504712431400203070ustar00rootroot00000000000000 base template base title more of base template base content end of base template erlydtl-0.15.0/test/files/expect/extends_chain000066400000000000000000000001421504712431400213620ustar00rootroot00000000000000

    A

    B

    C

    erlydtl-0.15.0/test/files/expect/extends_for000066400000000000000000000001411504712431400210650ustar00rootroot00000000000000before
    • broccoli
    • beans
    • peas
    • carrots
    after erlydtl-0.15.0/test/files/expect/extends_path000066400000000000000000000003711504712431400212400ustar00rootroot00000000000000base-barstring base2 template replacing the base title block title 2 from base1 more of base2 template replacing the base content - variable: test-barstring after variable block content2 in base 2, should pass through end of base2 template erlydtl-0.15.0/test/files/expect/extends_path2000066400000000000000000000001011504712431400213110ustar00rootroot00000000000000pre content start_content This is include1 end_content post erlydtl-0.15.0/test/files/expect/filters000066400000000000000000000010401504712431400202140ustar00rootroot00000000000000 Add: 2 + 2 = 4 Capfirst: Capitalized Centered:
           center       
    
    Date format: Thu, 24 Jul 1975 DateTime format: Thu, 24 Jul 1975 07:13:01 Escape JS: \u0022 \u0027 First letter: f Fix ampersands: & Force_escape: <b></b> Joined: eins, zwei, drei Last: t Length: 3 Length is 2?: false Left adjust:
    left                
    
    Line breaks: Line 1
    Line 2
    Line 3 Lowercase: lowercase Right adjust:
                   right
    
    Uppercase: UPPERCASE URL Encode: Let%27s%20go%21 erlydtl-0.15.0/test/files/expect/for000066400000000000000000000001271504712431400173370ustar00rootroot00000000000000before
    • 1. apple
    • 2. banana
    • 3. coconut
    after erlydtl-0.15.0/test/files/expect/for_list000066400000000000000000000002561504712431400203750ustar00rootroot00000000000000 More than one apple is called "apples". Only $1 each! More than one banana is called "bananas". Only $2 each! More than one coconut is called "coconuts". Only $500 each! erlydtl-0.15.0/test/files/expect/for_list_preset000066400000000000000000000002021504712431400217460ustar00rootroot00000000000000 More than one apple is called "apples". More than one banana is called "bananas". More than one coconut is called "coconuts". erlydtl-0.15.0/test/files/expect/for_preset000066400000000000000000000001451504712431400207210ustar00rootroot00000000000000before
    • preset-apple
    • preset-banana
    • preset-coconut
    aftererlydtl-0.15.0/test/files/expect/for_records000066400000000000000000000002661504712431400210640ustar00rootroot00000000000000before aftererlydtl-0.15.0/test/files/expect/for_records_preset000066400000000000000000000005561504712431400224500ustar00rootroot00000000000000before aftererlydtl-0.15.0/test/files/expect/for_tuple000066400000000000000000000001171504712431400205470ustar00rootroot00000000000000 One apple, two apples! One banana, two bananas! One coconut, two coconuts! erlydtl-0.15.0/test/files/expect/if000066400000000000000000000001001504712431400171360ustar00rootroot00000000000000One but not two: one Two but not one: two One: one None: erlydtl-0.15.0/test/files/expect/if_preset000066400000000000000000000001001504712431400205200ustar00rootroot00000000000000One but not two: one Two but not one: two One: one None: erlydtl-0.15.0/test/files/expect/ifequal000066400000000000000000000004131504712431400201750ustar00rootroot00000000000000 if: var1="foo" and var2="foo" are equal if: var1="foo" and var2="foo" are equal else: var1="foo" and var3="bar" are not equal if: "foo" and "foo" are equal else: "foo" and "bar" are not equal if: 99 and 99 are equal else: 77 and 99 are not equal erlydtl-0.15.0/test/files/expect/ifequal_preset000066400000000000000000000004131504712431400215570ustar00rootroot00000000000000 if: var1="foo" and var2="foo" are equal if: var1="foo" and var2="foo" are equal else: var1="foo" and var3="bar" are not equal if: "foo" and "foo" are equal else: "foo" and "bar" are not equal if: 99 and 99 are equal else: 77 and 99 are not equal erlydtl-0.15.0/test/files/expect/ifnotequal000066400000000000000000000004131504712431400207160ustar00rootroot00000000000000 else: var1="foo" and var2="foo" are not equal if: var1="foo" and var3="bar" are equal if: var1="foo" and var3="bar" are equal else: "foo" and "foo" are not equal if: "foo" and "bar" are equal else: 99 and 99 are not equal if: 77 and 99 are equal erlydtl-0.15.0/test/files/expect/ifnotequal_preset000066400000000000000000000004131504712431400223000ustar00rootroot00000000000000 else: var1="foo" and var2="foo" are not equal if: var1="foo" and var3="bar" are equal if: var1="foo" and var3="bar" are equal else: "foo" and "foo" are not equal if: "foo" and "bar" are equal else: 99 and 99 are not equal if: 77 and 99 are equal erlydtl-0.15.0/test/files/expect/include000066400000000000000000000000661504712431400201760ustar00rootroot00000000000000Including another file: This is included! foostring1 erlydtl-0.15.0/test/files/expect/include_path000066400000000000000000000001261504712431400212070ustar00rootroot00000000000000main file This is template 1. test-barstring This is template 2 base-barstring erlydtl-0.15.0/test/files/expect/include_template000066400000000000000000000002411504712431400220640ustar00rootroot00000000000000Including another template: base-barstring base template base title more of base template base content end of base template test variable: test-barstring erlydtl-0.15.0/test/files/expect/path1000066400000000000000000000003631504712431400175700ustar00rootroot00000000000000 base2 template block title in base2, should be hidden by main template block title 2 from base1 more of base2 template block content in base 2, should be overwritten block content2 in base 2, should pass through end of base2 template erlydtl-0.15.0/test/files/expect/reader_options000066400000000000000000000001451504712431400215660ustar00rootroot00000000000000base-barstring base template base title more of base template base content end of base template erlydtl-0.15.0/test/files/expect/recursive_block000066400000000000000000000000741504712431400217330ustar00rootroot00000000000000 testing:

    end testing
    erlydtl-0.15.0/test/files/expect/ssi000066400000000000000000000000341504712431400173440ustar00rootroot00000000000000{{ "Don't evaluate me!" }} erlydtl-0.15.0/test/files/expect/ssi_reader_options000066400000000000000000000000341504712431400224410ustar00rootroot00000000000000{{ "Don't evaluate me!" }} erlydtl-0.15.0/test/files/expect/super_escaped000066400000000000000000000001171504712431400213720ustar00rootroot00000000000000

    A

    B

    erlydtl-0.15.0/test/files/expect/trans000066400000000000000000000000171504712431400176760ustar00rootroot00000000000000gnirtS elpmaxE erlydtl-0.15.0/test/files/expect/var000066400000000000000000000001361504712431400173410ustar00rootroot00000000000000before varriable1 foostring1 after variable1 foostring2 after variable2 (HTML-comment-wrapped)erlydtl-0.15.0/test/files/expect/var_preset000066400000000000000000000001061504712431400207200ustar00rootroot00000000000000one foostring1 two preset-var1 three foostring2 four preset-var2 five erlydtl-0.15.0/test/files/expect/wrapper000066400000000000000000000006151504712431400202330ustar00rootroot00000000000000 including base b now base template base b more of base template base content end of base template including base a now base template base a more of base template base content end of base template including base c now base template base c more of base template base content end of base template erlydtl-0.15.0/test/files/input/000077500000000000000000000000001504712431400164755ustar00rootroot00000000000000erlydtl-0.15.0/test/files/input/autoescape000066400000000000000000000003051504712431400205470ustar00rootroot00000000000000{% autoescape on %} This is escaped: {{ var1 }} {% autoescape off %} This is not escaped: {{ var1 }} This is escaped: {{ var1|escape }} {% endautoescape %} {% endautoescape %} erlydtl-0.15.0/test/files/input/base000077500000000000000000000002451504712431400173360ustar00rootroot00000000000000{{ base_var }} base template {% block title %}base title{% endblock %} more of base template {% block content %}base content{% endblock %} end of base template erlydtl-0.15.0/test/files/input/base_a000066400000000000000000000000741504712431400176330ustar00rootroot00000000000000{% extends "base" %} {% block title %}base a{% endblock %} erlydtl-0.15.0/test/files/input/base_b000066400000000000000000000000741504712431400176340ustar00rootroot00000000000000{% extends "base" %} {% block title %}base b{% endblock %} erlydtl-0.15.0/test/files/input/base_c000066400000000000000000000000741504712431400176350ustar00rootroot00000000000000{% extends "base" %} {% block title %}base c{% endblock %} erlydtl-0.15.0/test/files/input/base_doubleblock000066400000000000000000000002441504712431400216770ustar00rootroot00000000000000 Chicago Boss Admin - {% block title %}{% endblock %}

    Chicago Boss Admin - {% block title %}{% endblock %}

    erlydtl-0.15.0/test/files/input/base_escape000066400000000000000000000001361504712431400206520ustar00rootroot00000000000000 {% block my_block %}

    A

    {% endblock %} erlydtl-0.15.0/test/files/input/base_for000066400000000000000000000002321504712431400201750ustar00rootroot00000000000000before {% block forloop %}
      {% for iterator in fruit_list %}
    • {{ forloop.counter }}. {{ iterator }}
    • {% endfor %}
    {% endblock %} after erlydtl-0.15.0/test/files/input/block_super000066400000000000000000000003261504712431400207310ustar00rootroot00000000000000{% extends "base" %} {% block title %}extending title: "{{ block.super }}"{% endblock %} {% block content %}replacing the base content - variable: {{ test_var }} after variable. Was: {{ block.super }}{% endblock %}erlydtl-0.15.0/test/files/input/comment000077500000000000000000000006061504712431400200670ustar00rootroot00000000000000 Test Comment {# comment1 #} bla {# comment2 #} blue {% comment %} Block Comment {% endcomment %} black erlydtl-0.15.0/test/files/input/custom_call000077500000000000000000000003331504712431400207270ustar00rootroot00000000000000>>>> before custom call tag 'comment' {% call functional_test_for_preset %} >>>> after custom call tag 'comment' >>>> before custom call tag 'if' {% call functional_test_if with var1 %} >>>> after custom call tag 'if' erlydtl-0.15.0/test/files/input/custom_tag000077500000000000000000000007601504712431400205730ustar00rootroot00000000000000 Test variable before {% flashvideo dom_id="myvideo" width="800" height="600" static="/static" path_to_video="/myvid.mp4" path_to_preview_image="/mypic.jpg" alt=_("Get Adobe Flash player") %} after erlydtl-0.15.0/test/files/input/custom_tag1000066400000000000000000000000161504712431400206430ustar00rootroot00000000000000{% custom1 %} erlydtl-0.15.0/test/files/input/custom_tag2000066400000000000000000000000161504712431400206440ustar00rootroot00000000000000{% custom2 %} erlydtl-0.15.0/test/files/input/custom_tag3000066400000000000000000000000161504712431400206450ustar00rootroot00000000000000{% custom3 %} erlydtl-0.15.0/test/files/input/custom_tag4000066400000000000000000000000221504712431400206430ustar00rootroot00000000000000{% custom4 "a" %} erlydtl-0.15.0/test/files/input/custom_tag_lib_var000066400000000000000000000001041504712431400222560ustar00rootroot00000000000000{% customtag2_var as tagvar %} {{ tagvar.name }} {{ tagvar.count }} erlydtl-0.15.0/test/files/input/custom_tag_var000066400000000000000000000001011504712431400214250ustar00rootroot00000000000000{% custom1_var as tagvar %} {{ tagvar.name }} {{ tagvar.count }} erlydtl-0.15.0/test/files/input/cycle000066400000000000000000000001671504712431400175230ustar00rootroot00000000000000before
      {% for i in test %}
    • {{ forloop.counter }}. {{ i }} - {% cycle a b c %}
    • {% endfor %}
    after erlydtl-0.15.0/test/files/input/extend_doubleblock000066400000000000000000000001151504712431400222510ustar00rootroot00000000000000{% extends "base_doubleblock" %} {% block title %}General Info{% endblock %} erlydtl-0.15.0/test/files/input/extend_recursive_block000066400000000000000000000001311504712431400231430ustar00rootroot00000000000000{% extends "recursive_block" %} {% block content %} extended content {% endblock %} erlydtl-0.15.0/test/files/input/extends000077500000000000000000000002631504712431400200760ustar00rootroot00000000000000{% extends "base" %} {% block title %}replacing the base title{% endblock %} {% block content %}replacing the base content - variable: {{ test_var }} after variable {% endblock %}erlydtl-0.15.0/test/files/input/extends2000066400000000000000000000002651504712431400201570ustar00rootroot00000000000000 {% extends "base" %} {% block title %}replacing the base title{% endblock %} {% block content %}replacing the base content - variable: {{ test_var }} after variable {% endblock %} erlydtl-0.15.0/test/files/input/extends3000066400000000000000000000002711504712431400201550ustar00rootroot00000000000000{% extends "imaginary" %} {% block title %}replacing the base title{% endblock %} {% block content %}replacing the base content - variable: {{ test_var }} after variable {% endblock %} erlydtl-0.15.0/test/files/input/extends4000066400000000000000000000001021504712431400201470ustar00rootroot00000000000000{% extends "base" %} bad, only block level tags should be here.. erlydtl-0.15.0/test/files/input/extends_chain000066400000000000000000000001461504712431400212350ustar00rootroot00000000000000{% extends "super_escaped" %} {% block my_block %} {{ block.super }}

    C

    {% endblock %} erlydtl-0.15.0/test/files/input/extends_for000077500000000000000000000002161504712431400207420ustar00rootroot00000000000000{% extends "base_for" %} {% block forloop %}
      {% for iterator in veggie_list %}
    • {{ iterator }}
    • {% endfor %}
    {% endblock %} erlydtl-0.15.0/test/files/input/extends_path000066400000000000000000000002721504712431400211070ustar00rootroot00000000000000{% extends "path1/base1" %} {% block title %}replacing the base title{% endblock %} {% block content %}replacing the base content - variable: {{ test_var }} after variable {% endblock %}erlydtl-0.15.0/test/files/input/extends_path2000066400000000000000000000001701504712431400211660ustar00rootroot00000000000000{% extends "path1/base2" %} {% block content %} start_content {% include "path1/include1" %} end_content {% endblock %} erlydtl-0.15.0/test/files/input/filters000066400000000000000000000014521504712431400200720ustar00rootroot00000000000000{% autoescape off %} Add: 2 + 2 = {{ 2|add:2 }} Capfirst: {{ "capitalized"|capfirst }} Centered:
    {{ "center"|center:20 }}
    
    Date format: {{ date_var1|date:"D, d M Y" }} DateTime format: {{ datetime_var1|date:"D, d M Y H:i:s" }} Escape JS: {{ "\" '"|escapejs }} First letter: {{ "first"|first }} Fix ampersands: {{ "&"|fix_ampersands }} Force_escape: {{ ""|force_escape }} Joined: {{ list|join:", " }} Last: {{ "last"|last }} Length: {{ list|length }} Length is 2?: {{ list|length_is:2 }} Left adjust:
    {{ "left"|ljust:20 }}
    
    Line breaks: {{ "Line 1\nLine 2\nLine 3"|linebreaksbr }} Lowercase: {{ "LOWERCASE"|lower }} Right adjust:
    {{ "right"|rjust:20 }}
    
    Uppercase: {{ "uppercase"|upper }} URL Encode: {{ "Let's go!"|urlencode }} {% endautoescape %} erlydtl-0.15.0/test/files/input/for000066400000000000000000000001671504712431400172120ustar00rootroot00000000000000before
      {% for iterator in fruit_list %}
    • {{ forloop.counter }}. {{ iterator }}
    • {% endfor %}
    after erlydtl-0.15.0/test/files/input/for_list000066400000000000000000000002131504712431400202350ustar00rootroot00000000000000{% for singular, plural, price in fruit_list %} More than one {{ singular }} is called "{{ plural }}". Only {{ price }} each! {% endfor %} erlydtl-0.15.0/test/files/input/for_list_preset000066400000000000000000000001551504712431400216240ustar00rootroot00000000000000{% for singular, plural in fruit_list %} More than one {{ singular }} is called "{{ plural }}". {% endfor %} erlydtl-0.15.0/test/files/input/for_preset000066400000000000000000000001401504712431400205630ustar00rootroot00000000000000before
      {% for iterator in fruit_list %}
    • {{ iterator }}
    • {% endfor %}
    aftererlydtl-0.15.0/test/files/input/for_records000066400000000000000000000002051504712431400207240ustar00rootroot00000000000000before aftererlydtl-0.15.0/test/files/input/for_records_preset000066400000000000000000000004041504712431400223070ustar00rootroot00000000000000before aftererlydtl-0.15.0/test/files/input/for_tuple000066400000000000000000000001341504712431400204150ustar00rootroot00000000000000{% for singular, plural in fruit_list %} One {{ singular }}, two {{ plural }}! {% endfor %} erlydtl-0.15.0/test/files/input/if000066400000000000000000000003071504712431400170160ustar00rootroot00000000000000One but not two: {% if var1 %} one {% else %} two {% endif %} Two but not one: {% if not var1 %} one {% else %} two {% endif %} One: {% if var1 %} one {% endif %} None: {% if var2 %} one {% endif %} erlydtl-0.15.0/test/files/input/if_preset000066400000000000000000000003071504712431400204000ustar00rootroot00000000000000One but not two: {% if var1 %} one {% else %} two {% endif %} Two but not one: {% if not var1 %} one {% else %} two {% endif %} One: {% if var1 %} one {% endif %} None: {% if var2 %} one {% endif %} erlydtl-0.15.0/test/files/input/ifequal000066400000000000000000000015701504712431400200510ustar00rootroot00000000000000{% ifequal var1 var2 %} if: var1="foo" and var2="foo" are equal {% endifequal %} {% ifequal var1 var2 %} if: var1="foo" and var2="foo" are equal {% else %} else: var1="foo" and var2="foo" are not equal {% endifequal %} {% ifequal var1 var3 %} if: var1="foo" and var3="bar" are equal {% endifequal %} {% ifequal var1 var3 %} if: var1="foo" and var3="bar" are equal {% else %} else: var1="foo" and var3="bar" are not equal {% endifequal %} {% ifequal "foo" "foo" %} if: "foo" and "foo" are equal {% else %} else: "foo" and "foo" are not equal {% endifequal %} {% ifequal "foo" "bar" %} if: "foo" and "bar" are equal {% else %} else: "foo" and "bar" are not equal {% endifequal %} {% ifequal 99 99 %} if: 99 and 99 are equal {% else %} else: 99 and 99 are not equal {% endifequal %} {% ifequal 77 99 %} if: 77 and 99 are equal {% else %} else: 77 and 99 are not equal {% endifequal %}erlydtl-0.15.0/test/files/input/ifequal_preset000066400000000000000000000015701504712431400214330ustar00rootroot00000000000000{% ifequal var1 var2 %} if: var1="foo" and var2="foo" are equal {% endifequal %} {% ifequal var1 var2 %} if: var1="foo" and var2="foo" are equal {% else %} else: var1="foo" and var2="foo" are not equal {% endifequal %} {% ifequal var1 var3 %} if: var1="foo" and var3="bar" are equal {% endifequal %} {% ifequal var1 var3 %} if: var1="foo" and var3="bar" are equal {% else %} else: var1="foo" and var3="bar" are not equal {% endifequal %} {% ifequal "foo" "foo" %} if: "foo" and "foo" are equal {% else %} else: "foo" and "foo" are not equal {% endifequal %} {% ifequal "foo" "bar" %} if: "foo" and "bar" are equal {% else %} else: "foo" and "bar" are not equal {% endifequal %} {% ifequal 99 99 %} if: 99 and 99 are equal {% else %} else: 99 and 99 are not equal {% endifequal %} {% ifequal 77 99 %} if: 77 and 99 are equal {% else %} else: 77 and 99 are not equal {% endifequal %}erlydtl-0.15.0/test/files/input/ifnotequal000066400000000000000000000016501504712431400205710ustar00rootroot00000000000000{% ifnotequal var1 var2 %} if: var1="foo" and var2="foo" are equal {% endifnotequal %} {% ifnotequal var1 var2 %} if: var1="foo" and var2="foo" are equal {% else %} else: var1="foo" and var2="foo" are not equal {% endifnotequal %} {% ifnotequal var1 var3 %} if: var1="foo" and var3="bar" are equal {% endifnotequal %} {% ifnotequal var1 var3 %} if: var1="foo" and var3="bar" are equal {% else %} else: var1="foo" and var3="bar" are not equal {% endifnotequal %} {% ifnotequal "foo" "foo" %} if: "foo" and "foo" are equal {% else %} else: "foo" and "foo" are not equal {% endifnotequal %} {% ifnotequal "foo" "bar" %} if: "foo" and "bar" are equal {% else %} else: "foo" and "bar" are not equal {% endifnotequal %} {% ifnotequal 99 99 %} if: 99 and 99 are equal {% else %} else: 99 and 99 are not equal {% endifnotequal %} {% ifnotequal 77 99 %} if: 77 and 99 are equal {% else %} else: 77 and 99 are not equal {% endifnotequal %}erlydtl-0.15.0/test/files/input/ifnotequal_preset000066400000000000000000000016501504712431400221530ustar00rootroot00000000000000{% ifnotequal var1 var2 %} if: var1="foo" and var2="foo" are equal {% endifnotequal %} {% ifnotequal var1 var2 %} if: var1="foo" and var2="foo" are equal {% else %} else: var1="foo" and var2="foo" are not equal {% endifnotequal %} {% ifnotequal var1 var3 %} if: var1="foo" and var3="bar" are equal {% endifnotequal %} {% ifnotequal var1 var3 %} if: var1="foo" and var3="bar" are equal {% else %} else: var1="foo" and var3="bar" are not equal {% endifnotequal %} {% ifnotequal "foo" "foo" %} if: "foo" and "foo" are equal {% else %} else: "foo" and "foo" are not equal {% endifnotequal %} {% ifnotequal "foo" "bar" %} if: "foo" and "bar" are equal {% else %} else: "foo" and "bar" are not equal {% endifnotequal %} {% ifnotequal 99 99 %} if: 99 and 99 are equal {% else %} else: 99 and 99 are not equal {% endifnotequal %} {% ifnotequal 77 99 %} if: 77 and 99 are equal {% else %} else: 77 and 99 are not equal {% endifnotequal %}erlydtl-0.15.0/test/files/input/include000066400000000000000000000001111504712431400200340ustar00rootroot00000000000000Including another file: {% include "include.html" with var1=var1 only %} erlydtl-0.15.0/test/files/input/include.html000066400000000000000000000000351504712431400210040ustar00rootroot00000000000000This is included! {{ var1 }} erlydtl-0.15.0/test/files/input/include_path000066400000000000000000000000761504712431400210620ustar00rootroot00000000000000main file {% ssi "path1/template1" parsed %} {{ base_var }} erlydtl-0.15.0/test/files/input/include_template000066400000000000000000000001201504712431400217270ustar00rootroot00000000000000Including another template: {% include "base" %} test variable: {{ test_var }} erlydtl-0.15.0/test/files/input/now000066400000000000000000000004241504712431400172230ustar00rootroot00000000000000Expected format : Thu, 21 Dec 2000 16:01:07 +0200 Got : {% now "r" %} Expected format : 27th February 2008 01:24 Got : {% now "jS F Y H:i" %} Expected format : It is the 4th of September 2007 Got : It is the {% now "jS o\f F Y" %} Expected format : '' Got : '{% now "" %}' erlydtl-0.15.0/test/files/input/path1/000077500000000000000000000000001504712431400175125ustar00rootroot00000000000000erlydtl-0.15.0/test/files/input/path1/base1000066400000000000000000000001331504712431400204250ustar00rootroot00000000000000{% extends "path2/base2" %} {% block title2 %}block title 2 from base1 {% endblock %} erlydtl-0.15.0/test/files/input/path1/base2000066400000000000000000000001041504712431400204240ustar00rootroot00000000000000pre content {% block content %}default content{% endblock %} post erlydtl-0.15.0/test/files/input/path1/include1000066400000000000000000000000211504712431400211320ustar00rootroot00000000000000This is include1 erlydtl-0.15.0/test/files/input/path1/template1000066400000000000000000000001051504712431400213250ustar00rootroot00000000000000This is template 1. {{ test_var }} {% include "path2/template2" %} erlydtl-0.15.0/test/files/input/path2/000077500000000000000000000000001504712431400175135ustar00rootroot00000000000000erlydtl-0.15.0/test/files/input/path2/base2000066400000000000000000000006311504712431400204320ustar00rootroot00000000000000{{ base_var }} base2 template {% block title %}block title in base2, should be hidden by main template{% endblock %} {% block title2 %}block title2 in base2, should be hidden by base1{% endblock %} more of base2 template {% block content %}block content in base 2, should be overwritten{% endblock %} {% block content2 %}block content2 in base 2, should pass through{% endblock %} end of base2 template erlydtl-0.15.0/test/files/input/path2/template2000066400000000000000000000000241504712431400213270ustar00rootroot00000000000000 This is template 2 erlydtl-0.15.0/test/files/input/reader_options000066400000000000000000000000251504712431400214320ustar00rootroot00000000000000{% extends "base" %} erlydtl-0.15.0/test/files/input/recursive_block000066400000000000000000000001761504712431400216050ustar00rootroot00000000000000{% block content %} testing:
    {% block content %}{% endblock %}
    end testing
    {% endblock %} erlydtl-0.15.0/test/files/input/ssi000066400000000000000000000000171504712431400172140ustar00rootroot00000000000000{% ssi path %} erlydtl-0.15.0/test/files/input/ssi_include.html000066400000000000000000000000331504712431400216600ustar00rootroot00000000000000{{ "Don't evaluate me!" }} erlydtl-0.15.0/test/files/input/ssi_reader_options000066400000000000000000000000171504712431400223110ustar00rootroot00000000000000{% ssi path %} erlydtl-0.15.0/test/files/input/super_escaped000066400000000000000000000001441504712431400212410ustar00rootroot00000000000000{% extends "base_escape" %} {% block my_block %} {{ block.super }}

    B

    {% endblock %} erlydtl-0.15.0/test/files/input/trans000066400000000000000000000000351504712431400175450ustar00rootroot00000000000000{% trans "Example String" %} erlydtl-0.15.0/test/files/input/var000077500000000000000000000001451504712431400172130ustar00rootroot00000000000000before varriable1 {{ var1 }} after variable1 after variable2 (HTML-comment-wrapped)erlydtl-0.15.0/test/files/input/var_preset000077500000000000000000000001311504712431400205700ustar00rootroot00000000000000one {{ var1 }} two {{ preset_var1 }} three four {{ preset_var2 }} five erlydtl-0.15.0/test/files/input/wrapper000066400000000000000000000004501504712431400200770ustar00rootroot00000000000000{% for type in types %} {% if type=="a" %} including base a now {% include "base_a" %} {% elif type=="b" %} including base b now {% include "base_b" %} {% else %} including base c now {% include "base_c" %} {% endif %} {% endfor %} erlydtl-0.15.0/test/py/000077500000000000000000000000001504712431400146645ustar00rootroot00000000000000erlydtl-0.15.0/test/py/erlydtl_python_test.py000066400000000000000000000033061504712431400213570ustar00rootroot00000000000000from erlport import Port, Protocol, String, Atom from erlport.erlterms import decode from django.template import Context, Template from django.conf import settings import types # Inherit custom protocol from erlport.Protocol class ErlydtlProtocol(Protocol): def handle_slice(self, command): list = command[0] slice = "%s" % String(command[1]) slice_test1_string = "%s[%s]" % (list,slice) try: result_list = eval(slice_test1_string) except(IndexError): result_list = Atom("indexError") except: result_list = Atom("error") #print "result_list: %s" % (result_list) return result_list #@doc Start list with 'object' to pass in a python term along with import statement in the form: #@doc object|module to import|term (three strings delimited by "|" def handle_template(self, command): file = open("/tmp/debug.txt",'a') template_text = "%s" % String(command[0]) #value = "%s" % String(command[1]) value = "%s" % String(command[1]) value_split = value.split("|") if value_split[0] == u"object": module = value_split[1] exec "import %s" % module value = eval(value_split[2]) #term(((term.year, term.month, term.day), (term.hour, term.minute, term.second))) c = Context({"value":value}) t = Template(template_text) result = String(t.render(c)) file.close() return result if __name__ == "__main__": settings.configure(DEBUG=True, TEMPLATE_DEBUG=True) proto = ErlydtlProtocol() # Run protocol with port open on STDIO proto.run(Port(use_stdio=True)) erlydtl-0.15.0/test/py/erlydtl_python_test.pyc000066400000000000000000000034171504712431400215250ustar00rootroot00000000000000Ñò ¾Mc @sÄddklZlZlZlZddklZddklZl Z ddk l Z ddk Z defd„ƒYZ edjo9e id ed eƒe ƒZeied eƒƒndS( iÿÿÿÿ(tPorttProtocoltStringtAtom(tdecode(tContexttTemplate(tsettingsNtErlydtlProtocolcBseZd„Zd„ZRS(cCsv|d}dt|dƒ}d||f}yt|ƒ}Wn1tj otdƒ}ntdƒ}nX|S(Nis%sis%s[%s]t indexErrorterror(Rtevalt IndexErrorR(tselftcommandtlisttslicetslice_test1_stringt result_list((serlydtl_python_test.pyt handle_slice s c BsÃeddƒ}de|dƒ}de|dƒ}|idƒ}|ddjo*|d}d|dUe|d ƒ}neh|d 6ƒ}e|ƒ}e|i|ƒƒ} |iƒ| S( Ns/tmp/debug.txttas%siit|uobjects import %sitvalue(topenRtsplitR RRtrendertclose( R Rtfilet template_textRt value_splittmoduletctttresult((serlydtl_python_test.pythandle_templates    (t__name__t __module__RR"(((serlydtl_python_test.pyRs t__main__tDEBUGtTEMPLATE_DEBUGt use_stdio(terlportRRRRterlport.erltermsRtdjango.templateRRt django.confRttypesRR#t configuretTruetprototrun(((serlydtl_python_test.pyts" $  erlydtl-0.15.0/test/py/python_dtl_setup.txt000066400000000000000000000023341504712431400210330ustar00rootroot00000000000000from erlport import Port, Protocol, String, Atom from erlport.erlterms import decode from django.template import Context, Template from django.conf import settings import types from erlydtl_python_test import ErlydtlProtocol as proto settings.configure(DEBUG=True, TEMPLATE_DEBUG=True) ep = proto() ep.handle_template([u"{{ value|date }}",u"object,datetime,datetime.date.today()"]) ep.handle_template([u"{{ value|time:\"H:i\" }}",u"object,datetime,datetime.datetime.now()"]) DateFormat = erlydtl_filters_tests:erlydtl_render("{{ value|random }}", [ {value, ["b","c","d","D","F","j","l","L","m","M","n","N","t","w","W","y","Y","z"]} ] ). Template = "{{ value|date:\"" ++ DateFormat ++ "\" }}". Value = { {2010,12,1}, {10,11,12} }. Port = erlydtl_python_test:start(). PyDate = lists:flatten(io_lib:format("object|datetime|~s", [erlydtl_filters_tests:python_datetime_encode(Value)])). erlydtl_filters_tests:py_template(Port, Template, PyDate). from django.template import Context, Template from django.conf import settings settings.configure(DEBUG=True, TEMPLATE_DEBUG=True) from erlydtl_python_test import ErlydtlProtocol as proto ep = proto() c = Context({"value": "Check out www.yahoo.com"}) t = Template("{{ value|urlize }}") t.render(c)erlydtl-0.15.0/test/py/run_erl_node.sh000077500000000000000000000007321504712431400177000ustar00rootroot00000000000000#! /bin/sh # A test case for testing packing/unpacking of erlang-terms: # # A message is sent from an erlang node to a python node. # That message is echoed back to the erlang node, which checks # if the received message matches the original message. # # First make sure epmd is up and running. (needed by the python-node) erl -noinput -detach -sname ensure_epmd_started@localhost -s erlang halt erl -sname enode1@localhost \ -setcookie cookie -pa ../../../../ebin/ erlydtl-0.15.0/test/sources_parser_tests.erl000066400000000000000000000314061504712431400212250ustar00rootroot00000000000000-module(sources_parser_tests). -include_lib("eunit/include/eunit.hrl"). -include("include/erlydtl_ext.hrl"). all_sources_parser_test_() -> [{Title, [test_fun(Test) || Test <- Tests]} || {Title, Tests} <- test_defs()]. test_fun({Name, Content, Output}) -> {Name, fun () -> Tokens = (catch sources_parser:process_content("dummy_path", Content)), ?assertMatch(Output, Tokens) end}. test_defs() -> [{"trans", [{"block with no trans", <<"{% block main %} {% endblock %}">>, []}, {"block with trans", <<"{% block main %} {% trans \"Hello\" %} {% endblock %}">>, [{"Hello",{"dummy_path",1,33}}]}, {"for with trans", <<"{% block main %} {%for thing in things %}{% trans \"Hello inside a for\" %} {% endfor %} {% endblock %}">>, [{"Hello inside a for",{"dummy_path",1,57}}]}, {"if with trans", <<"{% block content %}{% if thing %} {% trans \"Hello inside an if\" %} {% endif %} {% endblock %}">>, [{"Hello inside an if",{"dummy_path",1,50}}]}, {"if with trans inside a for", <<"{% block content %}{%for thin in things %}{% if thing %} {% trans \"Hello inside an if inside a for\" %} {% endif %} {% endfor %}{% endblock %}">>, [{"Hello inside an if inside a for",{"dummy_path",1,73}}]}, {"if and else both with trans", <<"{% block content %}{% if thing %} {% trans \"Hello inside an if\" %} {% else %} {% trans \"Hello inside an else\" %} {% endif %} {% endblock %}">>, [{"Hello inside an else",{"dummy_path",1,94}}, {"Hello inside an if",{"dummy_path",1,50}}]}, {"blocktrans with pretty format", <<"{% blocktrans %}\n This is a multiline\n message... \n{% endblocktrans %}">>, [{"\n This is a multiline\n message... \n", {"dummy_path",1,10}}]}, {"blocktrans with pretty format, trimmed", <<"{% blocktrans trimmed %}\n This is a multiline\n message... \n{% endblocktrans %}">>, [{"This is a multiline message...", {"dummy_path",1,18}}]} ]} ]. all_sources_parser_ext_test_() -> [test_ext_fun(Test) || Test <- ext_test_defs()]. test_ext_fun({Name, Tpl, {Fields, Output}}) -> {Name, fun() -> Tokens = [sources_parser:phrase_info(Fields, P) || P <- sources_parser:parse_content("dummy_path", Tpl)], ?assertEqual(Output, Tokens) end}. ext_test_defs() -> [{"trans with inline comments", <<"{#TrAnSlATORs: hi!#}{%trans 'phrase'%}">>, {[msgid, comment], [["phrase", "TrAnSlATORs: hi!"]]}}, {"trans with comments", <<"{%comment%}translators: com{{me}}nt{%endcomment%}{%trans 'phrase'%}">>, {[msgid, comment], [["phrase", "translators: com{{ me }}nt"]]}}, {"blocktrans with comments", <<"{%comment%}translators: comment{%endcomment%}{%blocktrans with a=b%}B={{b}}{%endblocktrans%}">>, {[msgid, comment], [["B={{ b }}", "translators: comment"]]}}, {"blocktrans with context", <<"{%blocktrans context 'ctxt'%}msg{%endblocktrans%}">>, {[msgid, context], [["msg", "ctxt"]]}}, {"blocktrans with plural form", <<"{%blocktrans%}msg{%plural%}msgs{%endblocktrans%}">>, {[msgid, msgid_plural], [["msg", "msgs"]]}}, {"trans with context", <<"{% trans 'msg' context 'ctxt' %}">>, {[msgid, context], [["msg", "ctxt"]]}}, {"trans noop", <<"{% trans 'msg' noop %}">>, {[msgid], [["msg"]]}}, {"trans noop with context", <<"{% trans 'msg' noop context 'ctxt' %}">>, {[msgid, context], [["msg", "ctxt"]]}} ]. unparser_test_() -> [test_unparser_fun(Test) || Test <- unparser_test_defs()]. test_unparser_fun({Name, Tpl}) -> {Name, fun() -> %% take input Tpl value, parse it, "unparse" it, then parse it again. %% both parsed values should be equvialent, even if the source versions %% are not an exact match (there can be whitespace differences) case erlydtl_compiler:do_parse_template( Tpl, #dtl_context{}) of {ok, Dpt} -> Unparsed = erlydtl_unparser:unparse(Dpt), case erlydtl_compiler:do_parse_template( Unparsed, #dtl_context{}) of {ok, DptU} -> case catch compare_tree(Dpt, DptU) of ok -> ok; Err -> throw({compare_failed, Err, {test_ast, Dpt}, {unparsed, {source, Unparsed}, {ast, DptU}}}) end; Err -> throw({unparsed_source, Err}) end; Err -> throw({test_source, Err}) end end}. unparser_test_defs() -> [{"comment tag", <<"here it is: {# this is my comment #} <-- it was right there.">>}, {"blocktrans plain", <<"{% blocktrans %}foo bar{% endblocktrans %}">>}, {"blocktrans trimmed", <<"{% blocktrans trimmed %}\n foo \n bar \n\n{% endblocktrans %}">>}, {"blocktrans with args", <<"{% blocktrans with var1=foo var2=bar count c=d %}blarg{% endblocktrans %}">>}, {"blocktrans with all", <<"{% blocktrans with var1=foo var2=bar trimmed context 'baz' count c=d %}blarg{% endblocktrans %}">>} ]. compare_tree([], []) -> ok; compare_tree([H1|T1], [H2|T2]) -> compare_token(H1, H2), compare_tree(T1, T2). compare_token({'extends', Value1}, {'extends', Value2}) -> ?assertEqual(Value1, Value2); compare_token({'autoescape', OnOrOff1, Contents1}, {'autoescape', OnOrOff2, Contents2}) -> ?assertEqual(OnOrOff1, OnOrOff2), compare_tree(Contents1, Contents2); compare_token({'block', Identifier1, Contents1}, {'block', Identifier2, Contents2}) -> compare_identifier(Identifier1, Identifier2), compare_tree(Contents1, Contents2); compare_token({'blocktrans', Args1, Contents1, Plural1}, {'blocktrans', Args2, Contents2, Plural2}) -> compare_blocktrans_args(Args1, Args2), compare_tree(Contents1, Contents2), case {Plural1, Plural2} of {undefined, undefined} -> ok; _ -> compare_tree(Plural1, Plural2) end; compare_token({'call', Identifier1}, {'call', Identifier2}) -> compare_identifier(Identifier1, Identifier2); compare_token({'call', Identifier1, With1}, {'call', Identifier2, With2}) -> ?assertEqual(With1, With2), compare_identifier(Identifier1, Identifier2); compare_token({'comment', Contents1}, {'comment', Contents2}) -> compare_tree(Contents1, Contents2); compare_token({'comment_tag', _Pos1, Text1}, {'comment_tag', _Pos2, Text2}) -> ?assertEqual(Text1, Text2); compare_token({'cycle', Names1}, {'cycle', Names2}) -> compare_tree(Names1, Names2); compare_token({'cycle_compat', Names1}, {'cycle_compat', Names2}) -> compare_cycle_compat_names(Names1, Names2); compare_token({'date', 'now', Value1}, {'date', 'now', Value2}) -> compare_value(Value1, Value2); compare_token({'filter', FilterList1, Contents1}, {'filter', FilterList2, Contents2}) -> compare_filters(FilterList1, FilterList2), compare_tree(Contents1, Contents2); compare_token({'firstof', Vars1}, {'firstof', Vars2}) -> compare_tree(Vars1, Vars2); %% TODO... %% compare_token({'for', {'in', IteratorList, Identifier}, Contents}, {'for', {'in', IteratorList, Identifier}, Contents}) -> ok; %% compare_token({'for', {'in', IteratorList, Identifier}, Contents, EmptyPartsContents}, {'for', {'in', IteratorList, Identifier}, Contents, EmptyPartsContents}) -> ok; compare_token({'if', Expression1, Contents1}, {'if', Expression2, Contents2}) -> compare_expression(Expression1, Expression2), compare_tree(Contents1, Contents2); %% compare_token({'ifchanged', Expression, IfContents}, {'ifchanged', Expression, IfContents}) -> ok; %% compare_token({'ifchangedelse', Expression, IfContents, ElseContents}, {'ifchangedelse', Expression, IfContents, ElseContents}) -> ok; %% compare_token({'ifelse', Expression, IfContents, ElseContents}, {'ifelse', Expression, IfContents, ElseContents}) -> ok; %% compare_token({'ifequal', [Arg1, Arg2], Contents}, {'ifequal', [Arg1, Arg2], Contents}) -> ok; %% compare_token({'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}, {'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}) -> ok; %% compare_token({'ifnotequal', [Arg1, Arg2], Contents}, {'ifnotequal', [Arg1, Arg2], Contents}) -> ok; %% compare_token({'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}, {'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}) -> ok; %% compare_token({'include', Value, []}, {'include', Value, []}) -> ok; %% compare_token({'include', Value, Args}, {'include', Value, Args}) -> ok; %% compare_token({'include_only', Value, []}, {'include_only', Value, []}) -> ok; %% compare_token({'include_only', Value, Args}, {'include_only', Value, Args}) -> ok; %% compare_token({'regroup', {Variable, Identifier1, Identifier2}, Contents}, {'regroup', {Variable, Identifier1, Identifier2}, Contents}) -> ok; %% compare_token({'spaceless', Contents}, {'spaceless', Contents}) -> ok; %% compare_token({'ssi', Arg}, {'ssi', Arg}) -> ok; %% compare_token({'ssi_parsed', Arg}, {'ssi_parsed', Arg}) -> ok; compare_token({'string', _, String1}, {'string', _, String2}) -> ?assertEqual(String1, String2); %% compare_token({'tag', Identifier, []}, {'tag', Identifier, []}) -> ok; %% compare_token({'tag', Identifier, Args}, {'tag', Identifier, Args}) -> ok; %% compare_token({'templatetag', Identifier}, {'templatetag', Identifier}) -> ok; %% compare_token({'trans', Value}, {'trans', Value}) -> ok; %% compare_token({'widthratio', Numerator, Denominator, Scale}, {'widthratio', Numerator, Denominator, Scale}) -> ok; %% compare_token({'with', Args, Contents}, {'with', Args, Contents}) -> ok; compare_token(ValueToken1, ValueToken2) -> compare_value(ValueToken1, ValueToken2). compare_identifier({identifier, _, Name1}, {identifier, _, Name2}) -> ?assertEqual(Name1, Name2). compare_filters(FilterList1, FilterList2) -> [compare_filter(F1, F2) || {F1, F2} <- lists:zip(FilterList1, FilterList2)]. compare_filter([Identifier1], [Identifier2]) -> compare_identifier(Identifier1, Identifier2); compare_filter([Identifier1, Arg1], [Identifier2, Arg2]) -> compare_identifier(Identifier1, Identifier2), compare_value(Arg1, Arg2). compare_expression({'expr', _, Arg11, Arg12}, {'expr', _, Arg21, Arg22}) -> compare_value(Arg11, Arg21), compare_value(Arg12, Arg22); compare_expression({'expr', "not", Expr1}, {'expr', "not", Expr2}) -> compare_expression(Expr1, Expr2); compare_expression(Other1, Other2) -> compare_value(Other1, Other2). compare_value({'string_literal', _, Value1}, {'string_literal', _, Value2}) -> ?assertEqual(Value1, Value2); compare_value({'number_literal', _, Value1}, {'number_literal', _, Value2}) -> ?assertEqual(Value1, Value2); compare_value({'apply_filter', Variable1, Filter1}, {'apply_filter', Variable2, Filter2}) -> compare_value(Variable1, Variable2), compare_filter(Filter1, Filter2); compare_value({'attribute', {Variable1, Identifier1}}, {'attribute', {Variable2, Identifier2}}) -> compare_value(Variable1, Variable2), compare_identifier(Identifier1, Identifier2); compare_value({'variable', Identifier1}, {'variable', Identifier2}) -> compare_identifier(Identifier1, Identifier2). compare_args(Args1, Args2) when length(Args1) =:= length(Args2) -> [compare_arg(A1, A2) || {A1, A2} <- lists:zip(Args1, Args2)]. compare_arg(Arg, Arg) when is_atom(Arg) -> ok; compare_arg({{identifier, _, Name1}, Value1}, {{identifier, _, Name2}, Value2}) -> ?assertEqual(Name1, Name2), compare_value(Value1, Value2). compare_blocktrans_args([], []) -> ok; compare_blocktrans_args([{args, WithArgs1}|Args1], Args2) -> {value, {args, WithArgs2}, Args3} = lists:keytake(args, 1, Args2), compare_args(WithArgs1, WithArgs2), compare_blocktrans_args(Args1, Args3); compare_blocktrans_args([{count, Count1}|Args1], Args2) -> {value, {count, Count2}, Args3} = lists:keytake(count, 1, Args2), compare_arg(Count1, Count2), compare_blocktrans_args(Args1, Args3); compare_blocktrans_args([{context, Context1}|Args1], Args2) -> {value, {context, Context2}, Args3} = lists:keytake(context, 1, Args2), compare_value(Context1, Context2), compare_blocktrans_args(Args1, Args3); compare_blocktrans_args([trimmed|Args1], Args2) -> Args3 = Args2 -- [trimmed], if Args2 =/= Args3 -> compare_blocktrans_args(Args1, Args3) end. compare_cycle_compat_names(Names1, Names2) -> [compare_identifier(N1, N2) || {N1, N2} <- lists:zip(Names1, Names2)]. erlydtl-0.15.0/test/testrunner.hrl000066400000000000000000000007561504712431400171640ustar00rootroot00000000000000-ifndef(noimport). -import(erlydtl_eunit_testrunner, [run_test/1, run_compile/1, run_render/1]). -endif. -record(test, { title = "", module = erly_test, source, renderer = render, output, compile_opts = [report, return, {auto_escape, false}, force_recompile, {out_dir, false}], compile_vars = [], render_opts = [], render_vars = [], warnings = [], errors = [] }).