1 /*
2 * Copyright 2019 by its authors. See AUTHORS.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #ifndef EOLIAN_MONO_DOCUMENTATION_HPP
17 #define EOLIAN_MONO_DOCUMENTATION_HPP
18
19 #include "grammar/generator.hpp"
20 #include "grammar/klass_def.hpp"
21 #include "grammar/html_escaped_string.hpp"
22 #include "using_decl.hh"
23 #include "name_helpers.hh"
24 #include "generation_contexts.hh"
25 #include "blacklist.hh"
26 #include "property_definition.hh"
27
28 #include <Eina.h>
29
30 static const std::string BETA_REF_SUFFIX = " (object still in beta stage)";
31 static const std::string BETA_CLASS_REMARK = "This is a \\<b\\>BETA\\</b\\> class. It can be modified or removed in the future. Do not use it for product development.";
32 static const std::string BETA_PROPERTY_REMARK = "\n\n\\<b\\>This is a BETA property\\</b\\>. It can be modified or removed in the future. Do not use it for product development.";
33 static const std::string BETA_METHOD_REMARK = "\n\n\\<b\\>This is a BETA method\\</b\\>. It can be modified or removed in the future. Do not use it for product development.";
34
35 namespace eolian_mono {
36
37 struct documentation_generator
38 {
39
40 int scope_size = 0;
41
documentation_generatoreolian_mono::documentation_generator42 documentation_generator(int scope_size = 0)
43 : scope_size(scope_size) {}
44
45
46 // Returns the number of keys that a property method requires
47 // Specify if you want the Setter or the Getter method.
property_num_keyseolian_mono::documentation_generator48 static int property_num_keys(const ::Eolian_Function *function, ::Eolian_Function_Type ftype)
49 {
50 Eina_Iterator *itr = ::eolian_property_keys_get(function, ftype);
51 Eolian_Function_Parameter *pr;
52 int n = 0;
53 EINA_ITERATOR_FOREACH(itr, pr) { n++; }
54 eina_iterator_free(itr);
55 return n;
56 }
57
58 // Gets the eolian ref for the given class, prepending I for interfaces.
object_ref_conversioneolian_mono::documentation_generator59 static std::string object_ref_conversion(const Eolian_Object *cls)
60 {
61 auto klass_type = ::eolian_class_type_get((const Eolian_Class *)cls);
62 auto full_eolian_name = name_helpers::managed_namespace(::eolian_object_name_get(cls));
63 if (klass_type == EOLIAN_CLASS_MIXIN || klass_type == EOLIAN_CLASS_INTERFACE)
64 {
65 size_t pos = full_eolian_name.rfind(".");
66 if (pos == std::string::npos)
67 pos = 0;
68 else
69 pos++;
70 full_eolian_name.insert(pos, "I");
71 }
72 return full_eolian_name;
73 }
74
75
76 // Turns a function name from EO convention to EFL# convention.
77 // The name_tail parameter is the last 4 chars of the original string, which
78 // could be ".set" or ".get" and in this case they are ignored by Eolian.
79 // We want them to know what the documentation intended to reference.
80 template <typename Context>
function_conversioneolian_mono::documentation_generator81 static std::string function_conversion(const ::Eolian_Object *klass, const ::Eolian_Function *function, std::string name_tail, Context const& context)
82 {
83 ::Eolian_Function_Type ftype = ::eolian_function_type_get(function);
84 const char* eo_name = ::eolian_function_name_get(function);
85 std::string name = object_ref_conversion(klass);
86
87 // Klass is needed to check the property naming rules
88 attributes::klass_def klass_d((const ::Eolian_Class *)klass, eolian_object_unit_get(klass));
89
90 // Comment the block below to enable @see reference conversion for non-public interface members.
91 // As they are not generated, this causes a doc warning that fails the build, but can be useful to track
92 // public methods referencing protected stuff.
93 if (ftype != EOLIAN_PROPERTY)
94 {
95 bool is_func_public = ::eolian_function_scope_get(function, ftype) == EOLIAN_SCOPE_PUBLIC;
96
97 if (helpers::is_managed_interface(klass_d) && !is_func_public)
98 return "";
99 }
100 else
101 {
102 bool is_get_public = ::eolian_function_scope_get(function, EOLIAN_PROP_GET) == EOLIAN_SCOPE_PUBLIC;
103 bool is_set_public = ::eolian_function_scope_get(function, EOLIAN_PROP_SET) == EOLIAN_SCOPE_PUBLIC;
104
105 if (helpers::is_managed_interface(klass_d) && !(is_get_public || is_set_public))
106 return "";
107 }
108
109 switch(ftype)
110 {
111 case ::EOLIAN_METHOD:
112 if (blacklist::is_function_blacklisted(
113 ::eolian_function_full_c_name_get(function, ftype))) return "";
114 name += ".";
115 name += name_helpers::managed_method_name({function, ftype, NULL, eolian_object_unit_get(EOLIAN_OBJECT(function))});
116 break;
117 case ::EOLIAN_PROP_SET:
118 case ::EOLIAN_PROP_GET:
119 case ::EOLIAN_PROPERTY:
120 {
121 efl::eina::optional<attributes::function_def> getter_func;
122 efl::eina::optional<attributes::function_def> setter_func;
123 auto unit = (const Eolian_Unit*) context_find_tag<eolian_state_context>(context).state;
124 if (ftype == ::EOLIAN_PROPERTY || ftype == ::EOLIAN_PROP_GET)
125 getter_func = attributes::function_def{function, ::EOLIAN_PROP_GET, nullptr, unit};
126 if (ftype == ::EOLIAN_PROPERTY || ftype == ::EOLIAN_PROP_SET)
127 setter_func = attributes::function_def{function, ::EOLIAN_PROP_SET, nullptr, unit};
128 attributes::property_def property{function, getter_func, setter_func, unit};
129
130 std::string short_name = name_helpers::property_managed_name(klass_d, eo_name);
131 class_context::wrapper_kind klass_kind;
132 if (helpers::is_managed_interface(klass_d))
133 klass_kind = class_context::interface;
134 else
135 klass_kind = class_context::inherit;
136 auto my_context = grammar::context_replace_tag(class_context{klass_kind}, context);
137
138 if (name_tail == ".get")
139 {
140 if (property_generate_wrapper_getter (property, my_context))
141 {
142 name += "." + short_name;
143 }
144 else
145 {
146 name += ".Get" + short_name;
147 }
148 }
149 else if (name_tail == ".set")
150 {
151 if (property_generate_wrapper_setter (property, my_context))
152 {
153 name += "." + short_name;
154 }
155 else
156 {
157 name += ".Set" + short_name;
158 }
159 }
160 else
161 {
162 switch (ftype)
163 {
164 case ::EOLIAN_PROPERTY:
165 case ::EOLIAN_PROP_GET:
166 if (property_generate_wrapper_getter (property, my_context))
167 name += "." + short_name;
168 else
169 name += ".Get" + short_name;
170 break;
171 default:
172 name += ".Set" + short_name;
173 break;
174 }
175 }
176 }
177 break;
178 default:
179 break;
180 }
181 return name;
182 }
183
184 template <typename Context>
function_conversioneolian_mono::documentation_generator185 static std::string function_conversion(attributes::function_def const& func, Context const& context)
186 {
187 // This function is called only from the constructor reference conversion, so it does not
188 // need to check whether this function non-public in a interface returning an empty reference (yet).
189 std::string name = name_helpers::klass_full_concrete_or_interface_name(func.klass);
190 switch (func.type)
191 {
192 // managed_method_name takes care of reordering the function name so the get/set goes first
193 // for properties
194 case attributes::function_type::method:
195 if (blacklist::is_function_blacklisted(func.c_name))return "";
196 if (!name.empty()) name += ".";
197 name += name_helpers::managed_method_name(func);
198 break;
199 case attributes::function_type::prop_set:
200 case attributes::function_type::prop_get:
201 case attributes::function_type::property:
202 {
203 auto unit = (const Eolian_Unit*) context_find_tag<eolian_state_context>(context).state;
204 auto klass = get_klass(func.klass, unit);
205 attributes::klass_def klass_d(klass, unit);
206
207 auto eo_name = func.name;
208 auto prop_name = eo_name;
209 if (prop_name.size () > 4
210 &&
211 ( prop_name.substr(prop_name.size() - 4) == "_set"
212 || prop_name.substr(prop_name.size() - 4) == "_get"))
213 {
214 prop_name = prop_name.substr(0, prop_name.size() - 4);
215 }
216 std::string short_name = name_helpers::property_managed_name(klass_d, prop_name);
217 assert (prop_name.size() <= 4 ||
218 (prop_name.substr(prop_name.size() - 4) != "_set"
219 && prop_name.substr(prop_name.size() - 4) != "_get"));
220 assert (short_name.size() <= 3 ||
221 (short_name.substr(short_name.size() - 3) != "Set"
222 && short_name.substr(short_name.size() - 3) != "Get"));
223
224 // We need to replace the current class context with the context
225 // from the class that originated this property.
226 class_context::wrapper_kind klass_kind;
227 if (helpers::is_managed_interface(klass_d))
228 klass_kind = class_context::interface;
229 else
230 klass_kind = class_context::inherit;
231
232 auto my_context = grammar::context_replace_tag(class_context{klass_kind}, context);
233
234 auto function = eolian_class_function_by_name_get (klass, prop_name.c_str(), EOLIAN_PROPERTY);
235 attributes::function_def getter_func{function, ::EOLIAN_PROP_GET, nullptr, unit};
236 attributes::function_def setter_func{function, ::EOLIAN_PROP_SET, nullptr, unit};
237 attributes::property_def prop{function, getter_func, setter_func, unit};
238
239 if (func.type == attributes::function_type::prop_get || func.type == attributes::function_type::property)
240 {
241 if (property_generate_wrapper_getter (prop, my_context))
242 name += "." + short_name;
243 else
244 name += ".Get" + short_name;
245 }
246 else if (func.type == attributes::function_type::prop_set)
247 {
248 if (property_generate_wrapper_setter (prop, my_context))
249 name += "." + short_name;
250 else
251 name += ".Set" + short_name;
252 }
253 }
254 break;
255 default:
256 // No need to deal with property as function_defs are converted to get/set when building a given klass_def.
257 break;
258 }
259
260 return name;
261 }
262
263 // Turns an Eolian reference like @Efl.Input.Pointer.tool into a <see> tag
264 template <typename Context>
ref_conversioneolian_mono::documentation_generator265 static std::string ref_conversion(const ::Eolian_Doc_Token *token, const Eolian_State *state, std::string name_tail,
266 bool want_beta, Context const& context)
267 {
268 const Eolian_Object *data, *data2;
269 ::Eolian_Object_Type type =
270 ::eolian_doc_token_ref_resolve(token, state, &data, &data2);
271 std::string ref;
272 bool is_beta = false;
273 switch(type)
274 {
275 case ::EOLIAN_OBJECT_STRUCT_FIELD:
276 ref = name_helpers::managed_namespace(::eolian_object_name_get(data));
277 ref += ".";
278 ref += ::eolian_object_name_get(data2);
279 is_beta = eolian_object_is_beta(data) || eolian_object_is_beta(data2);
280 if (blacklist::is_struct_blacklisted(ref)) return "";
281 break;
282 case ::EOLIAN_OBJECT_EVENT:
283 ref = object_ref_conversion(data);
284 ref += ".";
285 ref += name_helpers::managed_event_name(::eolian_object_name_get(data2));
286 is_beta = eolian_object_is_beta(data) || eolian_object_is_beta(data2);
287 break;
288 case ::EOLIAN_OBJECT_ENUM_FIELD:
289 ref = name_helpers::managed_namespace(::eolian_object_name_get(data));
290 ref += ".";
291 ref += name_helpers::enum_field_managed_name(::eolian_object_name_get(data2));
292 is_beta = eolian_object_is_beta(data) || eolian_object_is_beta(data2);
293 break;
294 case ::EOLIAN_OBJECT_FUNCTION:
295 ref += function_conversion(data, (const ::Eolian_Function *)data2, name_tail, context);
296 is_beta = eolian_object_is_beta(data) || eolian_object_is_beta(data2);
297 break;
298 case ::EOLIAN_OBJECT_CONSTANT:
299 {
300 auto names = utils::split(name_helpers::managed_namespace(::eolian_object_name_get(data)), '.');
301 names.pop_back(); // Remove var name
302 ref = name_helpers::join_namespaces(names, '.');
303 ref += "Constants.";
304 ref += name_helpers::managed_name(::eolian_object_short_name_get(data));
305 }
306 break;
307 case ::EOLIAN_OBJECT_UNKNOWN:
308 // If the reference cannot be resolved, just return an empty string and
309 // it won't be converted into a <see> tag.
310 break;
311 case ::EOLIAN_OBJECT_CLASS:
312 ref = object_ref_conversion(data);
313 is_beta = eolian_object_is_beta(data);
314 break;
315 default:
316 ref = name_helpers::managed_namespace(::eolian_object_name_get(data));
317 is_beta = eolian_object_is_beta(data);
318 break;
319 }
320
321 if (!ref.empty() && !want_beta && is_beta)
322 ref += BETA_REF_SUFFIX;
323 return ref;
324 }
325
326 // Turns EO documentation syntax into C# triple-slash XML comment syntax
327 template <typename Context>
syntax_conversioneolian_mono::documentation_generator328 static std::string syntax_conversion(std::string text, const Eolian_State *state, bool want_beta, Context const& context)
329 {
330 std::string new_text, ref;
331 ::Eolian_Doc_Token_Type previous_token_type = ::EOLIAN_DOC_TOKEN_UNKNOWN;
332 ::Eina_List *paragraphs = ::eolian_documentation_string_split(text.c_str());
333 if (!paragraphs) return new_text;
334 ::Eina_List *data = paragraphs;
335 // For every paragraph
336 do
337 {
338 char *par = (char *)::eina_list_data_get(data);
339 const char *text_ptr = par;
340 ::Eolian_Doc_Token token;
341 ::eolian_doc_token_init(&token);
342 // For every token inside the paragraph
343 while ((text_ptr = ::eolian_documentation_tokenize(text_ptr, &token)) != NULL)
344 {
345 std::string token_text, name_tail;
346 char *token_text_cstr = ::eolian_doc_token_text_get(&token);
347 if (token_text_cstr)
348 {
349 token_text = token_text_cstr;
350 free(token_text_cstr);
351 if (token_text.length() > 4)
352 name_tail = token_text.substr(token_text.length() - 4, 4);
353 }
354 ::Eolian_Doc_Token_Type token_type = ::eolian_doc_token_type_get(&token);
355 switch(token_type)
356 {
357 case ::EOLIAN_DOC_TOKEN_TEXT:
358 // If previous token was a reference and this text token starts with
359 // parentheses, remove them, since the reference will be rendered
360 // with the parentheses already.
361 if ((previous_token_type == ::EOLIAN_DOC_TOKEN_REF) &&
362 (token_text.substr(0, 2) == "()"))
363 token_text = token_text.substr(2, token_text.length() - 2);
364 new_text += token_text;
365 break;
366 case ::EOLIAN_DOC_TOKEN_REF:
367 ref = ref_conversion(&token, state, name_tail, want_beta, context);
368 if (ref != "")
369 {
370 if (utils::ends_with(ref, BETA_REF_SUFFIX))
371 new_text += "<span class=\"text-muted\">" + ref + "</span>";
372 else
373 new_text += "<see cref=\"" + ref + "\"/>";
374 }
375 else
376 // Unresolved references are passed through.
377 // They will appear in the docs as plain text, without link,
378 // but at least they won't be removed by DocFX.
379 new_text += token_text;
380 break;
381 case ::EOLIAN_DOC_TOKEN_MARK_NOTE:
382 new_text += "<b>NOTE: </b>";
383 break;
384 case ::EOLIAN_DOC_TOKEN_MARK_WARNING:
385 new_text += "<b>WARNING: </b>";
386 break;
387 case ::EOLIAN_DOC_TOKEN_MARK_REMARK:
388 new_text += "<b>REMARK: </b>";
389 break;
390 case ::EOLIAN_DOC_TOKEN_MARK_TODO:
391 new_text += "<b>TODO: </b>";
392 break;
393 case ::EOLIAN_DOC_TOKEN_MARKUP_MONOSPACE:
394 new_text += "<c>" + token_text + "</c>";
395 break;
396 default:
397 break;
398 }
399 previous_token_type = token_type;
400 }
401 // Free this paragraph
402 free(par);
403 // Fetch the next paragraph
404 data = ::eina_list_next(data);
405 // If there's another paragraph afterwards, separate them with a blank line
406 if (data) new_text += "\n\n";
407 } while (data);
408 ::eina_list_free(paragraphs);
409 return new_text;
410 }
411
412 /// Tag generator helpers
413 template<typename OutputIterator, typename Context>
generate_opening_tageolian_mono::documentation_generator414 bool generate_opening_tag(OutputIterator sink, std::string const& tag, Context const& context, std::string tag_params = "") const
415 {
416 auto tag_separator = tag_params.empty() ? "" : " ";
417
418 return as_generator("<" << tag << tag_separator << tag_params << ">").generate(sink, attributes::unused, context);
419 }
420
421 template<typename OutputIterator, typename Context>
generate_closing_tageolian_mono::documentation_generator422 bool generate_closing_tag(OutputIterator sink, std::string const& tag, Context const& context) const
423 {
424 return as_generator("</" << tag << ">").generate(sink, attributes::unused, context);
425 }
426
427 template<typename OutputIterator, typename Context>
generate_escaped_contenteolian_mono::documentation_generator428 bool generate_escaped_content(OutputIterator sink, std::string const &text, Context const& context) const
429 {
430 std::string new_text;
431 if (!as_generator(html_escaped_string).generate(std::back_inserter(new_text), text, context))
432 return false;
433 auto options = context_find_tag<options_context>(context);
434 new_text = syntax_conversion( new_text, context_find_tag<eolian_state_context>(context).state, options.want_beta, context);
435
436 std::string tabs;
437 as_generator(scope_tab(scope_size) << "/// ").generate (std::back_inserter(tabs), attributes::unused, context);
438
439 std::istringstream ss(new_text);
440 std::string para;
441 std::string final_text;
442 bool first = true;
443 while (std::getline(ss, para)) {
444 if (first) final_text += para;
445 else final_text += "\n" + tabs + para;
446 first = false;
447 }
448 return as_generator(final_text).generate(sink, attributes::unused, context);
449 }
450
451 template<typename OutputIterator, typename Context>
generate_tageolian_mono::documentation_generator452 bool generate_tag(OutputIterator sink, std::string const& tag, std::string const &text, Context const& context, std::string tag_params = "") const
453 {
454 if (text == "")
455 return true;
456
457 if (!as_generator(scope_tab(scope_size) << "/// ").generate(sink, attributes::unused, context)) return false;
458 if (!generate_opening_tag(sink, tag, context, tag_params)) return false;
459 if (!generate_escaped_content(sink, text, context)) return false;
460 if (!generate_closing_tag(sink, tag, context)) return false;
461 return as_generator("\n").generate(sink, attributes::unused, context);
462 }
463
464 template<typename OutputIterator, typename Context>
generate_tag_summaryeolian_mono::documentation_generator465 bool generate_tag_summary(OutputIterator sink, std::string const& text, Context const& context) const
466 {
467 return generate_tag(sink, "summary", text, context);
468 }
469
470 template<typename OutputIterator, typename Context>
generate_tag_parameolian_mono::documentation_generator471 bool generate_tag_param(OutputIterator sink, std::string const& name, std::string const& text, Context const& context) const
472 {
473 return generate_tag(sink, "param", text, context, " name=\"" + name + "\"");
474 }
475
476 template<typename OutputIterator, typename Context>
generate_tag_returneolian_mono::documentation_generator477 bool generate_tag_return(OutputIterator sink, std::string const& text, Context const& context) const
478 {
479 return generate_tag(sink, "returns", text, context);
480 }
481
482 template<typename OutputIterator, typename Context>
generate_tag_valueeolian_mono::documentation_generator483 bool generate_tag_value(OutputIterator sink, std::string const& text, Context const& context) const
484 {
485 return generate_tag(sink, "value", text, context);
486 }
487
488 template<typename OutputIterator, typename Context>
generate_tag_exampleeolian_mono::documentation_generator489 bool generate_tag_example(OutputIterator sink, std::string const& full_object_name, Context const& context) const
490 {
491 auto options = efl::eolian::grammar::context_find_tag<options_context>(context);
492 // Example embedding not requested
493 if (options.examples_dir.empty()) return true;
494 bool is_plain_code = false;
495 std::string file_name = options.examples_dir + full_object_name + ".xml";
496 std::ifstream exfile(file_name);
497 if (!exfile.good())
498 {
499 // There is no example XML file for this class, try a CS file
500 file_name = options.examples_dir + full_object_name + ".cs";
501 exfile.open(file_name);
502 // There are no example files for this class or method, just return
503 if (!exfile.good()) return true;
504 is_plain_code = true;
505 }
506 std::stringstream example_buff;
507 // Start with a newline so the first line renders with same indentation as the rest
508 example_buff << std::endl << exfile.rdbuf();
509
510 if (!as_generator(scope_tab(scope_size) << "/// ").generate(sink, attributes::unused, context)) return false;
511 if (is_plain_code)
512 {
513 if (!generate_opening_tag(sink, "example", context)) return false;
514 if (!generate_opening_tag(sink, "code", context)) return false;
515 }
516 if (!generate_escaped_content(sink, example_buff.str(), context)) return false;
517 if (is_plain_code)
518 {
519 if (!generate_closing_tag(sink, "code", context)) return false;
520 if (!generate_closing_tag(sink, "example", context)) return false;
521 }
522 return as_generator("\n").generate(sink, attributes::unused, context);
523 }
524
525 template<typename OutputIterator, typename Context>
generate_all_tag_exampleseolian_mono::documentation_generator526 bool generate_all_tag_examples(OutputIterator sink, std::string const & full_class_name, std::string const& object_name, Context const& context) const
527 {
528 // Take example from derived class
529 auto derived_klass = efl::eolian::grammar::context_find_tag<class_context>(context);
530 std::string derived_full_name =
531 derived_klass.name.empty() ? object_name : derived_klass.name + "." + object_name;
532 std::string base_full_name =
533 full_class_name.empty() ? object_name : full_class_name + "." + object_name;
534 if (!derived_klass.name.empty())
535 {
536 if (!generate_tag_example(sink, derived_full_name, context)) return false;
537 }
538 if (derived_full_name.compare(base_full_name) == 0) return true;
539 // Take example from base class
540 return generate_tag_example(sink, base_full_name, context);
541 }
542
543 // Actual exported generators
544 template<typename OutputIterator, typename Attribute, typename Context>
generateeolian_mono::documentation_generator545 bool generate(OutputIterator sink, Attribute const& attr, Context const& context) const
546 {
547 return generate(sink, attr.documentation, context);
548 }
549
550 template<typename OutputIterator, typename Context>
generateeolian_mono::documentation_generator551 bool generate(OutputIterator sink, attributes::klass_def const& klass, Context const& context) const
552 {
553 if (!generate(sink, klass.documentation, context)) return false;
554
555 if (klass.is_beta)
556 {
557 if (!generate_tag(sink, "remarks", BETA_CLASS_REMARK, context)) return false;
558 }
559
560 std::string klass_name = name_helpers::klass_full_concrete_or_interface_name(klass);
561 return generate_tag_example(sink, klass_name, context);
562 }
563
564 /*! Generates documentation for tuple-value properties.
565 *
566 * Example:
567 *
568 * A tuple containing the following information:
569 * <list type="bullet">
570 * <item><description><c>a</c>: Parameter a.</description></item>
571 * <item><description><c>b</c>: Parameter b.</description></item>
572 * </list>
573 *
574 * \param index_names If true, tuple items are referenced by their index. If
575 * false, they are referenced by their names instead.
576 */
577 template<typename OutputIterator, typename Context>
generate_tuple_parameters_doceolian_mono::documentation_generator578 bool generate_tuple_parameters_doc(OutputIterator sink,
579 std::vector<attributes::parameter_def> const& parameters,
580 Context const& context,
581 bool numbered_refs = false) const
582 {
583 if (!(as_generator(scope_tab(scope_size) << "/// ")
584 .generate(sink, attributes::unused, context)
585 && as_generator(
586 "A tuple containing the following information:\n"
587 << scope_tab(scope_size) << "/// "
588 ).generate(sink, attributes::unused, context)
589 && generate_opening_tag(sink, "list", context, "type=\"bullet\"")
590 && as_generator("\n" << scope_tab(scope_size) << "/// ")
591 .generate(sink, attributes::unused, context)))
592 return false;
593
594 auto i = 0u;
595 for (auto const& param: parameters)
596 {
597 auto name = param.param_name;
598 if (!(generate_opening_tag(sink, "item", context)
599 && generate_opening_tag(sink, "description", context)
600 && generate_opening_tag(sink, "c", context)
601 && as_generator(name).generate(sink, attributes::unused, context)
602 && generate_closing_tag(sink, "c", context)))
603 return false;
604
605 if (numbered_refs && !(
606 as_generator(" (").generate(sink, attributes::unused, context)
607 && generate_opening_tag(sink, "c", context)
608 && as_generator("Item" + std::to_string(i)).generate(sink, attributes::unused, context)
609 && generate_closing_tag(sink, "c", context)
610 && as_generator(")").generate(sink, attributes::unused, context)))
611 return false;
612
613 if (!(generate_escaped_content(sink, ": " + param.documentation.full_text, context)
614 && generate_closing_tag(sink, "description", context)
615 && generate_closing_tag(sink, "item", context)
616 && as_generator("\n" << scope_tab(scope_size) << "/// ")
617 .generate(sink, attributes::unused, context)))
618 return false;
619 ++i;
620 }
621
622 return generate_closing_tag(sink, "list", context)
623 && as_generator("\n")
624 .generate(sink, attributes::unused, context);
625 }
626
627 template<typename OutputIterator, typename Context>
generateeolian_mono::documentation_generator628 bool generate(OutputIterator sink, attributes::property_def const& prop, Context const& context) const
629 {
630 // Generate docs by merging Property, Accessor, Since and Beta pieces.
631 std::string text = prop.documentation.full_text;
632 if (!prop.documentation.since.empty())
633 text += "\\<br/\\>\nSince EFL " + prop.documentation.since + ".";
634 if (prop.setter.is_engaged() && !prop.setter->documentation.full_text.empty())
635 text += "\\<br/\\>\n\\<b\\>Note on writing:\\</b\\> " + prop.setter->documentation.full_text;
636 if (prop.getter.is_engaged() && !prop.getter->documentation.full_text.empty())
637 text += "\\<br/\\>\n\\<b\\>Note on reading:\\</b\\> " + prop.getter->documentation.full_text;
638 if (!prop.klass.is_beta)
639 {
640 if ((prop.setter.is_engaged() && prop.setter->is_beta) ||
641 (prop.getter.is_engaged() && prop.getter->is_beta))
642 text += BETA_PROPERTY_REMARK;
643 }
644 if (!generate_tag_summary(sink, text, context))
645 return false;
646
647 text = "";
648 if (prop.setter.is_engaged())
649 {
650 if (prop.setter.is_engaged() && prop.setter->values.size() > 1u)
651 {
652 if (!(
653 as_generator(scope_tab(scope_size) << "/// ")
654 .generate(sink, attributes::unused, context)
655 && generate_opening_tag(sink, "value", context)
656 && as_generator("\n")
657 .generate(sink, attributes::unused, context)
658 && generate_tuple_parameters_doc(sink, prop.setter->parameters, context, true)
659 && as_generator(scope_tab(scope_size) << "/// ")
660 .generate(sink, attributes::unused, context)
661 && generate_closing_tag(sink, "value", context)
662 && as_generator("\n")
663 .generate(sink, attributes::unused, context)
664 ))
665 return false;
666 }
667 else
668 text = prop.setter->parameters[0].documentation.full_text;
669 }
670 else if (prop.getter.is_engaged())
671 text = prop.getter->return_documentation.full_text;
672 // If there are no docs at all, do not generate <value> tag
673 if (!text.empty())
674 if (!generate_tag_value(
675 sink,
676 text,
677 context)) return false;
678
679 return generate_all_tag_examples(sink,
680 name_helpers::klass_full_concrete_or_interface_name(prop.klass),
681 name_helpers::property_managed_name(prop),
682 context);
683 }
684
685 template<typename OutputIterator, typename Context>
generateeolian_mono::documentation_generator686 bool generate(OutputIterator sink, attributes::function_def const& func, Context const& context) const
687 {
688 if (func.type == attributes::function_type::prop_get || func.type == attributes::function_type::prop_set)
689 return generate_property(sink, func, context);
690 else
691 return generate_function(sink, func, context);
692 return true;
693 }
694
695 template<typename OutputIterator, typename Context>
generate_propertyeolian_mono::documentation_generator696 bool generate_property(OutputIterator sink, attributes::function_def const& func, Context const& context) const
697 {
698 if (!func.documentation.full_text.empty() ||
699 !func.property_documentation.full_text.empty())
700 {
701 // Generate docs by merging Property, Accessor, Since and Beta pieces.
702 std::string text = func.property_documentation.full_text;
703 if (!func.property_documentation.since.empty())
704 text += "\\<br/\\>\nSince EFL " + func.property_documentation.since + ".";
705 if (!func.documentation.full_text.empty())
706 text += "\\<br/\\>\n\\<b\\>Note:\\</b\\> " + func.documentation.full_text;
707 if (!func.klass.is_beta && func.is_beta)
708 text += BETA_METHOD_REMARK;
709 if (!generate_tag_summary(sink, text, context))
710 return false;
711 }
712
713 for (auto&& param : func.parameters)
714 if (!generate_parameter(sink, param, context))
715 return false;
716
717 if (!generate_tag_return(sink, func.return_documentation.full_text, context))
718 return false;
719
720 return generate_all_tag_examples(sink,
721 name_helpers::klass_full_concrete_or_interface_name(func.klass),
722 name_helpers::managed_method_name(func),
723 context);
724 }
725
726 template<typename OutputIterator, typename Context>
generate_functioneolian_mono::documentation_generator727 bool generate_function(OutputIterator sink, attributes::function_def const& func, Context const& context) const
728 {
729 std::string tail_text = "";
730 if (!func.klass.is_beta && func.is_beta)
731 {
732 tail_text = BETA_METHOD_REMARK;
733 }
734
735 if (!generate(sink, func.documentation, context, tail_text))
736 return false;
737
738 for (auto&& param : func.parameters)
739 if (!generate_parameter(sink, param, context))
740 return false;
741
742 if (!generate_tag_return(sink, func.return_documentation.full_text, context))
743 return false;
744
745 return generate_all_tag_examples(sink,
746 name_helpers::klass_full_concrete_or_interface_name(func.klass),
747 name_helpers::managed_method_name(func),
748 context);
749 }
750
751 template<typename OutputIterator, typename Context>
generate_parametereolian_mono::documentation_generator752 bool generate_parameter(OutputIterator sink, attributes::parameter_def const& param, Context const& context) const
753 {
754 auto text = param.documentation.full_text;
755 if (param.default_value.is_engaged())
756 {
757 auto value = param.default_value->serialized;
758
759 if (param.default_value->is_name_ref)
760 {
761 value = name_helpers::full_managed_name(value);
762 text += "\\<br/\\>The default value is \\<see cref=\\\"" + value + "\\\"/\\>.";
763 }
764 else
765 {
766 text += "\\<br/\\>The default value is \\<c\\>" + value + "\\</c\\>.";
767 }
768 }
769 return generate_tag_param(sink, name_helpers::escape_keyword(param.param_name), text, context);
770 }
771
772 template<typename OutputIterator, typename Context>
generateeolian_mono::documentation_generator773 bool generate(OutputIterator sink, attributes::documentation_def const& doc, Context const& context, std::string tail_text = "") const
774 {
775 std::string str = doc.full_text;
776 if (!doc.since.empty())
777 str += "\\<br/\\>Since EFL " + doc.since + ".";
778 str += tail_text;
779 return generate_tag_summary(sink, str, context);
780 }
781
782 template<typename OutputIterator, typename Context>
generateeolian_mono::documentation_generator783 bool generate(OutputIterator sink, attributes::constructor_def const& ctor, Context const& context) const
784 {
785 // Not sure if this is the best way to generate a reference outside the full doc generator.
786 auto unit = (const Eolian_Unit*) context_find_tag<eolian_state_context>(context).state;
787 auto func = ctor.function;
788 auto eolian_klass = get_klass(func.klass, unit);
789 attributes::klass_def klass(eolian_klass, unit);
790 std::string summary;
791
792 if (func.type == attributes::function_type::prop_set)
793 summary = func.property_documentation.summary;
794 else
795 summary = func.documentation.summary;
796
797 for (auto &¶m : ctor.function.parameters)
798 {
799 auto ref = function_conversion(func, context);
800
801 if (!context_find_tag<options_context>(context).want_beta && func.is_beta)
802 {
803 ref += BETA_REF_SUFFIX;
804 ref = "<span class=\"text-muted\">" + ref + "</span>";
805 }
806 else
807 ref = "<see cref=\"" + ref + "\" />";
808
809 if (!as_generator(scope_tab(scope_size) << "/// <param name=\"" << constructor_parameter_name(ctor) << "\">" << summary << " See " << ref << "</param>\n")
810 .generate(sink, param, context))
811 return false;
812 }
813 return true;
814 }
815 };
816
817 struct documentation_terminal
818 {
operator ()eolian_mono::documentation_terminal819 documentation_generator operator()(int n) const
820 {
821 return documentation_generator(n);
822 }
823 } const documentation = {};
824
as_generator(documentation_terminal)825 documentation_generator as_generator(documentation_terminal)
826 {
827 return documentation_generator(0);
828 }
829
830 /// Escape a single string, HTML-escaping and converting the syntax
831 struct documentation_string_generator
832 {
833 template<typename OutputIterator, typename Context>
generateeolian_mono::documentation_string_generator834 bool generate(OutputIterator sink, std::string const& text, Context const& context) const
835 {
836 std::string escaped;
837 if (!as_generator(html_escaped_string).generate(std::back_inserter(escaped), text, context))
838 return false;
839
840 auto options = context_find_tag<options_context>(context);
841 auto state = context_find_tag<eolian_state_context>(context).state;
842 if (!as_generator(string).generate(sink, documentation_generator::syntax_conversion(escaped, state, options.want_beta, context), context))
843 return false;
844
845 return true;
846 }
847
848 } const documentation_string {};
849
850 namespace documentation_helpers
851 {
852
853 template<typename OutputIterator, typename Indent, typename Context>
generate_since_tag_line(OutputIterator sink,attributes::documentation_def const & doc,Indent indentation,Context context)854 bool generate_since_tag_line(OutputIterator sink, attributes::documentation_def const& doc, Indent indentation, Context context)
855 {
856 if (doc.since.empty())
857 return true;
858
859 return as_generator
860 (
861 indentation << ("/// <para>Since EFL " + doc.since + ".</para>\n")
862 ).generate(sink, attributes::unused, context);
863 }
864
865 } // documentation_helpers
866
867 } // namespace eolian_mono
868
869 namespace efl { namespace eolian { namespace grammar {
870
871 template<>
872 struct is_eager_generator<::eolian_mono::documentation_generator> : std::true_type {};
873 template<>
874 struct is_generator<::eolian_mono::documentation_generator> : std::true_type {};
875
876 template<>
877 struct is_eager_generator<::eolian_mono::documentation_string_generator> : std::true_type {};
878 template<>
879 struct is_generator<::eolian_mono::documentation_string_generator> : std::true_type {};
880
881 template<>
882 struct is_generator<::eolian_mono::documentation_terminal> : std::true_type {};
883
884 namespace type_traits {
885 template<>
886 struct attributes_needed<struct ::eolian_mono::documentation_generator> : std::integral_constant<int, 1> {};
887 template<>
888 struct attributes_needed<struct ::eolian_mono::documentation_terminal> : std::integral_constant<int, 1> {};
889 template<>
890 struct attributes_needed<struct ::eolian_mono::documentation_string_generator> : std::integral_constant<int, 1> {};
891 }
892 } } }
893
894 #endif
895