1 module handlebars.components.if_;
2 
3 import handlebars.components.base;
4 import handlebars.tpl;
5 
6 import std.exception;
7 import std.conv;
8 
9 version(unittest) {
10   import fluent.asserts;
11   import std.stdio;
12 }
13 
14 /// Component that will handle the if blocks at ctfe
15 class IfComponentCt(Token[] tokens, Properties properties) : HbsComponent!"" {
16   enum ComponentName = "if";
17 
18   private {
19     bool value;
20   }
21 
22   this(bool value) {
23     this.value = value;
24   }
25 
26   ///
27   string render(T, Components...)(T controller) {
28     string result;
29 
30     mixin(genIf());
31 
32     return result;
33   }
34 
35   private static string genIf() {
36     string code = `if(` ~ properties.list[0].toParam() ~ `) {`;
37 
38     size_t start_index;
39     size_t end_index;
40     size_t nested;
41 
42     foreach(token; tokens) {
43       if(token.value == "if" && token.type != Token.Type.openBlock) {
44         nested++;
45       }
46 
47       if(token.value == "if" && token.type != Token.Type.closeBlock) {
48         nested--;
49       }
50 
51       if(nested > 0) {
52         end_index++;
53         continue;
54       }
55 
56       if(token.value == "else" && token.type != Token.Type.plain) {
57         code ~= `result = handlebars.tpl.render!(tokens[`~start_index.to!string~`..`~end_index.to!string~`], T, Components)(controller); }`;
58 
59         if(token.properties.list.length == 0) {
60           code ~= ` else { `;
61         } else {
62           code ~= ` else if(` ~ token.properties.list[0].toParam() ~ `) { `;
63         }
64 
65         start_index = end_index+1;
66       }
67 
68       end_index++;
69     }
70 
71     code ~= `result = handlebars.tpl.render!(tokens[`~start_index.to!string~`..`~end_index.to!string~`], T, Components)(controller);`;
72 
73     return code ~ ` }`;
74   }
75 }
76 
77 /// Component that will handle the if blocks
78 class IfComponent : HbsComponent!"" {
79 
80   private bool value;
81 
82   enum ComponentName = "if";
83 
84   ///
85   this(bool value) {
86     this.value = value;
87   }
88 
89   ///
90   string render(Component, Components...)() {
91     bool shouldOutput = this.value;
92     Token[] result;
93 
94     foreach(item; this.content) {
95       if(item.value == "else" && item.type != Token.Type.plain && shouldOutput) {
96         break;
97       }
98 
99       if(shouldOutput) {
100         result ~= item;
101       }
102 
103       if(item.value == "else" && item.type == Token.Type.value) {
104         shouldOutput = true;
105       }
106 
107       if(item.value == "else" && item.type == Token.Type.helper) {
108         enforce(item.properties.list.length > 1, "Invalid else if format.");
109         enforce(item.properties.list[0].value == "if", "Expected `if` after `else`.");
110 
111         shouldOutput = this.lifecycle.evaluateBoolean(item.properties.list[1].value);
112       }
113     }
114 
115     if(result.length == 0) {
116       return "";
117     }
118 
119     return this.lifecycle.yield(result);
120   }
121 }
122 
123 /// Render an if block if the condition is satisfied
124 unittest {
125   string mockYeld(Token[] tokens) {
126     tokens.length.should.equal(1);
127     return tokens[0].value;
128   }
129 
130   auto mockLifecycle = new MockLifecycle();
131   mockLifecycle.onYield = &mockYeld;
132 
133   auto condition = new IfComponent(true);
134   condition.lifecycle = mockLifecycle;
135 
136   condition.content = [ Token(Token.Type.plain, "true") ];
137 
138   condition.render!(IfComponent).should.equal("true");
139 }
140 
141 /// Don't render an if block if the condition is not satisfied
142 unittest {
143   bool called;
144 
145   string mockYeld(Token[] tokens) {
146     called = true;
147     return tokens[0].value;
148   }
149 
150   auto mockLifecycle = new MockLifecycle();
151   mockLifecycle.onYield = &mockYeld;
152 
153   auto condition = new IfComponent(false);
154   condition.lifecycle = mockLifecycle;
155 
156   condition.content = [ Token(Token.Type.plain, "true") ];
157 
158   called.should.equal(false);
159   condition.render!(IfComponent).should.equal("");
160 }
161 
162 /// Render an if until the else token
163 unittest {
164   string mockYeld(Token[] tokens) {
165     tokens.length.should.equal(1);
166     return tokens[0].value;
167   }
168 
169   auto mockLifecycle = new MockLifecycle();
170   mockLifecycle.onYield = &mockYeld;
171 
172   auto condition = new IfComponent(true);
173   condition.lifecycle = mockLifecycle;
174 
175   condition.content = [
176     Token(Token.Type.plain, "true"),
177     Token(Token.Type.value, "else"),
178     Token(Token.Type.plain, "false") ];
179 
180   condition.render!(IfComponent).should.equal("true");
181 }
182 
183 /// Render the else when the if is not satisfied
184 unittest {
185   string mockYeld(Token[] tokens) {
186     tokens.length.should.equal(1);
187     return tokens[0].value;
188   }
189 
190   auto mockLifecycle = new MockLifecycle();
191   mockLifecycle.onYield = &mockYeld;
192 
193   auto condition = new IfComponent(false);
194   condition.lifecycle = mockLifecycle;
195 
196   condition.content = [
197     Token(Token.Type.plain, "true"),
198     Token(Token.Type.value, "else"),
199     Token(Token.Type.plain, "false") ];
200 
201   condition.render!(IfComponent).should.equal("false");
202 }
203 
204 /// Render the else when the if is not satisfied
205 unittest {
206   string mockYeld(Token[] tokens) {
207     tokens.length.should.equal(1);
208     return tokens[0].value;
209   }
210 
211   bool mockBoolEval(string value) {
212     return value == "true";
213   }
214 
215   auto mockLifecycle = new MockLifecycle();
216   mockLifecycle.onYield = &mockYeld;
217   mockLifecycle.onEvaluateBoolean = &mockBoolEval;
218 
219   auto condition = new IfComponent(false);
220   condition.lifecycle = mockLifecycle;
221 
222   condition.content = [
223     Token(Token.Type.plain, "true"),
224     Token(Token.Type.helper, "else", Properties("if false")),
225     Token(Token.Type.plain, "3"),
226     Token(Token.Type.helper, "else", Properties("if true")),
227     Token(Token.Type.plain, "2") ];
228 
229   condition.render!(IfComponent).should.equal("2");
230 }