Merge pull request #175 from stencilproject/break-continue
Break, continue and loops' labels
This commit is contained in:
@@ -30,6 +30,12 @@
|
||||
[Ilya Puchka](https://github.com/ilyapuchka)
|
||||
[#158](https://github.com/stencilproject/Stencil/issues/158)
|
||||
[#182](https://github.com/stencilproject/Stencil/pull/182)
|
||||
- Added `break` and `continue` tags to break or continue current loop.
|
||||
[Ilya Puchka](https://github.com/ilyapuchka)
|
||||
[#175](https://github.com/stencilproject/Stencil/pull/175)
|
||||
- You can now access outer loop's scope by labeling it: `{% outer: for ... %}... {% for ... %} {{ outer.counter }} {% endfor %}{% endfor %}`.
|
||||
[Ilya Puchka](https://github.com/ilyapuchka)
|
||||
[#175](https://github.com/stencilproject/Stencil/pull/175)
|
||||
|
||||
### Deprecations
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ class DefaultExtension: Extension {
|
||||
|
||||
fileprivate func registerDefaultTags() {
|
||||
registerTag("for", parser: ForNode.parse)
|
||||
registerTag("break", parser: LoopTerminationNode.parse)
|
||||
registerTag("continue", parser: LoopTerminationNode.parse)
|
||||
registerTag("if", parser: IfNode.parse)
|
||||
registerTag("ifnot", parser: IfNode.parse_ifnot)
|
||||
#if !os(Linux)
|
||||
|
||||
@@ -6,10 +6,16 @@ class ForNode: NodeType {
|
||||
let nodes: [NodeType]
|
||||
let emptyNodes: [NodeType]
|
||||
let `where`: Expression?
|
||||
let label: String?
|
||||
let token: Token?
|
||||
|
||||
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
||||
let components = token.components
|
||||
var components = token.components
|
||||
|
||||
var label: String?
|
||||
if components.first?.hasSuffix(":") == true {
|
||||
label = String(components.removeFirst().dropLast())
|
||||
}
|
||||
|
||||
func hasToken(_ token: String, at index: Int) -> Bool {
|
||||
components.count > (index + 1) && components[index] == token
|
||||
@@ -52,6 +58,7 @@ class ForNode: NodeType {
|
||||
nodes: forNodes,
|
||||
emptyNodes: emptyNodes,
|
||||
where: `where`,
|
||||
label: label,
|
||||
token: token
|
||||
)
|
||||
}
|
||||
@@ -62,6 +69,7 @@ class ForNode: NodeType {
|
||||
nodes: [NodeType],
|
||||
emptyNodes: [NodeType],
|
||||
where: Expression? = nil,
|
||||
label: String? = nil,
|
||||
token: Token? = nil
|
||||
) {
|
||||
self.resolvable = resolvable
|
||||
@@ -69,6 +77,7 @@ class ForNode: NodeType {
|
||||
self.nodes = nodes
|
||||
self.emptyNodes = emptyNodes
|
||||
self.where = `where`
|
||||
self.label = label
|
||||
self.token = token
|
||||
}
|
||||
|
||||
@@ -85,30 +94,53 @@ class ForNode: NodeType {
|
||||
|
||||
if !values.isEmpty {
|
||||
let count = values.count
|
||||
var result = ""
|
||||
|
||||
return try zip(0..., values)
|
||||
.map { index, item in
|
||||
let forContext: [String: Any] = [
|
||||
// collect parent loop contexts
|
||||
let parentLoopContexts = (context["forloop"] as? [String: Any])?
|
||||
.filter { ($1 as? [String: Any])?["label"] != nil } ?? [:]
|
||||
|
||||
for (index, item) in zip(0..., values) {
|
||||
var forContext: [String: Any] = [
|
||||
"first": index == 0,
|
||||
"last": index == (count - 1),
|
||||
"counter": index + 1,
|
||||
"counter0": index,
|
||||
"length": count
|
||||
]
|
||||
if let label = label {
|
||||
forContext["label"] = label
|
||||
forContext[label] = forContext
|
||||
}
|
||||
forContext.merge(parentLoopContexts) { lhs, _ in lhs }
|
||||
|
||||
return try context.push(dictionary: ["forloop": forContext]) {
|
||||
try push(value: item, context: context) {
|
||||
var shouldBreak = false
|
||||
result += try context.push(dictionary: ["forloop": forContext]) {
|
||||
defer {
|
||||
// if outer loop should be continued we should break from current loop
|
||||
if let shouldContinueLabel = context[LoopTerminationNode.continueContextKey] as? String {
|
||||
shouldBreak = shouldContinueLabel != label || label == nil
|
||||
} else {
|
||||
shouldBreak = context[LoopTerminationNode.breakContextKey] != nil
|
||||
}
|
||||
}
|
||||
return try push(value: item, context: context) {
|
||||
try renderNodes(nodes, context)
|
||||
}
|
||||
}
|
||||
|
||||
if shouldBreak {
|
||||
break
|
||||
}
|
||||
.joined()
|
||||
}
|
||||
|
||||
return result
|
||||
} else {
|
||||
return try context.push {
|
||||
try renderNodes(emptyNodes, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func push<Result>(value: Any, context: Context, closure: () throws -> (Result)) throws -> Result {
|
||||
if loopVariables.isEmpty {
|
||||
@@ -174,3 +206,69 @@ class ForNode: NodeType {
|
||||
return values
|
||||
}
|
||||
}
|
||||
|
||||
struct LoopTerminationNode: NodeType {
|
||||
static let breakContextKey = "_internal_forloop_break"
|
||||
static let continueContextKey = "_internal_forloop_continue"
|
||||
|
||||
let name: String
|
||||
let label: String?
|
||||
let token: Token?
|
||||
|
||||
var contextKey: String {
|
||||
"_internal_forloop_\(name)"
|
||||
}
|
||||
|
||||
private init(name: String, label: String? = nil, token: Token? = nil) {
|
||||
self.name = name
|
||||
self.label = label
|
||||
self.token = token
|
||||
}
|
||||
|
||||
static func parse(_ parser: TokenParser, token: Token) throws -> LoopTerminationNode {
|
||||
let components = token.components
|
||||
|
||||
guard components.count <= 2 else {
|
||||
throw TemplateSyntaxError("'\(token.contents)' can accept only one parameter")
|
||||
}
|
||||
guard parser.hasOpenedForTag() else {
|
||||
throw TemplateSyntaxError("'\(token.contents)' can be used only inside loop body")
|
||||
}
|
||||
|
||||
return LoopTerminationNode(name: components[0], label: components.count == 2 ? components[1] : nil, token: token)
|
||||
}
|
||||
|
||||
func render(_ context: Context) throws -> String {
|
||||
let offset = zip(0..., context.dictionaries).reversed().first { _, dictionary in
|
||||
guard let forContext = dictionary["forloop"] as? [String: Any],
|
||||
dictionary["forloop"] != nil else { return false }
|
||||
|
||||
if let label = label {
|
||||
return label == forContext["label"] as? String
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}?.0
|
||||
|
||||
if let offset = offset {
|
||||
context.dictionaries[offset][contextKey] = label ?? true
|
||||
} else if let label = label {
|
||||
throw TemplateSyntaxError("No loop labeled '\(label)' is currently running")
|
||||
} else {
|
||||
throw TemplateSyntaxError("No loop is currently running")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private extension TokenParser {
|
||||
func hasOpenedForTag() -> Bool {
|
||||
var openForCount = 0
|
||||
for parsedToken in parsedTokens.reversed() where parsedToken.kind == .block {
|
||||
if parsedToken.components.first == "endfor" { openForCount -= 1 }
|
||||
if parsedToken.components.first == "for" { openForCount += 1 }
|
||||
}
|
||||
return openForCount > 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,15 +11,24 @@ public protocol NodeType {
|
||||
|
||||
/// Render the collection of nodes in the given context
|
||||
public func renderNodes(_ nodes: [NodeType], _ context: Context) throws -> String {
|
||||
try nodes
|
||||
.map { node in
|
||||
var result = ""
|
||||
|
||||
for node in nodes {
|
||||
do {
|
||||
return try node.render(context)
|
||||
result += try node.render(context)
|
||||
} catch {
|
||||
throw error.withToken(node.token)
|
||||
}
|
||||
|
||||
let shouldBreak = context[LoopTerminationNode.breakContextKey] != nil
|
||||
let shouldContinue = context[LoopTerminationNode.continueContextKey] != nil
|
||||
|
||||
if shouldBreak || shouldContinue {
|
||||
break
|
||||
}
|
||||
.joined()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/// Simple node, used for triggering a closure during rendering
|
||||
|
||||
@@ -18,6 +18,7 @@ public class TokenParser {
|
||||
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||
|
||||
fileprivate var tokens: [Token]
|
||||
fileprivate(set) var parsedTokens: [Token] = []
|
||||
fileprivate let environment: Environment
|
||||
fileprivate var previousWhiteSpace: WhitespaceBehaviour.Behaviour?
|
||||
|
||||
@@ -53,8 +54,13 @@ public class TokenParser {
|
||||
return nodes
|
||||
}
|
||||
|
||||
if let tag = token.components.first {
|
||||
if var tag = token.components.first {
|
||||
do {
|
||||
// special case for labeled tags (such as for loops)
|
||||
if tag.hasSuffix(":") && token.components.count >= 2 {
|
||||
tag = token.components[1]
|
||||
}
|
||||
|
||||
let parser = try environment.findTag(name: tag)
|
||||
let node = try parser(self, token)
|
||||
nodes.append(node)
|
||||
@@ -74,7 +80,9 @@ public class TokenParser {
|
||||
/// Pop the next token (returning it)
|
||||
public func nextToken() -> Token? {
|
||||
if !tokens.isEmpty {
|
||||
return tokens.remove(at: 0)
|
||||
let nextToken = tokens.remove(at: 0)
|
||||
parsedTokens.append(nextToken)
|
||||
return nextToken
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -87,6 +95,9 @@ public class TokenParser {
|
||||
/// Insert a token
|
||||
public func prependToken(_ token: Token) {
|
||||
tokens.insert(token, at: 0)
|
||||
if parsedTokens.last == token {
|
||||
parsedTokens.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create filter expression from a string contained in provided token
|
||||
|
||||
@@ -45,7 +45,12 @@ extension String {
|
||||
|
||||
if !components.isEmpty {
|
||||
if let precedingChar = components.last?.last, specialCharacters.contains(precedingChar) {
|
||||
// special case for labeled for-loops
|
||||
if components.count == 1 && word == "for" {
|
||||
components.append(word)
|
||||
} else {
|
||||
components[components.count - 1] += word
|
||||
}
|
||||
} else if specialCharacters.contains(word) {
|
||||
components[components.count - 1] += word
|
||||
} else if word != "(" && word.first == "(" || word != ")" && word.first == ")" {
|
||||
|
||||
@@ -312,6 +312,253 @@ final class ForNodeTests: XCTestCase {
|
||||
)
|
||||
try expect(try parser.parse()).toThrow(error)
|
||||
}
|
||||
|
||||
func testBreak() {
|
||||
it("can break from loop") {
|
||||
let template = Template(templateString: """
|
||||
{% for item in items %}\
|
||||
{{ item }}{% break %}\
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
1
|
||||
"""
|
||||
}
|
||||
|
||||
it("can break from inner node") {
|
||||
let template = Template(templateString: """
|
||||
{% for item in items %}\
|
||||
{{ item }}\
|
||||
{% if forloop.first %}<{% break %}>{% endif %}!\
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
1<
|
||||
"""
|
||||
}
|
||||
|
||||
it("does not allow break outside loop") {
|
||||
let template = Template(templateString: "{% for item in items %}{% endfor %}{% break %}")
|
||||
let error = self.expectedSyntaxError(
|
||||
token: "break",
|
||||
template: template,
|
||||
description: "'break' can be used only inside loop body"
|
||||
)
|
||||
try expect(template.render(self.context)).toThrow(error)
|
||||
}
|
||||
}
|
||||
|
||||
func testBreakNested() {
|
||||
it("breaks outer loop") {
|
||||
let template = Template(templateString: """
|
||||
{% for item in items %}\
|
||||
outer: {{ item }}
|
||||
{% for item in items %}\
|
||||
inner: {{ item }}
|
||||
{% endfor %}\
|
||||
{% break %}\
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
outer: 1
|
||||
inner: 1
|
||||
inner: 2
|
||||
inner: 3
|
||||
|
||||
"""
|
||||
}
|
||||
|
||||
it("breaks inner loop") {
|
||||
let template = Template(templateString: """
|
||||
{% for item in items %}\
|
||||
outer: {{ item }}
|
||||
{% for item in items %}\
|
||||
inner: {{ item }}
|
||||
{% break %}\
|
||||
{% endfor %}\
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
outer: 1
|
||||
inner: 1
|
||||
outer: 2
|
||||
inner: 1
|
||||
outer: 3
|
||||
inner: 1
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
func testBreakLabeled() {
|
||||
it("breaks labeled loop") {
|
||||
let template = Template(templateString: """
|
||||
{% outer: for item in items %}\
|
||||
outer: {{ item }}
|
||||
{% for item in items %}\
|
||||
{% break outer %}\
|
||||
inner: {{ item }}
|
||||
{% endfor %}\
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
outer: 1
|
||||
|
||||
"""
|
||||
}
|
||||
|
||||
it("throws when breaking with unknown label") {
|
||||
let template = Template(templateString: """
|
||||
{% outer: for item in items %}
|
||||
{% break inner %}
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)).toThrow()
|
||||
}
|
||||
}
|
||||
|
||||
func testContinue() {
|
||||
it("can continue loop") {
|
||||
let template = Template(templateString: """
|
||||
{% for item in items %}\
|
||||
{{ item }}{% continue %}!\
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == "123"
|
||||
}
|
||||
|
||||
it("can continue from inner node") {
|
||||
let template = Template(templateString: """
|
||||
{% for item in items %}\
|
||||
{% if forloop.last %}<{% continue %}>{% endif %}!\
|
||||
{{ item }}\
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == "!1!2<"
|
||||
}
|
||||
|
||||
it("does not allow continue outside loop") {
|
||||
let template = Template(templateString: "{% for item in items %}{% endfor %}{% continue %}")
|
||||
let error = self.expectedSyntaxError(
|
||||
token: "continue",
|
||||
template: template,
|
||||
description: "'continue' can be used only inside loop body"
|
||||
)
|
||||
try expect(template.render(self.context)).toThrow(error)
|
||||
}
|
||||
}
|
||||
|
||||
func testContinueNested() {
|
||||
it("breaks outer loop") {
|
||||
let template = Template(templateString: """
|
||||
{% for item in items %}\
|
||||
{% for item in items %}\
|
||||
inner: {{ item }}\
|
||||
{% endfor %}
|
||||
{% continue %}
|
||||
outer: {{ item }}
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
inner: 1inner: 2inner: 3
|
||||
inner: 1inner: 2inner: 3
|
||||
inner: 1inner: 2inner: 3
|
||||
|
||||
"""
|
||||
}
|
||||
|
||||
it("breaks inner loop") {
|
||||
let template = Template(templateString: """
|
||||
{% for item in items %}\
|
||||
{% for item in items %}\
|
||||
{% continue %}\
|
||||
inner: {{ item }}
|
||||
{% endfor %}\
|
||||
outer: {{ item }}
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
outer: 1
|
||||
outer: 2
|
||||
outer: 3
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
func testContinueLabeled() {
|
||||
it("continues labeled loop") {
|
||||
let template = Template(templateString: """
|
||||
{% outer: for item in items %}\
|
||||
{% for item in items %}\
|
||||
inner: {{ item }}
|
||||
{% continue outer %}\
|
||||
{% endfor %}\
|
||||
outer: {{ item }}
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
inner: 1
|
||||
inner: 1
|
||||
inner: 1
|
||||
|
||||
"""
|
||||
}
|
||||
|
||||
it("throws when continuing with unknown label") {
|
||||
let template = Template(templateString: """
|
||||
{% outer: for item in items %}
|
||||
{% continue inner %}
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)).toThrow()
|
||||
}
|
||||
}
|
||||
|
||||
func testAccessLabeled() {
|
||||
it("can access labeled outer loop context from inner loop") {
|
||||
let template = Template(templateString: """
|
||||
{% outer: for item in 1...2 %}\
|
||||
{% for item in items %}\
|
||||
{{ forloop.counter }}-{{ forloop.outer.counter }},\
|
||||
{% endfor %}---\
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
1-1,2-1,3-1,---1-2,2-2,3-2,---
|
||||
"""
|
||||
}
|
||||
|
||||
it("can access labeled outer loop from double inner loop") {
|
||||
let template = Template(templateString: """
|
||||
{% outer: for item in 1...2 %}{% for item in 1...2 %}\
|
||||
{% for item in items %}\
|
||||
{{ forloop.counter }}-{{ forloop.outer.counter }},\
|
||||
{% endfor %}---{% endfor %}
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
1-1,2-1,3-1,---1-1,2-1,3-1,---
|
||||
1-2,2-2,3-2,---1-2,2-2,3-2,---
|
||||
|
||||
"""
|
||||
}
|
||||
|
||||
it("can access two labeled outer loop contexts from inner loop") {
|
||||
let template = Template(templateString: """
|
||||
{% outer1: for item in 1...2 %}{% outer2: for item in 1...2 %}\
|
||||
{% for item in items %}\
|
||||
{{ forloop.counter }}-{{ forloop.outer2.counter }}-{{ forloop.outer1.counter }},\
|
||||
{% endfor %}---{% endfor %}
|
||||
{% endfor %}
|
||||
""")
|
||||
try expect(template.render(self.context)) == """
|
||||
1-1-1,2-1-1,3-1-1,---1-2-1,2-2-1,3-2-1,---
|
||||
1-1-2,2-1-2,3-1-2,---1-2-2,2-2-2,3-2-2,---
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
@@ -41,8 +41,7 @@ You can iterate over range literals created using ``N...M`` syntax, both in asce
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
The ``for`` tag can contain optional ``where`` expression to filter out
|
||||
elements on which this expression evaluates to false.
|
||||
The ``for`` tag can contain optional ``where`` expression to filter out elements on which this expression evaluates to false.
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
@@ -52,8 +51,7 @@ elements on which this expression evaluates to false.
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
The ``for`` tag can take an optional ``{% empty %}`` block that will be
|
||||
displayed if the given list is empty or could not be found.
|
||||
The ``for`` tag can take an optional ``{% empty %}`` block that will be displayed if the given list is empty or could not be found.
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
@@ -89,12 +87,74 @@ For example:
|
||||
This is user number {{ forloop.counter }} user.
|
||||
{% endfor %}
|
||||
|
||||
The ``for`` tag accepts an optional label, so that it may later be referred to by name. The contexts of parent labeled loops can be accessed via the `forloop` property:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% outer: for item in users %}
|
||||
{% for item in 1..3 %}
|
||||
{% if forloop.outer.first %}
|
||||
This is the first user.
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
``break``
|
||||
~~~~~~~~~
|
||||
|
||||
The ``break`` tag lets you jump out of a for loop, for example if a certain condition is met:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% for user in users %}
|
||||
{% if user.inaccessible %}
|
||||
{% break %}
|
||||
{% endif %}
|
||||
This is user {{ user.name }}.
|
||||
{% endfor %}
|
||||
|
||||
Break tags accept an optional label parameter, so that you may break out of multiple loops:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% outer: for user in users %}
|
||||
{% for address in user.addresses %}
|
||||
{% if address.isInvalid %}
|
||||
{% break outer %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
``continue``
|
||||
~~~~~~~~~
|
||||
|
||||
The ``continue`` tag lets you skip the rest of the blocks in a loop, for example if a certain condition is met:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% for user in users %}
|
||||
{% if user.inaccessible %}
|
||||
{% continue %}
|
||||
{% endif %}
|
||||
This is user {{ user.name }}.
|
||||
{% endfor %}
|
||||
|
||||
Continue tags accept an optional label parameter, so that you may skip the execution of multiple loops:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% outer: for user in users %}
|
||||
{% for address in user.addresses %}
|
||||
{% if address.isInvalid %}
|
||||
{% continue outer %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
``if``
|
||||
~~~~~~
|
||||
|
||||
The ``{% if %}`` tag evaluates a variable, and if that variable evaluates to
|
||||
true the contents of the block are processed. Being true is defined as:
|
||||
The ``{% if %}`` tag evaluates a variable, and if that variable evaluates to true the contents of the block are processed. Being true is defined as:
|
||||
|
||||
* Present in the context
|
||||
* Being non-empty (dictionaries or arrays)
|
||||
@@ -115,8 +175,7 @@ true the contents of the block are processed. Being true is defined as:
|
||||
Operators
|
||||
^^^^^^^^^
|
||||
|
||||
``if`` tags may combine ``and``, ``or`` and ``not`` to test multiple variables
|
||||
or to negate a variable.
|
||||
``if`` tags may combine ``and``, ``or`` and ``not`` to test multiple variables or to negate a variable.
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
@@ -279,8 +338,7 @@ By default the included file gets passed the current context. You can pass a sub
|
||||
|
||||
{% include "comment.html" comment %}
|
||||
|
||||
The `include` tag requires you to provide a loader which will be used to lookup
|
||||
the template.
|
||||
The `include` tag requires you to provide a loader which will be used to lookup the template.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
@@ -301,8 +359,7 @@ See :ref:`template-inheritance` for more information.
|
||||
``block``
|
||||
~~~~~~~~~
|
||||
|
||||
Defines a block that can be overridden by child templates. See
|
||||
:ref:`template-inheritance` for more information.
|
||||
Defines a block that can be overridden by child templates. See :ref:`template-inheritance` for more information.
|
||||
|
||||
.. _built-in-filters:
|
||||
|
||||
@@ -312,8 +369,7 @@ Built-in Filters
|
||||
``capitalize``
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
The capitalize filter allows you to capitalize a string.
|
||||
For example, `stencil` to `Stencil`. Can be applied to array of strings to change each string.
|
||||
The capitalize filter allows you to capitalize a string. For example, `stencil` to `Stencil`. Can be applied to array of strings to change each string.
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
@@ -322,8 +378,7 @@ For example, `stencil` to `Stencil`. Can be applied to array of strings to chang
|
||||
``uppercase``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The uppercase filter allows you to transform a string to uppercase.
|
||||
For example, `Stencil` to `STENCIL`. Can be applied to array of strings to change each string.
|
||||
The uppercase filter allows you to transform a string to uppercase. For example, `Stencil` to `STENCIL`. Can be applied to array of strings to change each string.
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
@@ -332,8 +387,7 @@ For example, `Stencil` to `STENCIL`. Can be applied to array of strings to chang
|
||||
``lowercase``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The uppercase filter allows you to transform a string to lowercase.
|
||||
For example, `Stencil` to `stencil`. Can be applied to array of strings to change each string.
|
||||
The uppercase filter allows you to transform a string to lowercase. For example, `Stencil` to `stencil`. Can be applied to array of strings to change each string.
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
@@ -342,8 +396,7 @@ For example, `Stencil` to `stencil`. Can be applied to array of strings to chang
|
||||
``default``
|
||||
~~~~~~~~~~~
|
||||
|
||||
If a variable not present in the context, use given default. Otherwise, use the
|
||||
value of the variable. For example:
|
||||
If a variable not present in the context, use given default. Otherwise, use the value of the variable. For example:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
|
||||
Reference in New Issue
Block a user