diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e7a965..11c1762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ [Ilya Puchka](https://github.com/ilyapuchka) [#243](https://github.com/stencilproject/Stencil/pull/243) +- Now you can access string characters by index or get string length the same was as if it was an array, i.e. `{{ 'string'.first }}`, `{{ 'string'.last }}`, `{{ 'string'.1 }}`, `{{ 'string'.count }}`. + [Ilya Puchka](https://github.com/ilyapuchka) + [#245](https://github.com/stencilproject/Stencil/pull/245) + ### Internal Changes - Updated the codebase to use Swift 4 features. diff --git a/Sources/Variable.swift b/Sources/Variable.swift index d9620ed..eb30060 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -87,19 +87,9 @@ public struct Variable : Equatable, Resolvable { current = dictionary[bit] } } else if let array = current as? [Any] { - if let index = Int(bit) { - if index >= 0 && index < array.count { - current = array[index] - } else { - current = nil - } - } else if bit == "first" { - current = array.first - } else if bit == "last" { - current = array.last - } else if bit == "count" { - current = array.count - } + current = resolveCollection(array, bit: bit) + } else if let string = current as? String { + current = resolveCollection(string, bit: bit) } else if let object = current as? NSObject { // NSKeyValueCoding #if os(Linux) return nil @@ -128,6 +118,24 @@ public struct Variable : Equatable, Resolvable { } } +private func resolveCollection(_ collection: T, bit: String) -> Any? { + if let index = Int(bit) { + if index >= 0 && index < collection.count { + return collection[collection.index(collection.startIndex, offsetBy: index)] + } else { + return nil + } + } else if bit == "first" { + return collection.first + } else if bit == "last" { + return collection[collection.index(collection.endIndex, offsetBy: -1)] + } else if bit == "count" { + return collection.count + } else { + return nil + } +} + /// A structure used to represet range of two integer values expressed as `from...to`. /// Values should be numbers (they will be converted to integers). /// Rendering this variable produces array from range `from...to`. diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift index 8d3c4ac..2d052bb 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -86,42 +86,98 @@ func testVariable() { try expect(result) == "Kyle" } - $0.it("can resolve an item from a dictionary") { - let variable = Variable("profiles.github") - let result = try variable.resolve(context) as? String - try expect(result) == "kylef" + $0.context("given string") { + $0.it("can resolve an item via it's index") { + let variable = Variable("name.0") + let result = try variable.resolve(context) as? Character + try expect(result) == "K" + + let variable1 = Variable("name.1") + let result1 = try variable1.resolve(context) as? Character + try expect(result1) == "y" + } + + $0.it("can resolve an item via unknown index") { + let variable = Variable("name.5") + let result = try variable.resolve(context) as? Character + try expect(result).to.beNil() + + let variable1 = Variable("name.-5") + let result1 = try variable1.resolve(context) as? Character + try expect(result1).to.beNil() + } + + $0.it("can resolve the first item") { + let variable = Variable("name.first") + let result = try variable.resolve(context) as? Character + try expect(result) == "K" + } + + $0.it("can resolve the last item") { + let variable = Variable("name.last") + let result = try variable.resolve(context) as? Character + try expect(result) == "e" + } + + $0.it("can get the characters count") { + let variable = Variable("name.count") + let result = try variable.resolve(context) as? Int + try expect(result) == 4 + } } - $0.it("can resolve an item from an array via it's index") { - let variable = Variable("contacts.0") - let result = try variable.resolve(context) as? String - try expect(result) == "Katie" + $0.context("given dictionary") { + $0.it("can resolve an item") { + let variable = Variable("profiles.github") + let result = try variable.resolve(context) as? String + try expect(result) == "kylef" + } + + $0.it("can get the count") { + let variable = Variable("profiles.count") + let result = try variable.resolve(context) as? Int + try expect(result) == 1 + } + } + + $0.context("given array") { + $0.it("can resolve an item via it's index") { + let variable = Variable("contacts.0") + let result = try variable.resolve(context) as? String + try expect(result) == "Katie" let variable1 = Variable("contacts.1") let result1 = try variable1.resolve(context) as? String try expect(result1) == "Carlton" - } + } - $0.it("can resolve an item from an array via unknown index") { - let variable = Variable("contacts.5") - let result = try variable.resolve(context) as? String - try expect(result).to.beNil() + $0.it("can resolve an item via unknown index") { + let variable = Variable("contacts.5") + let result = try variable.resolve(context) as? String + try expect(result).to.beNil() - let variable1 = Variable("contacts.-5") - let result1 = try variable1.resolve(context) as? String - try expect(result1).to.beNil() - } + let variable1 = Variable("contacts.-5") + let result1 = try variable1.resolve(context) as? String + try expect(result1).to.beNil() + } - $0.it("can resolve the first item from an array") { - let variable = Variable("contacts.first") - let result = try variable.resolve(context) as? String - try expect(result) == "Katie" - } + $0.it("can resolve the first item") { + let variable = Variable("contacts.first") + let result = try variable.resolve(context) as? String + try expect(result) == "Katie" + } - $0.it("can resolve the last item from an array") { - let variable = Variable("contacts.last") - let result = try variable.resolve(context) as? String - try expect(result) == "Carlton" + $0.it("can resolve the last item") { + let variable = Variable("contacts.last") + let result = try variable.resolve(context) as? String + try expect(result) == "Carlton" + } + + $0.it("can get the count") { + let variable = Variable("contacts.count") + let result = try variable.resolve(context) as? Int + try expect(result) == 2 + } } $0.it("can resolve a property with reflection") { @@ -130,12 +186,6 @@ func testVariable() { try expect(result) == "Kyle" } - $0.it("can get the count of a dictionary") { - let variable = Variable("profiles.count") - let result = try variable.resolve(context) as? Int - try expect(result) == 1 - } - #if os(OSX) $0.it("can resolve a value via KVO") { let variable = Variable("object.title") diff --git a/docs/templates.rst b/docs/templates.rst index 147be45..7094ae6 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -20,9 +20,9 @@ following lookup: - Context lookup - Dictionary lookup -- Array lookup (first, last, count, index) +- Array and string lookup (first, last, count, by index) - Key value coding lookup -- Type introspection +- Type introspection (via ``Mirror``) For example, if `people` was an array: