Merge branch 'master' into iterating-tuple-arrays
This commit is contained in:
@@ -7,6 +7,11 @@
|
|||||||
- Added support for resolving superclass properties for not-NSObject subclasses
|
- Added support for resolving superclass properties for not-NSObject subclasses
|
||||||
- The `{% for %}` tag can now iterate over tuples, structures and classes via
|
- The `{% for %}` tag can now iterate over tuples, structures and classes via
|
||||||
their stored properties.
|
their stored properties.
|
||||||
|
- Added `split` filter
|
||||||
|
- Allow default string filters to be applied to arrays
|
||||||
|
- Similar filters are suggested when unknown filter is used
|
||||||
|
- Added `indent` filter
|
||||||
|
- Allow using new lines inside tags
|
||||||
- Added support for iterating arrays of tuples
|
- Added support for iterating arrays of tuples
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
// swift-tools-version:3.1
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Stencil",
|
name: "Stencil",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 8),
|
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 9),
|
||||||
|
.Package(url: "https://github.com/kylef/Spectre.git", majorVersion: 0, minor: 8),
|
||||||
// https://github.com/apple/swift-package-manager/pull/597
|
|
||||||
.Package(url: "https://github.com/kylef/Spectre.git", majorVersion: 0, minor: 7),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
10
Package@swift-3.swift
Normal file
10
Package@swift-3.swift
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// swift-tools-version:3.1
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "Stencil",
|
||||||
|
dependencies: [
|
||||||
|
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 8),
|
||||||
|
.Package(url: "https://github.com/kylef/Spectre.git", majorVersion: 0, minor: 7),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -63,6 +63,12 @@ Resources to help you integrate Stencil into a Swift project:
|
|||||||
- [API Reference](http://stencil.fuller.li/en/latest/api.html)
|
- [API Reference](http://stencil.fuller.li/en/latest/api.html)
|
||||||
- [Custom Template Tags and Filters](http://stencil.fuller.li/en/latest/custom-template-tags-and-filters.html)
|
- [Custom Template Tags and Filters](http://stencil.fuller.li/en/latest/custom-template-tags-and-filters.html)
|
||||||
|
|
||||||
|
## Projects that use Stencil
|
||||||
|
|
||||||
|
[Sourcery](https://github.com/krzysztofzablocki/Sourcery),
|
||||||
|
[SwiftGen](https://github.com/SwiftGen/SwiftGen),
|
||||||
|
[Kitura](https://github.com/IBM-Swift/Kitura)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Stencil is licensed under the BSD license. See [LICENSE](LICENSE) for more
|
Stencil is licensed under the BSD license. See [LICENSE](LICENSE) for more
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ class DefaultExtension: Extension {
|
|||||||
registerFilter("uppercase", filter: uppercase)
|
registerFilter("uppercase", filter: uppercase)
|
||||||
registerFilter("lowercase", filter: lowercase)
|
registerFilter("lowercase", filter: lowercase)
|
||||||
registerFilter("join", filter: joinFilter)
|
registerFilter("join", filter: joinFilter)
|
||||||
|
registerFilter("split", filter: splitFilter)
|
||||||
|
registerFilter("indent", filter: indentFilter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
func capitalise(_ value: Any?) -> Any? {
|
func capitalise(_ value: Any?) -> Any? {
|
||||||
|
if let array = value as? [Any?] {
|
||||||
|
return array.map { stringify($0).capitalized }
|
||||||
|
} else {
|
||||||
return stringify(value).capitalized
|
return stringify(value).capitalized
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func uppercase(_ value: Any?) -> Any? {
|
func uppercase(_ value: Any?) -> Any? {
|
||||||
|
if let array = value as? [Any?] {
|
||||||
|
return array.map { stringify($0).uppercased() }
|
||||||
|
} else {
|
||||||
return stringify(value).uppercased()
|
return stringify(value).uppercased()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lowercase(_ value: Any?) -> Any? {
|
func lowercase(_ value: Any?) -> Any? {
|
||||||
|
if let array = value as? [Any?] {
|
||||||
|
return array.map { stringify($0).lowercased() }
|
||||||
|
} else {
|
||||||
return stringify(value).lowercased()
|
return stringify(value).lowercased()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultFilter(value: Any?, arguments: [Any?]) -> Any? {
|
func defaultFilter(value: Any?, arguments: [Any?]) -> Any? {
|
||||||
@@ -40,3 +52,62 @@ func joinFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
||||||
|
guard arguments.count < 2 else {
|
||||||
|
throw TemplateSyntaxError("'split' filter takes a single argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
let separator = stringify(arguments.first ?? " ")
|
||||||
|
if let value = value as? String {
|
||||||
|
return value.components(separatedBy: separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
||||||
|
guard arguments.count <= 3 else {
|
||||||
|
throw TemplateSyntaxError("'indent' filter can take at most 3 arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
var indentWidth = 4
|
||||||
|
if arguments.count > 0 {
|
||||||
|
guard let value = arguments[0] as? Int else {
|
||||||
|
throw TemplateSyntaxError("'indent' filter width argument must be an Integer (\(String(describing: arguments[0])))")
|
||||||
|
}
|
||||||
|
indentWidth = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var indentationChar = " "
|
||||||
|
if arguments.count > 1 {
|
||||||
|
guard let value = arguments[1] as? String else {
|
||||||
|
throw TemplateSyntaxError("'indent' filter indentation argument must be a String (\(String(describing: arguments[1]))")
|
||||||
|
}
|
||||||
|
indentationChar = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var indentFirst = false
|
||||||
|
if arguments.count > 2 {
|
||||||
|
guard let value = arguments[2] as? Bool else {
|
||||||
|
throw TemplateSyntaxError("'indent' filter indentFirst argument must be a Bool")
|
||||||
|
}
|
||||||
|
indentFirst = value
|
||||||
|
}
|
||||||
|
|
||||||
|
let indentation = [String](repeating: indentationChar, count: indentWidth).joined(separator: "")
|
||||||
|
return indent(stringify(value), indentation: indentation, indentFirst: indentFirst)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func indent(_ content: String, indentation: String, indentFirst: Bool) -> String {
|
||||||
|
guard !indentation.isEmpty else { return content }
|
||||||
|
|
||||||
|
var lines = content.components(separatedBy: .newlines)
|
||||||
|
let firstLine = (indentFirst ? indentation : "") + lines.removeFirst()
|
||||||
|
let result = lines.reduce([firstLine]) { (result, line) in
|
||||||
|
return result + [(line.isEmpty ? "" : "\(indentation)\(line)")]
|
||||||
|
}
|
||||||
|
return result.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ struct Lexer {
|
|||||||
guard string.characters.count > 4 else { return "" }
|
guard string.characters.count > 4 else { return "" }
|
||||||
let start = string.index(string.startIndex, offsetBy: 2)
|
let start = string.index(string.startIndex, offsetBy: 2)
|
||||||
let end = string.index(string.endIndex, offsetBy: -2)
|
let end = string.index(string.endIndex, offsetBy: -2)
|
||||||
return String(string[start..<end]).trim(character: " ")
|
let trimmed = String(string[start..<end])
|
||||||
|
.components(separatedBy: "\n")
|
||||||
|
.filter({ !$0.isEmpty })
|
||||||
|
.map({ $0.trim(character: " ") })
|
||||||
|
.joined(separator: " ")
|
||||||
|
return trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
if string.hasPrefix("{{") {
|
if string.hasPrefix("{{") {
|
||||||
|
|||||||
@@ -88,7 +88,26 @@ public class TokenParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw TemplateSyntaxError("Unknown filter '\(name)'")
|
let suggestedFilters = self.suggestedFilters(for: name)
|
||||||
|
if suggestedFilters.isEmpty {
|
||||||
|
throw TemplateSyntaxError("Unknown filter '\(name)'.")
|
||||||
|
} else {
|
||||||
|
throw TemplateSyntaxError("Unknown filter '\(name)'. Found similar filters: \(suggestedFilters.map({ "'\($0)'" }).joined(separator: ", "))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func suggestedFilters(for name: String) -> [String] {
|
||||||
|
let allFilters = environment.extensions.flatMap({ $0.filters.keys })
|
||||||
|
|
||||||
|
let filtersWithDistance = allFilters
|
||||||
|
.map({ (filterName: $0, distance: $0.levenshteinDistance(name)) })
|
||||||
|
// do not suggest filters which names are shorter than the distance
|
||||||
|
.filter({ $0.filterName.characters.count > $0.distance })
|
||||||
|
guard let minDistance = filtersWithDistance.min(by: { $0.distance < $1.distance })?.distance else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
// suggest all filters with the same distance
|
||||||
|
return filtersWithDistance.filter({ $0.distance == minDistance }).map({ $0.filterName })
|
||||||
}
|
}
|
||||||
|
|
||||||
public func compileFilter(_ token: String) throws -> Resolvable {
|
public func compileFilter(_ token: String) throws -> Resolvable {
|
||||||
@@ -96,3 +115,45 @@ public class TokenParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
|
||||||
|
extension String {
|
||||||
|
|
||||||
|
subscript(_ i: Int) -> Character {
|
||||||
|
return self[self.index(self.startIndex, offsetBy: i)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func levenshteinDistance(_ target: String) -> Int {
|
||||||
|
// create two work vectors of integer distances
|
||||||
|
var last, current: [Int]
|
||||||
|
|
||||||
|
// initialize v0 (the previous row of distances)
|
||||||
|
// this row is A[0][i]: edit distance for an empty s
|
||||||
|
// the distance is just the number of characters to delete from t
|
||||||
|
last = [Int](0...target.characters.count)
|
||||||
|
current = [Int](repeating: 0, count: target.characters.count + 1)
|
||||||
|
|
||||||
|
for i in 0..<self.characters.count {
|
||||||
|
// calculate v1 (current row distances) from the previous row v0
|
||||||
|
|
||||||
|
// first element of v1 is A[i+1][0]
|
||||||
|
// edit distance is delete (i+1) chars from s to match empty t
|
||||||
|
current[0] = i + 1
|
||||||
|
|
||||||
|
// use formula to fill in the rest of the row
|
||||||
|
for j in 0..<target.characters.count {
|
||||||
|
current[j+1] = Swift.min(
|
||||||
|
last[j+1] + 1,
|
||||||
|
current[j] + 1,
|
||||||
|
last[j] + (self[i] == target[j] ? 0 : 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy v1 (current row) to v0 (previous row) for next iteration
|
||||||
|
last = current
|
||||||
|
}
|
||||||
|
|
||||||
|
return current[target.characters.count]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ public struct Variable : Equatable, Resolvable {
|
|||||||
if let number = Number(variable) {
|
if let number = Number(variable) {
|
||||||
return number
|
return number
|
||||||
}
|
}
|
||||||
|
// Boolean literal
|
||||||
|
if let bool = Bool(variable) {
|
||||||
|
return bool
|
||||||
|
}
|
||||||
|
|
||||||
for bit in lookup() {
|
for bit in lookup() {
|
||||||
current = normalize(current)
|
current = normalize(current)
|
||||||
|
|||||||
@@ -89,35 +89,48 @@ func testFilter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe("string filters") {
|
||||||
describe("capitalize filter") {
|
$0.context("given string") {
|
||||||
|
$0.it("transforms a string to be capitalized") {
|
||||||
let template = Template(templateString: "{{ name|capitalize }}")
|
let template = Template(templateString: "{{ name|capitalize }}")
|
||||||
|
|
||||||
$0.it("capitalizes a string") {
|
|
||||||
let result = try template.render(Context(dictionary: ["name": "kyle"]))
|
let result = try template.render(Context(dictionary: ["name": "kyle"]))
|
||||||
try expect(result) == "Kyle"
|
try expect(result) == "Kyle"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
describe("uppercase filter") {
|
|
||||||
let template = Template(templateString: "{{ name|uppercase }}")
|
|
||||||
|
|
||||||
$0.it("transforms a string to be uppercase") {
|
$0.it("transforms a string to be uppercase") {
|
||||||
|
let template = Template(templateString: "{{ name|uppercase }}")
|
||||||
let result = try template.render(Context(dictionary: ["name": "kyle"]))
|
let result = try template.render(Context(dictionary: ["name": "kyle"]))
|
||||||
try expect(result) == "KYLE"
|
try expect(result) == "KYLE"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
describe("lowercase filter") {
|
|
||||||
let template = Template(templateString: "{{ name|lowercase }}")
|
|
||||||
|
|
||||||
$0.it("transforms a string to be lowercase") {
|
$0.it("transforms a string to be lowercase") {
|
||||||
|
let template = Template(templateString: "{{ name|lowercase }}")
|
||||||
let result = try template.render(Context(dictionary: ["name": "Kyle"]))
|
let result = try template.render(Context(dictionary: ["name": "Kyle"]))
|
||||||
try expect(result) == "kyle"
|
try expect(result) == "kyle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.context("given array of strings") {
|
||||||
|
$0.it("transforms a string to be capitalized") {
|
||||||
|
let template = Template(templateString: "{{ names|capitalize }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
|
||||||
|
try expect(result) == "[\"Kyle\", \"Kyle\"]"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("transforms a string to be uppercase") {
|
||||||
|
let template = Template(templateString: "{{ names|uppercase }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
|
||||||
|
try expect(result) == "[\"KYLE\", \"KYLE\"]"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("transforms a string to be lowercase") {
|
||||||
|
let template = Template(templateString: "{{ names|lowercase }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["names": ["Kyle", "Kyle"]]))
|
||||||
|
try expect(result) == "[\"kyle\", \"kyle\"]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("default filter") {
|
describe("default filter") {
|
||||||
let template = Template(templateString: "Hello {{ name|default:\"World\" }}")
|
let template = Template(templateString: "Hello {{ name|default:\"World\" }}")
|
||||||
|
|
||||||
@@ -183,4 +196,78 @@ func testFilter() {
|
|||||||
try expect(result) == "OneTwo"
|
try expect(result) == "OneTwo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe("split filter") {
|
||||||
|
let template = Template(templateString: "{{ value|split:\", \" }}")
|
||||||
|
|
||||||
|
$0.it("split a string into array") {
|
||||||
|
let result = try template.render(Context(dictionary: ["value": "One, Two"]))
|
||||||
|
try expect(result) == "[\"One\", \"Two\"]"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("can split without arguments") {
|
||||||
|
let template = Template(templateString: "{{ value|split }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["value": "One, Two"]))
|
||||||
|
try expect(result) == "[\"One,\", \"Two\"]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
describe("filter suggestion") {
|
||||||
|
|
||||||
|
$0.it("made for unknown filter") {
|
||||||
|
let template = Template(templateString: "{{ value|unknownFilter }}")
|
||||||
|
let expectedError = TemplateSyntaxError("Unknown filter 'unknownFilter'. Found similar filters: 'knownFilter'")
|
||||||
|
|
||||||
|
let filterExtension = Extension()
|
||||||
|
filterExtension.registerFilter("knownFilter") { value, _ in value }
|
||||||
|
|
||||||
|
try expect(template.render(Context(dictionary: [:], environment: Environment(extensions: [filterExtension])))).toThrow(expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("made for multiple similar filters") {
|
||||||
|
let template = Template(templateString: "{{ value|lowerFirst }}")
|
||||||
|
let expectedError = TemplateSyntaxError("Unknown filter 'lowerFirst'. Found similar filters: 'lowerFirstWord', 'lowercase'")
|
||||||
|
|
||||||
|
let filterExtension = Extension()
|
||||||
|
filterExtension.registerFilter("lowerFirstWord") { value, _ in value }
|
||||||
|
filterExtension.registerFilter("lowerFirstLetter") { value, _ in value }
|
||||||
|
|
||||||
|
try expect(template.render(Context(dictionary: [:], environment: Environment(extensions: [filterExtension])))).toThrow(expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("not made when can't find similar filter") {
|
||||||
|
let template = Template(templateString: "{{ value|unknownFilter }}")
|
||||||
|
let expectedError = TemplateSyntaxError("Unknown filter 'unknownFilter'.")
|
||||||
|
try expect(template.render(Context(dictionary: [:]))).toThrow(expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
describe("indent filter") {
|
||||||
|
$0.it("indents content") {
|
||||||
|
let template = Template(templateString: "{{ value|indent:2 }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["value": "One\nTwo"]))
|
||||||
|
try expect(result) == "One\n Two"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("can indent with arbitrary character") {
|
||||||
|
let template = Template(templateString: "{{ value|indent:2,\"\t\" }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["value": "One\nTwo"]))
|
||||||
|
try expect(result) == "One\n\t\tTwo"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("can indent first line") {
|
||||||
|
let template = Template(templateString: "{{ value|indent:2,\" \",true }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["value": "One\nTwo"]))
|
||||||
|
try expect(result) == " One\n Two"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("does not indent empty lines") {
|
||||||
|
let template = Template(templateString: "{{ value|indent }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["value": "One\n\n\nTwo\n\n"]))
|
||||||
|
try expect(result) == "One\n\n\n Two\n\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,14 @@ func testFilterTag() {
|
|||||||
try expect(try template.render()).toThrow()
|
try expect(try template.render()).toThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.it("can render filters with arguments") {
|
||||||
|
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: "{% filter split:\",\"|join:\";\" %}{{ items|join:\",\" }}{% endfilter %}", context: ["items": [1, 2]])
|
||||||
|
try expect(result) == "1;2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,5 +64,27 @@ func testLexer() {
|
|||||||
let lexer = Lexer(templateString: "{{}}")
|
let lexer = Lexer(templateString: "{{}}")
|
||||||
let _ = lexer.tokenize()
|
let _ = lexer.tokenize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.it("can tokenize with new lines") {
|
||||||
|
let lexer = Lexer(templateString:
|
||||||
|
"My name is {%\n" +
|
||||||
|
" if name\n" +
|
||||||
|
" and\n" +
|
||||||
|
" name\n" +
|
||||||
|
"%}{{\n" +
|
||||||
|
"name\n" +
|
||||||
|
"}}{%\n" +
|
||||||
|
"endif %}.")
|
||||||
|
|
||||||
|
let tokens = lexer.tokenize()
|
||||||
|
|
||||||
|
try expect(tokens.count) == 5
|
||||||
|
try expect(tokens[0]) == Token.text(value: "My name is ")
|
||||||
|
try expect(tokens[1]) == Token.block(value: "if name and name")
|
||||||
|
try expect(tokens[2]) == Token.variable(value: "name")
|
||||||
|
try expect(tokens[3]) == Token.block(value: "endif")
|
||||||
|
try expect(tokens[4]) == Token.text(value: ".")
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import Spectre
|
|||||||
|
|
||||||
#if os(OSX)
|
#if os(OSX)
|
||||||
@objc class Superclass: NSObject {
|
@objc class Superclass: NSObject {
|
||||||
let name = "Foo"
|
@objc let name = "Foo"
|
||||||
}
|
}
|
||||||
@objc class Object : Superclass {
|
@objc class Object : Superclass {
|
||||||
let title = "Hello World"
|
@objc let title = "Hello World"
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -71,6 +71,13 @@ func testVariable() {
|
|||||||
try expect(result) == 3.14
|
try expect(result) == 3.14
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.it("can resolve boolean literal") {
|
||||||
|
try expect(Variable("true").resolve(context) as? Bool) == true
|
||||||
|
try expect(Variable("false").resolve(context) as? Bool) == false
|
||||||
|
try expect(Variable("0").resolve(context) as? Int) == 0
|
||||||
|
try expect(Variable("1").resolve(context) as? Int) == 1
|
||||||
|
}
|
||||||
|
|
||||||
$0.it("can resolve a string variable") {
|
$0.it("can resolve a string variable") {
|
||||||
let variable = Variable("name")
|
let variable = Variable("name")
|
||||||
let result = try variable.resolve(context) as? String
|
let result = try variable.resolve(context) as? String
|
||||||
|
|||||||
Reference in New Issue
Block a user