diff --git a/Examples/test-suite/common.mk b/Examples/test-suite/common.mk index 67a63287d..eece29b00 100644 --- a/Examples/test-suite/common.mk +++ b/Examples/test-suite/common.mk @@ -620,6 +620,7 @@ DOXYGEN_TEST_CASES += \ doxygen_alias \ doxygen_basic_notranslate \ doxygen_basic_translate \ + doxygen_basic_translate_style2 \ doxygen_ignore \ doxygen_misc_constructs \ doxygen_nested_class \ diff --git a/Examples/test-suite/doxygen_basic_translate_style2.i b/Examples/test-suite/doxygen_basic_translate_style2.i new file mode 100644 index 000000000..23e8de4f7 --- /dev/null +++ b/Examples/test-suite/doxygen_basic_translate_style2.i @@ -0,0 +1,105 @@ +%module doxygen_basic_translate_style2 + +%include "doxygen_basic_translate.h" + +// This test demonstrates a doxygen comment style that starts on the +// first line and so uses extra spacing in subsequent lines. + +%inline %{ + +/** \brief + * Brief description. + * + * The comment text. + * + * \author Some author + * + * \return Some number + * + * \sa function2 + */ +int function() +{ + return 0; +} + +/** A test of a very very very very very very very very very very very very very very very very + * very very very very very long comment string. + */ +void function2() +{ +} + +/** A test for overloaded functions + * This is function \b one + */ +void function3(int a) +{ +} + +/** A test for overloaded functions + * This is function \b two + */ +void function3(int a, int b) +{ +} + +/** A test of some mixed tag usage + * \if CONDITION + * This \a code fragment shows us something \. + * \par Minuses: + * \arg it's senseless + * \arg it's stupid + * \arg it's null + * + * \warning This may not work as expected + * \code + * int main() { while(true); } + * \endcode + * \endif + */ +void function4() +{ +} + + +void function5(int a) +{ +} +/**< This is a post comment. */ + +/** Test for default args + * @param a Some parameter, default is 42 + */ +void function6(int a=42) +{ +} + +class Shape +{ +public: + typedef Shape* superType; +}; + +/** Test for a parameter with difficult type + * (mostly for python) + * @param a Very strange param + */ +void function7(Shape::superType *a[10]) +{ +} + +/** Multiple parameters test. + * + * @param y Vertical coordinate. + * @param x Horizontal coordinate. + * @return Arc tangent of @c y/x. + */ +double Atan2(double y, double x) +{ + return 0; +} + +/** Comment at the end of file should be ignored. + */ +%} diff --git a/Examples/test-suite/doxygen_translate_all_tags.i b/Examples/test-suite/doxygen_translate_all_tags.i index 6e96a57c5..8da683d52 100644 --- a/Examples/test-suite/doxygen_translate_all_tags.i +++ b/Examples/test-suite/doxygen_translate_all_tags.i @@ -38,6 +38,10 @@ * \cite citationword * \class someClass headerFile.h headerName * \code some test code \endcode + * + * Code immediately following text. Pydoc translation must add an + * empty line before: + * \code more test code \endcode */ void func01(int a) { @@ -121,6 +125,12 @@ void func03(int a) * \sqrt{(x_2-x_1)^2+(y_2-y_1)^2} * \f} * + * Math immediately following text. Pydoc translation must add an + * empty line before: + * \f[ + * \sqrt{(x_2-x_1)^2+(y_2-y_1)^2} + * \f] + * * \file file.h * * \fn someFn diff --git a/Examples/test-suite/java/doxygen_basic_translate_style2_runme.java b/Examples/test-suite/java/doxygen_basic_translate_style2_runme.java new file mode 100644 index 000000000..aa015eeac --- /dev/null +++ b/Examples/test-suite/java/doxygen_basic_translate_style2_runme.java @@ -0,0 +1,99 @@ + +import doxygen_basic_translate_style2.*; +import com.sun.javadoc.*; +import java.util.HashMap; + +public class doxygen_basic_translate_style2_runme { + static { + try { + System.loadLibrary("doxygen_basic_translate_style2"); + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load. See the chapter on Dynamic Linking Problems in the SWIG Java documentation for help.\n" + e); + System.exit(1); + } + } + + public static void main(String argv[]) + { + /* + Here we are using internal javadoc tool, it accepts the name of the class as paramterer, + and calls the start() method of that class with parsed information. + */ + CommentParser parser = new CommentParser(); + com.sun.tools.javadoc.Main.execute("doxygen_basic_translate_style2 runtime test", + "CommentParser", + new String[]{"-quiet", "doxygen_basic_translate_style2"}); + + HashMap wantedComments = new HashMap(); + + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function()", + " \n" + + " Brief description.\n" + + " \n" + + " The comment text.\n" + + " @author Some author\n" + + " @return Some number\n" + + " @see function2\n" + + " \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function2()", + " A test of a very very very very very very very very very very very very very very very very \n" + + " very very very very very long comment string. \n" + + " \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function4()", + " A test of some mixed tag usage \n" + + " If: CONDITION {\n" + + " This code fragment shows us something . \n" + + "

\n" + + "

  • it's senseless \n" + + "
  • it's stupid \n" + + "
  • it's null \n" + + " \n" + + "
  • Warning: This may not work as expected \n" + + " \n" + + " {@code \n" + + "int main() { while(true); } \n" + + " }\n" + + " }\n" + + " \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function3(int)", + " A test for overloaded functions \n" + + " This is function one \n" + + " \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function5(int)", + " This is a post comment. \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function6(int)", + " Test for default args \n" + + " @param a Some parameter, default is 42" + + " \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function6()", + " Test for default args \n" + + " \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function7(doxygen_basic_translate_style2.SWIGTYPE_p_p_p_Shape)", + " Test for a parameter with difficult type \n" + + " (mostly for python) \n" + + " @param a Very strange param \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.function3(int, int)", + " A test for overloaded functions \n" + + " This is function two \n" + + " \n" + + ""); + wantedComments.put("doxygen_basic_translate_style2.doxygen_basic_translate_style2.Atan2(double, double)", + " Multiple parameters test.\n" + + " \n" + + " @param y Vertical coordinate.\n" + + " @param x Horizontal coordinate.\n" + + " @return Arc tangent of y/x.\n" + + ""); + + // and ask the parser to check comments for us + System.exit(parser.check(wantedComments)); + } +} diff --git a/Examples/test-suite/java/doxygen_translate_all_tags_runme.java b/Examples/test-suite/java/doxygen_translate_all_tags_runme.java index 8bd65224f..d5c533f4e 100644 --- a/Examples/test-suite/java/doxygen_translate_all_tags_runme.java +++ b/Examples/test-suite/java/doxygen_translate_all_tags_runme.java @@ -40,7 +40,10 @@ public class doxygen_translate_all_tags_runme { " Not everything works right now...\n" + " codeword\n\n\n\n\n\n" + " citationword\n" + - " {@code some test code }\n"); + " {@code some test code }\n\n" + + " Code immediately following text. Pydoc translation must add an\n" + + " empty line before:\n" + + " {@code more test code }"); wantedComments.put("doxygen_translate_all_tags.doxygen_translate_all_tags.func02(int)", " Conditional comment: SOMECONDITION \n" + @@ -63,8 +66,11 @@ public class doxygen_translate_all_tags_runme { " @exception SuperError \n" + " \\sqrt{(x_2-x_1)^2+(y_2-y_1)^2} \n" + " \\sqrt{(x_2-x_1)^2+(y_2-y_1)^2} \n" + - " \\sqrt{(x_2-x_1)^2+(y_2-y_1)^2} \n" + - " This will only appear in hmtl \n"); + " \\sqrt{(x_2-x_1)^2+(y_2-y_1)^2} \n\n" + + "Math immediately following text. Pydoc translation must add an\n" + + "empty line before:\n\n" + + " \\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}\n" + + " This will only appear in hmtl \n"); wantedComments.put("doxygen_translate_all_tags.doxygen_translate_all_tags.func05(int)", " If: ANOTHERCONDITION {\n" + diff --git a/Examples/test-suite/python/doxygen_basic_translate_runme.py b/Examples/test-suite/python/doxygen_basic_translate_runme.py index e664e06f6..b6023224d 100644 --- a/Examples/test-suite/python/doxygen_basic_translate_runme.py +++ b/Examples/test-suite/python/doxygen_basic_translate_runme.py @@ -49,9 +49,7 @@ Warning: This may not work as expected .. code-block:: c++ - int main() { while(true); } - }""" ) comment_verifier.check(inspect.getdoc(doxygen_basic_translate.function5), diff --git a/Examples/test-suite/python/doxygen_basic_translate_style2_runme.py b/Examples/test-suite/python/doxygen_basic_translate_style2_runme.py new file mode 100644 index 000000000..2d62eecbd --- /dev/null +++ b/Examples/test-suite/python/doxygen_basic_translate_style2_runme.py @@ -0,0 +1,80 @@ +import doxygen_basic_translate_style2 +import inspect +import string +import sys +import comment_verifier + +comment_verifier.check(inspect.getdoc(doxygen_basic_translate_style2.function), + """\ +Brief description. + +The comment text. + +Author: Some author + +:rtype: int +:return: Some number + +See also: function2""" +) +comment_verifier.check(inspect.getdoc(doxygen_basic_translate_style2.function2), + """\ +A test of a very very very very very very very very very very very very very very very very +very very very very very long comment string.""" +) +comment_verifier.check(inspect.getdoc(doxygen_basic_translate_style2.function3), + """*Overload 1:* +A test for overloaded functions +This is function **one** + +| + +*Overload 2:* +A test for overloaded functions +This is function **two**""" +) +comment_verifier.check(inspect.getdoc(doxygen_basic_translate_style2.function4), + """\ +A test of some mixed tag usage +If: CONDITION { +This *code* fragment shows us something . +Title: Minuses: +* it\'s senseless +* it\'s stupid +* it\'s null + +Warning: This may not work as expected + +.. code-block:: c++ + + int main() { while(true); } +}""" +) +comment_verifier.check(inspect.getdoc(doxygen_basic_translate_style2.function5), + """This is a post comment.""" +) +comment_verifier.check(inspect.getdoc(doxygen_basic_translate_style2.function6), + """\ +Test for default args +:type a: int +:param a: Some parameter, default is 42""" +) +comment_verifier.check(inspect.getdoc(doxygen_basic_translate_style2.function7), + """\ +Test for a parameter with difficult type +(mostly for python) +:type a: :py:class:`Shape` +:param a: Very strange param""" +) + +comment_verifier.check(inspect.getdoc(doxygen_basic_translate_style2.Atan2), + """\ +Multiple parameters test. + +:type y: float +:param y: Vertical coordinate. +:type x: float +:param x: Horizontal coordinate. +:rtype: float +:return: Arc tangent of ``y/x``.""" +) diff --git a/Examples/test-suite/python/doxygen_translate_all_tags_runme.py b/Examples/test-suite/python/doxygen_translate_all_tags_runme.py index 53d087e69..df1c0eba5 100644 --- a/Examples/test-suite/python/doxygen_translate_all_tags_runme.py +++ b/Examples/test-suite/python/doxygen_translate_all_tags_runme.py @@ -34,10 +34,16 @@ Not everything works right now... 'citationword' +.. code-block:: c++ + + some test code + +Code immediately following text. Pydoc translation must add an +empty line before: .. code-block:: c++ - some test code""") + more test code""") comment_verifier.check(inspect.getdoc(doxygen_translate_all_tags.func02), r"""Conditional comment: SOMECONDITION @@ -90,12 +96,16 @@ r""":raises: SuperError :math:`\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}` +.. math:: + + \sqrt{(x_2-x_1)^2+(y_2-y_1)^2} .. math:: \sqrt{(x_2-x_1)^2+(y_2-y_1)^2} - +Math immediately following text. Pydoc translation must add an +empty line before: .. math:: @@ -111,7 +121,6 @@ r""":raises: SuperError - This will only appear in hmtl""") comment_verifier.check(inspect.getdoc(doxygen_translate_all_tags.func05), @@ -283,13 +292,11 @@ r"""TODO: Some very important task - very long text with tags - Version: 0.0.0.2 Warning: This is senseless! diff --git a/Examples/test-suite/python/doxygen_translate_runme.py b/Examples/test-suite/python/doxygen_translate_runme.py index 2d0840a1f..d698ba873 100644 --- a/Examples/test-suite/python/doxygen_translate_runme.py +++ b/Examples/test-suite/python/doxygen_translate_runme.py @@ -20,11 +20,9 @@ Author: Zubr 'citationword' - .. code-block:: c++ - some test code - + some test code Conditional comment: SOMECONDITION Some conditional comment @@ -121,11 +119,9 @@ TODO: Some very important task :type b: float :param b: B is mentioned again... - very long text with tags - Version: 0.0.0.2 Warning: This is senseless! diff --git a/Source/Doxygen/pydoc.cxx b/Source/Doxygen/pydoc.cxx index fc3b0ea09..eb489932a 100644 --- a/Source/Doxygen/pydoc.cxx +++ b/Source/Doxygen/pydoc.cxx @@ -138,6 +138,52 @@ static void trimWhitespace(string &s) { 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)) { + // Must separate code or math blocks from the previous line + result += '\n'; + } + lastLineWasNonBlank = true; + } + } + return result; +} + /* static */ PyDocConverter::TagHandlersMap::mapped_type PyDocConverter::make_handler(tagHandler handler) { return make_pair(handler, std::string()); @@ -219,7 +265,7 @@ void PyDocConverter::fillStaticTables() { tagHandlers["short"] = make_handler(&PyDocConverter::handleParagraph); tagHandlers["todo"] = make_handler(&PyDocConverter::handleParagraph); tagHandlers["version"] = make_handler(&PyDocConverter::handleParagraph); - tagHandlers["verbatim"] = 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 @@ -419,6 +465,17 @@ void PyDocConverter::handleParagraph(DoxygenEntity &tag, std::string &translated 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; @@ -433,7 +490,6 @@ void PyDocConverter::handleMath(DoxygenEntity &tag, std::string &translatedComme indent.Init(translatedComment, m_indent); trimWhitespace(translatedComment); - translatedComment += '\n'; const string formulaIndent = indent.getFirstLineIndent(); translatedComment += formulaIndent; @@ -467,8 +523,6 @@ void PyDocConverter::handleMath(DoxygenEntity &tag, std::string &translatedComme if (inlineFormula) { translatedComment += "`"; - } else { - translatedComment += '\n'; } } @@ -476,23 +530,28 @@ void PyDocConverter::handleCode(DoxygenEntity &tag, std::string &translatedComme IndentGuard indent(translatedComment, m_indent); trimWhitespace(translatedComment); - translatedComment += '\n'; // Use the current indent for the code-block line itself. - string codeIndent = indent.getFirstLineIndent(); - translatedComment += codeIndent; + 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"; - // For now on, use extra indent level for all the subsequent lines. - codeIndent += m_indent; + // 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; std::string code; handleTagVerbatim(tag, code, arg); + // Try and remove leading newline, which is present for block \code + // command: + eraseLeadingNewLine(code); + translatedComment += codeIndent; for (size_t n = 0; n < code.length(); n++) { if (code[n] == '\n') { @@ -510,8 +569,11 @@ void PyDocConverter::handleCode(DoxygenEntity &tag, std::string &translatedComme } trimWhitespace(translatedComment); - if (*translatedComment.rbegin() != '\n') - translatedComment += '\n'; + + // 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 &) { @@ -519,7 +581,7 @@ void PyDocConverter::handlePlainString(DoxygenEntity &tag, std::string &translat } void PyDocConverter::handleTagVerbatim(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg) { - translatedComment += arg + " "; + translatedComment += arg; for (DoxygenEntityListCIt it = tag.entityList.begin(); it != tag.entityList.end(); it++) { translatedComment += it->data; } @@ -833,9 +895,10 @@ String *PyDocConverter::makeDocumentation(Node *n) { if (!pyDocString.empty()) { // remove the last '\n' since additional one is added during writing to file - if (pyDocString[pyDocString.size() - 1] == '\n') { - pyDocString.erase(pyDocString.size() - 1); - } + 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; diff --git a/Source/Doxygen/pydoc.h b/Source/Doxygen/pydoc.h index 8f432fd18..df8997d76 100644 --- a/Source/Doxygen/pydoc.h +++ b/Source/Doxygen/pydoc.h @@ -79,6 +79,11 @@ protected: */ void handleParagraph(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg = std::string()); + /* + * Handle Doxygen verbatim tag + */ + void handleVerbatimBlock(DoxygenEntity &tag, std::string &translatedComment, const std::string &arg = std::string()); + /* * Handle one of the Doxygen formula-related tags. */