Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow conditions in variable node #243

Merged
merged 4 commits into from
Sep 22, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

### New Features

_None_
- Now you can conditionally render variables with `{{ variable if condition }}`, which is a shorthand for `{% if condition %}{{ variable }}{% endif %}`. You can also use `else` like `{{ variable1 if condition else variable2 }}`, which is a shorthand for `{% if condition %}{{ variable1 }}{% else %}{{ variable2 }}{% endif %}`
[Ilya Puchka](https://github.com/ilyapuchka)
[#243](https://github.com/stencilproject/Stencil/pull/243)

### Internal Changes

Expand Down
46 changes: 46 additions & 0 deletions Sources/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,64 @@ public protocol Resolvable {
public class VariableNode : NodeType {
public let variable: Resolvable
public var token: Token?
let condition: Expression?
let elseExpression: Resolvable?

class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
var components = token.components()

func hasToken(_ token: String, at index: Int) -> Bool {
return components.count > (index + 1) && components[index] == token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find ~= harder to grasp than > 🤷‍♂️

}

let condition: Expression?
let elseExpression: Resolvable?

if hasToken("if", at: 1) {
let components = components.suffix(from: 2)
if let elseIndex = components.index(of: "else") {
condition = try parseExpression(components: Array(components.prefix(upTo: elseIndex)), tokenParser: parser, token: token)
let elseToken = components.suffix(from: elseIndex.advanced(by: 1)).joined(separator: " ")
elseExpression = try parser.compileResolvable(elseToken, containedIn: token)
} else {
condition = try parseExpression(components: Array(components), tokenParser: parser, token: token)
elseExpression = nil
}
} else {
condition = nil
elseExpression = nil
}

let filter = try parser.compileResolvable(components[0], containedIn: token)
return VariableNode(variable: filter, token: token, condition: condition, elseExpression: elseExpression)
}

public init(variable: Resolvable, token: Token? = nil) {
self.variable = variable
self.token = token
self.condition = nil
self.elseExpression = nil
}

init(variable: Resolvable, token: Token? = nil, condition: Expression?, elseExpression: Resolvable?) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not add the condition and elseExpression parameters to the public init above? (and make them nil by default)

Copy link
Collaborator Author

@ilyapuchka ilyapuchka Sep 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expression is not public, at least in this branch. TBH I don't even know why VariableNode needs to be public, but didn't want to make unrelated changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

self.variable = variable
self.token = token
self.condition = condition
self.elseExpression = elseExpression
}

public init(variable: String, token: Token? = nil) {
self.variable = Variable(variable)
self.token = token
self.condition = nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

self.elseExpression = nil
}

public func render(_ context: Context) throws -> String {
if let condition = self.condition, try condition.evaluate(context: context) == false {
return try elseExpression?.resolve(context).map(stringify) ?? ""
}

let result = try variable.resolve(context)
return stringify(result)
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ public class TokenParser {
case .text(let text, _):
nodes.append(TextNode(text: text))
case .variable:
let filter = try compileResolvable(token.contents, containedIn: token)
nodes.append(VariableNode(variable: filter, token: token))
try nodes.append(VariableNode.parse(self, token: token))
case .block:
if let parse_until = parse_until , parse_until(self, token) {
prependToken(token)
Expand Down
19 changes: 19 additions & 0 deletions Tests/StencilTests/VariableSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,23 @@ func testVariable() {
}

}

describe("inline if expression") {

$0.it("can conditionally render variable") {
let template: Template = "{{ variable if variable|uppercase == \"A\" }}"
try expect(template.render(Context(dictionary: ["variable": "a"]))) == "a"
try expect(template.render(Context(dictionary: ["variable": "b"]))) == ""
}

$0.it("can render with else expression") {
let template: Template = "{{ variable if variable|uppercase == \"A\" else fallback|uppercase }}"
try expect(template.render(Context(dictionary: ["variable": "b", "fallback": "c"]))) == "C"
}

$0.it("throws when used invalid condition") {
let template: Template = "{{ variable if variable \"A\" }}"
try expect(template.render(Context(dictionary: ["variable": "a"]))).toThrow()
}
}
}