1 /**
2 * Copyright © DiamondMVC 2018
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 (!isWebApi)
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             viewCodeGeneration ~= parseAppendPlaceholderContent(part);
87             break;
88           }
89 
90           case ContentMode.mixinContent:
91           {
92             viewCodeGeneration ~= part.content;
93             break;
94           }
95 
96           case ContentMode.metaContent:
97           {
98             parseMetaContent(
99               part,
100               viewName,
101               viewClassMembersGeneration, viewConstructorGeneration,
102               viewModelGenerateGeneration, viewPlaceHolderGeneration,
103               useBaseView,
104               hasController,
105               route
106             );
107             break;
108           }
109 
110           default : break;
111         }
112       }
113 
114       viewCodeGeneration ~= "break;
115 }
116 ";
117     }
118 
119     if (!hasDefaultSection) {
120       viewCodeGeneration ~= "default: break;";
121     }
122 
123     static if (isWebServer)
124     {
125       return viewClassFormat.format(
126         viewName,
127         viewClassMembersGeneration,
128         viewConstructorGeneration,
129         viewModelGenerateGeneration,
130         hasController ? controllerHandleFormat : "",
131         viewPlaceHolderGeneration,
132         viewCodeGeneration,
133         endFormat
134       );
135     }
136     else
137     {
138       return viewClassFormat.format(
139         viewName,
140         viewClassMembersGeneration,
141         viewConstructorGeneration,
142         viewModelGenerateGeneration,
143         viewPlaceHolderGeneration,
144         viewCodeGeneration,
145         endFormat
146       );
147     }
148   }
149 
150   private:
151   /**
152   * Parses content that can be appended as a place holder.
153   * Params:
154   *   part = The part to parse.
155   * Returns:
156   *   The appended result.
157   */
158   string parseAppendPlaceholderContent(Part part)
159   {
160     return appendFormat.format("getPlaceHolder(`" ~ part.content ~ "`)");
161   }
162 
163   /**
164   * Parses content that can be appended.
165   * Params:
166   *   part = The part to parse.
167   * Returns:
168   *   The appended result.
169   */
170   string parseAppendContent(Part part)
171   {
172     switch (part.name)
173     {
174       case "expressionValue":
175       {
176         return appendFormat.format(part.content);
177       }
178 
179       case "escapedValue":
180       {
181         return escapedFormat.format("`" ~ part.content ~ "`");
182       }
183 
184       case "expressionEscaped":
185       {
186         return escapedFormat.format(part.content);
187       }
188 
189       default:
190       {
191         return appendFormat.format("`" ~ part.content ~ "`");
192       }
193     }
194   }
195 
196   /**
197   * Parses the meta content of a view.
198   * Params:
199   *   part =                        The part of the meta content.
200   *   viewClassMembersGeneration =  The resulting string of the view's class members.
201   *   viewConstructorGeneration =   The resulting string of the view's constructor.
202   *   viewModelGenerateGeneration = The resulting string of the view's model-generate function.
203   *   viewPlaceHolderGeneration =   The resulting string of the view's placeholder generation.
204   *   useBaseView =                 Boolean determining whether the view should use the base view for controllers.
205   *   hasController =               Boolean determining whether the view has a controller or not.
206   *   route =                       The name of the view's route. (null if no route or if stand-alone.)
207   */
208   void parseMetaContent(Part part,
209     string viewName,
210     ref string viewClassMembersGeneration,
211     ref string viewConstructorGeneration,
212     ref string viewModelGenerateGeneration,
213     ref string viewPlaceHolderGeneration,
214     ref bool useBaseView,
215     ref bool hasController,
216     ref string route)
217   {
218     string[string] metaData;
219     auto metaContent = part.content.replace("\r", "").split("---");
220 
221     foreach (entry; metaContent)
222     {
223       if (entry && entry.length)
224       {
225         import std..string : indexOf;
226 
227         auto keyIndex = entry.indexOf(':');
228         auto key = entry[0 .. keyIndex].strip().replace("\n", "");
229 
230         metaData[key] = entry[keyIndex + 1 .. $].strip();
231       }
232     }
233 
234     foreach (key, value; metaData)
235     {
236       if (!value || !value.length)
237       {
238         continue;
239       }
240 
241       switch (key)
242       {
243         case "placeHolders":
244         {
245           viewPlaceHolderGeneration = placeHolderFormat.format(value);
246           break;
247         }
248 
249         case "route":
250         {
251           import std..string : toLower;
252           route = value.replace("\n", "").toLower();
253           break;
254         }
255 
256         case "model":
257         {
258           viewModelGenerateGeneration = modelGenerateFormat.format(value);
259           viewClassMembersGeneration ~= modelMemberFormat.format(value);
260           viewClassMembersGeneration ~= updateModelFromRenderViewFormat.format(viewName);
261           break;
262         }
263 
264         static if (isWebServer)
265         {
266           case "controllerUseBaseView":
267           {
268             useBaseView = to!bool(value);
269             break;
270           }
271 
272           case "controller":
273           {
274             hasController = true;
275             viewClassMembersGeneration ~= controllerMemberFormat.format(value, useBaseView ? "View" : "view_" ~ viewName);
276             viewConstructorGeneration ~= controllerConstructorFormat.format(value, useBaseView ? "View" : "view_" ~ viewName);
277             break;
278           }
279         }
280 
281         case "layout":
282         {
283           viewConstructorGeneration ~= layoutConstructorFormat.format(value.replace("\n", ""));
284           break;
285         }
286 
287         case "cache":
288         {
289           if (to!bool(value))
290           {
291             viewConstructorGeneration ~= "super.cached = true;\r\n";
292           }
293 
294           break;
295         }
296 
297         default: break;
298       }
299     }
300   }
301 }