From 05b5ed11bc726c24d878b2ea44850975f7414ff2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 3 Sep 2014 15:57:56 +0200 Subject: [PATCH] Add a possibility to flexibly ignore custom Doxygen tags. Add %feature("doxygen:ignore:") implementation, documentation and test case. This feature allows to use custom tags in C++ Doxygen comments for C++-specific things that don't make sense in the context of the target language and also allows to insert contents specific to the target language in the C++ comments using (different) custom commands, which is very useful in practice to explain the particularities of the API wrappers. --- Doc/Manual/Doxygen.html | 120 ++++++++++++++++++ Examples/test-suite/common.mk | 1 + Examples/test-suite/doxygen_ignore.i | 41 ++++++ .../test-suite/java/doxygen_ignore_runme.java | 44 +++++++ .../test-suite/python/doxygen_ignore_runme.py | 21 +++ .../DoxygenTranslator/src/DoxygenParser.cpp | 113 +++++++++++++++++ Source/DoxygenTranslator/src/DoxygenParser.h | 20 +++ 7 files changed, 360 insertions(+) create mode 100644 Examples/test-suite/doxygen_ignore.i create mode 100644 Examples/test-suite/java/doxygen_ignore_runme.java create mode 100644 Examples/test-suite/python/doxygen_ignore_runme.py diff --git a/Doc/Manual/Doxygen.html b/Doc/Manual/Doxygen.html index 916bd5e87..8b63d5b2b 100644 --- a/Doc/Manual/Doxygen.html +++ b/Doc/Manual/Doxygen.html @@ -226,6 +226,126 @@ instead of the corresponding language tool (javadoc, sphinx,

+

doxygen:ignore:<command-name>

+ +

+Specify that the Doxygen command with the given name should be ignored. This is +useful for custom Doxygen commands which can be defined using ALIASES +option for Doxygen itself but which are unknown to SWIG. "command-name" +is the real name of the command, e.g. you could use +

+ +
+%feature("doxygen:ignore:transferfull");
+
+ +

+if you use a custom Doxygen transferfull command to indicate that the +return value ownership is transferred to the caller, as this information doesn't +make much sense for the other languages without explicit ownership management. +

+ +

+Doxygen syntax is rather rich and, in addition to simple commands such as +@transferfull, it is also possible to define commands with arguments. +As explained in Doxygen documentation, +the arguments can have a range of a single word, everything until the end of +line or everything until the end of the next paragraph. Currently, only the "end +of line" case is supported using the range="line" argument of the +feature directive: +

+ +
+// Ignore occurrences of
+//
+//    @compiler-options Some special C++ compiler options.
+//
+// in Doxygen comments as C++ options are not interested for the target language
+// developers.
+%feature("doxygen:ignore:compileroptions", range="line");
+
+ +

+In addition, it is also possible to have custom pairs of begin/end tags, +similarly to the standard Doxygen @code/@endcode, for example. Such +tags can also be ignored using the special value of range starting with +end to indicate that the range is an interval, for example: +

+ +
+%feature("doxygen:ignore:forcpponly", range="end"); // same as "end:endforcpponly"
+
+ +

+would ignore everything between @forcpponly and @endforcpponly +commands in Doxygen comments. By default, the name of the end command is the +same as of the start one with "end" prefix, following Doxygen conventions, but +this can be overridden by providing the end command name after the colon. +

+

+This example shows how custom tags can be used to bracket anything specific to +C++ and prevent it from appearing in the target language documentation. +Conversely, another pair of custom tags could be used to put target language +specific information in the C++ comments. In this case, only the custom tags +themselves should be ignored, but their contents should be parsed as usual and +contents="parse" can be used for this: +

+ +
+%feature("doxygen:ignore:beginPythonOnly", range="end:endPythonOnly", contents="parse");
+
+ +

+Putting everything together, if these directives are in effect: +

+ +
+%feature("doxygen:ignore:transferfull");
+%feature("doxygen:ignore:compileroptions", range="line");
+%feature("doxygen:ignore:forcpponly", range="end");
+%feature("doxygen:ignore:beginPythonOnly", range="end:endPythonOnly", contents="parse");
+
+ +

+then the following C++ Doxygen comment: +

+ +
+/**
+    A contrived example of ignoring too many commands in one comment.
+
+    @forcpponly
+    This is C++-specific.
+    @endforcpponly
+
+    @beginPythonOnly
+    This is specific to @b Python.
+    @endPythonOnly
+
+    @transferfull Command ignored, but anything here is still included.
+
+    @compileroptions This function must be compiled with /EHa when using MSVC.
+ */
+void func();
+
+ +

+would be translated to this comment in Python: +

+ +
+def func():
+    r"""
+    A contrived example of ignoring too many commands in one comment.
+
+    This is specific to **Python**.
+
+    Command ignored, but anything here is still included.
+    """
+    ...
+
+ +

doxygen:nolinkranslate (Java-only currently)

diff --git a/Examples/test-suite/common.mk b/Examples/test-suite/common.mk index 39ad4c012..76c088cf7 100644 --- a/Examples/test-suite/common.mk +++ b/Examples/test-suite/common.mk @@ -551,6 +551,7 @@ $(eval HAS_DOXYGEN := $($(LANGUAGE)_HAS_DOXYGEN)) ifdef HAS_DOXYGEN DOXYGEN_TEST_CASES += \ doxygen_parsing \ + doxygen_ignore \ doxygen_basic_translate \ doxygen_basic_notranslate \ doxygen_translate \ diff --git a/Examples/test-suite/doxygen_ignore.i b/Examples/test-suite/doxygen_ignore.i new file mode 100644 index 000000000..d15110257 --- /dev/null +++ b/Examples/test-suite/doxygen_ignore.i @@ -0,0 +1,41 @@ +%module doxygen_ignore + +%feature("doxygen:ignore:transferfull"); +%feature("doxygen:ignore:compileroptions", range="line"); +%feature("doxygen:ignore:forcpponly", range="end"); + +#ifdef SWIGJAVA +%feature("doxygen:ignore:beginJavaOnly", range="end:endJavaOnly", contents="parse"); +%feature("doxygen:ignore:beginPythonOnly", range="end:endPythonOnly"); +#elif defined(SWIGPYTHON) +%feature("doxygen:ignore:beginJavaOnly", range="end:endJavaOnly"); +%feature("doxygen:ignore:beginPythonOnly", range="end:endPythonOnly", contents="parse"); +#else +%feature("doxygen:ignore:beginJavaOnly", range="end:endJavaOnly"); +%feature("doxygen:ignore:beginPythonOnly", range="end:endPythonOnly"); +#endif + +%inline %{ + +/** + A contrived example of ignoring too many commands in one comment. + + @forcpponly + This is C++-specific. + @endforcpponly + + @beginJavaOnly + This is specific to @e Java. + @endJavaOnly + + @beginPythonOnly + This is specific to @b Python. + @endPythonOnly + + @transferfull Command ignored, but anything here is still included. + + @compileroptions This function must be compiled with /EHa when using MSVC. + */ +void func() { } + +%} diff --git a/Examples/test-suite/java/doxygen_ignore_runme.java b/Examples/test-suite/java/doxygen_ignore_runme.java new file mode 100644 index 000000000..d6a4d8c8d --- /dev/null +++ b/Examples/test-suite/java/doxygen_ignore_runme.java @@ -0,0 +1,44 @@ + +import doxygen_ignore.*; +import com.sun.javadoc.*; +import java.util.HashMap; + +public class doxygen_ignore_runme { + static { + try { + System.loadLibrary("doxygen_ignore"); + } 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[]) + { + CommentParser parser = new CommentParser(); + com.sun.tools.javadoc.Main.execute("doxygen_ignore runtime test", + "CommentParser", + new String[]{"-quiet", "doxygen_ignore"}); + + HashMap wantedComments = new HashMap(); + wantedComments.put("doxygen_ignore.doxygen_ignore.func()", + " A contrived example of ignoring too many commands in one comment.
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " This is specific to Java.
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " Command ignored, but anything here is still included.
\n" + + "
\n" + + "\n" + + "\n" + + "\n" + + ""); + + System.exit(parser.check(wantedComments)); + } +} diff --git a/Examples/test-suite/python/doxygen_ignore_runme.py b/Examples/test-suite/python/doxygen_ignore_runme.py new file mode 100644 index 000000000..7fe2667d3 --- /dev/null +++ b/Examples/test-suite/python/doxygen_ignore_runme.py @@ -0,0 +1,21 @@ +#!/usr/bin/python + +import doxygen_ignore +import commentVerifier + +commentVerifier.check(doxygen_ignore.func.__doc__, + r""" + A contrived example of ignoring too many commands in one comment. + + + + + + + This is specific to **Python**. + + + Command ignored, but anything here is still included. + + + """) diff --git a/Source/DoxygenTranslator/src/DoxygenParser.cpp b/Source/DoxygenTranslator/src/DoxygenParser.cpp index 5e1f3a5d3..c8cb40ab8 100644 --- a/Source/DoxygenTranslator/src/DoxygenParser.cpp +++ b/Source/DoxygenTranslator/src/DoxygenParser.cpp @@ -135,6 +135,28 @@ int DoxygenParser::commandBelongs(const std::string &theCommand) if (it != doxygenCommands.end()) { return it->second; } + + // Check if this command should be ignored. + if (String* const ignore = getIgnoreFeature(theCommand)) { + // Check that no value is specified for this feature ("1" is the implicit + // one given to it by SWIG itself), we may use the value in the future, but + // for now we only use the attributes. + if (Strcmp(ignore, "1") != 0) { + Swig_warning(WARN_PP_UNEXPECTED_TOKENS, m_fileName.c_str(), m_fileLineNo, + "Feature \"doxygen:ignore\" value ignored for Doxygen command \"%s\".\n", + theCommand.c_str()); + } + + // Also ensure that the matching end command, if any, will be recognized. + const string endCommand = getIgnoreFeatureEndCommand(theCommand); + if (!endCommand.empty()) { + Setattr(m_node, ("feature:doxygen:ignore:" + endCommand).c_str(), + NewString("1")); + } + + return COMMAND_IGNORE; + } + return NONE; } @@ -878,6 +900,95 @@ int DoxygenParser::addCommandUnique(const std::string &theCommand, return 0; } +String* DoxygenParser::getIgnoreFeature(const std::string& theCommand, + const char* argument) const +{ + string feature_name = "feature:doxygen:ignore:" + theCommand; + if (argument) { + feature_name += ':'; + feature_name += argument; + } + + return Getattr(m_node, feature_name.c_str()); +} + +string DoxygenParser::getIgnoreFeatureEndCommand(const std::string& theCommand) const +{ + // We may be dealing either with a simple command or with the starting command + // of a block, as indicated by the value of "range" starting with "end". + string endCommand; + if (String* const range = getIgnoreFeature(theCommand, "range")) { + const char* const p = Char(range); + if (strncmp(p, "end", 3) == 0) { + if (p[3] == ':') { + // Normally the end command name follows after the colon. + endCommand = p + 4; + } else if (p[3] == '\0') { + // But it may be omitted in which case the default Doxygen convention of + // using "something"/"endsomething" is used. + endCommand = "end" + theCommand; + } + } + } + + return endCommand; +} + +int DoxygenParser::ignoreCommand(const std::string& theCommand, + const TokenList &tokList, + DoxygenEntityList &doxyList) +{ + const string endCommand = getIgnoreFeatureEndCommand(theCommand); + if (!endCommand.empty()) { + TokenListCIt itEnd = getEndCommand(endCommand, tokList); + if (itEnd == tokList.end()) { + printListError(WARN_DOXYGEN_COMMAND_EXPECTED, "Expected " + endCommand); + return 0; + } + + // Determine what to do with the part of the comment between the start and + // end commands: by default, we simply throw it away, but "contents" + // attribute may be used to change this. + if (String* const contents = getIgnoreFeature(theCommand, "contents")) { + // Currently only "parse" is supported but we may need to add "copy" to + // handle custom tags which contain text that is supposed to be copied + // verbatim in the future. + if (Strcmp(contents, "parse") == 0) { + DoxygenEntityList aNewList = parse(itEnd, tokList); + doxyList.splice(doxyList.end(), aNewList); + } else { + Swig_error(m_fileName.c_str(), m_fileLineNo, + "Invalid \"doxygen:ignore\" feature \"contents\" attribute \"%s\".\n", + Char(contents) + ); + return 0; + } + } + + m_tokenListIt = itEnd; + m_tokenListIt++; + } else if (String* const range = getIgnoreFeature(theCommand, "range")) { + // Currently we only support "line" but, in principle, we should also + // support "word" and "paragraph" for consistency with the built-in Doxygen + // commands which can have either of these three ranges (which are indicated + // using , (line-arg) and {para-arg} respectively in Doxygen + // documentation). + if (Strcmp(range, "line") == 0) { + // Consume everything until the end of line. + m_tokenListIt = getOneLine(tokList); + skipEndOfLine(); + } else { + Swig_error(m_fileName.c_str(), m_fileLineNo, + "Invalid \"doxygen:ignore\" feature \"range\" attribute \"%s\".\n", + Char(range) + ); + return 0; + } + } + + return 1; +} + int DoxygenParser::addCommand(const std::string &commandString, const TokenList &tokList, DoxygenEntityList &doxyList) @@ -920,6 +1031,8 @@ int DoxygenParser::addCommand(const std::string &commandString, return addCommandHtml(theCommand, tokList, doxyList); case COMMAND_HTML_ENTITY: return addCommandHtmlEntity(theCommand, tokList, doxyList); + case COMMAND_IGNORE: + return ignoreCommand(commandString, tokList, doxyList); } return 0; } diff --git a/Source/DoxygenTranslator/src/DoxygenParser.h b/Source/DoxygenTranslator/src/DoxygenParser.h index f86f94f44..c1e4994c0 100644 --- a/Source/DoxygenTranslator/src/DoxygenParser.h +++ b/Source/DoxygenTranslator/src/DoxygenParser.h @@ -39,6 +39,7 @@ private: COMMANDUNIQUE, COMMAND_HTML, COMMAND_HTML_ENTITY, + COMMAND_IGNORE, END_LINE, PARAGRAPH_END, PLAINSTRING, @@ -98,6 +99,17 @@ private: std::string m_fileName; int m_fileLineNo; + /* + * Return the end command for a command appearing in "ignore" feature or empty + * string if this is a simple command and not a block one. + */ + std::string getIgnoreFeatureEndCommand(const std::string& theCommand) const; + + /* + * Helper for getting the value of doxygen:ignore feature or its argument. + */ + String* getIgnoreFeature(const std::string& theCommand, const char* argument = NULL) const; + /* * Whether to print lots of debug info during parsing */ @@ -334,6 +346,14 @@ private: const TokenList &tokList, DoxygenEntityList &doxyList); + /* + * Simply ignore the given command, possibly with the word following it or + * until the matching end command. + */ + int ignoreCommand(const std::string& theCommand, + const TokenList &tokList, + DoxygenEntityList &doxyList); + /* * The actual "meat" of the doxygen parser. Calls the correct addCommand...() * function.