diff --git a/CHANGELOG.md b/CHANGELOG.md index 647f89b9..983af1e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ _None_ ### Enhancements -_None_ +- Allow shorthand syntax for filter tags, i.e. `{% uppercase %}String{% enduppercase %}`. + [Ilya Puchka](https://github.com/ilyapuchka) + [#257](https://github.com/stencilproject/Stencil/pull/257) ### Deprecations diff --git a/Sources/Extension.swift b/Sources/Extension.swift index d2ee907f..e0198b90 100644 --- a/Sources/Extension.swift +++ b/Sources/Extension.swift @@ -57,7 +57,7 @@ class DefaultExtension: Extension { registerTag("include", parser: IncludeNode.parse) registerTag("extends", parser: ExtendsNode.parse) registerTag("block", parser: BlockNode.parse) - registerTag("filter", parser: FilterNode.parse) + registerTag("filter", parser: FilterNode.parse(tag: "filter")) } fileprivate func registerDefaultFilters() { diff --git a/Sources/FilterTag.swift b/Sources/FilterTag.swift index 9371b3c3..74e901a8 100644 --- a/Sources/FilterTag.swift +++ b/Sources/FilterTag.swift @@ -3,21 +3,23 @@ class FilterNode : NodeType { let nodes: [NodeType] let token: Token? - 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") + class func parse(tag: String) -> Extension.TagParser { + return { parser, token in + 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(["end\(tag)"])) + + guard parser.nextToken() != nil else { + throw TemplateSyntaxError("`end\(tag)` was not found.") + } + + let resolvable = try parser.compileFilter("filter_value|\(bits[bits.count - 1])", containedIn: token) + return FilterNode(nodes: blocks, resolvable: resolvable, token: token) } - - let blocks = try parser.parse(until(["endfilter"])) - - guard parser.nextToken() != nil else { - throw TemplateSyntaxError("`endfilter` was not found.") - } - - let resolvable = try parser.compileFilter("filter_value|\(bits[1])", containedIn: token) - return FilterNode(nodes: blocks, resolvable: resolvable, token: token) } init(nodes: [NodeType], resolvable: Resolvable, token: Token) { diff --git a/Sources/Parser.swift b/Sources/Parser.swift index d95f546a..a0d86681 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -82,8 +82,13 @@ public class TokenParser { return filter } } - - throw TemplateSyntaxError("Unknown template tag '\(name)'") + + let (name, _) = parseFilterComponents(token: name) + if environment.extensions.contains(where: { $0.filters[name] != nil }) { + return FilterNode.parse(tag: name) + } else { + throw TemplateSyntaxError("Unknown template tag '\(name)'") + } } func findFilter(_ name: String) throws -> FilterType { diff --git a/Tests/StencilTests/FilterTagSpec.swift b/Tests/StencilTests/FilterTagSpec.swift index 54237479..69b6b927 100644 --- a/Tests/StencilTests/FilterTagSpec.swift +++ b/Tests/StencilTests/FilterTagSpec.swift @@ -46,6 +46,24 @@ class FilterTagTests: XCTestCase { """, context: ["items": ["\"1\"", "\"2\""]]) try expect(result) == "1,2" } + + $0.it("can render filter with shorthand syntax") { + let template = Template(templateString: "{% uppercase %}Test{% enduppercase %}") + let result = try template.render() + try expect(result) == "TEST" + } + + $0.it("can render multiple filters with shorthand syntax") { + let ext = Extension() + ext.registerFilter("split", filter: { + return ($0 as! String).components(separatedBy: $1[0] as! String) + }) + let env = Environment(extensions: [ext]) + let result = try env.renderTemplate(string: """ + {% split:","|join:";" %}{{ items|join:"," }}{% endsplit %} + """, context: ["items": [1, 2]]) + try expect(result) == "1;2" + } } } } diff --git a/docs/builtins.rst b/docs/builtins.rst index 0300783b..38a4f980 100644 --- a/docs/builtins.rst +++ b/docs/builtins.rst @@ -264,6 +264,15 @@ You can chain multiple filters with a pipe (`|`). Capitalised. {% endfilter %} +You can use shorthand syntax by dropping `filter` keyword and changing closing tag: + +.. code-block:: html+django + + {% lowercase %} + This Text Will First Be Lowercased, Then The First Character Will BE + Capitalised. + {% endlowercase %} + ``include`` ~~~~~~~~~~~