Merge branch 'doxy/commands'

* doxy/commands:
  Update documentation for doxygen tags
  Fix doxygen translation of \p command for python
  Fix doxygen handling of \em tag for python
  Minor formatting updates to doxygen docs
  Reformat tag lists in doxygen documentation
  Add doxygen_code_blocks_runme.java
  Special handling for python doctest code blocks
  Add new doxygen test doxygen_code_blocks
  Handle doxygen code command with language option
  Improve doxygen parser handling of \code content
  Flag optional arguments in doxygen pydoc output
  Add parameter direction to doxygen pydoc output
  Support doxygen \param[] commands
This commit is contained in:
William S Fulton 2020-01-14 18:36:50 +00:00
commit 00b47d4d1d
19 changed files with 699 additions and 437 deletions

View file

@ -18,11 +18,13 @@
const char *CMD_HTML_ONLY = "htmlonly";
// doxy commands are not processed inside this block
const char *CMD_VERBATIM = "verbatim";
const char *CMD_CODE = "code";
const char *CMD_LATEX_1 = "f$";
const char *CMD_LATEX_2 = "f{";
const char *CMD_LATEX_3 = "f[";
const char *CMD_END_HTML_ONLY = "endhtmlonly";
const char *CMD_END_VERBATIM = "endverbatim";
const char *CMD_END_CODE = "endcode";
const char *CMD_END_LATEX_1 = "f$";
const char *CMD_END_LATEX_2 = "f}";
const char *CMD_END_LATEX_3 = "f]";

View file

@ -34,6 +34,30 @@ std::set<std::string> DoxygenParser::doxygenSectionIndicators;
const int TOKENSPERLINE = 8; //change this to change the printing behaviour of the token list
const std::string END_HTML_TAG_MARK("/");
std::string getBaseCommand(const std::string &cmd) {
if (cmd.substr(0,5) == "param")
return "param";
else if (cmd.substr(0,4) == "code")
return "code";
else
return cmd;
}
// Find the first position beyond the word command. Extra logic is
// used to avoid putting the characters "," and "." in
// DOXYGEN_WORD_CHARS.
static size_t getEndOfWordCommand(const std::string &line, size_t pos) {
size_t endOfWordPos = line.find_first_not_of(DOXYGEN_WORD_CHARS, pos);
if (line.substr(pos, 6) == "param[")
// include ",", which can appear in param[in,out]
endOfWordPos = line.find_first_not_of(string(DOXYGEN_WORD_CHARS)+ ",", pos);
else if (line.substr(pos, 5) == "code{")
// include ".", which can appear in e.g. code{.py}
endOfWordPos = line.find_first_not_of(string(DOXYGEN_WORD_CHARS)+ ".", pos);
return endOfWordPos;
}
DoxygenParser::DoxygenParser(bool noisy) : noisy(noisy) {
fillTables();
}
@ -118,7 +142,7 @@ void DoxygenParser::printTree(const DoxygenEntityList &rootList) {
}
DoxygenParser::DoxyCommandEnum DoxygenParser::commandBelongs(const std::string &theCommand) {
DoxyCommandsMapIt it = doxygenCommands.find(stringToLower(theCommand));
DoxyCommandsMapIt it = doxygenCommands.find(stringToLower(getBaseCommand(theCommand)));
if (it != doxygenCommands.end()) {
return it->second;
@ -312,7 +336,7 @@ DoxygenParser::TokenListCIt DoxygenParser::getEndOfParagraph(const TokenList &to
} else if (endOfParagraph->m_tokenType == COMMAND) {
if (isSectionIndicator(endOfParagraph->m_tokenString)) {
if (isSectionIndicator(getBaseCommand(endOfParagraph->m_tokenString))) {
return endOfParagraph;
} else {
endOfParagraph++;
@ -666,7 +690,7 @@ void DoxygenParser::addCommandUnique(const std::string &theCommand, const TokenL
// \f{ ... \f}
// \f{env}{ ... \f}
// \f$ ... \f$
else if (theCommand == "code" || theCommand == "verbatim"
else if (getBaseCommand(theCommand) == "code" || theCommand == "verbatim"
|| theCommand == "dot" || theCommand == "msc" || theCommand == "f[" || theCommand == "f{" || theCommand == "f$") {
if (!endCommands.size()) {
// fill in static table of end commands
@ -683,7 +707,7 @@ void DoxygenParser::addCommandUnique(const std::string &theCommand, const TokenL
if (it != endCommands.end())
endCommand = it->second;
else
endCommand = "end" + theCommand;
endCommand = "end" + getBaseCommand(theCommand);
std::string content = getStringTilEndCommand(endCommand, tokList);
aNewList.push_back(DoxygenEntity("plainstd::string", content));
@ -1090,7 +1114,7 @@ size_t DoxygenParser::processVerbatimText(size_t pos, const std::string &line) {
size_t endOfWordPos = line.find_first_not_of(DOXYGEN_WORD_CHARS, pos);
string cmd = line.substr(pos, endOfWordPos - pos);
if (cmd == CMD_END_HTML_ONLY || cmd == CMD_END_VERBATIM || cmd == CMD_END_LATEX_1 || cmd == CMD_END_LATEX_2 || cmd == CMD_END_LATEX_3) {
if (cmd == CMD_END_HTML_ONLY || cmd == CMD_END_VERBATIM || cmd == CMD_END_LATEX_1 || cmd == CMD_END_LATEX_2 || cmd == CMD_END_LATEX_3 || cmd == CMD_END_CODE) {
m_isVerbatimText = false;
addDoxyCommand(m_tokenList, cmd);
@ -1154,22 +1178,34 @@ bool DoxygenParser::processEscapedChars(size_t &pos, const std::string &line) {
*/
void DoxygenParser::processWordCommands(size_t &pos, const std::string &line) {
pos++;
size_t endOfWordPos = line.find_first_not_of(DOXYGEN_WORD_CHARS, pos);
size_t endOfWordPos = getEndOfWordCommand(line, pos);
string cmd = line.substr(pos, endOfWordPos - pos);
addDoxyCommand(m_tokenList, cmd);
if (cmd == CMD_HTML_ONLY || cmd == CMD_VERBATIM || cmd == CMD_LATEX_1 || cmd == CMD_LATEX_2 || cmd == CMD_LATEX_3) {
// A flag for whether we want to skip leading spaces after the command
bool skipLeadingSpace = true;
if (cmd == CMD_HTML_ONLY || cmd == CMD_VERBATIM || cmd == CMD_LATEX_1 || cmd == CMD_LATEX_2 || cmd == CMD_LATEX_3 || getBaseCommand(cmd) == CMD_CODE) {
m_isVerbatimText = true;
} else {
// Skipping leading space is necessary with inline \code command,
// and it won't hurt anything for block \code (TODO: are the other
// commands also compatible with skip leading space? If so, just
// do it every time.)
if (getBaseCommand(cmd) == CMD_CODE) skipLeadingSpace = true;
else skipLeadingSpace = false;
}
if (skipLeadingSpace) {
// skip any possible spaces after command, because some commands have parameters,
// and spaces between command and parameter must be ignored.
if (endOfWordPos != string::npos) {
endOfWordPos = line.find_first_not_of(" \t", endOfWordPos);
}
}
pos = endOfWordPos;
}

View file

@ -21,6 +21,11 @@
#include "doxyentity.h"
// Utility function to return the base part of a command that may
// include options, e.g. param[in] -> param
std::string getBaseCommand(const std::string &cmd);
class DoxygenParser {
private:

View file

@ -334,7 +334,7 @@ std::string JavaDocConverter::translateSubtree(DoxygenEntity &doxygenEntity) {
void JavaDocConverter::translateEntity(DoxygenEntity &tag, std::string &translatedComment) {
std::map<std::string, std::pair<tagHandler, std::string> >::iterator it;
it = tagHandlers.find(tag.typeOfEntity);
it = tagHandlers.find(getBaseCommand(tag.typeOfEntity));
if (it != tagHandlers.end()) {
(this->*(it->second.first))(tag, translatedComment, it->second.second);

View file

@ -174,7 +174,8 @@ static string padCodeAndVerbatimBlocks(const string &docString) {
} else {
if (lastLineWasNonBlank &&
(line.compare(pos, 13, ".. code-block") == 0 ||
line.compare(pos, 7, ".. math") == 0)) {
line.compare(pos, 7, ".. math") == 0 ||
line.compare(pos, 3, ">>>") == 0)) {
// Must separate code or math blocks from the previous line
result += '\n';
}
@ -184,6 +185,21 @@ static string padCodeAndVerbatimBlocks(const string &docString) {
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());
@ -245,7 +261,7 @@ void PyDocConverter::fillStaticTables() {
tagHandlers["date"] = make_handler(&PyDocConverter::handleParagraph);
tagHandlers["deprecated"] = make_handler(&PyDocConverter::handleParagraph);
tagHandlers["details"] = make_handler(&PyDocConverter::handleParagraph);
tagHandlers["em"] = 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);
@ -254,7 +270,7 @@ void PyDocConverter::fillStaticTables() {
tagHandlers["link"] = make_handler(&PyDocConverter::handleParagraph);
tagHandlers["manonly"] = make_handler(&PyDocConverter::handleParagraph);
tagHandlers["note"] = make_handler(&PyDocConverter::handleParagraph);
tagHandlers["p"] = 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);
@ -428,6 +444,23 @@ std::string PyDocConverter::getParamType(std::string param) {
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 (Char(pname) != param)
continue;
String *pval = Getattr(p, "value");
if (pval) value = Char(pval);
break;
}
Delete(plist);
return value;
}
std::string PyDocConverter::translateSubtree(DoxygenEntity &doxygenEntity) {
std::string translatedComment;
@ -456,7 +489,7 @@ std::string PyDocConverter::translateSubtree(DoxygenEntity &doxygenEntity) {
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(doxyEntity.typeOfEntity);
it = tagHandlers.find(getBaseCommand(doxyEntity.typeOfEntity));
if (it != tagHandlers.end())
(this->*(it->second.first)) (doxyEntity, translatedComment, it->second.second);
}
@ -531,19 +564,29 @@ void PyDocConverter::handleCode(DoxygenEntity &tag, std::string &translatedComme
trimWhitespace(translatedComment);
// Use the current indent for the code-block line itself.
translatedComment += indent.getFirstLineIndent();
// Go out on a limb and assume that examples in the C or C++ sources use C++.
// In the worst case, we'll highlight C code using C++ syntax which is not a
// big deal (TODO: handle Doxygen code command language argument).
translatedComment += ".. code-block:: c++\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.
string codeIndent = m_indent;
// 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);
@ -552,6 +595,27 @@ void PyDocConverter::handleCode(DoxygenEntity &tag, std::string &translatedComme
// 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') {
@ -636,8 +700,26 @@ void PyDocConverter::handleTagParam(DoxygenEntity &tag, std::string &translatedC
const std::string &paramName = 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 + "\n";
translatedComment += ":type " + paramName + ": " + paramType + suffix + "\n";
translatedComment += indent.getFirstLineIndent();
}
@ -909,3 +991,4 @@ String *PyDocConverter::makeDocumentation(Node *n) {
return NewString(pyDocString.c_str());
}

View file

@ -178,6 +178,11 @@ protected:
*/
std::string getParamType(std::string name);
/*
* Simple helper function to retrieve the parameter value
*/
std::string getParamValue(std::string name);
private:
// temporary thing, should be refactored somehow
Node *currentNode;