diff --git a/CHANGELOG.md b/CHANGELOG.md index 874d136..003412c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ path. Any template names that try to escape the base path will raise a `SuspiciousFileOperation` error. +- New `{% filter %}` tag allowing you to perform a filter across the contents + of a block. + + ```html+django + {% filter lowercase %} + This Text Will Be Lowercased. + {% endfilter %} + ``` + ## 0.7.1 diff --git a/Sources/FilterTag.swift b/Sources/FilterTag.swift new file mode 100644 index 0000000..27fae15 --- /dev/null +++ b/Sources/FilterTag.swift @@ -0,0 +1,35 @@ +class FilterNode : NodeType { + let resolvable: Resolvable + let nodes: [NodeType] + + class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { + let bits = token.components() + + guard bits.count == 2 else { + throw TemplateSyntaxError("'filter' tag takes one argument, the filter expression") + } + + let blocks = try parser.parse(until(["endfilter"])) + + guard let token = parser.nextToken() else { + throw TemplateSyntaxError("`endfilter` was not found.") + } + + let resolvable = try parser.compileFilter("filter_value|\(bits[1])") + return FilterNode(nodes: blocks, resolvable: resolvable) + } + + init(nodes: [NodeType], resolvable: Resolvable) { + self.nodes = nodes + self.resolvable = resolvable + } + + func render(_ context: Context) throws -> String { + let value = try renderNodes(nodes, context) + + return try context.push(dictionary: ["filter_value": value]) { + return try VariableNode(variable: resolvable).render(context) + } + } +} + diff --git a/Sources/Namespace.swift b/Sources/Namespace.swift index 715dd20..1aafa58 100644 --- a/Sources/Namespace.swift +++ b/Sources/Namespace.swift @@ -42,6 +42,7 @@ public class Namespace { registerTag("include", parser: IncludeNode.parse) registerTag("extends", parser: ExtendsNode.parse) registerTag("block", parser: BlockNode.parse) + registerTag("filter", parser: FilterNode.parse) } fileprivate func registerDefaultFilters() { diff --git a/Tests/StencilTests/FilterTagSpec.swift b/Tests/StencilTests/FilterTagSpec.swift new file mode 100644 index 0000000..2470450 --- /dev/null +++ b/Tests/StencilTests/FilterTagSpec.swift @@ -0,0 +1,25 @@ +import Spectre +import Stencil + + +func testFilterTag() { + describe("Filter Tag") { + $0.it("allows you to use a filter") { + let template = Template(templateString: "{% filter uppercase %}Test{% endfilter %}") + let result = try template.render() + try expect(result) == "TEST" + } + + $0.it("allows you to chain filters") { + let template = Template(templateString: "{% filter lowercase|capitalize %}TEST{% endfilter %}") + let result = try template.render() + try expect(result) == "Test" + } + + $0.it("errors without a filter") { + let template = Template(templateString: "{% filter %}Test{% endfilter %}") + try expect(try template.render()).toThrow() + } + + } +} diff --git a/Tests/StencilTests/XCTest.swift b/Tests/StencilTests/XCTest.swift index cea0c34..8e875a0 100644 --- a/Tests/StencilTests/XCTest.swift +++ b/Tests/StencilTests/XCTest.swift @@ -17,6 +17,7 @@ public func stencilTests() { testNowNode() testInclude() testInheritence() + testFilterTag() testStencil() } diff --git a/docs/builtins.rst b/docs/builtins.rst index 767f702..33f8d92 100644 --- a/docs/builtins.rst +++ b/docs/builtins.rst @@ -177,6 +177,26 @@ Will be treated as: ``now`` ~~~~~~~ +``filter`` +~~~~~~~~~~ + +Filters the contents of the block. + +.. code-block:: html+django + + {% filter lowercase %} + This Text Will Be Lowercased. + {% endfilter %} + +You can chain multiple filters with a pipe (`|`). + +.. code-block:: html+django + + {% filter lowercase|capitalize %} + This Text Will First Be Lowercased, Then The First Character Will BE + Capitalised. + {% endfilter %} + ``include`` ~~~~~~~~~~~