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 &&param : 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