1 | // Aseprite Code Generator |
2 | // Copyright (C) 2019 Igara Studio S.A. |
3 | // Copyright (C) 2014-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #include "base/exception.h" |
9 | #include "base/file_handle.h" |
10 | #include "base/fs.h" |
11 | #include "base/split_string.h" |
12 | #include "base/string.h" |
13 | #include "gen/common.h" |
14 | #include "gen/pref_types.h" |
15 | |
16 | #include <iostream> |
17 | #include <stdexcept> |
18 | #include <vector> |
19 | |
20 | typedef std::vector<TiXmlElement*> XmlElements; |
21 | |
22 | static void print_pref_class_def(TiXmlElement* elem, const std::string& className, const char* section, int indentSpaces) |
23 | { |
24 | std::string indent(indentSpaces, ' '); |
25 | std::cout |
26 | << "\n" |
27 | << indent << "class " << className << " : public Section {\n" |
28 | << indent << "public:\n" |
29 | << indent << " " << className << "(const std::string& name);\n" ; |
30 | |
31 | if (elem->Attribute("canforce" )) |
32 | std::cout << indent << " void forceSection();\n" ; |
33 | if (elem->Attribute("canclear" )) |
34 | std::cout << indent << " void clearSection();\n" ; |
35 | |
36 | std::cout |
37 | << indent << " void load();\n" |
38 | << indent << " void save();\n" |
39 | << indent << " Section* section(const char* id) override;\n" |
40 | << indent << " OptionBase* option(const char* id) override;\n" ; |
41 | |
42 | TiXmlElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL); |
43 | while (child) { |
44 | if (child->Value()) { |
45 | std::string name = child->Value(); |
46 | const char* childId = child->Attribute("id" ); |
47 | |
48 | if (name == "option" ) { |
49 | if (!child->Attribute("type" )) throw std::runtime_error("missing 'type' attr in <option>" ); |
50 | if (!childId) throw std::runtime_error("missing 'id' attr in <option>" ); |
51 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
52 | std::cout |
53 | << indent << " Option<" << child->Attribute("type" ) << "> " << memberName << ";\n" ; |
54 | } |
55 | else if (name == "section" ) { |
56 | if (!childId) throw std::runtime_error("missing 'id' attr in <section>" ); |
57 | std::string childClassName = convert_xmlid_to_cppid(childId, true); |
58 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
59 | print_pref_class_def(child, childClassName, childId, indentSpaces+2); |
60 | std::cout |
61 | << indent << " " << childClassName << " " << memberName << ";\n" ; |
62 | } |
63 | } |
64 | child = child->NextSiblingElement(); |
65 | } |
66 | |
67 | std::cout |
68 | << indent << "};\n" ; |
69 | } |
70 | |
71 | static void print_pref_class_impl(TiXmlElement* elem, const std::string& prefix, const std::string& className, const char* section) |
72 | { |
73 | std::cout |
74 | << "\n" |
75 | << prefix << className << "::" << className << "(const std::string& name)\n" ; |
76 | |
77 | if (section) |
78 | std::cout << " : Section(std::string(!name.empty() ? name + \".\": \"\") + \"" << section << "\")\n" ; |
79 | else |
80 | std::cout << " : Section(name)\n" ; |
81 | |
82 | TiXmlElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL); |
83 | while (child) { |
84 | if (child->Value()) { |
85 | std::string name = child->Value(); |
86 | const char* childId = child->Attribute("id" ); |
87 | |
88 | if (name == "option" ) { |
89 | if (!child->Attribute("type" )) throw std::runtime_error("missing 'type' attr in <option>" ); |
90 | if (!childId) throw std::runtime_error("missing 'id' attr in <option>" ); |
91 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
92 | std::cout << " , " |
93 | << memberName << "(this, \"" << childId << "\"" ; |
94 | if (child->Attribute("default" )) |
95 | std::cout << ", " << child->Attribute("default" ); |
96 | std::cout << ")\n" ; |
97 | } |
98 | else if (name == "section" ) { |
99 | if (!childId) throw std::runtime_error("missing 'id' attr in <option>" ); |
100 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
101 | std::cout << " , " << memberName << "(name)\n" ; |
102 | } |
103 | } |
104 | child = child->NextSiblingElement(); |
105 | } |
106 | |
107 | std::cout |
108 | << "{\n" |
109 | << "}\n" ; |
110 | |
111 | // Section::forceSection() |
112 | if (elem->Attribute("canforce" )) { |
113 | std::cout |
114 | << "\n" |
115 | << "void " << prefix << className << "::forceSection()\n" |
116 | << "{\n" ; |
117 | |
118 | child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): nullptr); |
119 | while (child) { |
120 | if (child->Value()) { |
121 | std::string name = child->Value(); |
122 | const char* childId = child->Attribute("id" ); |
123 | |
124 | if (name == "option" ) { |
125 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
126 | std::cout << " " << memberName << ".forceDirtyFlag();\n" ; |
127 | } |
128 | } |
129 | child = child->NextSiblingElement(); |
130 | } |
131 | std::cout |
132 | << "}\n" ; |
133 | } |
134 | |
135 | // Section::clearSection() |
136 | if (elem->Attribute("canclear" )) { |
137 | std::cout |
138 | << "\n" |
139 | << "void " << prefix << className << "::clearSection()\n" |
140 | << "{\n" ; |
141 | |
142 | child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): nullptr); |
143 | while (child) { |
144 | if (child->Value()) { |
145 | std::string name = child->Value(); |
146 | const char* childId = child->Attribute("id" ); |
147 | |
148 | if (name == "option" ) { |
149 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
150 | std::cout << " " << memberName << ".clearValue();\n" ; |
151 | } |
152 | } |
153 | child = child->NextSiblingElement(); |
154 | } |
155 | std::cout |
156 | << "}\n" ; |
157 | } |
158 | |
159 | // Section::load() |
160 | |
161 | std::cout |
162 | << "\n" |
163 | << "void " << prefix << className << "::load()\n" |
164 | << "{\n" ; |
165 | |
166 | child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL); |
167 | while (child) { |
168 | if (child->Value()) { |
169 | std::string name = child->Value(); |
170 | const char* childId = child->Attribute("id" ); |
171 | |
172 | if (name == "option" ) { |
173 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
174 | |
175 | const char* migrate = child->Attribute("migrate" ); |
176 | if (migrate) { |
177 | std::vector<std::string> parts; |
178 | base::split_string(migrate, parts, "." ); |
179 | |
180 | std::cout << " load_option_with_migration(" << memberName |
181 | << ", \"" << parts[0] |
182 | << "\", \"" << parts[1] << "\");\n" ; |
183 | } |
184 | else |
185 | std::cout << " load_option(" << memberName << ");\n" ; |
186 | } |
187 | else if (name == "section" ) { |
188 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
189 | std::cout << " " << memberName << ".load();\n" ; |
190 | } |
191 | } |
192 | child = child->NextSiblingElement(); |
193 | } |
194 | |
195 | std::cout |
196 | << "}\n" |
197 | << "\n" ; |
198 | |
199 | // Section::save() |
200 | |
201 | std::cout |
202 | << "void " << prefix << className << "::save()\n" |
203 | << "{\n" ; |
204 | |
205 | child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL); |
206 | while (child) { |
207 | if (child->Value()) { |
208 | std::string name = child->Value(); |
209 | if (name == "option" ) { |
210 | std::string memberName = convert_xmlid_to_cppid(child->Attribute("id" ), false); |
211 | std::cout << " save_option(" << memberName << ");\n" ; |
212 | } |
213 | else if (name == "section" ) { |
214 | std::string memberName = convert_xmlid_to_cppid(child->Attribute("id" ), false); |
215 | std::cout << " " << memberName << ".save();\n" ; |
216 | } |
217 | } |
218 | child = child->NextSiblingElement(); |
219 | } |
220 | |
221 | std::cout |
222 | << "}\n" |
223 | << "\n" ; |
224 | |
225 | // Section::section(id) |
226 | |
227 | std::cout |
228 | << "Section* " << prefix << className << "::section(const char* id)\n" |
229 | << "{\n" ; |
230 | |
231 | child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL); |
232 | while (child) { |
233 | if (child->Value()) { |
234 | std::string name = child->Value(); |
235 | const char* childId = child->Attribute("id" ); |
236 | if (name == "section" ) { |
237 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
238 | std::cout << " if (std::strcmp(id, " << memberName << ".name()) == 0) return &" << memberName << ";\n" ; |
239 | } |
240 | } |
241 | child = child->NextSiblingElement(); |
242 | } |
243 | |
244 | std::cout |
245 | << " return nullptr;\n" |
246 | << "}\n" |
247 | << "\n" ; |
248 | |
249 | // Section::option(id) |
250 | |
251 | std::cout |
252 | << "OptionBase* " << prefix << className << "::option(const char* id)\n" |
253 | << "{\n" ; |
254 | |
255 | child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL); |
256 | while (child) { |
257 | if (child->Value()) { |
258 | std::string name = child->Value(); |
259 | const char* childId = child->Attribute("id" ); |
260 | if (name == "option" ) { |
261 | std::string memberName = convert_xmlid_to_cppid(childId, false); |
262 | std::cout << " if (std::strcmp(id, " << memberName << ".id()) == 0) return &" << memberName << ";\n" ; |
263 | } |
264 | } |
265 | child = child->NextSiblingElement(); |
266 | } |
267 | |
268 | std::cout |
269 | << " return nullptr;\n" |
270 | << "}\n" ; |
271 | |
272 | // Sub-sections |
273 | |
274 | child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL); |
275 | while (child) { |
276 | if (child->Value()) { |
277 | std::string name = child->Value(); |
278 | if (name == "section" ) { |
279 | std::string childClassName = convert_xmlid_to_cppid(child->Attribute("id" ), true); |
280 | print_pref_class_impl(child, className + "::" , childClassName, child->Attribute("id" )); |
281 | } |
282 | } |
283 | child = child->NextSiblingElement(); |
284 | } |
285 | } |
286 | |
287 | void (TiXmlDocument* doc, const std::string& inputFn) |
288 | { |
289 | std::cout |
290 | << "// Don't modify, generated file from " << inputFn << "\n" |
291 | << "\n" ; |
292 | |
293 | std::cout |
294 | << "#ifndef GENERATED_PREF_TYPES_H_INCLUDED\n" |
295 | << "#define GENERATED_PREF_TYPES_H_INCLUDED\n" |
296 | << "#pragma once\n" |
297 | << "\n" |
298 | << "#include <string>\n" |
299 | << "\n" |
300 | << "namespace app {\n" |
301 | << "namespace gen {\n" ; |
302 | |
303 | TiXmlHandle handle(doc); |
304 | TiXmlElement* elem = handle |
305 | .FirstChild("preferences" ) |
306 | .FirstChild("types" ) |
307 | .FirstChild("enum" ).ToElement(); |
308 | while (elem) { |
309 | if (!elem->Attribute("id" )) throw std::runtime_error("missing 'id' attr in <enum>" ); |
310 | std::cout |
311 | << "\n" |
312 | << " enum class " << elem->Attribute("id" ) << " {\n" ; |
313 | |
314 | TiXmlElement* child = elem->FirstChildElement("value" ); |
315 | while (child) { |
316 | if (!child->Attribute("id" )) throw std::runtime_error("missing 'id' attr in <value>" ); |
317 | if (!child->Attribute("value" )) throw std::runtime_error("missing 'value' attr in <value>" ); |
318 | |
319 | std::cout << " " << child->Attribute("id" ) << " = " |
320 | << child->Attribute("value" ) << ",\n" ; |
321 | child = child->NextSiblingElement("value" ); |
322 | } |
323 | |
324 | std::cout |
325 | << " };\n" ; |
326 | |
327 | elem = elem->NextSiblingElement("enum" ); |
328 | } |
329 | |
330 | elem = handle |
331 | .FirstChild("preferences" ) |
332 | .FirstChild("global" ).ToElement(); |
333 | if (elem) |
334 | print_pref_class_def(elem, "GlobalPref" , NULL, 2); |
335 | |
336 | elem = handle |
337 | .FirstChild("preferences" ) |
338 | .FirstChild("tool" ).ToElement(); |
339 | if (elem) |
340 | print_pref_class_def(elem, "ToolPref" , NULL, 2); |
341 | |
342 | elem = handle |
343 | .FirstChild("preferences" ) |
344 | .FirstChild("document" ).ToElement(); |
345 | if (elem) |
346 | print_pref_class_def(elem, "DocPref" , NULL, 2); |
347 | |
348 | std::cout |
349 | << "\n" |
350 | << "} // namespace gen\n" |
351 | << "} // namespace app\n" |
352 | << "\n" |
353 | << "#endif\n" ; |
354 | } |
355 | |
356 | void gen_pref_impl(TiXmlDocument* doc, const std::string& inputFn) |
357 | { |
358 | std::cout |
359 | << "// Don't modify, generated file from " << inputFn << "\n" |
360 | << "\n" |
361 | << "#ifdef HAVE_CONFIG_H\n" |
362 | << "#include \"config.h\"\n" |
363 | << "#endif\n" |
364 | << "\n" |
365 | << "#include \"app/pref/option_io.h\"\n" |
366 | << "#include \"app/pref/preferences.h\"\n" |
367 | << "\n" |
368 | << "#include <cstring>\n" |
369 | << "\n" |
370 | << "namespace app {\n" |
371 | << "namespace gen {\n" ; |
372 | |
373 | TiXmlHandle handle(doc); |
374 | TiXmlElement* elem = handle |
375 | .FirstChild("preferences" ) |
376 | .FirstChild("global" ).ToElement(); |
377 | if (elem) |
378 | print_pref_class_impl(elem, "" , "GlobalPref" , NULL); |
379 | |
380 | elem = handle |
381 | .FirstChild("preferences" ) |
382 | .FirstChild("tool" ).ToElement(); |
383 | if (elem) |
384 | print_pref_class_impl(elem, "" , "ToolPref" , NULL); |
385 | |
386 | elem = handle |
387 | .FirstChild("preferences" ) |
388 | .FirstChild("document" ).ToElement(); |
389 | if (elem) |
390 | print_pref_class_impl(elem, "" , "DocPref" , NULL); |
391 | |
392 | std::cout |
393 | << "\n" |
394 | << "} // namespace gen\n" |
395 | << "} // namespace app\n" ; |
396 | } |
397 | |