diff options
author | robot-piglet <[email protected]> | 2025-08-28 14:27:58 +0300 |
---|---|---|
committer | robot-piglet <[email protected]> | 2025-08-28 14:57:06 +0300 |
commit | 81d828c32c8d5477cb2f0ce5da06a1a8d9392ca3 (patch) | |
tree | 3081d566f0d5158d76e9093261344f6406fd09f7 /contrib/tools/swig/Source/Doxygen/pydoc.cxx | |
parent | 77ea11423f959e51795cc3ef36a48d808b4ffb98 (diff) |
Intermediate changes
commit_hash:d5b1af16dbe9030537a04c27eb410c88c2f496cd
Diffstat (limited to 'contrib/tools/swig/Source/Doxygen/pydoc.cxx')
-rw-r--r-- | contrib/tools/swig/Source/Doxygen/pydoc.cxx | 993 |
1 files changed, 993 insertions, 0 deletions
diff --git a/contrib/tools/swig/Source/Doxygen/pydoc.cxx b/contrib/tools/swig/Source/Doxygen/pydoc.cxx new file mode 100644 index 00000000000..03986a49c9a --- /dev/null +++ b/contrib/tools/swig/Source/Doxygen/pydoc.cxx @@ -0,0 +1,993 @@ +/* ----------------------------------------------------------------------------- + * This file is part of SWIG, which is licensed as a whole under version 3 + * (or any later version) of the GNU General Public License. Some additional + * terms also apply to certain portions of SWIG. The full details of the SWIG + * license and copyrights can be found in the LICENSE and COPYRIGHT files + * included with the SWIG source code as distributed by the SWIG developers + * and at https://www.swig.org/legal.html. + * + * pydoc.cxx + * + * Module to return documentation for nodes formatted for PyDoc + * ----------------------------------------------------------------------------- */ + +#include "pydoc.h" +#include "doxyparser.h" +#include <sstream> +#include <string> +#include <vector> +#include <iostream> + +#include "swigmod.h" + +// define static tables, they are filled in PyDocConverter's constructor +PyDocConverter::TagHandlersMap PyDocConverter::tagHandlers; +std::map<std::string, std::string> PyDocConverter::sectionTitles; + +using std::string; + +// Helper class increasing the provided indent string in its ctor and decreasing +// it in its dtor. +class IndentGuard { +public: + // One indent level. + static const char *Level() { + return " "; + } + // Default ctor doesn't do anything and prevents the dtor from doing anything// too and should only be used when the guard needs to be initialized// conditionally as Init() can then be called after checking some condition.// Otherwise, prefer to use the non default ctor below. + IndentGuard() { + m_initialized = false; + } + + // Ctor takes the output to determine the current indent and to remove the + // extra indent added to it in the dtor and the variable containing the indent + // to use, which must be used after every new line by the code actually + // updating the output. + IndentGuard(string &output, string &indent) { + Init(output, indent); + } + + // Really initializes the object created using the default ctor. + void Init(string &output, string &indent) { + m_output = &output; + m_indent = &indent; + + const string::size_type lastNonSpace = m_output->find_last_not_of(' '); + if (lastNonSpace == string::npos) { + m_firstLineIndent = m_output->length(); + } else if ((*m_output)[lastNonSpace] == '\n') { + m_firstLineIndent = m_output->length() - (lastNonSpace + 1); + } else { + m_firstLineIndent = 0; + } + + // Notice that the indent doesn't include the first line indent because it's + // implicit, i.e. it is present in the input and so is copied into the + // output anyhow. + *m_indent = Level(); + + m_initialized = true; + } + + // Get the indent for the first line of the paragraph, which is smaller than + // the indent for the subsequent lines. + string getFirstLineIndent() const { + return string(m_firstLineIndent, ' '); + } + + ~IndentGuard() { + if (!m_initialized) + return; + + m_indent->clear(); + + // Get rid of possible remaining extra indent, e.g. if there were any trailing + // new lines: we shouldn't add the extra indent level to whatever follows + // this paragraph. + static const size_t lenIndentLevel = strlen(Level()); + if (m_output->length() > lenIndentLevel) { + const size_t start = m_output->length() - lenIndentLevel; + if (m_output->compare(start, string::npos, Level()) == 0) + m_output->erase(start); + } + } + +private: + string *m_output; + string *m_indent; + string::size_type m_firstLineIndent; + bool m_initialized; + + IndentGuard(const IndentGuard &); + IndentGuard &operator=(const IndentGuard &); +}; + +// Return the indent of the given multiline string, i.e. the maximal number of +// spaces present in the beginning of all its non-empty lines. +static size_t determineIndent(const string &s) { + size_t minIndent = static_cast<size_t>(-1); + + for (size_t lineStart = 0; lineStart < s.length();) { + const size_t lineEnd = s.find('\n', lineStart); + const size_t firstNonSpace = s.find_first_not_of(' ', lineStart); + + // If inequality doesn't hold, it means that this line contains only spaces + // (notice that this works whether lineEnd is valid or string::npos), in + // which case it doesn't matter when determining the indent. + if (firstNonSpace < lineEnd) { + // Here we can be sure firstNonSpace != string::npos. + const size_t lineIndent = firstNonSpace - lineStart; + if (lineIndent < minIndent) + minIndent = lineIndent; + } + + if (lineEnd == string::npos) + break; + + lineStart = lineEnd + 1; + } + + return minIndent; +} + +static void trimWhitespace(string &s) { + const string::size_type lastNonSpace = s.find_last_not_of(' '); + if (lastNonSpace == string::npos) + s.clear(); + else + s.erase(lastNonSpace + 1); +} + +// Erase the first character in the string if it is a newline +static void eraseLeadingNewLine(string &s) { + if (!s.empty() && s[0] == '\n') + s.erase(s.begin()); +} + +// Erase the last character in the string if it is a newline +static void eraseTrailingNewLine(string &s) { + if (!s.empty() && s[s.size() - 1] == '\n') + s.erase(s.size() - 1); +} + +// Check the generated docstring line by line and make sure that any +// code and verbatim blocks have an empty line preceding them, which +// is necessary for Sphinx. Additionally, this strips any empty lines +// appearing at the beginning of the docstring. +static string padCodeAndVerbatimBlocks(const string &docString) { + std::string result; + + std::istringstream iss(docString); + + // Initialize to false because there is no previous line yet + bool lastLineWasNonBlank = false; + + for (string line; std::getline(iss, line); result += line) { + if (!result.empty()) { + // Terminate the previous line + result += '\n'; + } + + const size_t pos = line.find_first_not_of(" \t"); + if (pos == string::npos) { + lastLineWasNonBlank = false; + } else { + if (lastLineWasNonBlank && + (line.compare(pos, 13, ".. code-block") == 0 || + line.compare(pos, 7, ".. math") == 0 || + line.compare(pos, 3, ">>>") == 0)) { + // Must separate code or math blocks from the previous line + result += '\n'; + } + lastLineWasNonBlank = true; + } + } + return result; +} + +// Helper function to extract the option value from a command, +// e.g. param[in] -> in +static std::string getCommandOption(const std::string &command, char openChar, char closeChar) { + string option; + + size_t opt_begin, opt_end; + opt_begin = command.find(openChar); + opt_end = command.find(closeChar); + if (opt_begin != string::npos && opt_end != string::npos) + option = command.substr(opt_begin+1, opt_end-opt_begin-1); + + return option; +} + + +/* static */ +PyDocConverter::TagHandlersMap::mapped_type PyDocConverter::make_handler(tagHandler handler) { + return make_pair(handler, std::string()); +} + +/* static */ +PyDocConverter::TagHandlersMap::mapped_type PyDocConverter::make_handler(tagHandler handler, const char *arg) { + return make_pair(handler, arg); +} + +void PyDocConverter::fillStaticTables() { + if (tagHandlers.size()) // fill only once + return; + + // table of section titles, they are printed only once + // for each group of specified doxygen commands + sectionTitles["author"] = "Author: "; + sectionTitles["authors"] = "Authors: "; + sectionTitles["copyright"] = "Copyright: "; + sectionTitles["deprecated"] = "Deprecated: "; + sectionTitles["example"] = "Example: "; + sectionTitles["note"] = "Notes: "; + sectionTitles["remark"] = "Remarks: "; + sectionTitles["remarks"] = "Remarks: "; + sectionTitles["warning"] = "Warning: "; +// sectionTitles["sa"] = "See also: "; +// sectionTitles["see"] = "See also: "; + sectionTitles["since"] = "Since: "; + sectionTitles["todo"] = "TODO: "; + sectionTitles["version"] = "Version: "; + + tagHandlers["a"] = make_handler(&PyDocConverter::handleTagWrap, "*"); + tagHandlers["b"] = make_handler(&PyDocConverter::handleTagWrap, "**"); + // \c command is translated as single quotes around next word + tagHandlers["c"] = make_handler(&PyDocConverter::handleTagWrap, "``"); + tagHandlers["cite"] = make_handler(&PyDocConverter::handleTagWrap, "'"); + tagHandlers["e"] = make_handler(&PyDocConverter::handleTagWrap, "*"); + // these commands insert just a single char, some of them need to be escaped + tagHandlers["$"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["@"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["\\"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["<"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers[">"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["&"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["#"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["%"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["~"] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["\""] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["."] = make_handler(&PyDocConverter::handleTagChar); + tagHandlers["::"] = make_handler(&PyDocConverter::handleTagChar); + // these commands are stripped out, and only their content is printed + tagHandlers["attention"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["author"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["authors"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["brief"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["bug"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["code"] = make_handler(&PyDocConverter::handleCode); + tagHandlers["copyright"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["date"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["deprecated"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["details"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["em"] = make_handler(&PyDocConverter::handleTagWrap, "*"); + tagHandlers["example"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["exception"] = tagHandlers["throw"] = tagHandlers["throws"] = make_handler(&PyDocConverter::handleTagException); + tagHandlers["htmlonly"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["invariant"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["latexonly"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["link"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["manonly"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["note"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["p"] = make_handler(&PyDocConverter::handleTagWrap, "``"); + tagHandlers["partofdescription"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["rtfonly"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["remark"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["remarks"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["sa"] = make_handler(&PyDocConverter::handleTagMessage, "See also: "); + tagHandlers["see"] = make_handler(&PyDocConverter::handleTagMessage, "See also: "); + tagHandlers["since"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["short"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["todo"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["version"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["verbatim"] = make_handler(&PyDocConverter::handleVerbatimBlock); + tagHandlers["warning"] = make_handler(&PyDocConverter::handleParagraph); + tagHandlers["xmlonly"] = make_handler(&PyDocConverter::handleParagraph); + // these commands have special handlers + tagHandlers["arg"] = make_handler(&PyDocConverter::handleTagMessage, "* "); + tagHandlers["cond"] = make_handler(&PyDocConverter::handleTagMessage, "Conditional comment: "); + tagHandlers["else"] = make_handler(&PyDocConverter::handleTagIf, "Else: "); + tagHandlers["elseif"] = make_handler(&PyDocConverter::handleTagIf, "Else if: "); + tagHandlers["endcond"] = make_handler(&PyDocConverter::handleTagMessage, "End of conditional comment."); + tagHandlers["if"] = make_handler(&PyDocConverter::handleTagIf, "If: "); + tagHandlers["ifnot"] = make_handler(&PyDocConverter::handleTagIf, "If not: "); + tagHandlers["image"] = make_handler(&PyDocConverter::handleTagImage); + tagHandlers["li"] = make_handler(&PyDocConverter::handleTagMessage, "* "); + tagHandlers["overload"] = make_handler(&PyDocConverter::handleTagMessage, + "This is an overloaded member function, provided for" + " convenience.\nIt differs from the above function only in what" " argument(s) it accepts."); + tagHandlers["par"] = make_handler(&PyDocConverter::handleTagPar); + tagHandlers["param"] = tagHandlers["tparam"] = make_handler(&PyDocConverter::handleTagParam); + tagHandlers["ref"] = make_handler(&PyDocConverter::handleTagRef); + tagHandlers["result"] = tagHandlers["return"] = tagHandlers["returns"] = make_handler(&PyDocConverter::handleTagReturn); + + // this command just prints its contents + // (it is internal command of swig's parser, contains plain text) + tagHandlers["plainstd::string"] = make_handler(&PyDocConverter::handlePlainString); + tagHandlers["plainstd::endl"] = make_handler(&PyDocConverter::handleNewLine); + tagHandlers["n"] = make_handler(&PyDocConverter::handleNewLine); + + // \f commands output literal Latex formula, which is still better than nothing. + tagHandlers["f$"] = tagHandlers["f["] = tagHandlers["f{"] = make_handler(&PyDocConverter::handleMath); + + // HTML tags + tagHandlers["<a"] = make_handler(&PyDocConverter::handleDoxyHtmlTag_A); + tagHandlers["<b"] = make_handler(&PyDocConverter::handleDoxyHtmlTag2, "**"); + tagHandlers["<blockquote"] = make_handler(&PyDocConverter::handleDoxyHtmlTag_A, "Quote: "); + tagHandlers["<body"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<br"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, "\n"); + + // there is no formatting for this tag as it was deprecated in HTML 4.01 and + // not used in HTML 5 + tagHandlers["<center"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<caption"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<code"] = make_handler(&PyDocConverter::handleDoxyHtmlTag2, "``"); + + tagHandlers["<dl"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<dd"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, " "); + tagHandlers["<dt"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + + tagHandlers["<dfn"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<div"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<em"] = make_handler(&PyDocConverter::handleDoxyHtmlTag2, "**"); + tagHandlers["<form"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<hr"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, "--------------------------------------------------------------------\n"); + tagHandlers["<h1"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, "# "); + tagHandlers["<h2"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, "## "); + tagHandlers["<h3"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, "### "); + tagHandlers["<i"] = make_handler(&PyDocConverter::handleDoxyHtmlTag2, "*"); + tagHandlers["<input"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<img"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, "Image:"); + tagHandlers["<li"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, "* "); + tagHandlers["<meta"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<multicol"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<ol"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<p"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, "\n"); + tagHandlers["<pre"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<small"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<span"] = make_handler(&PyDocConverter::handleDoxyHtmlTag2, "'"); + tagHandlers["<strong"] = make_handler(&PyDocConverter::handleDoxyHtmlTag2, "**"); + + // make a space between text and super/sub script. + tagHandlers["<sub"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, " "); + tagHandlers["<sup"] = make_handler(&PyDocConverter::handleDoxyHtmlTag, " "); + + tagHandlers["<table"] = make_handler(&PyDocConverter::handleDoxyHtmlTagNoParam); + tagHandlers["<td"] = make_handler(&PyDocConverter::handleDoxyHtmlTag_td); + tagHandlers["<th"] = make_handler(&PyDocConverter::handleDoxyHtmlTag_th); + tagHandlers["<tr"] = make_handler(&PyDocConverter::handleDoxyHtmlTag_tr); + tagHandlers["<tt"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<kbd"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<ul"] = make_handler(&PyDocConverter::handleDoxyHtmlTag); + tagHandlers["<var"] = make_handler(&PyDocConverter::handleDoxyHtmlTag2, "*"); + + // HTML entities + tagHandlers["©"] = make_handler(&PyDocConverter::handleHtmlEntity, "(C)"); + tagHandlers["&trade"] = make_handler(&PyDocConverter::handleHtmlEntity, " TM"); + tagHandlers["®"] = make_handler(&PyDocConverter::handleHtmlEntity, "(R)"); + tagHandlers["<"] = make_handler(&PyDocConverter::handleHtmlEntity, "<"); + tagHandlers[">"] = make_handler(&PyDocConverter::handleHtmlEntity, ">"); + tagHandlers["&"] = make_handler(&PyDocConverter::handleHtmlEntity, "&"); + tagHandlers["&apos"] = make_handler(&PyDocConverter::handleHtmlEntity, "'"); + tagHandlers["""] = make_handler(&PyDocConverter::handleHtmlEntity, "\""); + tagHandlers["&lsquo"] = make_handler(&PyDocConverter::handleHtmlEntity, "`"); + tagHandlers["&rsquo"] = make_handler(&PyDocConverter::handleHtmlEntity, "'"); + tagHandlers["&ldquo"] = make_handler(&PyDocConverter::handleHtmlEntity, "\""); + tagHandlers["&rdquo"] = make_handler(&PyDocConverter::handleHtmlEntity, "\""); + tagHandlers["&ndash"] = make_handler(&PyDocConverter::handleHtmlEntity, "-"); + tagHandlers["&mdash"] = make_handler(&PyDocConverter::handleHtmlEntity, "--"); + tagHandlers[" "] = make_handler(&PyDocConverter::handleHtmlEntity, " "); + tagHandlers["×"] = make_handler(&PyDocConverter::handleHtmlEntity, "x"); + tagHandlers["&minus"] = make_handler(&PyDocConverter::handleHtmlEntity, "-"); + tagHandlers["&sdot"] = make_handler(&PyDocConverter::handleHtmlEntity, "."); + tagHandlers["&sim"] = make_handler(&PyDocConverter::handleHtmlEntity, "~"); + tagHandlers["&le"] = make_handler(&PyDocConverter::handleHtmlEntity, "<="); + tagHandlers["&ge"] = make_handler(&PyDocConverter::handleHtmlEntity, ">="); + tagHandlers["&larr"] = make_handler(&PyDocConverter::handleHtmlEntity, "<--"); + tagHandlers["&rarr"] = make_handler(&PyDocConverter::handleHtmlEntity, "-->"); +} + +PyDocConverter::PyDocConverter(int flags): +DoxygenTranslator(flags), m_tableLineLen(0), m_prevRowIsTH(false) { + fillStaticTables(); +} + +// Return the type as it should appear in the output documentation. +static std::string getPyDocType(Node *n, const_String_or_char_ptr lname = "") { + std::string type; + + String *s = Swig_typemap_lookup("doctype", n, lname, 0); + if (!s) { + if (String *t = Getattr(n, "type")) + s = SwigType_str(t, NULL); + } + + if (!s) + return type; + + if (Language::classLookup(s)) { + // In Python C++ namespaces are flattened, so remove all but last component + // of the name. + String *const last = Swig_scopename_last(s); + + // We are not actually sure whether it's a documented class or not, but + // there doesn't seem to be any harm in making it a reference if it isn't, + // while there is a lot of benefit in having a hyperlink if it is. + type = ":py:class:`"; + type += Char(last); + type += "`"; + + Delete(last); + } else { + type = Char(s); + } + + Delete(s); + + return type; +} + +std::string PyDocConverter::getParamType(std::string param) { + std::string type; + + ParmList *plist = CopyParmList(Getattr(currentNode, "parms")); + for (Parm *p = plist; p; p = nextSibling(p)) { + String *pname = Getattr(p, "name"); + if (pname && Char(pname) == param) { + type = getPyDocType(p, pname); + break; + } + } + Delete(plist); + return type; +} + +std::string PyDocConverter::getParamValue(std::string param) { + std::string value; + + ParmList *plist = CopyParmList(Getattr(currentNode, "parms")); + for (Parm *p = plist; p; p = nextSibling(p)) { + String *pname = Getattr(p, "name"); + if (pname && Char(pname) == param) { + String *pval = Getattr(p, "value"); + if (pval) + value = Char(pval); + break; + } + } + Delete(plist); + return value; +} + +std::string PyDocConverter::translateSubtree(DoxygenEntity &doxygenEntity) { + std::string translatedComment; + + if (doxygenEntity.isLeaf) + return translatedComment; + + std::string currentSection; + std::list<DoxygenEntity>::iterator p = doxygenEntity.entityList.begin(); + while (p != doxygenEntity.entityList.end()) { + std::map<std::string, std::string>::iterator it; + it = sectionTitles.find(p->typeOfEntity); + if (it != sectionTitles.end()) { + if (it->second != currentSection) { + currentSection = it->second; + translatedComment += currentSection; + } + } + translateEntity(*p, translatedComment); + translateSubtree(*p); + p++; + } + + return translatedComment; +} + +void PyDocConverter::translateEntity(DoxygenEntity &doxyEntity, std::string &translatedComment) { + // check if we have needed handler and call it + std::map<std::string, std::pair<tagHandler, std::string> >::iterator it; + it = tagHandlers.find(getBaseCommand(doxyEntity.typeOfEntity)); + if (it != tagHandlers.end()) + (this->*(it->second.first)) (doxyEntity, translatedComment, it->second.second); +} + +void PyDocConverter::handleParagraph(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + translatedComment += translateSubtree(tag); +} + +void PyDocConverter::handleVerbatimBlock(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + string verb = translateSubtree(tag); + + eraseLeadingNewLine(verb); + + // Remove the last newline to prevent doubling the newline already present after \endverbatim + trimWhitespace(verb); // Needed to catch trailing newline below + eraseTrailingNewLine(verb); + translatedComment += verb; +} + +void PyDocConverter::handleMath(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + IndentGuard indent; + + // Only \f$ is translated to inline formulae, \f[ and \f{ are for the block ones. + const bool inlineFormula = tag.typeOfEntity == "f$"; + + string formulaNL; + + if (inlineFormula) { + translatedComment += ":math:`"; + } else { + indent.Init(translatedComment, m_indent); + + trimWhitespace(translatedComment); + + const string formulaIndent = indent.getFirstLineIndent(); + translatedComment += formulaIndent; + translatedComment += ".. math::\n"; + + formulaNL = '\n'; + formulaNL += formulaIndent; + formulaNL += m_indent; + translatedComment += formulaNL; + } + + std::string formula; + handleTagVerbatim(tag, formula, arg); + + // It is important to ensure that we have no spaces around the inline math + // contents, so strip them. + const size_t start = formula.find_first_not_of(" \t\n"); + const size_t end = formula.find_last_not_of(" \t\n"); + if (start != std::string::npos) { + for (size_t n = start; n <= end; n++) { + if (formula[n] == '\n') { + // New lines must be suppressed in inline maths and indented in the block ones. + if (!inlineFormula) + translatedComment += formulaNL; + } else { + // Just copy everything else. + translatedComment += formula[n]; + } + } + } + + if (inlineFormula) { + translatedComment += "`"; + } +} + +void PyDocConverter::handleCode(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + IndentGuard indent(translatedComment, m_indent); + + trimWhitespace(translatedComment); + + // Check for an option given to the code command (e.g. code{.py}), + // and try to set the code-block language accordingly. + string option = getCommandOption(tag.typeOfEntity, '{', '}'); + // Set up the language option to the code-block command, which can + // be any language supported by pygments: + string codeLanguage; + if (option == ".py") + // Other possibilities here are "default" or "python3". In Sphinx + // 2.1.2, basic syntax doesn't render quite the same in these as + // with "python", which for basic keywords seems to provide + // slightly richer formatting. Another option would be to leave + // the language empty, but testing with Sphinx 1.8.5 has produced + // an error "1 argument required". + codeLanguage = "python"; + else if (option == ".java") + codeLanguage = "java"; + else if (option == ".c") + codeLanguage = "c"; + else + // If there is not a match, or if no option was given, go out on a + // limb and assume that the examples in the C or C++ sources use + // C++. + codeLanguage = "c++"; + + std::string code; + handleTagVerbatim(tag, code, arg); + + // Try and remove leading newline, which is present for block \code + // command: + eraseLeadingNewLine(code); + + // Check for python doctest blocks, and treat them specially: + bool isDocTestBlock = false; + size_t startPos; + // ">>>" would normally appear at the beginning, but doxygen comment + // style may have space in front, so skip leading whitespace + if ((startPos=code.find_first_not_of(" \t")) != string::npos && code.substr(startPos,3) == ">>>") + isDocTestBlock = true; + + string codeIndent; + if (! isDocTestBlock) { + // Use the current indent for the code-block line itself. + translatedComment += indent.getFirstLineIndent(); + translatedComment += ".. code-block:: " + codeLanguage + "\n\n"; + + // Specify the level of extra indentation that will be used for + // subsequent lines within the code block. Note that the correct + // "starting indentation" is already present in the input, so we + // only need to add the desired code block indentation. + codeIndent = m_indent; + } + + translatedComment += codeIndent; + for (size_t n = 0; n < code.length(); n++) { + if (code[n] == '\n') { + // Don't leave trailing white space, this results in PEP8 validation + // errors in Python code (which are performed by our own unit tests). + trimWhitespace(translatedComment); + translatedComment += '\n'; + + // Ensure that we indent all the lines by the code indent. + translatedComment += codeIndent; + } else { + // Just copy everything else. + translatedComment += code[n]; + } + } + + trimWhitespace(translatedComment); + + // For block commands, the translator adds the newline after + // \endcode, so try and compensate by removing the last newline from + // the code text: + eraseTrailingNewLine(translatedComment); +} + +void PyDocConverter::handlePlainString(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + translatedComment += tag.data; +} + +void PyDocConverter::handleTagVerbatim(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + translatedComment += arg; + for (DoxygenEntityListCIt it = tag.entityList.begin(); it != tag.entityList.end(); it++) { + translatedComment += it->data; + } +} + +void PyDocConverter::handleTagMessage(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + translatedComment += arg; + handleParagraph(tag, translatedComment); +} + +void PyDocConverter::handleTagChar(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + translatedComment += tag.typeOfEntity; +} + +void PyDocConverter::handleTagIf(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + translatedComment += arg; + if (tag.entityList.size()) { + translatedComment += tag.entityList.begin()->data; + tag.entityList.pop_front(); + translatedComment += " {" + translateSubtree(tag) + "}"; + } +} + +void PyDocConverter::handleTagPar(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + translatedComment += "Title: "; + if (tag.entityList.size()) + translatedComment += tag.entityList.begin()->data; + tag.entityList.pop_front(); + handleParagraph(tag, translatedComment); +} + +void PyDocConverter::handleTagImage(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + if (tag.entityList.size() < 2) + return; + tag.entityList.pop_front(); + translatedComment += "Image: "; + translatedComment += tag.entityList.begin()->data; + tag.entityList.pop_front(); + if (tag.entityList.size()) + translatedComment += "(" + tag.entityList.begin()->data + ")"; +} + +void PyDocConverter::handleTagParam(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + if (tag.entityList.size() < 2) + return; + + IndentGuard indent(translatedComment, m_indent); + + DoxygenEntity paramNameEntity = *tag.entityList.begin(); + tag.entityList.pop_front(); + + const std::string ¶mName = paramNameEntity.data; + + const std::string paramType = getParamType(paramName); + const std::string paramValue = getParamValue(paramName); + + // Get command option, e.g. "in", "out", or "in,out" + string commandOpt = getCommandOption(tag.typeOfEntity, '[', ']'); + if (commandOpt == "in,out") commandOpt = "in/out"; + + // If provided, append the parameter direction to the type + // information via a suffix: + std::string suffix; + if (commandOpt.size() > 0) + suffix = ", " + commandOpt; + + // If the parameter has a default value, flag it as optional in the + // generated type definition. Particularly helpful when the python + // call is generated with *args, **kwargs. + if (paramValue.size() > 0) + suffix += ", optional"; + + if (!paramType.empty()) { + translatedComment += ":type " + paramName + ": " + paramType + suffix + "\n"; + translatedComment += indent.getFirstLineIndent(); + } + + translatedComment += ":param " + paramName + ":"; + + handleParagraph(tag, translatedComment); +} + + +void PyDocConverter::handleTagReturn(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + IndentGuard indent(translatedComment, m_indent); + + const std::string pytype = getPyDocType(currentNode); + if (!pytype.empty()) { + translatedComment += ":rtype: "; + translatedComment += pytype; + translatedComment += "\n"; + translatedComment += indent.getFirstLineIndent(); + } + + translatedComment += ":return: "; + handleParagraph(tag, translatedComment); +} + + +void PyDocConverter::handleTagException(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + IndentGuard indent(translatedComment, m_indent); + + translatedComment += ":raises: "; + handleParagraph(tag, translatedComment); +} + + +void PyDocConverter::handleTagRef(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + if (!tag.entityList.size()) + return; + + string anchor = tag.entityList.begin()->data; + tag.entityList.pop_front(); + string anchorText = anchor; + if (!tag.entityList.empty()) { + anchorText = tag.entityList.begin()->data; + } + translatedComment += "'" + anchorText + "'"; +} + + +void PyDocConverter::handleTagWrap(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + if (tag.entityList.size()) { // do not include empty tags + std::string tagData = translateSubtree(tag); + // wrap the thing, ignoring whitespace + size_t wsPos = tagData.find_last_not_of("\n\t "); + if (wsPos != std::string::npos && wsPos != tagData.size() - 1) + translatedComment += arg + tagData.substr(0, wsPos + 1) + arg + tagData.substr(wsPos + 1); + else + translatedComment += arg + tagData + arg; + } +} + +void PyDocConverter::handleDoxyHtmlTag(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + std::string htmlTagArgs = tag.data; + if (htmlTagArgs == "/") { + // end html tag, for example "</ul> + // translatedComment += "</" + arg.substr(1) + ">"; + } else { + translatedComment += arg + htmlTagArgs; + } +} + +void PyDocConverter::handleDoxyHtmlTagNoParam(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + std::string htmlTagArgs = tag.data; + if (htmlTagArgs == "/") { + // end html tag, for example "</ul> + } else { + translatedComment += arg; + } +} + +void PyDocConverter::handleDoxyHtmlTag_A(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + std::string htmlTagArgs = tag.data; + if (htmlTagArgs == "/") { + // end html tag, "</a> + translatedComment += " (" + m_url + ')'; + m_url.clear(); + } else { + m_url.clear(); + size_t pos = htmlTagArgs.find('='); + if (pos != string::npos) { + m_url = htmlTagArgs.substr(pos + 1); + } + translatedComment += arg; + } +} + +void PyDocConverter::handleDoxyHtmlTag2(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { + std::string htmlTagArgs = tag.data; + if (htmlTagArgs == "/") { + // end html tag, for example "</em> + translatedComment += arg; + } else { + translatedComment += arg; + } +} + +void PyDocConverter::handleDoxyHtmlTag_tr(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + std::string htmlTagArgs = tag.data; + size_t nlPos = translatedComment.rfind('\n'); + if (htmlTagArgs == "/") { + // end tag, </tr> appends vertical table line '|' + translatedComment += '|'; + if (nlPos != string::npos) { + size_t startOfTableLinePos = translatedComment.find_first_not_of(" \t", nlPos + 1); + if (startOfTableLinePos != string::npos) { + m_tableLineLen = translatedComment.size() - startOfTableLinePos; + } + } + } else { + if (m_prevRowIsTH) { + // if previous row contained <th> tag, add horizontal separator + // but first get leading spaces, because they'll be needed for the next row + size_t numLeadingSpaces = translatedComment.size() - nlPos - 1; + + translatedComment += string(m_tableLineLen, '-') + '\n'; + + if (nlPos != string::npos) { + translatedComment += string(numLeadingSpaces, ' '); + } + m_prevRowIsTH = false; + } + } +} + +void PyDocConverter::handleDoxyHtmlTag_th(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + std::string htmlTagArgs = tag.data; + if (htmlTagArgs == "/") { + // end tag, </th> is ignored + } else { + translatedComment += '|'; + m_prevRowIsTH = true; + } +} + +void PyDocConverter::handleDoxyHtmlTag_td(DoxygenEntity &tag, std::string &translatedComment, const std::string &) { + std::string htmlTagArgs = tag.data; + if (htmlTagArgs == "/") { + // end tag, </td> is ignored + } else { + translatedComment += '|'; + } +} + +void PyDocConverter::handleHtmlEntity(DoxygenEntity &, std::string &translatedComment, const std::string &arg) { + // html entities + translatedComment += arg; +} + +void PyDocConverter::handleNewLine(DoxygenEntity &, std::string &translatedComment, const std::string &) { + trimWhitespace(translatedComment); + + translatedComment += "\n"; + if (!m_indent.empty()) + translatedComment += m_indent; +} + +String *PyDocConverter::makeDocumentation(Node *n) { + String *documentation; + std::string pyDocString; + + // store the node, we may need it later + currentNode = n; + + // for overloaded functions we must concat documentation for underlying overloads + if (Getattr(n, "sym:overloaded")) { + // rewind to the first overload + while (Getattr(n, "sym:previousSibling")) + n = Getattr(n, "sym:previousSibling"); + + std::vector<std::string> allDocumentation; + + // minimal indent of any documentation comments, not initialized yet + size_t minIndent = static_cast<size_t>(-1); + + // for each real method (not a generated overload) append the documentation + string oneDoc; + while (n) { + documentation = getDoxygenComment(n); + if (!Swig_is_generated_overload(n) && documentation) { + currentNode = n; + if (GetFlag(n, "feature:doxygen:notranslate")) { + String *comment = NewString(""); + Append(comment, documentation); + Replaceall(comment, "\n *", "\n"); + oneDoc = Char(comment); + Delete(comment); + } else { + std::list<DoxygenEntity> entityList = parser.createTree(n, documentation); + DoxygenEntity root("root", entityList); + + oneDoc = translateSubtree(root); + } + + // find the minimal indent of this documentation comment, we need to + // ensure that the entire comment is indented by it to avoid the leading + // parts of the other lines being simply discarded later + const size_t oneIndent = determineIndent(oneDoc); + if (oneIndent < minIndent) + minIndent = oneIndent; + + allDocumentation.push_back(oneDoc); + } + n = Getattr(n, "sym:nextSibling"); + } + + // construct final documentation string + if (allDocumentation.size() > 1) { + string indentStr; + if (minIndent != static_cast<size_t>(-1)) + indentStr.assign(minIndent, ' '); + + std::ostringstream concatDocString; + for (size_t realOverloadCount = 0; realOverloadCount < allDocumentation.size(); realOverloadCount++) { + if (realOverloadCount != 0) { + // separate it from the preceding one. + concatDocString << '\n' << indentStr << "|\n\n"; + } + + oneDoc = allDocumentation[realOverloadCount]; + trimWhitespace(oneDoc); + concatDocString << indentStr << "*Overload " << (realOverloadCount + 1) << ":*\n" << oneDoc; + } + pyDocString = concatDocString.str(); + } else if (allDocumentation.size() == 1) { + pyDocString = *(allDocumentation.begin()); + } + } + // for other nodes just process as normal + else { + documentation = getDoxygenComment(n); + if (documentation != NULL) { + if (GetFlag(n, "feature:doxygen:notranslate")) { + String *comment = NewString(""); + Append(comment, documentation); + Replaceall(comment, "\n *", "\n"); + pyDocString = Char(comment); + Delete(comment); + } else { + std::list<DoxygenEntity> entityList = parser.createTree(n, documentation); + DoxygenEntity root("root", entityList); + pyDocString = translateSubtree(root); + } + } + } + + // if we got something log the result + if (!pyDocString.empty()) { + + // remove the last '\n' since additional one is added during writing to file + eraseTrailingNewLine(pyDocString); + + // ensure that a blank line occurs before code or math blocks + pyDocString = padCodeAndVerbatimBlocks(pyDocString); + + if (m_flags & debug_translator) { + std::cout << "\n---RESULT IN PYDOC---" << std::endl; + std::cout << pyDocString; + std::cout << std::endl; + } + } + + return NewString(pyDocString.c_str()); +} + |