Procházet zdrojové kódy

Use a doxygen input filter for CMake file to generate documentation.

Sascha Zelzer před 14 roky
rodič
revize
3c52d8668c

+ 87 - 0
CMake/ctkFunctionCMakeDoxygenFilterCompile.cmake

@@ -0,0 +1,87 @@
+#!
+#! \brief Compile a CMake doxygen input filter
+#!
+#! \param OUT <out-file> (optional) Supply an absolute filename for
+#!                       the generated executable.
+#! \param NAMESPACE <namespace> (optional) Supply a C++ namespace in
+#!                              which the generated function declrarations
+#!                              should be wrapped.
+#!
+#! \return This function sets the <code>CMakeDoxygenFilter_EXECUTABLE</code>
+#!         variable to the absolute path of the generated input filter executable
+#!         in the parent scope. If <out-file> is specified, they will be the same.
+#!
+#! This CMake function compiles the http://github.com/saschazelzer/CMakeDoxygenFilter
+#! project into a doxygen input filter executable. See
+#! http://github.com/saschazelzer/CMakeDoxygenFilter/blob/master/README for more details.
+#!
+function(ctkFunctionCMakeDoxygenFilterCompile)
+
+  #-------------------- parse function arguments -------------------
+
+  set(DEFAULT_ARGS)
+  set(prefix "FILTER")
+  set(arg_names "OUT;NAMESPACE")
+  set(option_names "")
+
+  foreach(arg_name ${arg_names})
+    set(${prefix}_${arg_name})
+  endforeach(arg_name)
+
+  foreach(option ${option_names})
+    set(${prefix}_${option} FALSE)
+  endforeach(option)
+
+  set(current_arg_name DEFAULT_ARGS)
+  set(current_arg_list)
+
+  foreach(arg ${ARGN})
+    set(larg_names ${arg_names})
+    list(FIND larg_names "${arg}" is_arg_name)
+    if(is_arg_name GREATER -1)
+      set(${prefix}_${current_arg_name} ${current_arg_list})
+      set(current_arg_name "${arg}")
+      set(current_arg_list)
+    else(is_arg_name GREATER -1)
+      set(loption_names ${option_names})
+      list(FIND loption_names "${arg}" is_option)
+      if(is_option GREATER -1)
+        set(${prefix}_${arg} TRUE)
+      else(is_option GREATER -1)
+        set(current_arg_list ${current_arg_list} "${arg}")
+      endif(is_option GREATER -1)
+    endif(is_arg_name GREATER -1)
+  endforeach(arg ${ARGN})
+
+  set(${prefix}_${current_arg_name} ${current_arg_list})
+
+  #------------------- finished parsing arguments ----------------------
+
+  if(FILTER_OUT)
+    set(copy_file "${FILTER_OUT}")
+  else()
+    set(copy_file "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/CMakeDoxygenFilter${CMAKE_EXECUTABLE_SUFFIX}")
+  endif()
+
+  set(compile_defs "")
+  if(FILTER_NAMESPACE)
+    set(compile_defs "${compile_defs} -DUSE_NAMESPACE=${FILTER_NAMESPACE}")
+  endif()
+
+  set(cmake_doxygen_filter_src "${CTK_SOURCE_DIR}/Documentation/CMakeDoxygenFilter.cpp")
+
+  try_compile(result_var
+              "${CMAKE_CURRENT_BINARY_DIR}"
+              "${cmake_doxygen_filter_src}"
+              COMPILE_DEFINITIONS ${compile_defs}
+              OUTPUT_VARIABLE compile_output
+              COPY_FILE ${copy_file}
+             )
+
+  if(NOT result_var)
+    message(FATAL_ERROR "error: Faild to compile ${cmake_doxygen_filter_src} (result: ${result_var})\n${compile_output}")
+  endif()
+
+  set(CMakeDoxygenFilter_EXECUTABLE "${copy_file}" PARENT_SCOPE)
+
+endfunction()

+ 407 - 0
Documentation/CMakeDoxygenFilter.cpp

@@ -0,0 +1,407 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#include <cstdlib>
+#include <string>
+#include <fstream>
+#include <iostream>
+
+#include <assert.h>
+
+//--------------------------------------
+// Utilitiy classes and functions
+//--------------------------------------
+
+struct ci_char_traits : public std::char_traits<char>
+    // just inherit all the other functions
+    //  that we don't need to override
+{
+  static bool eq(char c1, char c2)
+  { return toupper(c1) == toupper(c2); }
+
+  static bool ne(char c1, char c2)
+  { return toupper(c1) != toupper(c2); }
+
+  static bool lt(char c1, char c2)
+  { return toupper(c1) <  toupper(c2); }
+
+  static bool gt(char c1, char c2)
+  { return toupper(c1) >  toupper(c2); }
+
+  static int compare(const char* s1, const char* s2, std::size_t n)
+  {
+    while (n-- > 0)
+    {
+      if (lt(*s1, *s2)) return -1;
+      if (gt(*s1, *s2)) return 1;
+      ++s1; ++s2;
+    }
+    return 0;
+  }
+
+  static const char* find(const char* s, int n, char a)
+  {
+    while (n-- > 0 && toupper(*s) != toupper(a))
+    {
+      ++s;
+    }
+    return s;
+  }
+};
+
+typedef std::basic_string<char, ci_char_traits> ci_string;
+
+//--------------------------------------
+// Lexer
+//--------------------------------------
+
+class CMakeLexer
+{
+public:
+
+  enum Token {
+    TOK_EOF = -1,
+
+    // commands
+    TOK_MACRO = -2, TOK_ENDMACRO = -3,
+    TOK_FUNCTION = -4, TOK_ENDFUNCTION = -5,
+    TOK_DOXYGEN_COMMENT = -6,
+
+    TOK_STRING_LITERAL = -100,
+
+    // primary
+    TOK_IDENTIFIER = -200
+  };
+
+  CMakeLexer(std::istream& is)
+    : _lastChar(' '), _is(is), _line(1), _col(1)
+  {}
+
+  int getToken()
+  {
+    // skip whitespace
+    while (isspace(_lastChar))
+    {
+      _lastChar = getChar();
+    }
+
+    if (isalpha(_lastChar) || _lastChar == '_')
+    {
+      _identifier = _lastChar;
+      while (isalnum(_lastChar = getChar()) || _lastChar == '-' || _lastChar == '_')
+      {
+        _identifier += _lastChar;
+      }
+
+      if (_identifier == "function")
+        return TOK_FUNCTION;
+      if (_identifier == "macro")
+        return TOK_MACRO;
+      if (_identifier == "endfunction")
+        return TOK_ENDFUNCTION;
+      if (_identifier == "endmacro")
+        return TOK_ENDMACRO;
+      return TOK_IDENTIFIER;
+    }
+
+    if (_lastChar == '#')
+    {
+      _lastChar = getChar();
+      if (_lastChar == '!')
+      {
+        // found a doxygen comment marker
+        _identifier.clear();
+
+        _lastChar = getChar();
+        while (_lastChar != EOF && _lastChar != '\n' && _lastChar != '\r')
+        {
+          _identifier += _lastChar;
+          _lastChar = getChar();
+        }
+        return TOK_DOXYGEN_COMMENT;
+      }
+
+      // skip the comment
+      while (_lastChar != EOF && _lastChar != '\n' && _lastChar != '\r')
+      {
+        _lastChar = getChar();
+      }
+    }
+
+    if (_lastChar == '"')
+    {
+      _lastChar = getChar();
+      _identifier.clear();
+      while (_lastChar != EOF && _lastChar != '"')
+      {
+        _identifier += _lastChar;
+        _lastChar = getChar();
+      }
+
+      // eat the closing "
+      _lastChar = getChar();
+      return TOK_STRING_LITERAL;
+    }
+
+    // don't eat the EOF
+    if (_lastChar == EOF) return TOK_EOF;
+
+    // return the character as its ascii value
+    int thisChar = _lastChar;
+    _lastChar = getChar();
+    return thisChar;
+  }
+
+  std::string getIdentifier() const
+  {
+    return std::string(_identifier.c_str());
+  }
+
+  int curLine() const
+  { return _line; }
+
+  int curCol() const
+  { return _col; }
+
+  int getChar()
+  {
+    int c = _is.get();
+    updateLoc(c);
+    return c;
+  }
+
+private:
+
+  void updateLoc(int c)
+  {
+    if (c == '\n' || c == '\r')
+    {
+      ++_line;
+      _col = 1;
+    }
+    else
+    {
+      ++_col;
+    }
+  }
+
+  ci_string _identifier;
+  int _lastChar;
+  std::istream& _is;
+
+  int _line;
+  int _col;
+};
+
+//--------------------------------------
+// Parser
+//--------------------------------------
+
+class CMakeParser
+{
+
+public:
+
+  CMakeParser(std::istream& is, std::ostream& os)
+    : _is(is), _os(os), _lexer(is), _curToken(CMakeLexer::TOK_EOF)
+  { }
+
+  int curToken()
+  {
+    return _curToken;
+  }
+
+  int nextToken()
+  {
+    return _curToken = _lexer.getToken();
+  }
+
+  void handleMacro()
+  {
+    if(!parseMacro())
+    {
+      // skip token for error recovery
+      nextToken();
+    }
+  }
+
+  void handleFunction()
+  {
+    if(!parseFunction())
+    {
+      // skip token for error recovery
+      nextToken();
+    }
+  }
+
+  void handleDoxygenComment()
+  {
+    _os << "///" << _lexer.getIdentifier() << std::endl;
+    nextToken();
+  }
+
+  void handleTopLevelExpression()
+  {
+    // skip token
+    nextToken();
+  }
+
+private:
+
+  void printError(const char* str)
+  {
+    std::cerr << "Error: " << str << " (at line " << _lexer.curLine() << ", col " << _lexer.curCol() << ")\n";
+  }
+
+  bool parseMacro()
+  {
+    if (nextToken() != '(')
+    {
+      printError("Expected '(' after MACRO");
+      return false;
+    }
+
+    nextToken();
+    std::string macroName = _lexer.getIdentifier();
+    if (curToken() != CMakeLexer::TOK_IDENTIFIER || macroName.empty())
+    {
+      printError("Expected macro name");
+      return false;
+    }
+
+    _os << macroName << '(';
+    if (nextToken() == CMakeLexer::TOK_IDENTIFIER)
+    {
+      _os << _lexer.getIdentifier();
+      while (nextToken() == CMakeLexer::TOK_IDENTIFIER)
+      {
+        _os << ", " << _lexer.getIdentifier();
+      }
+    }
+
+    if (curToken() != ')')
+    {
+      printError("Missing expected ')'");
+    }
+    else
+    {
+      _os << ");" << std::endl;
+    }
+
+    // eat the ')'
+    nextToken();
+    return true;
+  }
+
+  bool parseFunction()
+  {
+    if (nextToken() != '(')
+    {
+      printError("Expected '(' after FUNCTION");
+      return false;
+    }
+
+    nextToken();
+    std::string funcName = _lexer.getIdentifier();
+    if (curToken() != CMakeLexer::TOK_IDENTIFIER || funcName.empty())
+    {
+      printError("Expected function name");
+      return false;
+    }
+
+    _os << funcName << '(';
+    if (nextToken() == CMakeLexer::TOK_IDENTIFIER)
+    {
+      _os << _lexer.getIdentifier();
+      while (nextToken() == CMakeLexer::TOK_IDENTIFIER)
+      {
+        _os << ", " << _lexer.getIdentifier();
+      }
+    }
+
+    if (curToken() != ')')
+    {
+      printError("Missing expected ')'");
+    }
+    else
+    {
+      _os << ");" << std::endl;
+    }
+
+    // eat the ')'
+    nextToken();
+
+    return true;
+  }
+
+  std::istream& _is;
+  std::ostream& _os;
+  CMakeLexer _lexer;
+  int _curToken;
+};
+
+
+#define STRINGIFY(a) #a
+#define DOUBLESTRINGIFY(a) STRINGIFY(a)
+
+int main(int argc, char** argv)
+{
+  assert(argc > 1);
+
+  for (int i = 1; i < argc; ++i)
+  {
+    std::ifstream ifs(argv[i]);
+    std::ostream& os = std::cout;
+
+    #ifdef USE_NAMESPACE
+    os << "namespace " << DOUBLESTRINGIFY(USE_NAMESPACE) << " {\n";
+    #endif
+
+    CMakeParser parser(ifs, os);
+    parser.nextToken();
+    while (ifs.good())
+    {
+      switch (parser.curToken())
+      {
+      case CMakeLexer::TOK_EOF:
+        return ifs.get(); // eat EOF
+      case CMakeLexer::TOK_MACRO:
+        parser.handleMacro();
+        break;
+      case CMakeLexer::TOK_FUNCTION:
+        parser.handleFunction();
+        break;
+      case CMakeLexer::TOK_DOXYGEN_COMMENT:
+        parser.handleDoxygenComment();
+        break;
+      default:
+        parser.handleTopLevelExpression();
+        break;
+      }
+    }
+
+    #ifdef USE_NAMESPACE
+    os << "}\n";
+    #endif
+  }
+
+  return EXIT_SUCCESS;
+}

+ 4 - 0
Documentation/CMakeLists.txt

@@ -3,6 +3,10 @@ FIND_PACKAGE( Doxygen QUIET)
 IF( DOXYGEN_FOUND )
   IF( DOXYGEN_DOT_FOUND )
 
+    # Compile a doxygen input filter for processing CMake scripts
+    INCLUDE(ctkFunctionCMakeDoxygenFilterCompile)
+    ctkFunctionCMakeDoxygenFilterCompile(NAMESPACE "CMake")
+
     # Automatically generate documentation at build time
     SET(all_arg)
     IF (DOCUMENTATION_TARGET_IN_ALL)

+ 3 - 4
Documentation/Doxyfile.txt.in

@@ -669,7 +669,8 @@ FILE_PATTERNS          = @CTK_ADDITIONAL_FILE_PATTERN@ \
                          *.diff \
                          *.patch \
                          *.xpm \
-                         *.dox
+                         *.dox \
+                         */CMake/*.cmake
 
 # The RECURSIVE tag can be used to turn specify whether or not subdirectories
 # should be searched for input files as well. Possible values are YES and NO.
@@ -682,7 +683,6 @@ RECURSIVE              = YES
 # subdirectory from a directory tree whose root is specified with the INPUT tag.
 
 EXCLUDE                = @CTK_BINARY_DIR@ \
-                         @CTK_SOURCE_DIR@/CMake \
                          @CTK_SOURCE_DIR@/Utilities
 
 # The EXCLUDE_SYMLINKS tag can be used select whether or not files or
@@ -703,7 +703,6 @@ EXCLUDE_PATTERNS       = moc_*.cxx \
                          */Testing/* \
                          */Utilities/* \
                          */Widgets/Plugins/* \
-                         */CMake/* \
                          @CTK_ADDITIONAL_EXCLUDE_PATTERN@
 
 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
@@ -760,7 +759,7 @@ INPUT_FILTER           =
 # info on how filters are used. If FILTER_PATTERNS is empty or if
 # non of the patterns match the file name, INPUT_FILTER is applied.
 
-FILTER_PATTERNS        =
+FILTER_PATTERNS        = *.cmake=@CMakeDoxygenFilter_EXECUTABLE@
 
 # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
 # INPUT_FILTER) will be used to filter the input files when producing source