1 /**
2 * Copyright © DiamondMVC 2019
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.views.viewparser;
7 
8 import diamond.core.apptype;
9 
10 static if (isWebServer || !isWeb)
11 {
12   import diamond.views.viewformats;
13   import diamond.templates;
14 
15   import std..string : strip, format;
16   import std.array : replace, split;
17   import std.conv : to;
18 
19   /**
20   * Parses the view parts into a view class.
21   * Params:
22   *   allParts = All the parsed parts of the view template.
23   *   viewName = The name of the view.
24   *   route =    The route of the view. (This is null if no route is specified or if using stand-alone)
25   * Returns:
26   *   A string equivalent to the generated view class.
27   */
28   string parseViewParts(Part[][string] allParts, string viewName, out string route)
29   {
30     route = null;
31 
32     string viewClassMembersGeneration = "";
33     string viewConstructorGeneration = "";
34     string viewModelGenerateGeneration = "";
35     string viewCodeGeneration = "";
36     string viewPlaceholderGeneration = "";
37     bool hasController;
38     bool useBaseView;
39     bool hasDefaultSection;
40 
41     foreach (sectionName,parts; allParts)
42     {
43       if (sectionName && sectionName.length)
44       {
45         viewCodeGeneration ~= "case \"" ~ sectionName ~ "\":
46         {
47   ";
48       }
49       else
50       {
51         hasDefaultSection = true;
52         viewCodeGeneration ~= "default:
53         {
54 ";
55       }
56 
57       foreach (part; parts)
58       {
59         if (!part.content || !part.content.strip().length)
60         {
61           continue;
62         }
63 
64         import diamond.extensions;
65         mixin ExtensionEmit!(ExtensionType.partParser, q{
66           {{extensionEntry}}.parsePart(
67             part,
68             viewName,
69             viewClassMembersGeneration, viewConstructorGeneration,
70             viewModelGenerateGeneration,
71             viewCodeGeneration
72           );
73         });
74         emitExtension();
75 
76         switch (part.contentMode)
77         {
78           case ContentMode.appendContent:
79           {
80             viewCodeGeneration ~= parseAppendContent(part);
81             break;
82           }
83 
84           case ContentMode.appendContentPlaceholder:
85           {
86             if (part.content[0] == '%' && part.content[$-1] == '%')
87             {
88               viewCodeGeneration ~= parseAppendTranslateContent(part);
89             }
90             else if (part.content[0] == '#' && part.content[$-1] == '#')
91             {
92               viewCodeGeneration ~= parseAppendPartialViewContent(part);
93             }
94             else
95             {
96               viewCodeGeneration ~= parseAppendPlaceholderContent(part);
97             }
98             break;
99           }
100 
101           case ContentMode.mixinContent:
102           {
103             viewCodeGeneration ~= part.content;
104             break;
105           }
106 
107           case ContentMode.metaContent:
108           {
109             parseMetaContent(
110               part,
111               viewName,
112               viewClassMembersGeneration, viewConstructorGeneration,
113               viewModelGenerateGeneration, viewPlaceholderGeneration,
114               useBaseView,
115               hasController,
116               route,
117               viewCodeGeneration
118             );
119             break;
120           }
121 
122           default : break;
123         }
124       }
125 
126       viewCodeGeneration ~= "break;
127 }
128 ";
129     }
130 
131     if (!hasDefaultSection) {
132       viewCodeGeneration ~= "default: break;";
133     }
134 
135     static if (isWebServer)
136     {
137       return viewClassFormat.format(
138         viewName,
139         viewClassMembersGeneration,
140         viewConstructorGeneration,
141         viewModelGenerateGeneration,
142         hasController ? controllerHandleFormat : "",
143         viewPlaceholderGeneration,
144         viewCodeGeneration,
145         endFormat
146       );
147     }
148     else
149     {
150       return viewClassFormat.format(
151         viewName,
152         viewClassMembersGeneration,
153         viewConstructorGeneration,
154         viewModelGenerateGeneration,
155         viewPlaceholderGeneration,
156         viewCodeGeneration,
157         endFormat
158       );
159     }
160   }
161 
162   private:
163   /**
164   * Parses content that can be appended as a place holder.
165   * Params:
166   *   part = The part to parse.
167   * Returns:
168   *   The appended result.
169   */
170   string parseAppendPlaceholderContent(Part part)
171   {
172     return appendFormat.format("getPlaceholder(`" ~ part.content ~ "`)");
173   }
174 
175   /**
176   * Parses content that can be appended as i18n.
177   * Params:
178   *   part = The part to parse.
179   * Returns:
180   *   The appended result.
181   */
182   string parseAppendTranslateContent(Part part)
183   {
184     return appendFormat.format("i18n.getMessage(super.client, \"" ~ part.content[1 .. $-1] ~ "\")");
185   }
186 
187   /**
188   * Parses content that can be appended as partial view.
189   * Params:
190   *   part = The part to parse.
191   * Returns:
192   *   The appended result.
193   */
194   string parseAppendPartialViewContent(Part part)
195   {
196     auto viewData = part.content[1 .. $-1].split(",");
197 
198     if (viewData.length == 2)
199     {
200       return appendFormat.format("retrieveModel!\"%s\"(%s)".format(viewData[0], viewData[1]));
201     }
202     else
203     {
204       return appendFormat.format("retrieve(\"%s\")".format(viewData[0]));
205     }
206   }
207 
208   /**
209   * Parses content that can be appended.
210   * Params:
211   *   part = The part to parse.
212   * Returns:
213   *   The appended result.
214   */
215   string parseAppendContent(Part part)
216   {
217     switch (part.name)
218     {
219       case "expressionValue":
220       {
221         return appendFormat.format(part.content);
222       }
223 
224       case "escapedValue":
225       {
226         return escapedFormat.format("`" ~ part.content ~ "`");
227       }
228 
229       case "expressionEscaped":
230       {
231         return escapedFormat.format(part.content);
232       }
233 
234       default:
235       {
236         return appendFormat.format("`" ~ part.content ~ "`");
237       }
238     }
239   }
240 
241   /**
242   * Parses the meta content of a view.
243   * Params:
244   *   part =                        The part of the meta content.
245   *   viewClassMembersGeneration =  The resulting string of the view's class members.
246   *   viewConstructorGeneration =   The resulting string of the view's constructor.
247   *   viewModelGenerateGeneration = The resulting string of the view's model-generate function.
248   *   viewPlaceholderGeneration =   The resulting string of the view's placeholder generation.
249   *   useBaseView =                 Boolean determining whether the view should use the base view for controllers.
250   *   hasController =               Boolean determining whether the view has a controller or not.
251   *   route =                       The name of the view's route. (null if no route or if stand-alone.)
252   */
253   void parseMetaContent(Part part,
254     string viewName,
255     ref string viewClassMembersGeneration,
256     ref string viewConstructorGeneration,
257     ref string viewModelGenerateGeneration,
258     ref string viewPlaceholderGeneration,
259     ref bool useBaseView,
260     ref bool hasController,
261     ref string route,
262     ref string viewCodeGeneration)
263   {
264     string[string] metaData;
265     auto metaContent = part.content.replace("\r", "").split("---");
266 
267     foreach (entry; metaContent)
268     {
269       if (entry && entry.length)
270       {
271         import std..string : indexOf;
272 
273         auto keyIndex = entry.indexOf(':');
274         auto key = entry[0 .. keyIndex].strip().replace("\n", "");
275 
276         metaData[key] = entry[keyIndex + 1 .. $].strip();
277       }
278     }
279 
280     foreach (key, value; metaData)
281     {
282       if (!value || !value.length)
283       {
284         continue;
285       }
286 
287       switch (key)
288       {
289         case "placeholders":
290         {
291           viewPlaceholderGeneration = placeholderFormat.format(value);
292           break;
293         }
294 
295         case "route":
296         {
297           import std..string : toLower;
298           route = value.replace("\n", "").toLower();
299           break;
300         }
301 
302         case "model":
303         {
304           viewModelGenerateGeneration = modelGenerateFormat.format(value);
305           viewClassMembersGeneration ~= modelMemberFormat.format(value);
306           viewClassMembersGeneration ~= updateModelFromRenderViewFormat.format(viewName);
307           break;
308         }
309 
310         static if (isWebServer)
311         {
312           case "controllerUseBaseView":
313           {
314             useBaseView = to!bool(value);
315             break;
316           }
317 
318           case "controller":
319           {
320             hasController = true;
321             viewClassMembersGeneration ~= controllerMemberFormat.format(value, useBaseView ? "View" : "view_" ~ viewName);
322             viewConstructorGeneration ~= controllerConstructorFormat.format(value, useBaseView ? "View" : "view_" ~ viewName);
323             break;
324           }
325         }
326 
327         case "layout":
328         {
329           viewConstructorGeneration ~= layoutConstructorFormat.format(value.replace("\n", ""));
330           break;
331         }
332 
333         case "cache":
334         {
335           if (to!bool(value))
336           {
337             viewConstructorGeneration ~= "super.cached = true;\r\n";
338           }
339 
340           break;
341         }
342 
343         case "staticCache":
344         {
345           if (to!bool(value))
346           {
347             viewConstructorGeneration ~= "super.staticCache = true;\r\n";
348           }
349 
350           break;
351         }
352 
353         case "staticCacheTime":
354         {
355           viewConstructorGeneration ~= "super.cacheTime = " ~ to!string(to!size_t(value)) ~ ";\r\n";
356 
357           break;
358         }
359 
360         case "contentType":
361         {
362           viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"%s\";\r\n".format(value.replace("\n", ""));
363           break;
364         }
365 
366         case "type":
367         {
368           switch (value.replace("\n", ""))
369           {
370             case "text":
371             {
372               viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"text/plain; charset=UTF-8\";\r\n";
373               break;
374             }
375 
376             case "xml":
377             {
378               viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"application/xml; charset=UTF-8\";\r\n";
379               break;
380             }
381 
382             case "rss":
383             {
384               viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"application/rss+xml; charset=UTF-8\";\r\n";
385               break;
386             }
387 
388             case "atom":
389             {
390               viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"application/atom+xml; charset=UTF-8\";\r\n";
391               break;
392             }
393 
394             case "json":
395             {
396               viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"application/json; charset=UTF-8\";\r\n";
397               break;
398             }
399 
400             default: break;
401           }
402           break;
403         }
404 
405         case "title":
406         {
407           viewConstructorGeneration ~= "super.addPlaceholder(\"title\", \"" ~ value.strip() ~ "\");\r\n";
408           break;
409         }
410 
411         default: break;
412       }
413     }
414   }
415 }