Add a possibility to flexibly ignore custom Doxygen tags.

Add %feature("doxygen:ignore:<command>") 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.
This commit is contained in:
Vadim Zeitlin 2014-09-03 15:57:56 +02:00
commit 05b5ed11bc
7 changed files with 360 additions and 0 deletions

View file

@ -226,6 +226,126 @@ instead of the corresponding language tool (<tt>javadoc</tt>, <tt>sphinx</tt>,
</p>
<h4>doxygen:ignore:&lt;command-name&gt;</h4>
<p>
Specify that the Doxygen command with the given name should be ignored. This is
useful for custom Doxygen commands which can be defined using <tt>ALIASES</tt>
option for Doxygen itself but which are unknown to SWIG. <tt>"command-name"</tt>
is the real name of the command, e.g. you could use
</p>
<div class="code"><pre>
%feature("doxygen:ignore:transferfull");
</pre></div>
<p>
if you use a custom Doxygen <tt>transferfull</tt> 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.
</p>
<p>
Doxygen syntax is rather rich and, in addition to simple commands such as
<tt>@transferfull</tt>, it is also possible to define commands with arguments.
As explained in <a href="http://www.stack.nl/~dimitri/doxygen/manual/commands.html">Doxygen documentation</a>,
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 <tt>range="line"</tt> argument of the
feature directive:
</p>
<div class="code"><pre>
// 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");
</pre></div>
<p>
In addition, it is also possible to have custom pairs of begin/end tags,
similarly to the standard Doxygen <tt>@code/@endcode</tt>, for example. Such
tags can also be ignored using the special value of <tt>range</tt> starting with
<tt>end</tt> to indicate that the range is an interval, for example:
</p>
<div class="code"><pre>
%feature("doxygen:ignore:forcpponly", range="end"); // same as "end:endforcpponly"
</pre></div>
<p>
would ignore everything between <tt>@forcpponly</tt> and <tt>@endforcpponly</tt>
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.
</p>
<p>
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
<tt>contents="parse"</tt> can be used for this:
</p>
<div class="code"><pre>
%feature("doxygen:ignore:beginPythonOnly", range="end:endPythonOnly", contents="parse");
</pre></div>
<p>
Putting everything together, if these directives are in effect:
</p>
<div class="code"><pre>
%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");
</pre></div>
<p>
then the following C++ Doxygen comment:
</p>
<div class="code"><pre>
/**
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();
</pre></div>
<p>
would be translated to this comment in Python:
</p>
<div class="code"><pre>
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.
"""
...
</pre></div>
<h4>doxygen:nolinkranslate (Java-only currently)</h4>
<p>

View file

@ -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 \

View file

@ -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() { }
%}

View file

@ -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<String, String> wantedComments = new HashMap<String, String>();
wantedComments.put("doxygen_ignore.doxygen_ignore.func()",
" A contrived example of ignoring too many commands in one comment.<br>\n" +
" <br>\n" +
" <br>\n" +
" <br>\n" +
" <br>\n" +
" This is specific to <i>Java</i>.<br>\n" +
" <br>\n" +
" <br>\n" +
" <br>\n" +
" <br>\n" +
" Command ignored, but anything here is still included.<br>\n" +
" <br>\n" +
"\n" +
"\n" +
"\n" +
"");
System.exit(parser.check(wantedComments));
}
}

View file

@ -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.
""")

View file

@ -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 <word-arg>, (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;
}

View file

@ -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.