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 }