Promote 6.x as the main branch

This commit is contained in:
Eric MORAND 2023-12-04 06:29:33 +00:00
commit fa96f41c36
1453 changed files with 22220 additions and 37302 deletions

View File

@ -3,7 +3,6 @@ root = true
[*.ts]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4

View File

@ -1,10 +0,0 @@
.idea
.nyc_output
dist
coverage
docs/.bundle
docs/_site
docs/vendor
node_modules
package-lock.json
tmp

View File

@ -1,36 +0,0 @@
{
"env": {
"browser": true,
"commonjs": false,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"import"
],
"rules": {
"no-unused-vars": "off", //would throw on imports just for setting the type and not being used otherwise
"@typescript-eslint/no-unused-vars": "off", //see no-unused-vars
"no-case-declarations": "off",
"no-constant-condition": "off",
"no-useless-escape": "off", //also checks normal strings
"no-fallthrough": "off", //fails on switch fallthroughs
"no-prototype-builtins": "off", //should be fixed!
"no-empty": ["error", { "allowEmptyCatch": true }], //maybe this should be fixed
"require-atomic-updates": "off" // this is the test suite purpose to check that a code works as expected, not the purpose of code quality check
}
}

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
.idea
.nyc_output
dist
coverage

View File

@ -1,10 +0,0 @@
*
!/dist/**
!/lib/**
!/browser.d.ts
!/browser.js
!/index.d.ts
!/index.js
!/LICENSE
!/CHANGELOG
!/README.md

1
.npmrc
View File

@ -1 +0,0 @@
package-lock=false

View File

@ -3,15 +3,27 @@
"lines": 100,
"branches": 100,
"functions": 100,
"watermarks": {
"lines": [
100,
100
],
"functions": [
100,
100
],
"branches": [
100,
100
],
"statements": [
100,
100
]
},
"extension": [
".ts"
],
"include": "src",
"exclude": [
"src/base.ts",
"src/browser.ts",
"src/main.ts"
],
"reporter": [
"text-summary",
"html"

View File

@ -1,36 +0,0 @@
language: node_js
node_js:
- "8"
- "9"
- "10"
- "11"
- "12"
jobs:
include:
- stage: test & cover
node_js: "13"
script:
- npm run cover
- npm run coverage
- stage: test in web browser
node_js: "13"
script:
- npm run test:browser
addons:
apt:
packages:
- xvfb
install:
- export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- npm install
- stage: test documentation
node_js: "13"
script:
- npm run test:docs
install:
- cd docs && bundle install --path ./vendor/bundle
- stage: test code quality
node_js: "13"
script:
- npm run test:qc

292
CHANGELOG
View File

@ -1,292 +0,0 @@
5.1.0
* #541 Add a way to parse templates in a "loose" way
5.0.2
* #513 Fix errors in relative filesystem loader
5.0.1
* Fix an issue with node/module test
5.0.0
* #508 Allows functions, filters and tests to use the template itself
* #502 Relatively loaded templates are cached under multiple keys
* #501 Add support for node@13
* #499 Remove the deprecated classes and functions
* #498 Remove the native "optimizer" extension
* #479 Binary nodes all share the same type
* #497 Output buffering doesn't support concurrent rendering
4.0.6
* #495 Relative filesystem loader fails to resolve template included from a relatively resolved template
4.0.5
*#493 Chain loader fails to resolve when it contains more than one sub-loader
4.0.4
* #486 "iterable1 is not iterable" error when merging into a hash
* #485 Twing render output is incorrect when \ (backslash) is present in source twig file(s)
* #484 String interpolation doesn't work for string with double and single quotes
* #483 The property object is async but the filter callback is sync
* #480 `json_encode` cannot serialize `_context`
* #449 Environment is not publicly available in TwingTemplate
4.0.3
* #474 Issues with embed tag
* #475 TwingNodeComment is not exported in lib index
4.0.2
* #466 - min function does not working as expected
* #469 - concatenation using ~ outputs "undefined" (variables)
4.0.1
* #462 length filter does't work as expected on empty array
4.0.0
* #452 Remove support for sourceMap environment option being a string
* #451 Remove TwingSource path property entirely
* #347 Async function support
* #288 Custom asynchronous loader
3.1.1
* #450 Getting the source map of a template loaded from an array loader throws an error
3.1.0
* #424 Add support for the line tag
* #435 Rename "eslint" test suite to "code quality"
3.0.3
* #441 empty tmp file in dist
3.0.2
* #432 Sandbox is always enabled, whatever the options passed to the environment
* #428 added eslint and fixed minor eslint problems
* #417 Rewrite the test suite in TypeScript
* #422 Map and reduce filters documentation exposes a non-existing "array" argument
3.0.1
* #419 TwingLoaderNull doesn't behave as expected
* #418 Types are not available to consumers
* #414 Include function should be called with undefined when no variables is set to benefit from the default values
* #412 Performance is way lower than pre-2.3
* #411 Source maps are exported in the npm package
3.0.0
* #409 3.x: compatibility chart is not up-to-date
* #403 3.x: Lexer doesn't support custom operators
* #399 Move the project to NightlyCommit
* #398 Add support for Node.js 12
* #391 Add a way to register templates module in the environment cache
* #390 Get rid of TwingTemplateWrapper
* #375 Clean the included extensions
* #368 filter tag is supposed to create a new context scope
* #365 Add support for Twig 2.11
* #362 Array syntax should always resolve to a Map
* #348 The lexer is lossy
* #345 Provide an easier way to load the main pre-compiled template
* #338 Rework filter and function declaration APIs to enforce explicit parameters naming
* #337 Clarify documentation of the auto_reload option
* #310 Move deprecation messages to warnings instead of errors
* #305 Provide an ES along with CJS version of Twing
2.3.6
* #385 Get rid of most constructor.name usage
2.3.5
* #381 Browser and Webpack (TypeScript)
* #380 Doesn't run without the types luxon
2.3.4
* #378 Add a "Related projects" section to the README
* #376 Error thrown when for tag is used inside a with tag
2.3.3
* #373 Browser flavor lacks some exports
2.3.2
* #371 Add a compatibility chart
* #369 Add a "known issues" section in the documentation
2.3.1
* #360 slice filter returns a map
2.3.0
* #333 Add support for Twig 2.10
2.2.7
* #358 package-lock.json prevents grabbing latest dependencies security patches
* #349 Variable wrongly interpreted as operator when used as assignment of a for tag
* #327 Comments are not part of the AST
2.2.6
* #352 twing fails to render if the the html tag has an attribute lang="de" - Cannot read property 'LC_CTYPE' of undefined
* #354 Twing crashes when template contains JS interpolation syntax
2.2.5
* #341 Syntax errors don't show the location where the error occurred
2.2.4
* #334 Changing autoescape option doesn't invalidate the cache
* #298 Add support for sourceContents
2.2.3
* #323 include function doesn't support relative filesystem loader
* #322 Test suite fails with node.js 11
2.2.2
* #225 [RCF] npm found 3 vulnerabilities (2 low, 1 moderate)
* #313 TwingEnvironmentOptions.cache typing is incorrect
* #316 `json_encode` cannot serialize Map
2.2.1
* #314 Twing 2.2.0 not working in browser
2.2.0
* #299 Add support for Twig 2.6.3
2.1.4
* #308 Source map for spaceless tag is still faulty in some case
2.1.3
* #303 Incorrect souce map when spaceless tag is used
* #302 TwingErrorSyntax: Unknown "dump" function
* #300 Probable bug in TwingFileSystemLoader
2.1.2
* #289 - "include" tag uses merge filter instead of merge helper
* #286 - Types are incorrect - i.e. impossible to build against Twing
2.1.1
* #284 - Uncaught TwingErrorSyntax: An exception has been thrown during the compilation of a template ("parseFunction is not a function")
* #282 - Template code and path not send to TwingError when TwingTemplate#loadTemplate is called with a template name
2.1.0
* #280 - Documentation doesn't build anymore
* #9 - Rewrite varDump helper
* #277 - Add core support for template-relative resolving
* #227 - date filter doesn't support all required format
* #274 - CHANGELOG not up-to-date
2.0.0
* #267 - Update documentation
* #271 - Improve the packaging process
* #200 - Cannot find module 'twing/lib/runtime'
* #237 - Can twing be run on the frontend?
* #258 - Twing should return source map as string instead of SourceMapGenerator
* #209 - How to use base_template_class?
1.3.1
* #266 - CHANGELOG not up-to-date on v1.3.0
1.3.0
* #251 - Support Twig 2.5.0
1.2.7
* #262 - TwingToken is not exported
1.2.6
* #256 - Source map sources are not relative to the project root
* #254 - Changing source map config doesn't invalidate template cache
* #255 - TypeError: Cannot read property 'toMappings' of undefined
* #253 - "Error: ENOENT: no such file or directory" on non-existent namespace folder
* #249 - Interfaces are not exported
1.2.5
* #247 - Package manifest uses deprecated licenses key
1.2.4
* #236 - Batch filter throws an error when used on an undefined variable
* #238 - "types" entry missing from package.json
* #242 - Improve Travis CI support
* #240 - Remove benchmark and tasks folders and dependencies
* #239 - TwingEnvironmentOptions is not exported
1.2.3
* Fix #234 - loop.last & loop.length & iterable not available in nested for
1.2.2
* Fix #230 - Compiler should not escape quotation mark
* Fix #232 - Twing throws: "SyntaxError: missing ) after argument list" whenever it encounters a ` character.
1.2.1
* Fix #218 - Twing@1.2.0 created a breaking changes with extension filters and functions
* Fix #219 - An exception has been thrown during the compilation of a template ("The comparison function must be either a function or undefined").
* Fix #220 - Error when getSourceMap is called with source_map option set to false
1.2.0
* Fix #12 - Add source map support enhancement in progress
* Fix #205 - Unable to register extension "_default" when using babel + twing extensions
* Fix #216 - Restore auto reload behavior from pre-#194
1.1.1
* Fix #210 - If with multiple elseif not working as expected
1.1.0
* Fix #204 - Export TwingNodeType as part of twing
* Fix #206 - Make TwingEnvironment emit an event when encountering a template
1.0.3
* Fix #194 - Autoreload broken
1.0.2
* Fix #201 - The merge filter returns a Map even if the filtered object is an array
1.0.1
* Catch-up with TwigPHP 2.4.8
* Add a changelog
1.0.0
* Initial release based on TwigPHP 2.4.4

122
README.md
View File

@ -1,96 +1,66 @@
# Twing
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage percentage][coveralls-image]][coveralls-url] [![Donate][donate-image]][donate-url]
[![NPM version][npm-image]][npm-url] [![Build Status][build-image]][build-url] [![Coverage percentage][coveralls-image]][coveralls-url] [![Donate][donate-image]][donate-url]
First-class Twig engine for Node.js
## Philosophy behind Twing
We believe that a first-class Twig engine should be able to render any template to the exact same result as the official PHP engine. That means that it should implement 100% of the syntax defined by the language specifications and that it should render that syntax using PHP logic.
We also believe that a first-class Twig engine should be able to catch-up easily when Twig specifications evolve. Its code architecture and philosophy should then be as close as possible as the PHP implementation.
Finally, we believe that a first-class Twig engine should allow users to build on their experience with TwigPHP and get support from the huge community that comes with it.
That's what Twing is. A maintainability-first engine that pass 100% of the TwigPHP integration tests, is as close as possible to its code structure and expose an as-close-as-possible API.
First-class TypeScript and JavaScript Twig compiler
## Prerequisites
Twing needs at least **node.js 8.0.0** to run.
This projects needs at least **node.js 16.0.0** to run.
## Installation
It is also strongly recommended to have [ts-node](https://www.npmjs.com/package/ts-node) and [nyc](https://www.npmjs.com/package/nyc) installed globally to ease the writing of tests and the tracking of the code coverage.
The recommended way to install Twing is via npm:
## Usage
`npm install twing --save`
### Installation
## Basic API Usage
```js
const {TwingEnvironment, TwingLoaderArray} = require('twing');
let loader = new TwingLoaderArray({
'index.twig': 'Hello {{ name }}!'
});
let twing = new TwingEnvironment(loader);
twing.render('index.twig', {name: 'Fabien'}).then((output) => {
// do something with the output
});
```shell
npm install
```
## Usage with Express
### Build the library
Twing and Express work quite well together. Have a look at the [documentation](http://NightlyCommit.github.io/twing/intro.html#real-world-example-using-express) for an example of usage with Express.
```shell
npm run build
```
## Browser support
### Build and run the test suite
Starting with version 2.0.0, Twing can be used in web browsers with very few compromise. Filesystem components are obviously not available (namely filesystem loader and cache) but everything else is fully supported.
```shell
npm run build:test
npm run test
```
### Module bundler
### Build and run the test suite in a browser
Module bundlers will automatically grab the browser-specific flavor of Twing when Twing module is imported. Either `const {TwingEnvironment} = require('twing');` or `import {TwingEnvironment} from 'twing';` will work in both node.js and the browser - once bundled in the latter case.
```shell
npm run build:test
npm run test:browser
```
### Script tag
### Writing and executing tests
Use [jsdelivr](https://www.jsdelivr.com/) CDN to include Twing in your HTML document:
Assuming one want to execute the test located in `test/tests/integration/comparison/to-array.ts`, one would run:
`<script src="https://cdn.jsdelivr.net/npm/twing/dist/lib.min.js"></script>`
```shell
ts-node test/tests/integration/comparison/to-array.ts
```
Once loaded by the browser, Twing is available under the global `Twing` variable.
It is even possible - and recommended - to track the coverage while writing tests:
## Twig specifications implementation
```shell
nyc ts-node test/tests/integration/comparison/to-array.ts
```
Twing aims at implementing Twig specifications perfectly, without compromise. This is not an easy task due to the nature of Twig specifications: they don't exist officially and can only be deduced from the public documentation, the source code documentation and the test suite of the PHP reference implementation. It sometimes happens that something that was not part of either the documentations or the test suite suddenly becomes part of the specifications like the [`filter` tag](https://github.com/twigphp/Twig/issues/3091) or the [macros rework](https://github.com/twigphp/Twig/issues/3090) issues, putting Twing and all other non-reference implementations in the uncomfortable position of having to deal with a potential breaking change. Since Twig's team doesn't plan on releasing some official specifications for the language, we can't expect the problem to be solved anytime soon.
Of course, it is also perfectly possible to pipe the result of the test to your favorite tap formatter:
Twing's strategy here is to stick strictly to Semantic Versioning rules and *never* introduce a breaking change into a minor version - its extensive test suite with 100% code coverage guarantees that. Twig teams's mistakes will be managed by either issuing a [known issue](#known-issues), if the mistake is trivial, or bumping to a new major version, if it is not.
### Compatibility chart
Here is the compatibility chart between minor versions of Twing and Twig specifications levels, along with a summary of notable features provided by each Twig specifications level. Note that Twig minor versions don't always provide new language-related features (because of Twig's team perpetuating the confusion between Twig and their reference implementation, TwigPHP).
| Twing version | Twig specifications level | Notable features |
|:-------------:|:-------------------------:|----------------------------------------------------------------------------------------------------------------------------------|
| 5.2 | 2.14 | `spaceship` operator, `sort` filter comparator`, hash “short” syntax |
| 3.0 | 2.11 | [Macros scoping](https://twig.symfony.com/doc/2.x/tags/macro.html#macros-scoping) |
| 2.3 | 2.10 | `spaceless`, `column`, `filter`, `map` and `reduce` filters, `apply` tag, `line whitespace trimming` whitespace control modifier |
| 2.2 | 2.6 | `deprecated` tag |
| 1.3 | 2.5 | `spaceless` and `block`-related deprecations |
| 1.0 | 2.4 | |
It is highly recommended to always use the latest version of Twing available as bug fixes will always target the latest version.
### Known issues
You can find the list of known issues of Twing regarding Twig specifications implementation [here](http://NightlyCommit.github.io/twing/known_issues). Note that known issues are guaranteed to be addressed in the next major version bump of Twing.
## More information
Read the [documentation](http://NightlyCommit.github.io/twing) for more information.
## Related projects
* [gulp-twing](https://www.npmjs.com/package/gulp-twing): Compile Twig templates with gulp. Build upon Twing.
* [twing-loader](https://www.npmjs.com/package/twing-loader): Webpack loader that compiles Twig templates using Twing.
```shell
test/tests/integration/comparison$ ts-node . | tap-nyan
9 -_-_-_-_-_,------,
0 -_-_-_-_-_| /\_/\
0 -_-_-_-_-^|__( ^ .^)
-_-_-_-_- "" ""
Pass!
```
## Contributing
@ -101,13 +71,13 @@ Read the [documentation](http://NightlyCommit.github.io/twing) for more informat
## License
Copyright © 2018 [Eric MORAND](https://github.com/ericmorand). Released under the [2-Clause BSD License](https://github.com/ericmorand/twing/blob/master/LICENSE).
Copyright © 2018-2023 [Eric MORAND](https://github.com/ericmorand). Released under the [2-Clause BSD License](https://github.com/ericmorand/twing/blob/master/LICENSE).
[npm-image]: https://badge.fury.io/js/twing.svg
[npm-url]: https://npmjs.org/package/twing
[travis-image]: https://travis-ci.com/NightlyCommit/twing.svg?branch=master
[travis-url]: https://travis-ci.com/NightlyCommit/twing
[coveralls-image]: https://coveralls.io/repos/github/NightlyCommit/twing/badge.svg
[coveralls-url]: https://coveralls.io/github/NightlyCommit/twing
[build-image]: https://gitlab.com/nightlycommit/twing/badges/main/pipeline.svg
[build-url]: https://gitlab.com/nightlycommit/twing/-/pipelines
[coveralls-image]: https://coveralls.io/repos/gitlab/nightlycommit/twing/badge.svg
[coveralls-url]: https://coveralls.io/gitlab/nightlycommit/twing
[donate-image]: https://img.shields.io/badge/Donate-PayPal-green.svg
[donate-url]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=7YZU3L2JL2KJA
[donate-url]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=7YZU3L2JL2KJA

View File

@ -1,2 +0,0 @@
source 'https://rubygems.org'
gem 'github-pages', group: :jekyll_plugins

View File

@ -1,248 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.11.1)
colorator (1.1.0)
commonmarker (0.17.13)
ruby-enum (~> 0.5)
concurrent-ruby (1.0.5)
dnsruby (1.61.2)
addressable (~> 2.5)
em-websocket (0.5.1)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
ethon (0.11.0)
ffi (>= 1.3.0)
eventmachine (1.2.7)
execjs (2.7.0)
faraday (0.15.3)
multipart-post (>= 1.2, < 3)
ffi (1.9.25)
forwardable-extended (2.6.0)
gemoji (3.0.0)
github-pages (192)
activesupport (= 4.2.10)
github-pages-health-check (= 1.8.1)
jekyll (= 3.7.4)
jekyll-avatar (= 0.6.0)
jekyll-coffeescript (= 1.1.1)
jekyll-commonmark-ghpages (= 0.1.5)
jekyll-default-layout (= 0.1.4)
jekyll-feed (= 0.10.0)
jekyll-gist (= 1.5.0)
jekyll-github-metadata (= 2.9.4)
jekyll-mentions (= 1.4.1)
jekyll-optional-front-matter (= 0.3.0)
jekyll-paginate (= 1.1.0)
jekyll-readme-index (= 0.2.0)
jekyll-redirect-from (= 0.14.0)
jekyll-relative-links (= 0.5.3)
jekyll-remote-theme (= 0.3.1)
jekyll-sass-converter (= 1.5.2)
jekyll-seo-tag (= 2.5.0)
jekyll-sitemap (= 1.2.0)
jekyll-swiss (= 0.4.0)
jekyll-theme-architect (= 0.1.1)
jekyll-theme-cayman (= 0.1.1)
jekyll-theme-dinky (= 0.1.1)
jekyll-theme-hacker (= 0.1.1)
jekyll-theme-leap-day (= 0.1.1)
jekyll-theme-merlot (= 0.1.1)
jekyll-theme-midnight (= 0.1.1)
jekyll-theme-minimal (= 0.1.1)
jekyll-theme-modernist (= 0.1.1)
jekyll-theme-primer (= 0.5.3)
jekyll-theme-slate (= 0.1.1)
jekyll-theme-tactile (= 0.1.1)
jekyll-theme-time-machine (= 0.1.1)
jekyll-titles-from-headings (= 0.5.1)
jemoji (= 0.10.1)
kramdown (= 1.17.0)
liquid (= 4.0.0)
listen (= 3.1.5)
mercenary (~> 0.3)
minima (= 2.5.0)
nokogiri (>= 1.8.2, < 2.0)
rouge (= 2.2.1)
terminal-table (~> 1.4)
github-pages-health-check (1.8.1)
addressable (~> 2.3)
dnsruby (~> 1.60)
octokit (~> 4.0)
public_suffix (~> 2.0)
typhoeus (~> 1.3)
html-pipeline (2.8.4)
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.6.0)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
jekyll (3.7.4)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
i18n (~> 0.7)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 2.0)
kramdown (~> 1.14)
liquid (~> 4.0)
mercenary (~> 0.3.3)
pathutil (~> 0.9)
rouge (>= 1.7, < 4)
safe_yaml (~> 1.0)
jekyll-avatar (0.6.0)
jekyll (~> 3.0)
jekyll-coffeescript (1.1.1)
coffee-script (~> 2.2)
coffee-script-source (~> 1.11.1)
jekyll-commonmark (1.2.0)
commonmarker (~> 0.14)
jekyll (>= 3.0, < 4.0)
jekyll-commonmark-ghpages (0.1.5)
commonmarker (~> 0.17.6)
jekyll-commonmark (~> 1)
rouge (~> 2)
jekyll-default-layout (0.1.4)
jekyll (~> 3.0)
jekyll-feed (0.10.0)
jekyll (~> 3.3)
jekyll-gist (1.5.0)
octokit (~> 4.2)
jekyll-github-metadata (2.9.4)
jekyll (~> 3.1)
octokit (~> 4.0, != 4.4.0)
jekyll-mentions (1.4.1)
html-pipeline (~> 2.3)
jekyll (~> 3.0)
jekyll-optional-front-matter (0.3.0)
jekyll (~> 3.0)
jekyll-paginate (1.1.0)
jekyll-readme-index (0.2.0)
jekyll (~> 3.0)
jekyll-redirect-from (0.14.0)
jekyll (~> 3.3)
jekyll-relative-links (0.5.3)
jekyll (~> 3.3)
jekyll-remote-theme (0.3.1)
jekyll (~> 3.5)
rubyzip (>= 1.2.1, < 3.0)
jekyll-sass-converter (1.5.2)
sass (~> 3.4)
jekyll-seo-tag (2.5.0)
jekyll (~> 3.3)
jekyll-sitemap (1.2.0)
jekyll (~> 3.3)
jekyll-swiss (0.4.0)
jekyll-theme-architect (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-cayman (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-dinky (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-hacker (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-leap-day (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-merlot (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-midnight (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-minimal (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-modernist (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-primer (0.5.3)
jekyll (~> 3.5)
jekyll-github-metadata (~> 2.9)
jekyll-seo-tag (~> 2.0)
jekyll-theme-slate (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-tactile (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-time-machine (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-titles-from-headings (0.5.1)
jekyll (~> 3.3)
jekyll-watch (2.0.0)
listen (~> 3.0)
jemoji (0.10.1)
gemoji (~> 3.0)
html-pipeline (~> 2.2)
jekyll (~> 3.0)
kramdown (1.17.0)
liquid (4.0.0)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
mercenary (0.3.6)
mini_portile2 (2.3.0)
minima (2.5.0)
jekyll (~> 3.5)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.11.3)
multipart-post (2.0.0)
nokogiri (1.8.5)
mini_portile2 (~> 2.3.0)
octokit (4.12.0)
sawyer (~> 0.8.0, >= 0.5.3)
pathutil (0.16.1)
forwardable-extended (~> 2.6)
public_suffix (2.0.5)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rouge (2.2.1)
ruby-enum (0.7.2)
i18n
ruby_dep (1.5.0)
rubyzip (1.2.2)
safe_yaml (1.0.4)
sass (3.6.0)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
typhoeus (1.3.0)
ethon (>= 0.9.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
unicode-display_width (1.4.0)
PLATFORMS
ruby
DEPENDENCIES
github-pages
BUNDLED WITH
1.16.6

View File

@ -1,4 +0,0 @@
sass:
sass_dir: ./assets
baseurl: /twing
title: "Twing"

View File

@ -1,28 +0,0 @@
docs:
introduction:
title: Introduction
url: intro.html
installation:
title: Installation
url: installation.html
templates:
title: Twing for Template Designers
url: templates.html
api:
title: Twing for Developers
url: api.html
extending:
title: Extending Twing
url: advanced.html
internals:
title: Twing Internals
url: internals.html
recipes:
title: Twing recipes
url: recipes.html
coding_standards:
title: Coding standards
url: coding_standards.html
known_issues:
title: Known issues
url: known_issues.html

View File

@ -1,281 +0,0 @@
sections:
tags:
title: tags
url: language-reference/tags/index.html
items:
apply:
title: apply
url: language-reference/tags/apply.html
autoescape:
title: autoescape
url: language-reference/tags/autoescape.html
block:
title: block
url: language-reference/tags/block.html
deprecated:
title: deprecated
url: language-reference/tags/deprecated.html
do:
title: do
url: language-reference/tags/do.html
embed:
title: embed
url: language-reference/tags/embed.html
extends:
title: extends
url: language-reference/tags/extends.html
filter:
title: filter
url: language-reference/tags/filter.html
flush:
title: flush
url: language-reference/tags/flush.html
for:
title: for
url: language-reference/tags/for.html
from:
title: from
url: language-reference/tags/from.html
if:
title: if
url: language-reference/tags/if.html
import:
title: import
url: language-reference/tags/import.html
include:
title: include
url: language-reference/tags/include.html
macro:
title: macro
url: language-reference/tags/macro.html
sandbox:
title: sandbox
url: language-reference/tags/sandbox.html
set:
title: set
url: language-reference/tags/set.html
spaceless:
title: spaceless
url: language-reference/tags/spaceless.html
use:
title: use
url: language-reference/tags/use.html
verbatim:
title: verbatim
url: language-reference/tags/verbatim.html
with:
title: with
url: language-reference/tags/with.html
filters:
title: filters
url: filters/index.html
items:
abs:
title: abs
url: language-reference/filters/abs.html
batch:
title: batch
url: language-reference/filters/batch.html
capitalize:
title: capitalize
url: language-reference/filters/capitalize.html
column:
title: column
url: language-reference/filters/column.html
convert_encoding:
title: convert_encoding
url: language-reference/filters/convert_encoding.html
date:
title: date
url: language-reference/filters/date.html
date_modify:
title: date_modify
url: language-reference/filters/date_modify.html
default:
title: default
url: language-reference/filters/default.html
escape:
title: escape
url: language-reference/filters/escape.html
filter:
title: filter
url: language-reference/filters/filter.html
first:
title: first
url: language-reference/filters/first.html
format:
title: format
url: language-reference/filters/format.html
join:
title: join
url: language-reference/filters/join.html
json_encode:
title: json_encode
url: language-reference/filters/json_encode.html
keys:
title: keys
url: language-reference/filters/key.html
last:
title: last
url: language-reference/filters/last.html
length:
title: length
url: language-reference/filters/length.html
lower:
title: lower
url: language-reference/filters/lower.html
map:
title: map
url: language-reference/filters/map.html
merge:
title: merge
url: language-reference/filters/merge.html
nl2br:
title: nl2br
url: language-reference/filters/nl2br.html
number_format:
title: number_format
url: language-reference/filters/number_format.html
raw:
title: raw
url: language-reference/filters/raw.html
reduce:
title: reduce
url: language-reference/filters/reduce.html
replace:
title: replace
url: language-reference/filters/replace.html
reverse:
title: reverse
url: language-reference/filters/reverse.html
round:
title: round
url: language-reference/filters/round.html
slice:
title: slice
url: language-reference/filters/slice.html
sort:
title: sort
url: language-reference/filters/sort.html
spaceless:
title: spaceless
url: language-reference/filters/spaceless.html
split:
title: split
url: language-reference/filters/split.html
striptags:
title: striptags
url: language-reference/filters/striptags.html
title:
title: title
url: language-reference/filters/title.html
trim:
title: trim
url: language-reference/filters/trim.html
upper:
title: upper
url: language-reference/filters/upper.html
url_encode:
title: url_encode
url: language-reference/filters/url_encode.html
functions:
title: functions
url: functions/index.html
items:
attribute:
title: attribute
url: language-reference/functions/attribute.html
block:
title: block
url: language-reference/functions/block.html
constant:
title: constant
url: language-reference/functions/constant.html
cycle:
title: cycle
url: language-reference/functions/cycle.html
date:
title: date
url: language-reference/functions/date.html
dump:
title: dump
url: language-reference/functions/dump.html
include:
title: include
url: language-reference/functions/include.html
max:
title: max
url: language-reference/functions/max.html
min:
title: min
url: language-reference/functions/min.html
parent:
title: parent
url: language-reference/functions/parent.html
random:
title: random
url: language-reference/functions/random.html
range:
title: range
url: language-reference/functions/range.html
source:
title: source
url: language-reference/functions/source.html
template_from_string:
title: template_from_string
url: language-reference/functions/template_from_string.html
tests:
title: tests
url: tests/index.html
items:
constant:
title: constant
url: language-reference/tests/constant.html
defined:
title: defined
url: language-reference/tests/defined.html
divisibleby:
title: divisibleby
url: language-reference/tests/divisibleby.html
empty:
title: empty
url: language-reference/tests/empty.html
even:
title: even
url: language-reference/tests/even.html
iterable:
title: iterable
url: language-reference/tests/iterable.html
none:
title: none
url: language-reference/tests/none.html
"null":
title: "null"
url: language-reference/tests/null.html
odd:
title: odd
url: language-reference/tests/odd.html
sameas:
title: sameas
url: language-reference/tests/sameas.html
operators:
title: operators
items:
in:
title: in
url: templates.html#containment-operator
is:
title: is
url: templates.html#test-operator
math:
title: Math (+, -, /, %, //, *, **)
url: templates.html#math
logic:
title: Logic (and, or, not, (), b-and, b-xor, b-or)
url: templates.html#logic
comparisons:
title: Comparisons (==, !=, <, >, >=, <=, ===, starts with, ends with, matches)
url: templates.html#comparisons
others:
title: Others (.., &#124;, ~, ., [], ?:)
url: templates.html#other-operators

View File

@ -1,14 +0,0 @@
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-1">
<ul class="social-media-list">
<li>
{% include icon-github.html project="twing" label="View the Project on GitHub" %}
</li>
<li><a href="{{ site.github.repository_url }}/blob/master/LICENSE">License</a></li>
</ul>
</div>
</div>
</div>
</footer>

View File

@ -1,11 +0,0 @@
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-115935756-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-115935756-1');
</script>

View File

@ -1,17 +0,0 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
<meta name="description" content="{{ page.excerpt | default: site.description | strip_html | normalize_whitespace | truncate: 160 | escape }}">
<link rel="stylesheet" href="{{ "/assets/main.css" | relative_url }}">
<link rel="canonical" href="{{ page.url | replace:'index.html','' | absolute_url }}">
<link rel="alternate" type="application/rss+xml" title="{{ site.title | escape }}" href="{{ "/feed.xml" | relative_url }}">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
{% if jekyll.environment == 'production' %}
{% include google-analytics.html %}
{% endif %}
</head>

View File

@ -1,35 +0,0 @@
<header class="site-header" role="banner">
<div class="wrapper">
<div class="site-title">
<a class="project-link" href="{{ site.baseurl }}/">{{ site.title | escape }}</a>
<ul class="social-media-list">
<li>
{% include icon-github.html project="twing" %}
</li>
</ul>
</div>
<nav class="site-nav">
<input type="checkbox" id="nav-trigger" class="nav-trigger"/>
<label for="nav-trigger">
<span class="menu-icon">
<svg viewBox="0 0 18 15" width="18px" height="15px">
<path fill="#424242"
d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242"
d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242"
d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</span>
</label>
<div class="trigger">
<a class="page-link" href="{{ site.baseurl }}/">Home</a>
<a class="page-link" href="{{ site.baseurl }}{% link templates.md %}">Twing for Template Designers</a>
<a class="page-link" href="{{ site.baseurl }}{% link api.md %}">Twing for Developers</a>
<a class="page-link" href="{{ site.baseurl }}{% link language-reference/index.md %}">Twig Language
Reference</a>
</div>
</nav>
</div>
</header>

View File

@ -1 +0,0 @@
<a href="https://github.com/NightlyCommit/{{ include.project }}"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">{{ include.label }}</span></a>

View File

@ -1 +0,0 @@
<svg viewBox="0 0 16 16" width="16px" height="16px"><path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z"/></svg>

Before

Width:  |  Height:  |  Size: 953 B

View File

@ -1,16 +0,0 @@
<div>
{% for section in site.data.navigation_reference.sections %}
<h3>{{ section[1].title }}</h3>
<ul>
{% for item in section[1].items %}
<li>
{% if item[1].url %}
<a href="{{ site.baseurl }}/{{ item[1].url }}" alt="{{ item[1].title }}">{{ item[1].title }}</a>
{% else %}
<span>{{ item[1].title }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endfor %}
</div>

View File

@ -1,11 +0,0 @@
<ul class="toc">
{% for item in include.items %}
<li>
{% if item[1].url %}
<a href="{{ site.baseurl }}/{{ item[1].url }}" alt="{{ item[1].title }}">{{ item[1].title }}</a>
{% else %}
<span>{{ item[1].title }}</span>
{% endif %}
</li>
{% endfor %}
</ul>

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="{{ page.lang | default: site.lang | default: " en" }}">
{% include head.html %}
<body>
{% include header.html %}
<main class="page-content" aria-label="Content">
<div class="wrapper">
{{ content }}
</div>
</main>
{% include footer.html %}
</body>
</html>

View File

@ -1,7 +0,0 @@
---
layout: default
---
<div class="home">
{{ content }}
</div>

View File

@ -1,8 +0,0 @@
---
layout: default
---
<article class="post">
<div class="post-content">
{{ content }}
</div>
</article>

View File

@ -1,8 +0,0 @@
@charset "utf-8";
// Import partials.
@import
"minima/base",
"minima/layout",
"minima/syntax-highlighting"
;

View File

@ -1,186 +0,0 @@
@import "../variables";
@import "../mixins";
/**
* Reset some basic elements
*/
body, h1, h2, h3, h4, h5, h6,
p, blockquote, pre, hr,
dl, dd, ol, ul, figure {
margin: 0;
padding: 0;
}
/**
* Basic styling
*/
body {
font-family: $base-font-family;
color: $text-color;
background-color: $background-color;
-webkit-text-size-adjust: 100%;
-webkit-font-feature-settings: "kern" 1;
-moz-font-feature-settings: "kern" 1;
-o-font-feature-settings: "kern" 1;
font-feature-settings: "kern" 1;
font-kerning: normal;
}
/**
* Images
*/
img {
max-width: 100%;
vertical-align: middle;
}
/**
* Figures
*/
figure > img {
display: block;
}
figcaption {
font-size: $small-font-size;
}
/**
* Lists
*/
ul, ol {
}
li {
> ul,
> ol {
margin-bottom: 0;
}
}
/**
* Headings
*/
h1, h2, h3, h4, h5, h6 {
font-weight: $base-font-weight;
}
h1 {
font-size: 1.5em;
@include media-query($on-tablet-landscape) {
font-size: 3.0em;
}
}
/**
* Links
*/
a {
color: $brand-color;
text-decoration: none;
&:visited {
color: darken($brand-color, 15%);
}
&:hover {
color: $text-color;
text-decoration: underline;
}
.social-media-list &:hover {
text-decoration: none;
.username {
text-decoration: underline;
}
}
}
/**
* Blockquotes
*/
blockquote {
color: $grey-color;
border-left: 4px solid $grey-color-light;
padding-left: $spacing-unit / 2 * 1px;
@include relative-font-size(1);
letter-spacing: -1px;
font-style: italic;
> :last-child {
margin-bottom: 0;
}
}
/**
* Code formatting
*/
pre,
code {
@include relative-font-size(0.9375);
border: 1px solid $grey-color-light;
border-radius: 3px;
background-color: #eef;
line-height: 1.25;
}
code {
padding: 1px 5px;
}
pre {
padding: 8px 12px;
overflow-x: auto;
> code {
border: 0;
padding-right: 0;
padding-left: 0;
}
}
/**
* Wrapper
*/
.wrapper {
margin: auto;
padding-right: $spacing-unit * 1px;
padding-left: $spacing-unit * 1px;
@include clear-fix;
@include media-query($on-desktop-large) {
max-width: 1440px;
}
}
/**
* Icons
*/
.icon > svg {
display: inline-block;
vertical-align: middle;
path {
fill: $grey-color;
}
}
.social-media-list {
.icon {
padding-right: 5px;
}
li + li {
padding-top: 5px;
}
}
.icon--github {
svg {
width: 2.0em;
height: 2.0em;
}
}

View File

@ -1,267 +0,0 @@
@import "../variables";
@import "../mixins";
/**
* Site header
*/
.site-header {
border-top: 5px solid $grey-color-dark;
border-bottom: 1px solid $grey-color-light;
min-height: $spacing-unit * 1.865 - 1px;
// Positioning context for the mobile navigation icon
position: relative;
}
.site-title {
font-weight: 300;
line-height: $base-line-height * 2.25;
letter-spacing: -1px;
margin-bottom: 0;
float: left;
a {
&, &:visited {
color: $grey-color-dark;
}
}
a, ul {
display: inline-block;
}
ul {
margin-left: 2.0em;
}
.project-link {
font-size: 1.5em;
vertical-align: middle;
}
@include clear-fix;
@include media-query($on-tablet-landscape) {
.project-link {
font-size: 2.0em;
}
}
}
.site-nav {
float: right;
line-height: $base-line-height * 2.25;
position: absolute;
top: 9px;
right: $spacing-unit / 2 * 1px;
background-color: $background-color;
border: 1px solid $grey-color-light;
border-radius: 5px;
text-align: right;
.nav-trigger {
display: none;
}
label[for="nav-trigger"] {
display: block;
float: right;
width: 36px;
height: 36px;
z-index: 2;
cursor: pointer;
}
.menu-icon {
display: block;
float: right;
width: 36px;
height: 26px;
line-height: 0;
padding-top: 10px;
text-align: center;
> svg path {
fill: $grey-color-dark;
}
}
input ~ .trigger {
clear: both;
display: none;
}
input:checked ~ .trigger {
display: block;
padding-bottom: 5px;
}
.page-link {
display: block;
padding: 5px 10px;
}
.page-link {
color: $text-color;
line-height: $base-line-height;
}
@include media-query($on-tablet-landscape) {
position: initial;
border: none;
background: none;
float: none;
text-align: left;
input ~ .trigger {
display: block;
}
label[for="nav-trigger"] {
display: none;
}
.page-link {
display: inline-block;
padding: 0;
// Gaps between nav items, but not on the last one
&:not(:last-child) {
margin-right: 20px;
}
}
}
}
/**
* Site footer
*/
.site-footer {
border-top: 1px solid $grey-color-light;
padding: $spacing-unit * 1px 0;
}
.footer-heading {
@include relative-font-size(1.125);
margin-bottom: $spacing-unit / 2 * 1px;
}
.contact-list,
.social-media-list {
list-style: none;
margin-left: 0;
li {
display: inline-block;
padding-right: 1.0em;
}
}
.footer-col-wrapper {
@include relative-font-size(0.9375);
color: $grey-color;
margin-left: -$spacing-unit / 2 * 1px;
@include clear-fix;
}
/**
* Page content
*/
.page-content {
padding: $spacing-unit * 1px 0;
h1, h2, h3, h4, h5, h6, p, ul, .highlight {
margin: 1.0em 0;
}
h1, h2, h3, h4, h5, h6 {
a {
&, &:visited {
color: $text-color;
}
}
}
h1 {
padding-bottom: 0.50em;
border-bottom: solid 1px $grey-color-light;
@include media-query($on-tablet-landscape) {
margin: 0.5em 0;
padding-bottom: 0.25em;
}
}
ul {
margin-left: 1.0em;
}
}
.page-heading {
@include relative-font-size(1.25);
}
.post-list {
margin-left: 0;
list-style: none;
> li {
margin-bottom: $spacing-unit * 1px;
}
}
.post-meta {
font-size: $small-font-size;
color: $grey-color;
}
.post-link {
display: block;
@include relative-font-size(1.5);
}
/**
* Posts
*/
.post-header {
margin-bottom: $spacing-unit * 1px;
}
.post-title {
@include relative-font-size(2.625);
letter-spacing: -1px;
line-height: 1;
@include media-query($on-desktop-small) {
@include relative-font-size(2.25);
}
}
.post-content {
margin-bottom: $spacing-unit * 1px;
h2 {
@include relative-font-size(1.5);
@include media-query($on-tablet-landscape) {
@include relative-font-size(2);
}
}
h3 {
@include relative-font-size(1.25);
@include media-query($on-tablet-landscape) {
@include relative-font-size(1.75);
}
}
h4 {
@include relative-font-size(1);
@include media-query($on-tablet-landscape) {
@include relative-font-size(1.50);
}
}
}

View File

@ -1,70 +0,0 @@
/**
* Syntax highlighting styles
*/
.highlight {
background: #fff;
.highlighter-rouge & {
background: #eef;
}
.c { color: #998; font-style: italic } // Comment
//.err { color: #a61717; background-color: #e3d2d2 } // Error
.k { font-weight: bold } // Keyword
.o { font-weight: bold } // Operator
.cm { color: #998; font-style: italic } // Comment.Multiline
.cp { color: #999; font-weight: bold } // Comment.Preproc
.c1 { color: #998; font-style: italic } // Comment.Single
.cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special
.gd { color: #000; background-color: #fdd } // Generic.Deleted
.gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific
.ge { font-style: italic } // Generic.Emph
.gr { color: #a00 } // Generic.Error
.gh { color: #999 } // Generic.Heading
.gi { color: #000; background-color: #dfd } // Generic.Inserted
.gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific
.go { color: #888 } // Generic.Output
.gp { color: #555 } // Generic.Prompt
.gs { font-weight: bold } // Generic.Strong
.gu { color: #aaa } // Generic.Subheading
.gt { color: #a00 } // Generic.Traceback
.kc { font-weight: bold } // Keyword.Constant
.kd { font-weight: bold } // Keyword.Declaration
.kp { font-weight: bold } // Keyword.Pseudo
.kr { font-weight: bold } // Keyword.Reserved
.kt { color: #458; font-weight: bold } // Keyword.Type
.m { color: #099 } // Literal.Number
.s { color: #d14 } // Literal.String
.na { color: #008080 } // Name.Attribute
.nb { color: #0086B3 } // Name.Builtin
.nc { color: #458; font-weight: bold } // Name.Class
.no { color: #008080 } // Name.Constant
.ni { color: #800080 } // Name.Entity
.ne { color: #900; font-weight: bold } // Name.Exception
.nf { color: #900; font-weight: bold } // Name.Function
.nn { color: #555 } // Name.Namespace
.nt { color: #000080 } // Name.Tag
.nv { color: #008080 } // Name.Variable
.ow { font-weight: bold } // Operator.Word
.w { color: #bbb } // Text.Whitespace
.mf { color: #099 } // Literal.Number.Float
.mh { color: #099 } // Literal.Number.Hex
.mi { color: #099 } // Literal.Number.Integer
.mo { color: #099 } // Literal.Number.Oct
.sb { color: #d14 } // Literal.String.Backtick
.sc { color: #d14 } // Literal.String.Char
.sd { color: #d14 } // Literal.String.Doc
.s2 { color: #d14 } // Literal.String.Double
.se { color: #d14 } // Literal.String.Escape
.sh { color: #d14 } // Literal.String.Heredoc
.si { color: #d14 } // Literal.String.Interpol
.sx { color: #d14 } // Literal.String.Other
.sr { color: #009926 } // Literal.String.Regex
.s1 { color: #d14 } // Literal.String.Single
.ss { color: #990073 } // Literal.String.Symbol
.bp { color: #999 } // Name.Builtin.Pseudo
.vc { color: #008080 } // Name.Variable.Class
.vg { color: #008080 } // Name.Variable.Global
.vi { color: #008080 } // Name.Variable.Instance
.il { color: #099 } // Literal.Number.Integer.Long
}

View File

@ -1,19 +0,0 @@
@import "variables";
@mixin media-query($device) {
@media screen and (min-width: ($device / $base-font-size) * 1em) {
@content;
}
}
@mixin relative-font-size($ratio) {
font-size: $ratio * 1em;
}
@mixin clear-fix() {
&:after {
clear: both;
display: block;
content: "";
}
}

View File

@ -1,23 +0,0 @@
$base-font-family: "Roboto", Helvetica, Arial, sans-serif;
$base-font-size: 16;
$base-font-weight: 400;
$small-font-size: $base-font-size * 0.875;
$base-line-height: 1.5;
$spacing-unit: 30;
$text-color: #333;
$background-color: #fdfdfd;
$brand-color: #2a7ae2;
$grey-color: #828282;
$grey-color-light: lighten($grey-color, 40%);
$grey-color-dark: darken($grey-color, 25%);
$on-mobile-large: 375;
$on-tablet-portrait: 768;
$on-tablet-landscape: 1024;
$on-desktop-small: 1280;
$on-desktop-medium: 1440;
$on-desktop-large: 1920;

View File

@ -1,745 +0,0 @@
Extending Twing
===============
{% raw %}
Twing can be extended in many ways; you can add extra tags, filters, tests, operators, global variables, and functions. You can even extend the parser itself with node visitors.
> The first section of this chapter describes how to extend Twing easily. If you want to reuse your changes in different projects or if you want to share them with others, you should then create an extension as described in the following section.
Before extending Twing, you must understand the differences between all the different possible extension points and when to use them.
First, remember that Twing has two main language constructs:
* `{{ }}`: used to print the result of an expression evaluation;
* `{% %}`: used to execute statements.
To understand why Twing exposes so many extension points, let's see how to implement a *Lorem ipsum* generator (it needs to know the number of words to generate).
You can use a `lipsum` *tag*:
```twig
{% lipsum 40 %}
```
That works, but using a tag for `lipsum` is not a good idea for at least three main reasons:
* `lipsum` is not a language construct;
* The tag outputs something;
* The tag is not flexible as you cannot use it in an expression:
```twig
{{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
```
In fact, you rarely need to create tags; and that's good news because tags are the most complex extension point of Twing.
Now, let's use a `lipsum` *filter*:
```twig
{{ 40|lipsum }}
```
Again, it works, but it looks weird. A filter transforms the passed value to something else but here we use the value to indicate the number of words to generate (so, `40` is an argument of the filter, not the value we want to transform).
Next, let's use a `lipsum` *function*:
```twig
{{ lipsum(40) }}
```
Here we go. For this specific example, the creation of a function is the extension point to use. And you can use it anywhere an expression is accepted:
```twig
{{ 'some text' ~ lipsum(40) ~ 'some more text' }}
{% set lipsum = lipsum(40) %}
```
Last but not the least, you can also use a *global* object with a method able to generate lorem ipsum text:
```twig
{{ text.lipsum(40) }}
```
As a rule of thumb, use functions for frequently used features and global objects for everything else.
Keep in mind the following when you want to extend Twing:
```
========== ========================== ========== =========================
What? Implementation difficulty? How often? When?
========== ========================== ========== =========================
*macro* trivial frequent Content generation
*global* trivial frequent Helper object
*function* trivial frequent Content generation
*filter* trivial frequent Value transformation
*tag* complex rare DSL language construct
*test* trivial rare Boolean decision
*operator* trivial rare Values transformation
========== ========================== ========== =========================
```
Globals
-------
A global variable is like any other template variable, except that it's available in all templates and macros:
```javascript
let twing = new TwingEnvironment(loader);
twing.addGlobal('text', new Text());
```
You can then use the `text` variable anywhere in a template:
```twig
{{ text.lipsum(40) }}
```
Filters
-------
Creating a filter is as simple as associating a name with a JavaScript callable:
```javascript
let rot13 = require('rot13');
// an anonymous function
let filter = new TwingFilter('rot13', function (string) {
return Promise.resolve(rot13(string));
});
// or a simple JavaScript function
filter = new TwingFilter('rot13', rot13);
// or a class static method
class Rot13Handler {
static handle(string) {
return Promise.resolve(rot13(string));
}
}
filter = new TwingFilter('rot13', Rot13Handler.handle);
// or a class instance method
class Rot13Handler {
handle(string) {
return Promise.resolve(rot13(string));
}
}
let handler = new Rot13Handler();
filter = new TwingFilter('rot13', handler.handle);
```
The first argument passed to the `TwingFilter` constructor is the name of the filter you will use in templates and the second one is the JavaScript callable to associate with it.
Then, add the filter to your Twing environment:
```javascript
let twing = new TwingEnvironment(loader);
twing.addFilter(filter);
```
And here is how to use it in a template:
```twig
{{ 'Twing'|rot13 }}
{# will output Gjvat #}
```
When called by Twing, the JavaScript callable receives the left side of the filter (before the pipe `|`) as the first argument and the extra arguments passed to the filter (within parentheses `()`) as extra arguments.
For instance, the following code:
```twig
{{ 'Twing'|lower }}
{{ now|date('d/m/Y') }}
```
is compiled to something like the following:
```javascript
Runtime.echo('Twing'.toLowerCase());
Runtime.echo(twing_date_format_filter(new Date(), 'd/m/Y'));
```
The `TwingFilter` class takes an array of options as its last argument:
```javascript
let filter = new TwingFilter('rot13', rot13, options);
```
### Environment-aware Filters
If you want to access the current environment instance in your filter, set the `needs_environment` option to `true`. Twing will pass the current environment as the first argument to the filter call:
```javascript
let filter = new TwingFilter('rot13', function (env, string) {
// get the current charset for instance
let charset = env.getCharset();
return Promise.resolve(rot13(string));
}, {needs_environment: true});
```
### Context-aware Filters
If you want to access the current context in your filter, set the `needs_context` option to `true`. Twing will pass the current context as the first argument to the filter call (or the second one if `needs_environment` is also set to `true`):
```javascript
let filter = new TwingFilter('rot13', function (context, string) {
// ...
}, {needs_context: true});
let filter = new TwingFilter('rot13', function (env, context, string) {
// ...
}, {needs_context: true, needs_environment: true});
```
### Automatic Escaping
If automatic escaping is enabled, the output of the filter may be escaped before printing. If your filter acts as an escaper (or explicitly outputs HTML or JavaScript code), you will want the raw output to be printed. In such a case, set the `is_safe` option:
```javascript
let filter = new TwingFilter('nl2br', nl2br, {is_safe: ['html']});
```
Some filters may need to work on input that is already escaped or safe, for example when adding (safe) HTML tags to originally unsafe output. In such a case, set the ``pre_escape`` option to escape the input data before it is run through your filter:
```javascript
let filter = new TwingFilter('somefilter', somefilter, {pre_escape: 'html', is_safe: ['html']});
```
### Variadic Filters
When a filter should accept an arbitrary number of arguments, set the `is_variadic` option to `true`. Twing will pass the extra arguments as the last argument to the filter call as an array:
```javascript
let filter = new TwingFilter('thumbnail', function (file, options = []) {
// ...
}, {is_variadic: true});
```
Be warned that named arguments passed to a variadic filter cannot be checked for validity as they will automatically end up in the option array.
### Dynamic Filters
A filter name containing the special `*` character is a dynamic filter as the `*` can be any string:
```javascript
let filter = new TwingFilter('*_path', function (name, arguments) {
// ...
});
```
The following filters will be matched by the above defined dynamic filter:
* `product_path`
* `category_path`
A dynamic filter can define more than one dynamic parts:
```javascript
let filter = new TwingFilter('*_path_*', function (name, suffix, arguments) {
// ...
});
```
The filter will receive all dynamic part values before the normal filter arguments, but after the environment and the context. For instance, a call to `'foo'|a_path_b()` will result in the following arguments to be passed to the filter: `('a', 'b', 'foo')`.
### Deprecated Filters
You can mark a filter as being deprecated by setting the `deprecated` option to `true`. You can also give an alternative filter that replaces the deprecated one when that makes sense:
```javascript
let filter = new TwingFilter('obsolete', function () {
// ...
}, {deprecated: true, alternative: 'new_one'});
```
When a filter is deprecated, Twing emits a deprecation warning when compiling a template using it. See [deprecation-warnings][deprecation-warnings-url] for more information.
### Functions
Functions are defined in the exact same way as filters, but you need to create an instance of `TwingFunction`:
```javascript
let twing = new TwingEnvironment(loader);
let function = new TwingFunction('function_name', function () {
// ...
});
twing.addFunction(function);
```
Functions support the same features as filters, except for the `pre_escape` and `preserves_safety` options.
### Tests
Tests are defined in the exact same way as filters and functions, but you need to create an instance of `TwingTest`:
```javascript
let twing = new TwingEnvironment(loader);
let test = new TwingTest('test_name', function () {
// ...
});
twing.addTest(test);
```
Tests allow you to create custom application specific logic for evaluating boolean conditions. As a simple example, let's create a Twing test that checks if objects are 'red':
```javascript
let twing = new TwingEnvironment(loader);
let test = new TwingTest('red', function (value) {
if (value.color && value.color === 'red') {
return Promise.resolve(true);
}
if (value.paint && value.paint === 'red') {
return Promise.resolve(true);
}
return Promise.resolve(false);
});
twing.addTest(test);
```
Test functions should always return true/false.
When creating tests you can use the `node_factory` option to provide custom test compilation. This is useful if your test can be compiled into JavaScript primitives. This is used by many of the tests built into Twing:
```javascript
class TwingNodeExpressionTestOdd extends TwingNodeExpressionTest {
compile(compiler) {
compiler
.raw('(')
.subcompile(this.getNode('node'))
.raw(' % 2 === 1')
.raw(')')
;
}
}
let twing = new TwingEnvironment(loader);
let test = new TwingTest('odd', null, {
node_factory: function (node, name, nodeArguments, lineno) {
return new TwingNodeExpressionTestOdd(node, name, nodeArguments, lineno);
}
});
twing.addTest(test);
```
The above example shows how you can create tests that use a node class. The node class has access to one sub-node called 'node'. This sub-node contains the value that is being tested. When the `odd` filter is used in code such as:
```twig
{% if my_value is odd %}
```
The `node` sub-node will contain an expression of `my_value`. Node-based tests also have access to the `arguments` node. This node will contain the various other arguments that have been provided to your test.
If you want to pass a variable number of positional or named arguments to the test, set the `is_variadic` option to `true`. Tests support dynamic names (see dynamic filters and functions for the syntax).
### Tags
One of the most exciting features of a template engine like Twig is the possibility to define new language constructs. This is also the most complex feature as you need to understand how Twing's internals work.
Most of the time though, a tag is not needed:
* If your tag generates some output, use a **function** instead.
* If your tag modifies some content and returns it, use a **filter** instead.
For instance, if you want to create a tag that converts a Markdown formatted text to HTML, create a `markdown` filter instead:
```twig
{{ '**markdown** text'|markdown }}
```
If you want use this filter on large amounts of text, wrap it with the [apply][tag-apply] tag:
```twig
{% apply markdown %}
Title
=====
Much better than creating a tag as you can **compose** filters.
{% endapply %}
```
* If your tag does not output anything, but only exists because of a side effect, create a **function** that returns nothing and call it via the [do][tag-do] tag.
For instance, if you want to create a tag that logs text, create a `log` function instead and call it via the [do][tag-do] tag:
```twig
{% do log('Log some things') %}
```
If you still want to create a tag for a new language construct, great!
Let's create a simple `set` tag that allows the definition of simple variables from within a template. The tag can be used like follows:
```twig
{% set name = "value" %}
{{ name }}
{# should output value #}
```
> The `set` tag is part of the Core extension and as such is always available. The built-in version is slightly more powerful and supports multiple assignments by default (cf. the template designers chapter for more information).
Three steps are needed to define a new tag:
* Defining a Token Parser class (responsible for parsing the template code);
* Defining a Node class (responsible for converting the parsed code to JavaScript);
* Registering the tag.
### Registering a new tag
Adding a tag is as simple as calling the `addTokenParser` method on the `TwingEnvironment` instance:
```javascript
let twing = new TwingEnvironment(loader);
twing.addTokenParser(new ProjectSetTokenParser());
```
### Defining a Token Parser
Now, let's see the actual code of this class:
```javascript
class ProjectSetTokenParser extends TwingTokenParser {
parse(token) {
let parser = this.parser;
let stream = parser.getStream();
let name = stream.expect(TwingToken.NAME_TYPE).getValue();
stream.expect(TwingToken.OPERATOR_TYPE, '=');
let value = parser.getExpressionParser().parseExpression();
stream.expect(TwingToken.BLOCK_END_TYPE);
return new ProjectSetNode(name, value, token.getLine(), this.getTag());
}
getTag() {
return 'set';
}
}
```
The `getTag()` method must return the tag we want to parse, here `set`.
The `parse()` method is invoked whenever the parser encounters a `set` tag. It should return a `TwingNode` instance that represents the node (the `ProjectSetNode` calls creating is explained in the next section).
The parsing process is simplified thanks to a bunch of methods you can call from the token stream (`this.parser.getStream()`):
* `getCurrent()`: Gets the current token in the stream.
* `next()`: Moves to the next token in the stream, *but returns the old one*.
* `test(type)`, `test(value)` or `test(type, value)`: Determines whether the current token is of a particular type or value (or both). The value may be an array of several possible values.
* `expect(type[, value[, message]])`: If the current token isn't of the given type/value a syntax error is thrown. Otherwise, if the type and value are correct, the token is returned and the stream moves to the next token.
* `look()`: Looks at the next token without consuming it.
Parsing expressions is done by calling the `parseExpression()` like we did for the `set` tag.
> Reading the existing `TokenParser` classes is the best way to learn all the nitty-gritty details of the parsing process.
### Defining a Node
The `ProjectSetNode` class itself is rather simple:
```javascript
class ProjectSetNode extends TwingNode {
constructor(name, value, line, tag = null) {
super(new Map([['value', value]]), new Map([['name': name]]), line, tag);
}
compile(compiler) {
compiler
.addDebugInfo(this)
.write('context.set(\'' + this.getAttribute('name') + '\', ')
.subcompile(this.getNode('value'))
.raw(");\n")
;
}
}
```
The compiler implements a fluid interface and provides methods that helps the developer generate beautiful and readable JavaScript code:
* `subcompile()`: Compiles a node.
* `raw()`: Writes the given string as is.
* `write()`: Writes the given string by adding indentation at the beginning of each line.
* `string()`: Writes a quoted string.
* `repr()`: Writes a JavaScript representation of a given value (see `TwingNodeFor` for a usage example).
* `addDebugInfo()`: Adds the line of the original template file related to the current node as a comment.
* `indent()`: Indents the generated code (see `TwingNodeBlock` for a usage example).
* `outdent()`: Outdents the generated code (see `TwingNodeBlock` for a usage example).
## Creating an Extension
The main motivation for writing an extension is to move often used code into a reusable class like adding support for internationalization. An extension can define tags, filters, tests, operators, global variables, functions, and node visitors.
Most of the time, it is useful to create a single extension for your project, to host all the specific tags and filters you want to add to Twing.
An extension is a class that implements the following TypeScript interface:
```typescript
interface TwingExtensionInterface {
/**
* Returns the token parser instances to add to the existing list.
*
* @return Array<TwingTokenParserInterface>
*/
getTokenParsers(): Array<TwingTokenParserInterface>;
/**
* Returns the node visitor instances to add to the existing list.
*
* @return Array<TwingNodeVisitorInterface>
*/
getNodeVisitors(): Array<TwingNodeVisitorInterface>;
/**
* Returns a list of filters to add to the existing list.
*
* @return Array<TwingFilter>
*/
getFilters(): Array<TwingFilter>;
/**
* Returns a list of tests to add to the existing list.
*
* @returns Array<TwingTest>
*/
getTests(): Array<TwingTest>;
/**
* Returns a list of functions to add to the existing list.
*
* @return Array<TwingFunction>
*/
getFunctions(): Array<TwingFunction>;
/**
* Returns a list of operators to add to the existing list.
*
* @return array<Map<string, TwingOperatorDefinitionInterface>> First array of unary operators, second array of binary operators
*/
getOperators(): { unary: Map<string, TwingOperatorDefinitionInterface>, binary: Map<string, TwingOperatorDefinitionInterface> };
/**
* Gets the default strategy to use when not defined by the user.
*
* @param {string} name The template name
*
* @returns string|Function The default strategy to use for the template
*/
getDefaultStrategy(name: string): string | Function | false;
}
```
To keep your extension class clean and lean, inherit from the built-in `TwingExtension` class instead of implementing the interface as it provides empty implementations for all methods:
```
class ProjectTwingExtension extends TwingExtension {
}
```
Of course, this extension does nothing for now. We will customize it in the next sections.
All extensions must be registered explicitly to be available in your templates.
You can register an extension by using the `addExtension()` method on your main `TwingEnvironment` object:
```
let twing = new TwingEnvironment(loader);
twing.addExtension(new ProjectTwingExtension());
```
The extension will be registered under the name of its constructor - `ProjectTwingExtension` in this case. You can also pass an explicit extension name under which the extension will be registered:
```
twing.addExtension(new ProjectTwingExtension(), 'foo');
```
> The Twing core extensions are great examples of how extensions work.
### Globals
Global variables can be registered in an extension via the `getGlobals()` method:
```
class ProjectTwingExtension extends TwingExtension {
getGlobals() {
return new Map([
['text': new Text()],
]);
}
// ...
}
```
### Functions
Functions can be registered in an extension via the `getFunctions()` method:
```javascript
class ProjectTwingExtension extends TwingExtension {
getFunctions() {
return [
new TwingFunction('lipsum', generate_lipsum),
];
}
// ...
}
```
### Filters
To add a filter to an extension, you need to override the `getFilters()` method. This method must return an array of filters to add to the Twing environment:
```javascript
class ProjectTwingExtension extends TwingExtension {
getFilters() {
return [
new TwingFilter('rot13', rot13),
];
}
// ...
}
```
### Tags
Adding a tag in an extension can be done by overriding the `getTokenParsers()` method. This method must return an array of tags to add to the Twing environment:
```javascript
class ProjectTwingExtension extends TwingExtension {
getTokenParsers() {
return [new ProjectSetTokenParser()];
}
// ...
}
```
In the above code, we have added a single new tag, defined by the `ProjectSetTokenParser` class. The `ProjectSetTokenParser` class is responsible for parsing the tag and compiling it to JavaScript.
### Operators
The `getOperators()` methods lets you add new operators. Here is how to add `!`, `||`, and `&&` operators:
```javascript
class ProjectTwingExtension extends TwingExtension {
getOperators() {
return [
new Map([
['!', {
precedence: 50,
factory: function (expr, lineno) {
return new TwingNodeExpressionUnaryNot(expr, lineno);
}
}]
]),
new Map([
['||', {
precedence: 10,
factory: function (expr, lineno) {
return new TwingNodeExpressionBinaryOr(expr, lineno);
},
associativity: TwingExpressionParser.OPERATOR_LEFT
}],
['&&', {
precedence: 15,
factory: function (expr, lineno) {
return new TwingNodeExpressionBinaryAnd(expr, lineno);
},
associativity: TwingExpressionParser.OPERATOR_LEFT
}]
])
];
}
// ...
}
```
### Tests
The `getTests()` method lets you add new test functions:
```javascript
class ProjectTwingExtension extends TwingExtension {
getTests() {
return [
new TwingTest('even', twing_test_even),
];
}
// ...
}
```
### Overloading
To overload an already defined filter, test, operator, global variable, or function, re-define it in an extension and register it **as late as possible** (order matters):
```javascript
class MyCoreExtension extends TwingExtension {
getFilters() {
return [
new TwingFilter('date', this.dateFilter),
];
}
dateFilter(timestamp, format = 'F j, Y H:i') {
// do something different from the built-in date filter
}
}
let twing = new TwingEnvironment(loader);
twing.addExtension(new MyCoreExtension());
```
Here, we have overloaded the built-in `date` filter with a custom one.
If you do the same on the `TwingEnvironment` itself, beware that it takes precedence over any other registered extensions:
```javascript
let twing = new TwingEnvironment(loader);
twing.addFilter(new TwingFilter('date', function (timestamp, $format = 'F j, Y H:i') {
// do something different from the built-in date filter
}));
// the date filter will come from the above registration, not
// from the registered extension below
twing.addExtension(new MyCoreExtension());
```
> Note that overloading the built-in Twing elements is not recommended as it might be confusing.
{% endraw %}
[back][back-url]
[back-url]: {{ site.baseurl }}{% link index.md %}
[deprecation-warnings-url]: {{ site.baseurl }}{% link recipes.md %}#deprecation-warnings
[tag-apply]: {{ site.baseurl }}{% link language-reference/tags/apply.md %}
[tag-do]: {{ site.baseurl }}{% link language-reference/tags/do.md %}

View File

@ -1,324 +0,0 @@
Twing for Developers
====================
This chapter describes the API to Twing and not the template language. It will
be most useful as reference to those implementing the template interface to
the application and not those who are creating Twig templates.
## Basics
Twing uses a central object called the **environment** (exported as class
``TwingEnvironment``). Instances of this class are used to store the
configuration and extensions, and are used to load templates from the file
system or other locations.
Most applications will create one ``TwingEnvironment`` object on application
initialization and use that to load templates. In some cases it's however
useful to have multiple environments side by side, if different configurations
are in use.
The simplest way to configure Twing to load templates for your application
looks roughly like this:
```javascript
let {TwingEnvironment, TwingLoaderFilesystem} = require('twing');
let loader = new TwingLoaderFilesystem('/path/to/templates');
let twing = new TwingEnvironment(loader, {
'cache': '/path/to/compilation_cache',
});
```
This will create a template environment with the default settings and a loader
that looks up the templates in the ``/path/to/templates/`` folder. Different
loaders are available and you can also write your own if you want to load
templates from a database or other resources.
> Notice that the second argument of the environment is a hash of options.
The ``cache`` option is a compilation cache directory, where Twing caches
the compiled templates to avoid the parsing phase for sub-sequent
requests. It is very different from the cache you might want to add for
the evaluated templates. For such a need, you can use any available
JavaScript cache library.
## Rendering Templates
To load a template from a Twing environment, call the ``load()`` function which returns a ``TwingTemplate`` instance:
```javascript
twing.load('index.html').then((template) => {
// ...
});
```
To render the template with some variables, call the ``render()`` function:
```javascript
template.render({'the': 'variables', 'go': 'here'}).then((output) => {
// ...
});
```
> The ``display()`` method is a shortcut to output the template directly. See [Output buffering](#output-buffering) section for more details.
You can also load and render the template in one fell swoop:
```javascript
twing.render('index.html', {'the': 'variables', 'go': 'here'}).then((output) => {
// ...
});
```
If a template defines blocks, they can be rendered individually via the
``renderBlock()`` call:
```javascript
template.renderBlock('block_name', {'the': 'variables', 'go': 'here'}).then((output) => {
// ...
});
```
## Events
### twing.on('template', (name: string, from: TwingSource) => {})
When a template is encountered, Twing environment emits a `template` event with the name of the encountered template and the source of the template that initiated the loading.
## Environment Options
When creating a new ``TwingEnvironment`` instance, you can pass a hash of
options as the constructor second argument:
```javascript
let twing = new TwingEnvironment(loader, {debug: true});
```
The following options are available:
* `debug` *boolean*
When set to `true`, the generated templates have a `__toString()` method that you can use to display the generated nodes (defaults to `false`).
* `charset` *string* (defaults to `utf-8`)
The charset used by the templates.
* `cache` *string* or `false`
An absolute path where to store the compiled templates, or `false` to disable caching (which is the default).
* `auto_reload` *boolean*
Setting the `auto_reload` option to `true` enables templates to be recompiled whenever their content changes instead of fetching them from the cache. Note that this won't invalidate the environment inner cache but only the cache passed using the `cache` option. If you don't provide a value for the `auto_reload` option, it will be determined automatically based on the `debug` value.
* `strict_variables` *boolean*
If set to `false`, Twing will silently ignore invalid variables (variables and or attributes/methods that do not exist) and replace them with a `null` value. When set to `true`, Twing throws an exception instead (default to `false`).
* `autoescape` *string*
Sets the default auto-escaping strategy (`name`, `html`, `js`, `css`, `url`, `html_attr`, or a JavaScript callback that takes the template "filename" and returns the escaping strategy to use); set it to `false` to disable auto-escaping. The `name` escaping strategy determines the escaping strategy to use for a template based on the template filename extension (this strategy does not incur any overhead at runtime as auto-escaping is done at compilation time.)
* `optimizations` *integer*
A flag that indicates which optimizations to apply (default to `-1` -- all optimizations are enabled; set it to `0` to disable).
* `source_map` *boolean* (defaults to `false`)
If set to `true`, Twing will add source map support to the compiled template. The source map can then be retrieved after the rendering by calling `TwingEnvironment.getSourceMap()` method.
## Loaders
Loaders are responsible for loading templates from a resource such as the file
system.
### Compilation Cache
All template loaders can cache the compiled templates on the filesystem for
future reuse. It speeds up Twing a lot as templates are only compiled once.
See the ``cache`` and ``auto_reload`` options of ``TwingEnvironment`` above
for more information.
### Built-in Loaders
Here is a list of the built-in loaders Twig provides:
#### ``TwingLoaderFilesystem``
``TwingLoaderFilesystem`` loads templates from the file system. This loader
can find templates in folders on the file system and is the preferred way to
load them:
```javascript
let loader = new TwingLoaderFilesystem(templateDir);
```
It can also look for templates in an array of directories:
```javascript
let loader = new TwingLoaderFilesystem([templateDir1, templateDir2]);
```
With such a configuration, Twing will first look for templates in
``templateDir1`` and if they do not exist, it will fallback to look for them
in the ``templateDir2``.
You can add or prepend paths via the ``addPath()`` and ``prependPath()``
methods:
```javascript
loader.addPath(templateDir3);
loader.prependPath(templateDir4);
```
The filesystem loader also supports namespaced templates. This allows to group
your templates under different namespaces which have their own template paths.
When using the ``setPaths()``, ``addPath()``, and ``prependPath()`` methods,
specify the namespace as the second argument (when not specified, these
methods act on the "main" namespace):
```javascript
loader.addPath(templateDir, 'admin');
```
Namespaced templates can be accessed via the special
``@namespace_name/template_path`` notation:
```javascript
twing.render('@admin/index.html', {});
```
``TwingLoaderFilesystem`` support absolute and relative paths. Using relative
paths is preferred as it makes the cache keys independent of the project root
directory (for instance, it allows warming the cache from a build server where
the directory might be different from the one used on production servers):
```javascript
let loader = new TwingLoaderFilesystem('templates', process.cwd() + '/..');
```
> When not passing the root path as a second argument, Twing uses ``process.cwd()``
for relative paths.
#### `TwingLoaderRelativeFilesystem`
`TwingLoaderRelativeFilesystem` loads templates from the filesystem, relatively to the template that initiated the loading.
Consider for example the following template located in `/foo/bar`:
{% raw %}
```twig
{% include "../index.html" %}
```
{% endraw %}
`../index.html` would resolve to `/foo/index.html`.
#### ``TwingLoaderArray``
``TwingLoaderArray`` loads a template from an object or a Map. It's passed strings bound to template names:
{% raw %}
```javascript
let loader = new TwingLoaderArray({
'index.html': 'Hello {{ name }}!',
});
let twing = new TwingEnvironment(loader);
twing.render('index.html', {'name': 'Fabien'}).then((output) => {
// ...
});
```
{% endraw %}
This loader is very useful for unit testing. It can also be used for small
projects where storing all templates in a single JavaScript file might make sense.
> When using the ``Array`` loader with a cache mechanism, you
should know that a new cache key is generated each time a template content
"changes" (the cache key being the source code of the template). If you
don't want to see your cache grows out of control, you need to take care
of clearing the old cache file by yourself.
#### ``TwingLoaderChain``
``TwingLoaderChain`` delegates the loading of templates to other loaders:
{% raw %}
```javascript
let loader1 = new TwingLoaderArray({
'base.html': '{% block content %}{% endblock %}',
});
let loader2 = new TwingLoaderArray({
'index.html': '{% extends "base.html" %}{% block content %}Hello {{ name }}{% endblock %}',
'base.html': 'Will never be loaded',
});
let loader = new TwingLoaderChain([loader1, loader2]);
let twing = new TwingEnvironment(loader).then((output) => {
// ...
});
```
{% endraw %}
When looking for a template, Twing will try each loader in turn and it will
return as soon as the template is found. When rendering the ``index.html``
template from the above example, Twing will load it with ``loader2`` but the
``base.html`` template will be loaded from ``loader1``.
``TwingLoaderChain`` accepts any loader that implements
``TwingLoaderInterface``.
> You can also add loaders via the ``addLoader()`` method.
### Create your own Loader
All loaders must implement the ``TwingLoaderInterface`` declared in `lib/loader-interface.d.ts`:
The `isFresh()` method must return `true` if the current cached template is still fresh, given the last modification time, or `false` otherwise.
The `getSourceContext()` method must return an instance of `TwingSource`.
## Using Extensions
Twing extensions are packages that add new features to Twing. Using an extension is as simple as using the `addExtension()` method:
```javascript
twing.addExtension(new TwingExtensionSandbox());
```
## Exceptions
Twing can throw exceptions:
* ``TwingError``: The base exception for all errors.
* ``TwingErrorSyntax``: Thrown to tell the user that there is a problem with
the template syntax.
* ``TwingErrorRuntime``: Thrown when an error occurs at runtime (when a filter
does not exist for instance).
* ``TwingErrorLoader``: Thrown when an error occurs during template loading.
* ``TwingSandboxSecurityError``: Thrown when an unallowed tag, filter, or
method is called in a sandboxed template.
## Output buffering<a name="output-buffering"></a>
Twing offers an output buffering mechanism that allows developers to control when output is sent. This mechanism is implemented by `TwingOutputBuffering`.
Output buffers are stackable, that is, you may call `TwingOutputBuffering.obStart()` while another `TwingOutputBuffering.obStart()` is active.
When no output buffer is active, `TwingOutputBuffering.echo()` writes to _process.stdout_. This is what happens when the function `TwingTemplate.display` is called. On most systems, _process.stdout_ is linked to the console, but it is trivial to route _process.stdout_ somewhere else.
When an output buffer is active, `TwingOutputBuffering.echo()` writes to this active buffer. The content of the active output buffer can be retrieved by calling `TwingOutputBuffering.obGetContents()`. The active buffer can be ended and unstacked by calling `TwingOutputBuffering.obEndClean()`.
> See `lib/output-buffering.d.ts` for implementation details.
[back][back-url]
[back-url]: {{ site.baseurl }}{% link index.md %}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,5 +0,0 @@
---
# Only the main Sass file needs front matter (the dashes are enough)
---
@import "../_sass/minima";

View File

@ -1,96 +0,0 @@
Coding Standards
================
{% raw %}
When writing Twig templates, we recommend you to follow these official coding standards:
* Put one (and only one) space after the start of a delimiter (`{{`, `{%`, and `{#`) and before the end of a delimiter (`}}`, `%}`, and `#}`):
````twig
{{ foo }}
{# comment #}
{% if foo %}{% endif %}
````
When using the whitespace control character, do not put any spaces between it and the delimiter:
````twig
{{- foo -}}
{#- comment -#}
{%- if foo -%}{%- endif -%}
````
* Put one (and only one) space before and after the following operators: comparison operators (`==`, `!=`, `<`, `>`, `>=`, `<=`), math operators (`+`, `-`, `/`, `*`, `%`, `//`, `**`), logic operators (`not`, `and`, `or`), `~`, `is`, `in`, and the ternary operator (`?:`):
````twig
{{ 1 + 2 }}
{{ foo ~ bar }}
{{ true ? true : false }}
````
* Put one (and only one) space after the `:` sign in hashes and `,` in arrays and hashes:
````twig
{{ [1, 2, 3] }}
{{ {'foo': 'bar'} }}
````
* Do not put any spaces after an opening parenthesis and before a closing parenthesis in expressions:
````twig
{{ 1 + (2 * 3) }}
````
* Do not put any spaces before and after string delimiters:
````twig
{{ 'foo' }}
{{ "foo" }}
````
* Do not put any spaces before and after the following operators: `|`, `.`, `..`, `[]`:
````twig
{{ foo|upper|lower }}
{{ user.name }}
{{ user[name] }}
{% for i in 1..12 %}{% endfor %}
````
* Do not put any spaces before and after the parenthesis used for filter and function calls:
````twig
{{ foo|default('foo') }}
{{ range(1..10) }}
````
* Do not put any spaces before and after the opening and the closing of arrays and hashes:
````twig
{{ [1, 2, 3] }}
{{ {'foo': 'bar'} }}
````
* Use lower cased and underscored variable names:
````twig
{% set foo = 'foo' %}
{% set foo_bar = 'foo' %}
````
* Indent your code inside tags (use the same indentation as the one used for the target language of the rendered template):
````twig
{% block foo %}
{% if true %}
true
{% endif %}
{% endblock %}
````
{% endraw %}
[back][back-url]
[back-url]: {{ site.baseurl }}{% link index.md %}

View File

@ -1,25 +0,0 @@
{% raw %}
# Forewords about terminology
In this documentation, the `Twig` term represents the Twig language *specifications*, not the SensioLabs PHP *implementation* of the language - implementation being known as `TwigPHP`. SensioLabs use `Twig` to talk about both the specifications of the language and their implementation, and their documentation mix both specifications and implementation details. This should be considered as an oversight instead of an official convention.
`Twing` is a Node.js implementation of `Twig` language specifications.
# Deprecated Features
This document lists deprecated features in Twig 2.x. Deprecated features are kept for backward compatibility and removed in the next major release (a feature that was deprecated in Twig 2.x is removed in Twig 3.0).
## Inheritance
* Defining a "block" definition in a non-capturing block in a child template is deprecated since Twig 2.5.0. When Twig 3.0 is available, Twing will throw a `TwingErrorSyntax` error. It does not work anyway, so most projects won't need to do anything to upgrade.
## Tags
* Using the `spaceless` tag at the root level of a child template is deprecated since Twig 2.5.0. This does not work as one would expect it to work anyway. When Twig 3.0 is available, Twing will throw a `TwingErrorSyntax` exception.
* The `spaceless` tag is deprecated since Twig 2.7. Use the `spaceless` filter instead or `{% filter spaceless %}`.
* Adding an `if` condition on a `for` tag is deprecated in Twig 2.10. Use a `filter` filter or an "if" condition inside the "for" body instead (if your condition depends on a variable updated inside the loop)
{% endraw %}

View File

@ -1,25 +0,0 @@
# Forewords about terminology
In this documentation, the `Twig` term represents the Twig language *specifications*, not the SensioLabs PHP *implementation* of the language - implementation being known as `TwigPHP`. SensioLabs use `Twig` to talk about both the specifications of the language and their implementation, and their documentation mix both specifications and implementation details. This should be considered as an oversight instead of an official convention.
`Twing` is a Node.js implementation of `Twig` language specifications.
# Twing Documentation
Read the online documentation to learn more about Twing.
{% include toc.html items=site.data.navigation_documentation.docs %}
# [Twig Language Reference][language-reference-url]
Browse the online Twig language reference to learn more about built-in features.
<div>
{% for section in site.data.navigation_reference.sections %}
<h2>{{ section[1].title }}</h2>
{% assign items = section[1].items %}
{% include toc.html items=items %}
{% endfor %}
</div>
[language-reference-url]: {{ site.baseurl }}{% link language-reference/index.md %}

View File

@ -1,12 +0,0 @@
Installation
============
Install [npm][npm-url] and run the following command to get the latest version:
```bash
npm install twing --save
```
[back]({{ site.baseurl }}{% link index.md %})
[npm-url]: https://docs.npmjs.com/getting-started/installing-node

View File

@ -1,148 +0,0 @@
Twing Internals
===============
{% raw %}
Twing is very extensible and you can easily hack it. Keep in mind that you should probably try to create an extension before hacking the core, as most features and enhancements can be handled with extensions. This chapter is also useful for people who want to understand how Twing works under the hood.
## How does Twing work?
The rendering of a Twig template can be summarized into four key steps:
* **Load** the template: If the template is already compiled, load it and go to the *evaluation* step, otherwise:
* First, the **lexer** tokenizes the template source code into small pieces for easier processing;
* Then, the **parser** converts the token stream into a meaningful tree of nodes (the Abstract Syntax Tree);
* Eventually, the *compiler* transforms the AST into JavaScript code.
* **Evaluate** the template: It basically means calling the ``display()`` method of the compiled template and passing it the context.
## The Lexer
The lexer tokenizes a template source code into a token stream. Each token is an instance of [twig-lexer][twig-lexer-url] `Token` and the stream is an instance of `TwingTokenStream`.
You can manually convert a source code into a token stream by calling the `tokenize()` method of an environment:
````javascript
let stream = twing.tokenize(new TwingSource(source, identifier));
````
As the stream has a `toString()` method, you can have a textual representation of it:
````javascript
console.log(stream.toString());
````
Here is the output for the `Hello {{ name }}` template:
````
TEXT(Hello )
VARIABLE_START({{)
NAME(name)
VARIABLE_END(}})
EOF()
````
> The default lexer (`TwingLexer`) can be changed by calling the `setLexer()` method:
````javascript
twing.setLexer(lexer);
````
## The Parser
The parser converts the token stream into an AST (Abstract Syntax Tree), or a node tree (an instance of `TwingNodeModule`). The core extension defines the basic nodes like: `for`, `if`, ... and the expression nodes.
You can manually convert a token stream into a node tree by calling the `parse()` method of an environment:
````javascript
let nodes = twing.parse(stream);
````
Displaying the node object gives you a nice representation of the tree:
````javascript
console.warn(nodes);
````
Here is the output for the `Hello {{ name }}` template:
````
TwingNodeModule(index: null, embedded_templates: array ()
body: TwingNodeBody(
0: TwingNode(
0: TwingNodeText(data: 'Hello ')
1: TwingNodePrint(
expr: TwingNodeExpressionFilter(
node: TwingNodeExpressionName(name: 'name', is_defined_test: false, ignore_strict_check: false, always_defined: false)
filter: TwingNodeExpressionConstant(value: 'escape')
arguments: TwingNode(
0: TwingNodeExpressionConstant(value: 'html')
1: TwingNodeExpressionConstant(value: null)
2: TwingNodeExpressionConstant(value: true)
)
)
)
)
)
blocks: TwingNode()
macros: TwingNode()
traits: TwingNode()
display_start: TwingNode()
display_end: TwingNode()
constructor_start: TwingNode()
constructor_end: TwingNode()
class_end: TwingNode()
)
````
> The default parser (`TwingTokenParser`) can be changed by calling the `setParser()` method:
````javascript
twing.setParser(parser);
````
## The Compiler
The last step is done by the compiler. It takes a node tree as an input and generates JavaScript code usable for runtime execution of the template.
You can manually compile a node tree to JavaScript code with the `compile()` method of an environment:
````javascript
let javaScript = twing.compile(nodes);
````
The generated template for a `Hello {{ name }}` template reads as follows (the actual output can differ depending on the version of Twing you are using):
````javascript
module.exports = (TwingTemplate) => {
return new Map([
[0, class extends TwingTemplate {
constructor(env) {
super(env);
this.sourceContext = new this.Source(``, `index`, ``);
}
async doDisplay(context, blocks = new Map()) {
this.echo(`Hello `);
this.echo(await this.env.getFilter('escape').traceableCallable(1, this.getSourceContext())(...[this.env, (context.has(`name`) ? context.get(`name`) : null), `html`, null, true]));
}
}],
]);
};
````
> The default compiler (`TwingCompiler`) can be changed by calling the `setCompiler()` method:
````javascript
twing.setCompiler(compiler);
````
{% endraw %}
[back][back-url]
[back-url]: {{ site.baseurl }}{% link index.md %}
[twig-lexer-url]: https://www.npmjs.com/package/twig-lexer

View File

@ -1,154 +0,0 @@
Introduction
============
{% raw %}
This is the documentation for Twing, the TypeScript-written Node.js implementation of the [Twig Language][language-reference-url].
## Prerequisites
Twing needs at least **node.js 8.0.0** to run.
## Installation
The recommended way to install Twing is via npm:
`npm i twing --save`
## Basic API Usage
This section gives you a brief introduction to the node.js API for Twing.
```js
const {TwingEnvironment, TwingLoaderArray} = require('twing');
let loader = new TwingLoaderArray({
'index.twig': 'Hello {{ name }}!'
});
let twing = new TwingEnvironment(loader);
twing.render('index.twig', {name: 'Fabien'}).then((output) => {
// do something with the output
});
```
Twing uses a loader (`TwingLoaderArray`) to locate templates, and an environment (`TwingEnvironment`) to store the configuration.
The `render()` method loads the template passed as a first argument and renders it with the variables passed as a second argument.
As templates are generally stored on the filesystem, Twing also comes with a filesystem loader:
```js
const {TwingEnvironment, TwingLoaderFilesystem} = require('twing');
let loader = new TwingLoaderFilesystem('/path/to/templates');
let environment = new TwingEnvironment(loader);
environment.render('index.twig', {name: 'Fabien'}).then((output) => {
// do something with the output
});
```
### Real-world example using Express
_Credit for this example goes to [stela5](https://github.com/stela5)._
1. Create working directory and initialize dependencies:
```
mkdir myapp myapp/templates && cd myapp
npm init -y
npm install --save express twing
```
2. Create server.js:
```javascript
const app = require('express')();
const port = process.env.NODE_PORT || 3000;
const {TwingEnvironment, TwingLoaderFilesystem} = require('twing');
let loader = new TwingLoaderFilesystem('./templates');
let twing = new TwingEnvironment(loader);
app.get('/', function (req, res) {
twing.render('index.twig', {'name': 'World'}).then((output) => {
res.end(output);
});
});
app.get('/name/:name', function (req, res) {
twing.render('index.twig', req.params).then((output) => {
res.end(output);
});
});
app.listen(port, () => {
console.log('Node.js Express server listening on port '+port);
});
```
3. Create templates/index.twig:
```html
<!doctype html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css?family=Baloo+Tamma" rel="stylesheet">
<style>
body {
margin: 0;
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iNiIgaGVpZ2h0PSI2Ij4KPHJlY3Qgd2lkdGg9IjYiIGhlaWdodD0iNiIgZmlsbD0iI2VlZSI+PC9yZWN0Pgo8ZyBpZD0iYyI+CjxyZWN0IHdpZHRoPSIzIiBoZWlnaHQ9IjMiIGZpbGw9IiNlNmU2ZTYiPjwvcmVjdD4KPHJlY3QgeT0iMSIgd2lkdGg9IjMiIGhlaWdodD0iMiIgZmlsbD0iI2Q4ZDhkOCI+PC9yZWN0Pgo8L2c+Cjx1c2UgeGxpbms6aHJlZj0iI2MiIHg9IjMiIHk9IjMiPjwvdXNlPgo8L3N2Zz4=");
}
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #282828;
font-family: Baloo Tamma, cursive;
height: 70vh;
}
span {
font-size: 5vw;
}
em {
font-size: 1.5vw;
}
</style>
</head>
<body>
<main>
<img src="https://svgur.com/i/1ja.svg">
<span>Hello{{ ' ' + name|trim }}!</span>
<em>Powered by Twing</em>
</main>
</body>
</html>
```
4. Start server:
```
node server.js
```
5. Browse to website:
![Hello World!][hello-world-image]
6. Change url to /name/Bob (or any name you choose):
![Hello Bob!][hello-bob-image]
{% endraw %}
[back][back-url]
[back-url]: {{ site.baseurl }}{% link index.md %}
[language-reference-url]: {{ site.baseurl }}{% link language-reference/index.md %}
[hello-world-image]: {{ site.baseurl }}/assets/hello-world.png
[hello-bob-image]: {{ site.baseurl }}/assets/hello-bob.png

View File

@ -1,13 +0,0 @@
# Known issues
This document lists the known issues of Twing regarding Twig specifications implementation.
{% raw %}
> There are currently no known issues.
{% endraw %}
[back]({{ site.baseurl }}{% link index.md %})
[filter-tag-doc]: https://twig.symfony.com/doc/2.x/tags/filter.html

View File

@ -1,17 +0,0 @@
`abs`
=====
{% raw %}
The `abs` filter returns the absolute value.
````twig
{# number = -5 #}
{{ number|abs }}
{# outputs 5 #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,53 +0,0 @@
`batch`
=======
{% raw %}
The `batch` filter "batches" items by returning a list of lists with the given number of items. A second parameter can be provided and used to fill in missing items:
````twig
{% set items = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] %}
<table>
{% for row in items|batch(3, 'No item') %}
<tr>
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
````
The above example will be rendered as:
````html
<table>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
</tr>
<tr>
<td>d</td>
<td>e</td>
<td>f</td>
</tr>
<tr>
<td>g</td>
<td>No item</td>
<td>No item</td>
</tr>
</table>
````
Arguments
---------
* `size`: The size of the batch; fractional numbers will be rounded up
* `fill`: Used to fill in missing items
* `preserve_keys`: Whether to preserve keys or not
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
`capitalize`
============
{% raw %}
The `capitalize` filter capitalizes a value. The first character will be uppercase, all others lowercase:
````twig
{{ 'my first car'|capitalize }}
{# outputs 'My first car' #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,23 +0,0 @@
`column`
========
{% raw %}
The `column` filter returns the values from a single column in the input array.
```twig
{% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
{% set fruits = items|column('fruit') %}
{# fruits now contains ['apple', 'orange'] #}
```
Arguments
---------
* `name`: The column name to extract
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,20 +0,0 @@
`convert_encoding`
==================
{% raw %}
The `convert_encoding` filter converts a string from one encoding to another. The first argument is the expected output charset and the second one is the input charset:
````twig
{{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
````
Arguments
---------
* `to`: The output charset
* `from`: The input charset
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,20 +0,0 @@
`date`
======
{% raw %}
The `date` filter formats a date to a given format:
````twig
{{ post.published_at|date("m/d/Y") }}
````
Arguments
---------
* `format`: The date format
* `timezone`: The date timezone
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,19 +0,0 @@
`date_modify`
=============
{% raw %}
The `date_modify` filter modifies a date with a given modifier string:
````twig
{{ post.published_at|date_modify("+1 day")|date("m/d/Y") }}
````
Arguments
---------
* `modifier`: The modifier
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,25 +0,0 @@
`default`
=========
{% raw %}
The `default` filter returns the passed default value if the value is undefined or empty, otherwise the value of the variable:
````twig
{{ var|default('var is not defined') }}
{{ var.foo|default('foo item on var is not defined') }}
{{ var['foo']|default('foo item on var is not defined') }}
{{ ''|default('passed var is empty') }}
````
Arguments
---------
* `default`: The default value
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,44 +0,0 @@
`escape`
========
{% raw %}
The `escape` filter escapes a string for safe insertion into the final output. It supports different escaping strategies depending on the template context.
By default, it uses the HTML escaping strategy:
````twig
{{ user.username|escape }}
````
The `e` filter is defined as an alias:
````twig
{{ user.username|e }}
````
The `escape` filter can also be used in other contexts than HTML thanks to an optional argument which defines the escaping strategy to use:
````twig
{{ user.username|e }}
{# is equivalent to #}
{{ user.username|e('html') }}
````
The `escape` filter supports the following escaping strategies:
* `html`: escapes a string for the **HTML body** context.
* `js`: escapes a string for the **JavaScript context**.
* `css`: escapes a string for the **CSS context**. CSS escaping can be applied to any string being inserted into CSS and escapes everything except alphanumerics.
* `url`: escapes a string for the **URI or parameter contexts**. This should not be used to escape an entire URI; only a subcomponent being inserted.
* `html_attr`: escapes a string for the **HTML attribute** context.
Arguments
---------
* `strategy`: The escaping strategy
* `charset`: The string charset
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,60 +0,0 @@
`filter`
=======
{% raw %}
The `filter` filter filters elements of a sequence or a mapping using an arrow function. The arrow function receives the value of the sequence or mapping:
```twig
{% set sizes = [34, 36, 38, 40, 42] %}
{{ sizes|filter(v => v > 38)|join(', ') }}
{# output 40, 42 #}
```
Combined with the `for` tag, it allows to filter the items to iterate over:
```twig
{% for v in sizes|filter(v => v > 38) -%}
{{ v }}
{% endfor %}
{# output 40 42 #}
```
It also works with mappings:
```twig
{% set sizes = {
xs: 34,
s: 36,
m: 38,
l: 40,
xl: 42,
} %}
{% for k, v in sizes|filter(v => v > 38) -%}
{{ k }} = {{ v }}
{% endfor %}
{# output l = 40 xl = 42 #}
```
The arrow function also receives the key as a second argument:
```twig
{% for k, v in sizes|filter((v, k) => v > 38 and k != "xl") -%}
{{ k }} = {{ v }}
{% endfor %}
{# output l = 40 #}
```
Note that the arrow function has access to the current context.
Arguments
---------
* `array`: The sequence or mapping
* `arrow`: The arrow function
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,21 +0,0 @@
`first`
=======
{% raw %}
The `first` filter returns the first "element" of a sequence, a mapping, or a string:
````twig
{{ [1, 2, 3, 4]|first }}
{# outputs 1 #}
{{ { a: 1, b: 2, c: 3, d: 4 }|first }}
{# outputs 1 #}
{{ '1234'|first }}
{# outputs 1 #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,17 +0,0 @@
`format`
========
{% raw %}
The `format` filter formats a given string by replacing the placeholders:
````twig
{{ "I like %s and %s."|format(foo, "bar") }}
{# outputs I like foo and bar
if the foo parameter equals to the foo string. #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
Filters
=======
<ul>
{% for item in site.data.navigation_reference.sections.filters.items %}
<li>
{% if item[1].url %}
<a href="{{ site.baseurl }}/{{ item[1].url }}" alt="{{ item[1].title }}">{{ item[1].title }}</a>
{% else %}
<span>{{ item[1].title }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
[back]({{ site.baseurl }}{% link index.md %})

View File

@ -1,36 +0,0 @@
`join`
======
{% raw %}
The `join` filter returns a string which is the concatenation of the items of a sequence:
````twig
{{ [1, 2, 3]|join }}
{# returns 123 #}
````
The separator between elements is an empty string per default, but you can define it with the optional first parameter:
````twig
{{ [1, 2, 3]|join('|') }}
{# outputs 1|2|3 #}
````
A second parameter can also be provided that will be the separator used between
the last two items of the sequence:
````twig
{{ [1, 2, 3]|join(', ', ' and ') }}
{# outputs 1, 2 and 3 #}
````
Arguments
---------
* `glue`: The separator
* `and`: The separator for the last pair of input items
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,14 +0,0 @@
`json_encode`
=============
{% raw %}
The `json_encode` filter returns the JSON representation of a value:
````twig
{{ data|json_encode() }}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
`keys`
========
{% raw %}
The `keys` filter returns the keys of an array. It is useful when you want to iterate over the keys of an array:
````twig
{% for key in array|keys %}
...
{% endfor %}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,21 +0,0 @@
`last`
======
{% raw %}
The `last` filter returns the last "element" of a sequence, a mapping, or a string:
````twig
{{ [1, 2, 3, 4]|last }}
{# outputs 4 #}
{{ { a: 1, b: 2, c: 3, d: 4 }|last }}
{# outputs 4 #}
{{ '1234'|last }}
{# outputs 4 #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
`length`
========
{% raw %}
The `length` filter returns the number of items of a sequence or mapping, or the length of a string.
````twig
{% if users|length > 10 %}
...
{% endif %}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
`lower`
=======
{% raw %}
The `lower` filter converts a value to lowercase:
````twig
{{ 'WELCOME'|lower }}
{# outputs 'welcome' #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,39 +0,0 @@
`map`
=====
{% raw %}
The `map` filter applies an arrow function to the elements of a sequence or a mapping. The arrow function receives the value of the sequence or mapping:
```twig
{% set people = [
{first: "Bob", last: "Smith"},
{first: "Alice", last: "Dupond"},
] %}
{{ people|map(p => "#{p.first} #{p.last}")|join(', ') }}
{# outputs Bob Smith, Alice Dupond #}
```
The arrow function also receives the key as a second argument:
```twig
{% set people = {
"Bob": "Smith",
"Alice": "Dupond",
} %}
{{ people|map((value, key) => "#{key} #{value}")|join(', ') }}
{# outputs Bob Smith, Alice Dupond #}
```
Note that the arrow function has access to the current context.
Arguments
---------
* `arrow`: The arrow function
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,32 +0,0 @@
`merge`
=======
{% raw %}
The `merge` filter merges an array with another array:
````twig
{% set values = [1, 2] %}
{% set values = values|merge(['apple', 'orange']) %}
{# values now contains [1, 2, 'apple', 'orange'] #}
````
New values are added at the end of the existing ones.
The `merge` filter also works on hashes:
````twig
{% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %}
{% set items = items|merge({ 'peugeot': 'car', 'renault': 'car' }) %}
{# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car' } #}
````
For hashes, the merging process occurs on the keys: if the key does not already exist, it is added but if the key already exists, its value is overridden.
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,22 +0,0 @@
`nl2br`
=======
{% raw %}
The `nl2br` filter inserts HTML line breaks before all newlines in a string:
````twig
{{ "I like Twig.\nYou will like it too."|nl2br }}
{# outputs
I like Twig.<br />
You will like it too.
#}
````
> The `nl2br` filter pre-escapes the input before applying the transformation.
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,33 +0,0 @@
`number_format`
===============
{% raw %}
The `number_format` filter formats numbers:
````twig
{{ 200.35|number_format }}
````
You can control the number of decimal places, decimal point, and thousands separator using the additional arguments:
````twig
{{ 9800.333|number_format(2, '.', ',') }}
````
If no formatting options are provided then Twig will use the default formatting options of:
* 0 decimal places.
* `.` as the decimal point.
* `,` as the thousands separator.
Arguments
---------
* `decimal`: The number of decimal points to display
* `decimal_point`: The character(s) to use for the decimal point
* `thousand_sep`: The character(s) to use for the thousands separator
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
`raw`
=====
{% raw %}
The `raw` filter marks the value as being "safe", which means that in an environment with automatic escaping enabled this variable will not be escaped if `raw` is the last filter applied to it:
````twig
{% autoescape %}
{{ var|raw }} {# var won't be escaped #}
{% endautoescape %}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,33 +0,0 @@
`reduce`
=======
{% raw %}
The `reduce` filter iteratively reduces a sequence or a mapping to a single value using an arrow function, so as to reduce it to a single value. The arrow function receives the return value of the previous iteration and the current value of the sequence or mapping:
```twig
{% set numbers = [1, 2, 3] %}
{{ numbers|reduce((carry, v) => carry + v) }}
{# output 6 #}
```
The `reduce` filter takes an `initial` value as a second argument:
```twig
{{ numbers|reduce((carry, v) => carry + v, 10) }}
{# output 16 #}
```
Note that the arrow function has access to the current context.
Arguments
---------
* `arrow`: The arrow function
* `initial`: The initial value
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,28 +0,0 @@
`replace`
=========
{% raw %}
The `replace` filter formats a given string by replacing the placeholders (placeholders are free-form):
````twig
{{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }}
{# outputs I like foo and bar
if the foo parameter equals to the foo string. #}
{# using % as a delimiter is purely conventional and optional #}
{{ "I like this and --that--."|replace({'this': foo, '--that--': "bar"}) }}
{# outputs I like foo and bar #}
````
Arguments
---------
* `from`: The placeholder values
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,25 +0,0 @@
`reverse`
=========
{% raw %}
The `reverse` filter reverses a sequence, a mapping, or a string:
````twig
{% for user in users|reverse %}
...
{% endfor %}
{{ '1234'|reverse }}
{# outputs 4321 #}
````
Arguments
---------
* `preserve_keys`: Preserve keys when reversing a mapping or a sequence.
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,30 +0,0 @@
`round`
=======
{% raw %}
The `round` filter rounds a number to a given precision:
````twig
{{ 42.55|round }}
{# outputs 43 #}
{{ 42.55|round(1, 'floor') }}
{# outputs 42.5 #}
````
The `round` filter takes two optional arguments; the first one specifies the precision (default is `0`) and the second the rounding method (default is `common`):
* `common` rounds either up or down (rounds the value up to precision decimal places away from zero, when it is half way there -- making 1.5 into 2 and -1.5 into -2);
* `ceil` always rounds up;
* `floor` always rounds down.
Arguments
---------
* `precision`: The rounding precision
* `method`: The rounding method
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,55 +0,0 @@
`slice`
=======
{% raw %}
The `slice` filter extracts a slice of a sequence, a mapping, or a string:
````twig
{% for i in [1, 2, 3, 4, 5]|slice(1, 2) %}
{# will iterate over 2 and 3 #}
{% endfor %}
{{ '12345'|slice(1, 2) }}
{# outputs 23 #}
````
You can use any valid expression for both the start and the length:
````twig
{% for i in [1, 2, 3, 4, 5]|slice(start, length) %}
{# ... #}
{% endfor %}
````
As syntactic sugar, you can also use the `[]` notation:
````twig
{% for i in [1, 2, 3, 4, 5][start:length] %}
{# ... #}
{% endfor %}
{{ '12345'[1:2] }} {# will display "23" #}
{# you can omit the first argument -- which is the same as 0 #}
{{ '12345'[:2] }} {# will display "12" #}
{# you can omit the last argument -- which will select everything till the end #}
{{ '12345'[2:] }} {# will display "345" #}
````
If the start is non-negative, the sequence will start at that start in the variable. If start is negative, the sequence will start that far from the end of the variable.
If length is given and is positive, then the sequence will have up to that many elements in it. If the variable is shorter than the length, then only the available variable elements will be present. If length is given and is negative then the sequence will stop that many elements from the end of the variable. If it is omitted, then the sequence will have everything from offset up until the end of the variable.
Arguments
---------
* `start`: The start of the slice
* `length`: The size of the slice
* `preserve_keys`: Whether to preserve key or not (when the input is an array)
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
`sort`
======
{% raw %}
The `sort` filter sorts an array:
````twig
{% for user in users|sort %}
...
{% endfor %}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,38 +0,0 @@
`spaceless`
===========
{% raw %}
Use the `spaceless` filter to remove whitespace *between HTML tags*, not whitespace within HTML tags or whitespace in plain text:
```twig
{{
"<div>
<strong>foo</strong>
</div>
"|spaceless }}
{# output will be <div><strong>foo</strong></div> #}
```
You can combine `spaceless` with the `apply` tag to apply the transformation on large amounts of HTML:
```twig
{% apply spaceless %}
<div>
<strong>foo</strong>
</div>
{% endapply %}
{# output will be <div><strong>foo</strong></div> #}
```
This tag is not meant to "optimize" the size of the generated HTML content but merely to avoid extra whitespace between HTML tags to avoid browser rendering quirks under some circumstances.
> For more information on whitespace control, read the [dedicated section][whitespace-control-url] of the documentation and learn how you can also use the whitespace control modifier on your tags.
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/tags/index.md %})
[whitespace-control-url]: {{ site.baseurl }}{% link templates.md %}#whitespace-control

View File

@ -1,42 +0,0 @@
`split`
=======
{% raw %}
The `split` filter splits a string by the given delimiter and returns a list of strings:
````twig
{% set foo = "one,two,three"|split(',') %}
{# foo contains ['one', 'two', 'three'] #}
````
You can also pass a `limit` argument:
* If `limit` is positive, the returned array will contain a maximum of limit elements with the last element containing the rest of string;
* If `limit` is negative, all components except the last -limit are returned;
* If `limit` is zero, then this is treated as 1.
````twig
{% set foo = "one,two,three,four,five"|split(',', 3) %}
{# foo contains ['one', 'two', 'three,four,five'] #}
````
If the `delimiter` is an empty string, then value will be split by equal chunks. Length is set by the `limit` argument (one character by default).
````twig
{% set foo = "123"|split('') %}
{# foo contains ['1', '2', '3'] #}
{% set bar = "aabbcc"|split('', 2) %}
{# bar contains ['aa', 'bb', 'cc'] #}
````
Arguments
---------
* `delimiter`: The delimiter
* `limit`: The limit argument
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,27 +0,0 @@
`striptags`
===========
{% raw %}
The `striptags` filter strips SGML/XML tags and replace adjacent whitespace by one space:
````twig
{{ some_html|striptags }}
````
You can also provide tags which should not be stripped:
````twig
{{ some_html|striptags('<br><p>') }}
````
In this example, the `<br/>`, `<br>`, `<p>`, and `</p>` tags won't be removed from the string.
Arguments
---------
* `allowable_tags`: Tags which should not be stripped
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
`title`
=======
{% raw %}
The `title` filter returns a titlecased version of the value. Words will start with uppercase letters, all remaining characters are lowercase:
````twig
{{ 'my first car'|title }}
{# outputs 'My First Car' #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,34 +0,0 @@
`trim`
======
{% raw %}
The `trim` filter strips whitespace (or other characters) from the beginning and end of a string:
````twig
{{ ' I like Twig. '|trim }}
{# outputs 'I like Twig.' #}
{{ ' I like Twig.'|trim('.') }}
{# outputs ' I like Twig' #}
{{ ' I like Twig. '|trim(side='left') }}
{# outputs 'I like Twig. ' #}
{{ ' I like Twig. '|trim(' ', 'right') }}
{# outputs ' I like Twig.' #}
````
Arguments
---------
* `character_mask`: The characters to strip
* `side`: The default is to strip from the left and the right (`both`) sides, but `left` and `right` will strip from either the left side or right side only
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,16 +0,0 @@
`upper`
=======
{% raw %}
The `upper` filter converts a value to uppercase:
````twig
{{ 'welcome'|upper }}
{# outputs 'WELCOME' #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,21 +0,0 @@
`url_encode`
============
{% raw %}
The `url_encode` filter percent encodes a given string as URL segment or an array as query string:
````twig
{{ "path-seg*ment"|url_encode }}
{# outputs "path-seg%2Ament" #}
{{ "string with spaces"|url_encode }}
{# outputs "string%20with%20spaces" #}
{{ {'param': 'value', 'foo': 'bar'}|url_encode }}
{# outputs "param=value&foo=bar" #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/filters/index.md %})

View File

@ -1,19 +0,0 @@
`attribute`
===========
{% raw %}
The `attribute` function can be used to access a "dynamic" attribute of a variable:
````twig
{{ attribute(object, method) }}
{{ attribute(object, method, arguments) }}
{{ attribute(array, item) }}
````
> The resolution algorithm is the same as the one used for the `.` notation, except that the item can be any valid expression.
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,24 +0,0 @@
`block`
=======
{% raw %}
When a template uses inheritance and if you want to print a block multiple times, use the `block` function:
````twig
<title>{% block title %}{% endblock %}</title>
<h1>{{ block('title') }}</h1>
{% block body %}{% endblock %}
````
The `block` function can also be used to display one block from another template:
````twig
{{ block("title", "common_blocks.twig") }}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,10 +0,0 @@
`constant`
==========
{% raw %}
> There is no language syntax to set nor to retrieve a constant. Twig specifications don't even describe what a constant is. As such, it is highly recommended to not use the `constant` function in your template and to consult your Twig engine implementation details if you really need to rely on this function.
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,34 +0,0 @@
`cycle`
=======
{% raw %}
The `cycle` function cycles on an array of values:
````twig
{% set start_year = date() | date('Y') %}
{% set end_year = start_year + 5 %}
{% for year in start_year..end_year %}
{{ cycle(['odd', 'even'], loop.index0) }}
{% endfor %}
````
The array can contain any number of values:
````twig
{% set fruits = ['apple', 'orange', 'citrus'] %}
{% for i in 0..10 %}
{{ cycle(fruits, i) }}
{% endfor %}
````
Arguments
---------
* `position`: The cycle position
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,37 +0,0 @@
`date`
======
{% raw %}
Converts an argument to a date to allow date comparison:
````twig
{% if date(user.created_at) < date('-2days') %}
{# do something #}
{% endif %}
````
You can pass a timezone as the second argument:
````twig
{% if date(user.created_at) < date('-2days', 'Europe/Paris') %}
{# do something #}
{% endif %}
````
If no argument is passed, the function returns the current date:
````twig
{% if date(user.created_at) < date() %}
{# always! #}
{% endif %}
````
Arguments
---------
* `date`: The date
* `timezone`: The timezone
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,40 +0,0 @@
`dump`
======
{% raw %}
The `dump` function dumps information about a template variable. This is mostly useful to debug a template that does not behave as expected by introspecting its variables:
````twig
{{ dump(user) }}
````
In an HTML context, wrap the output with a `pre` tag to make it easier to read:
````html
<pre>
{{ dump(user) }}
</pre>
````
You can debug several variables by passing them as additional arguments:
````twig
{{ dump(user, categories) }}
````
If you don't pass any value, all variables from the current context are
dumped:
````twig
{{ dump() }}
````
Arguments
---------
* `context`: The context to dump
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,60 +0,0 @@
`include`
=========
{% raw %}
The `include` function returns the rendered content of a template:
````twig
{{ include('template.html') }}
{{ include(some_var) }}
````
Included templates have access to the variables of the active context.
The context is passed by default to the template but you can also pass
additional variables:
````twig
{# template.html will have access to the variables from the current context and the additional ones provided #}
{{ include('template.html', {foo: 'bar'}) }}
````
You can disable access to the context by setting `with_context` to `false`:
````twig
{# only the foo variable will be accessible #}
{{ include('template.html', {foo: 'bar'}, with_context = false) }}
````
````twig
{# no variables will be accessible #}
{{ include('template.html', with_context = false) }}
````
When you set the `ignore_missing` flag, Twig will return an empty string if the template does not exist:
````twig
{{ include('sidebar.html', ignore_missing = true) }}
````
You can also provide a list of templates that are checked for existence before inclusion. The first template that exists will be rendered:
````twig
{{ include(['page_detailed.html', 'page.html']) }}
````
If `ignore_missing` is set, it will fall back to rendering nothing if none of the templates exist, otherwise it will throw an exception.
Arguments
---------
* `template`: The template to render
* `variables`: The variables to pass to the template
* `with_context`: Whether to pass the current context variables or not
* `ignore_missing`: Whether to ignore missing templates or not
* `sandboxed`: Whether to sandbox the template or not
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,16 +0,0 @@
Functions
=========
<ul>
{% for item in site.data.navigation_reference.sections.functions.items %}
<li>
{% if item[1].url %}
<a href="{{ site.baseurl }}/{{ item[1].url }}" alt="{{ item[1].title }}">{{ item[1].title }}</a>
{% else %}
<span>{{ item[1].title }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
[back]({{ site.baseurl }}{% link index.md %})

View File

@ -1,22 +0,0 @@
`max`
=====
{% raw %}
`max` returns the biggest value of a sequence or a set of values:
````twig
{{ max(1, 3, 2) }}
{{ max([1, 3, 2]) }}
````
When called with a hash, max ignores keys and only compares values:
````twig
{{ max({2: "e", 1: "a", 3: "b", 5: "d", 4: "c"}) }}
{# returns "e" #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,22 +0,0 @@
`min`
=====
{% raw %}
`min` returns the lowest value of a sequence or a set of values:
````twig
{{ min(1, 3, 2) }}
{{ min([1, 3, 2]) }}
````
When called with a hash, min ignores keys and only compares values:
````twig
{{ min({2: "e", 3: "a", 1: "b", 5: "d", 4: "c"}) }}
{# returns "a" #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,22 +0,0 @@
`parent`
========
{% raw %}
When a template uses inheritance, it's possible to render the contents of the parent block when overriding a block by using the `parent` function:
````twig
{% extends "base.html" %}
{% block sidebar %}
<h3>Table Of Contents</h3>
...
{{ parent() }}
{% endblock %}
````
The `parent()` call will return the content of the `sidebar` block as defined in the `base.html` template.
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,29 +0,0 @@
`random`
========
{% raw %}
The `random` function returns a random value depending on the supplied parameter type:
* a random item from a sequence;
* a random character from a string;
* a random integer between 0 and the integer parameter (inclusive).
* a random integer between the integer parameter (when negative) and 0 (inclusive).
* a random integer between the first integer and the second integer parameter (inclusive).
````twig
{{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}
{{ random('ABC') }} {# example output: C #}
{{ random() }} {# example output: 15386094 (works as the native PHP mt_rand function) #}
{{ random(5) }} {# example output: 3 #}
{{ random(50, 100) }} {# example output: 63 #}
````
Arguments
---------
* `values`: The values
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,53 +0,0 @@
`range`
=======
{% raw %}
Returns a list containing an arithmetic progression of integers:
````twig
{% for i in range(0, 3) %}
{{ i }},
{% endfor %}
{# outputs 0, 1, 2, 3, #}
````
When step is given (as the third parameter), it specifies the increment (or decrement for negative values):
````twig
{% for i in range(0, 6, 2) %}
{{ i }},
{% endfor %}
{# outputs 0, 2, 4, 6, #}
````
> Note that if the start is greater than the end, `range` assumes a step of `-1`:
````twig
{% for i in range(3, 0) %}
{{ i }},
{% endfor %}
{# outputs 3, 2, 1, 0, #}
````
The Twig built-in `..` operator is just syntactic sugar for the `range` function (with a step of `1`, or `-1` if the start is greater than the end):
````twig
{% for i in 0..3 %}
{{ i }},
{% endfor %}
````
Arguments
---------
* `low`: The first value of the sequence.
* `high`: The highest possible value of the sequence.
* `step`: The increment between elements of the sequence.
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,27 +0,0 @@
`source`
========
{% raw %}
The `source` function returns the content of a template without rendering it:
````twig
{{ source('template.html') }}
{{ source(some_var) }}
````
When you set the `ignore_missing` flag, Twig will return an empty string if the template does not exist:
````twig
{{ source('template.html', ignore_missing = true) }}
````
Arguments
---------
* `name`: The name of the template to read
* `ignore_missing`: Whether to ignore missing templates or not
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,20 +0,0 @@
`template_from_string`
======================
{% raw %}
The `template_from_string` function loads a template from a string:
````twig
{{ include(template_from_string("Hello {{ name }}")) }}
{{ include(template_from_string(page.template)) }}
````
Arguments
---------
* `template`: The template
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/functions/index.md %})

View File

@ -1,6 +0,0 @@
Twig Language Reference
=======================
Browse the online Twig language reference to learn more about built-in features.
{% include language-reference-toc.html %}

View File

@ -1,25 +0,0 @@
`apply`
=======
{% raw %}
Filter sections allow you to apply filters on a block of template data. Just wrap the code in the special `apply` section:
````twig
{% apply upper %}
This text becomes uppercase
{% endapply %}
````
You can also chain filters:
````twig
{% apply lower|escape %}
<strong>SOME TEXT</strong>
{% endapply %}
{# outputs "&lt;strong&gt;some text&lt;/strong&gt;" #}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/tags/index.md %})

View File

@ -1,25 +0,0 @@
`autoescape`
============
{% raw %}
Force the escaping strategy of a section of a template.
````twig
{# Mark the section as needing escaping using HTML strategy #}
{% autoescape %}{% endautoescape %}
{# Mark the section as needing escaping using HTML strategy #}
{% autoescape 'html' %}{% endautoescape %}
{# Mark the section as needing escaping using JS strategy #}
{% autoescape 'js' %}{% endautoescape %}
{# Mark the section as not needing escaping #}
{% autoescape false %}{% endautoescape %}
````
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/tags/index.md %})

View File

@ -1,25 +0,0 @@
`block`
=======
{% raw %}
Declare a section as a reusable block.
````twig
{% block "foo" %}
{% endblock %}
````
Block names should consist of alphanumeric characters, and underscores. Dashes are not permitted.
> See also [`block`][function-block-url] function, [`parent`][function-parent-url], [`use`][tag-use-url] and [`extends`][tag-extends-url].
{% endraw %}
[back]({{ site.baseurl }}{% link language-reference/tags/index.md %})
[function-block-url]: {{ site.baseurl }}{% link language-reference/functions/block.md %}
[function-parent-url]: {{ site.baseurl }}{% link language-reference/functions/parent.md %}
[tag-use-url]: {{ site.baseurl }}{% link language-reference/tags/use.md %}
[tag-extends-url]: {{ site.baseurl }}{% link language-reference/tags/extends.md %}

Some files were not shown because too many files have changed in this diff Show More