Skip to content
This repository was archived by the owner on Apr 8, 2018. It is now read-only.

Commit 9dc8d5a

Browse files
committed
Introduce context and renderer. This can be considered the first alpha. We are able to render a mustache template now.
1 parent 9938a28 commit 9dc8d5a

File tree

6 files changed

+226
-16
lines changed

6 files changed

+226
-16
lines changed

Diff for: Mustache.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
AB3B66DA1C2848CA00152024 /* Scanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB3B66D71C2848CA00152024 /* Scanner.swift */; };
1313
AB5A87571C2A92260095E648 /* Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB5A87561C2A92260095E648 /* Template.swift */; };
1414
AB5A87591C2A92FB0095E648 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB5A87581C2A92FB0095E648 /* Operation.swift */; };
15+
AB5A875B1C2B74310095E648 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB5A875A1C2B74310095E648 /* Context.swift */; };
1516
AB9DCEBE1C26A8BC00B277FC /* Mustache.h in Headers */ = {isa = PBXBuildFile; fileRef = AB9DCEBD1C26A8BC00B277FC /* Mustache.h */; settings = {ATTRIBUTES = (Public, ); }; };
1617
AB9DCEC51C26A8BD00B277FC /* Mustache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB9DCEBA1C26A8BC00B277FC /* Mustache.framework */; };
1718
AB9DCECA1C26A8BD00B277FC /* MustacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB9DCEC91C26A8BD00B277FC /* MustacheTests.swift */; };
19+
ABCB02BF1C2BA45F00BED745 /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCB02BE1C2BA45F00BED745 /* Renderer.swift */; };
1820
ABCF38101C29655800B32850 /* Delimiter.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCF380F1C29655800B32850 /* Delimiter.swift */; };
1921
ABCF38121C29659D00B32850 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCF38111C29659D00B32850 /* Token.swift */; };
2022
ABCF38141C2967DA00B32850 /* Lookahead.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCF38131C2967DA00B32850 /* Lookahead.swift */; };
@@ -38,12 +40,14 @@
3840
AB3B66D71C2848CA00152024 /* Scanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scanner.swift; sourceTree = "<group>"; };
3941
AB5A87561C2A92260095E648 /* Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Template.swift; sourceTree = "<group>"; };
4042
AB5A87581C2A92FB0095E648 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
43+
AB5A875A1C2B74310095E648 /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = "<group>"; };
4144
AB9DCEBA1C26A8BC00B277FC /* Mustache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Mustache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4245
AB9DCEBD1C26A8BC00B277FC /* Mustache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Mustache.h; sourceTree = "<group>"; };
4346
AB9DCEBF1C26A8BC00B277FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4447
AB9DCEC41C26A8BD00B277FC /* MustacheTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MustacheTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4548
AB9DCEC91C26A8BD00B277FC /* MustacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MustacheTests.swift; sourceTree = "<group>"; };
4649
AB9DCECB1C26A8BD00B277FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50+
ABCB02BE1C2BA45F00BED745 /* Renderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = "<group>"; };
4751
ABCF380F1C29655800B32850 /* Delimiter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Delimiter.swift; sourceTree = "<group>"; };
4852
ABCF38111C29659D00B32850 /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
4953
ABCF38131C2967DA00B32850 /* Lookahead.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lookahead.swift; sourceTree = "<group>"; };
@@ -83,6 +87,8 @@
8387
ABCF38171C29850800B32850 /* Stack.swift */,
8488
AB5A87561C2A92260095E648 /* Template.swift */,
8589
AB5A87581C2A92FB0095E648 /* Operation.swift */,
90+
AB5A875A1C2B74310095E648 /* Context.swift */,
91+
ABCB02BE1C2BA45F00BED745 /* Renderer.swift */,
8692
);
8793
path = Sources;
8894
sourceTree = "<group>";
@@ -236,6 +242,8 @@
236242
ABCF38121C29659D00B32850 /* Token.swift in Sources */,
237243
ABCF38101C29655800B32850 /* Delimiter.swift in Sources */,
238244
AB5A87591C2A92FB0095E648 /* Operation.swift in Sources */,
245+
ABCB02BF1C2BA45F00BED745 /* Renderer.swift in Sources */,
246+
AB5A875B1C2B74310095E648 /* Context.swift in Sources */,
239247
AB5A87571C2A92260095E648 /* Template.swift in Sources */,
240248
AB3B66DA1C2848CA00152024 /* Scanner.swift in Sources */,
241249
ABCF38181C29850800B32850 /* Stack.swift in Sources */,

Diff for: MustacheTests/MustacheTests.swift

+44-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ class MustacheTests: XCTestCase {
1616
let tokens = try lexer.tokenize()
1717
let parser = Parser(tokens: tokens)
1818
let mainOperation = try parser.parse()
19-
print(mainOperation)
19+
let template = try! Template(operation: mainOperation)
20+
let context: DictionaryContext = [
21+
"name": "Stan"
22+
]
23+
print(template.render(context))
2024
} catch {
2125
XCTFail()
2226
}
@@ -28,19 +32,48 @@ class MustacheTests: XCTestCase {
2832
let tokens = try lexer.tokenize()
2933
let parser = Parser(tokens: tokens)
3034
let mainOperation = try parser.parse()
31-
print(mainOperation)
35+
let template = try! Template(operation: mainOperation)
36+
let context: DictionaryContext = [
37+
"value": "$5"
38+
]
39+
print(template.render(context))
3240
} catch {
3341
XCTFail()
3442
}
3543
}
3644

3745
func testExample3() {
38-
var lexer = Lexer("{{#in_ca}}\nWell, {{taxed_value}} dollars, after taxes.\n{{/in_ca}}")
46+
var lexer = Lexer("{{#in_ca}}\nWell, {{taxed_value}} dollars, after taxes.\n{{/in_ca}}{{^in_ca}}\nNo tax burden!\n{{/in_ca}}")
3947
do {
4048
let tokens = try lexer.tokenize()
4149
let parser = Parser(tokens: tokens)
4250
let mainOperation = try parser.parse()
43-
print(mainOperation)
51+
let template = try! Template(operation: mainOperation)
52+
let context: DictionaryContext = [
53+
"in_ca": ArrayContext([
54+
DictionaryContext([
55+
"taxed_value" : "$100"
56+
]),
57+
DictionaryContext([
58+
"taxed_value" : "$200"
59+
]),
60+
DictionaryContext([
61+
"taxed_value" : "$300"
62+
]),
63+
DictionaryContext([
64+
"taxed_value" : "$400"
65+
]),
66+
])
67+
]
68+
print(template.render(context))
69+
let context2: DictionaryContext = [
70+
"in_ca": DictionaryContext([
71+
"taxed_value" : "$100"
72+
]),
73+
]
74+
print(template.render(context2))
75+
let context3: DictionaryContext = DictionaryContext(Dictionary<String, Contextual>())
76+
print(template.render(context3))
4477
} catch {
4578
XCTFail()
4679
}
@@ -52,7 +85,13 @@ class MustacheTests: XCTestCase {
5285
let tokens = try lexer.tokenize()
5386
let parser = Parser(tokens: tokens)
5487
let mainOperation = try parser.parse()
55-
print(mainOperation)
88+
let template = try! Template(operation: mainOperation)
89+
let context: DictionaryContext = [
90+
"name": "Stan",
91+
"age": "29",
92+
"company": "trifia",
93+
]
94+
print(template.render(context))
5695
} catch {
5796
XCTFail()
5897
}

Diff for: Sources/Context.swift

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//
2+
// Context.swift
3+
// Mustache
4+
//
5+
// Created by Stan Chang Khin Boon on 24/12/15.
6+
// Copyright © 2015 Trifia. All rights reserved.
7+
//
8+
9+
protocol Contextual : CustomStringConvertible {
10+
func contextForName(name: String) -> Contextual?
11+
func renderWithOperation(operation: Operation) -> String?
12+
func renderWithOperations(operations: [Operation]) -> String?
13+
}
14+
15+
// MARK: Extensions
16+
17+
// The extension will use the renderer to render instead.
18+
19+
extension String : Contextual {
20+
func contextForName(name: String) -> Contextual? {
21+
return nil
22+
}
23+
public var description: String {
24+
return self
25+
}
26+
func renderWithOperation(operation: Operation) -> String? {
27+
return Renderer.renderContext(self, withOperation: operation)
28+
}
29+
func renderWithOperations(operations: [Operation]) -> String? {
30+
return Renderer.renderContext(self, withOperations: operations)
31+
}
32+
}
33+
34+
// FIXME: ([email protected]) [Swift 2.1 Limitation, rdar://23196859] Constrainted extension can't inherited protocol
35+
//extension Dictionary /*: Contextual*/ where Key:Hashable, Value:Contextual {
36+
//}
37+
// Workaround for the above limitation…
38+
struct DictionaryContext : Contextual, DictionaryLiteralConvertible {
39+
typealias Key = String
40+
typealias Value = Contextual
41+
42+
let _value: [Key : Value]
43+
init(_ value: [Key : Value]) {
44+
self._value = value
45+
}
46+
init(dictionaryLiteral elements: (Key, Value)...) {
47+
// FIXME: ([email protected]) Swift 2.1 limitation…
48+
//self._value = Dictionary<Key, Value>(dictionaryLiteral: elements)
49+
var dictionary = Dictionary<Key, Value>()
50+
for (key, value) in elements {
51+
dictionary[key] = value
52+
}
53+
self.init(dictionary)
54+
}
55+
func contextForName(name: String) -> Contextual? {
56+
return self._value[name]
57+
}
58+
var description: String {
59+
return self._value.description
60+
}
61+
func renderWithOperation(operation: Operation) -> String? {
62+
return Renderer.renderContext(self, withOperation: operation)
63+
}
64+
func renderWithOperations(operations: [Operation]) -> String? {
65+
return Renderer.renderContext(self, withOperations: operations)
66+
}
67+
}
68+
69+
// FIXME: ([email protected]) [Swift 2.1 Limitation, rdar://23196859] Constrainted extension can't inherited protocol
70+
//extension Array /*: Contextual*/ where Element:Contextual {
71+
//}
72+
// Workaround for the above limitation…
73+
struct ArrayContext : Contextual, ArrayLiteralConvertible {
74+
typealias Element = Contextual
75+
let _value: Array<Element>
76+
init(_ value: Array<Element>) {
77+
self._value = value
78+
}
79+
init(arrayLiteral elements: Element...) {
80+
self._value = elements
81+
}
82+
func contextForName(name: String) -> Contextual? {
83+
return nil
84+
}
85+
var description: String {
86+
return self._value.description
87+
}
88+
func renderWithOperation(operation: Operation) -> String? {
89+
return Renderer.renderContexts(self._value, withOperation: operation)
90+
}
91+
func renderWithOperations(operations: [Operation]) -> String? {
92+
return Renderer.renderContexts(self._value, withOperations: operations)
93+
}
94+
}

Diff for: Sources/Parser.swift

+7-9
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,12 @@ struct Parser {
4949
stack.push((scope, operations))
5050
(scope, operations) = (Scope.Section(name: name, inverted: inverted), [Operation]())
5151
case .SectionEnd(let endName):
52-
switch scope {
53-
case .Section(let name, let inverted) where name == endName:
54-
let section = inverted ? Operation.InvertedSection(name: name, operations: operations) : Operation.Section(name: name, operations: operations)
55-
(scope, operations) = stack.pop()
56-
operations.append(section)
57-
default:
52+
guard case Scope.Section(let name, let inverted) = scope where name == endName else {
5853
throw Error.SyntaxError("Section ended before it began…")
5954
}
55+
let section = inverted ? Operation.InvertedSection(name: name, operations: operations) : Operation.Section(name: name, operations: operations)
56+
(scope, operations) = stack.pop()
57+
operations.append(section)
6058
case .Partial(let name):
6159
operations.append(Operation.RenderPartial(name: name))
6260
break;
@@ -68,10 +66,10 @@ struct Parser {
6866
break
6967
}
7068
}
71-
switch scope {
72-
case .Main:
69+
70+
if case Scope.Main = scope {
7371
return Operation.Main(operations: operations)
74-
default:
72+
} else {
7573
throw Error.SyntaxError("Section not closed before file ended…")
7674
}
7775
}

Diff for: Sources/Renderer.swift

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// Renderer.swift
3+
// Mustache
4+
//
5+
// Created by Stan Chang Khin Boon on 24/12/15.
6+
// Copyright © 2015 Trifia. All rights reserved.
7+
//
8+
9+
// Renderer render the operation(s) with the provided context if possible.
10+
// Otherwise, it give control to the context, which based on its underlying implementation provide some form of control flow. The context might pass back control to renderer to attempt to rendering its subcontext. This resultant in a recursive rendering process.
11+
struct Renderer {
12+
static func renderContext(context: Contextual, withOperation operation: Operation) -> String? {
13+
switch operation {
14+
case .Main(operations: let operations):
15+
return context.renderWithOperations(operations)
16+
case .RenderPartial(name: _):
17+
// FIXME: ([email protected]) Handle partial
18+
return nil
19+
case .RenderValue(value: let value):
20+
return value
21+
case .RenderVariable(name: let name, escaped: _):
22+
// FIXME: ([email protected]) Handle escaped
23+
return context.contextForName(name)?.description
24+
case .Section(name: let name, operations: let operations):
25+
guard let newContext = context.contextForName(name) else {
26+
return nil
27+
}
28+
return newContext.renderWithOperations(operations)
29+
case .InvertedSection(name: let name, operations: let operations):
30+
guard context.contextForName(name) == nil else {
31+
return nil
32+
}
33+
return context.renderWithOperations(operations)
34+
}
35+
}
36+
37+
static func renderContext(context: Contextual, withOperations operations: [Operation]) -> String? {
38+
var stringBuffer = ""
39+
for operation in operations {
40+
if let renderedString = context.renderWithOperation(operation) {
41+
stringBuffer.appendContentsOf(renderedString)
42+
}
43+
}
44+
return stringBuffer.isEmpty ? nil : stringBuffer
45+
}
46+
47+
static func renderContexts(contexts: [Contextual], withOperation operation: Operation) -> String? {
48+
var stringBuffer = ""
49+
for context in contexts {
50+
if let renderedString = renderContext(context, withOperation: operation) {
51+
stringBuffer.appendContentsOf(renderedString)
52+
}
53+
}
54+
return stringBuffer.isEmpty ? nil : stringBuffer
55+
}
56+
57+
static func renderContexts(contexts: [Contextual], withOperations operations: [Operation]) -> String? {
58+
var stringBuffer = ""
59+
for context in contexts {
60+
if let renderedString = renderContext(context, withOperations: operations) {
61+
stringBuffer.appendContentsOf(renderedString)
62+
}
63+
}
64+
return stringBuffer.isEmpty ? nil : stringBuffer
65+
}
66+
}

Diff for: Sources/Template.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@
2424
struct Template {
2525
let operation: Operation
2626

27-
func compile() {
27+
init(operation: Operation) throws {
28+
guard case Operation.Main(operations: _) = operation else {
29+
throw Error.SyntaxError("Template can only hold main operation as it root.")
30+
}
31+
self.operation = operation
2832
}
2933

30-
func render(context: Any) {
34+
func render(context: Contextual) -> String? {
35+
return context.renderWithOperation(operation)
3136
}
3237
}

0 commit comments

Comments
 (0)