mirror of
https://gitlab.com/nightlycommit/twing.git
synced 2025-01-18 08:46:50 +02:00
Promote 6.x as the main branch
This commit is contained in:
commit
fa96f41c36
@ -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
|
@ -1,10 +0,0 @@
|
||||
.idea
|
||||
.nyc_output
|
||||
dist
|
||||
coverage
|
||||
docs/.bundle
|
||||
docs/_site
|
||||
docs/vendor
|
||||
node_modules
|
||||
package-lock.json
|
||||
tmp
|
@ -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
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
.idea
|
||||
.nyc_output
|
||||
dist
|
||||
coverage
|
||||
|
10
.npmignore
10
.npmignore
@ -1,10 +0,0 @@
|
||||
*
|
||||
!/dist/**
|
||||
!/lib/**
|
||||
!/browser.d.ts
|
||||
!/browser.js
|
||||
!/index.d.ts
|
||||
!/index.js
|
||||
!/LICENSE
|
||||
!/CHANGELOG
|
||||
!/README.md
|
24
.nycrc.json
24
.nycrc.json
@ -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"
|
||||
|
36
.travis.yml
36
.travis.yml
@ -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
292
CHANGELOG
@ -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
122
README.md
@ -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
|
@ -1,2 +0,0 @@
|
||||
source 'https://rubygems.org'
|
||||
gem 'github-pages', group: :jekyll_plugins
|
@ -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
|
@ -1,4 +0,0 @@
|
||||
sass:
|
||||
sass_dir: ./assets
|
||||
baseurl: /twing
|
||||
title: "Twing"
|
@ -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
|
@ -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 (.., |, ~, ., [], ?:)
|
||||
url: templates.html#other-operators
|
@ -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>
|
@ -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>
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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 |
@ -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>
|
@ -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>
|
@ -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>
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
<div class="home">
|
||||
{{ content }}
|
||||
</div>
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
<article class="post">
|
||||
<div class="post-content">
|
||||
{{ content }}
|
||||
</div>
|
||||
</article>
|
@ -1,8 +0,0 @@
|
||||
@charset "utf-8";
|
||||
|
||||
// Import partials.
|
||||
@import
|
||||
"minima/base",
|
||||
"minima/layout",
|
||||
"minima/syntax-highlighting"
|
||||
;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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: "";
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
745
docs/advanced.md
745
docs/advanced.md
@ -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 %}
|
324
docs/api.md
324
docs/api.md
@ -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 |
@ -1,5 +0,0 @@
|
||||
---
|
||||
# Only the main Sass file needs front matter (the dashes are enough)
|
||||
---
|
||||
|
||||
@import "../_sass/minima";
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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
|
@ -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
|
154
docs/intro.md
154
docs/intro.md
@ -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
|
@ -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
|
@ -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 %})
|
||||
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
||||
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %})
|
@ -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 %}
|
@ -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 "<strong>some text</strong>" #}
|
||||
````
|
||||
{% endraw %}
|
||||
|
||||
[back]({{ site.baseurl }}{% link language-reference/tags/index.md %})
|
@ -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 %})
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user