Browse Source

Move all PythonQt wrapping code into one script. See #449

Jean-Christophe Fillion-Robin 11 years ago
parent
commit
d3f81f44fb

+ 4 - 83
CMake/ctkMacroWrapPythonQt.cmake

@@ -25,7 +25,7 @@
 #!
 #!
 #! Depends on:
 #! Depends on:
 #!  PythonQt
 #!  PythonQt
-#!  PythonInterp (See function reSearchFile)
+#!  PythonInterp
 #!
 #!
 
 
 #!
 #!
@@ -63,38 +63,6 @@ function(ctkMacroWrapPythonQt_log msg)
   file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/ctkMacroWrapPythonQt_log.txt" "${msg}\n")
   file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/ctkMacroWrapPythonQt_log.txt" "${msg}\n")
 endfunction()
 endfunction()
 
 
-include(${CTK_CMAKE_DIR}/ctkMacroSetPaths.cmake)
-
-#!
-#! Convenient function allowing to invoke re.search(regex, string) using the given interpreter.
-#! Note that is_matching will be set to True if there is a match
-#!
-#! \ingroup CMakeUtilities
-function(ctkMacroWrapPythonQt_reSearchFile python_exe python_library_path regex file is_matching)
-
-  set(python_cmd "import re
-f = open('${file}', 'r')
-res = re.search('${regex}', f.read(), re.MULTILINE)
-if res == None: print 'FALSE'
-else: print 'TRUE'
-")
-  #message("python_cmd: ${python_cmd}")
-
-  ctkMacroSetPaths("${python_library_path}")
-  execute_process(
-    COMMAND ${python_exe} -c ${python_cmd}
-    RESULT_VARIABLE result
-    OUTPUT_VARIABLE output
-    ERROR_VARIABLE error
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-    )
-
-  if(result)
-    message(FATAL_ERROR "reSearchFile - Problem with regex: ${regex}\n${error}\nPython exe: ${python_exe}\nPython lib path: ${python_library_path}")
-  endif()
-  set(is_matching ${output} PARENT_SCOPE)
-endfunction()
-
 #! \ingroup CMakeUtilities
 #! \ingroup CMakeUtilities
 macro(ctkMacroWrapPythonQt WRAPPING_NAMESPACE TARGET SRCS_LIST_NAME SOURCES IS_WRAP_FULL HAS_DECORATOR)
 macro(ctkMacroWrapPythonQt WRAPPING_NAMESPACE TARGET SRCS_LIST_NAME SOURCES IS_WRAP_FULL HAS_DECORATOR)
 
 
@@ -158,50 +126,8 @@ macro(ctkMacroWrapPythonQt WRAPPING_NAMESPACE TARGET SRCS_LIST_NAME SOURCES IS_W
       endif()
       endif()
     endif()
     endif()
 
 
-    # what is the filename without the extension
-    get_filename_component(TMP_FILENAME ${FILE} NAME_WE)
-
-    # Extract classname - NOTE: We assume the filename matches the associated class
-    set(className ${TMP_FILENAME})
-
-    if(NOT skip_wrapping)
-      # Skip wrapping if file do NOT contain Q_OBJECT
-      file(READ ${CMAKE_CURRENT_SOURCE_DIR}/${FILE} file_content)
-      if(NOT "${file_content}" MATCHES "Q_OBJECT")
-        set(skip_wrapping TRUE)
-        ctkMacroWrapPythonQt_log("${FILE}: skipping - No Q_OBJECT macro")
-      endif()
-    endif()
-
-    if(NOT skip_wrapping)
-      # Skip wrapping if constructor doesn't match:
-      #    my_class()
-      #    my_class(QObject* newParent ...)
-      #    my_class(QWidget* newParent ...)
-      # Constructor with either QWidget or QObject as first parameter
-      set(regex "[^~]${className}[\\s\\n]*\\([\\s\\n]*((QObject|QWidget)[\\s\\n]*\\*[\\s\\n]*\\w+[\\s\\n]*(\\=[\\s\\n]*(0|NULL)|,.*\\=.*\\)|\\)|\\)))")
-      ctkMacroWrapPythonQt_reSearchfile(${PYTHON_EXECUTABLE} ${PYTHON_LIBRARY_PATH}
-                                        ${regex} ${CMAKE_CURRENT_SOURCE_DIR}/${FILE} is_matching)
-      if(NOT is_matching)
-        set(skip_wrapping TRUE)
-        ctkMacroWrapPythonQt_log("${FILE}: skipping - Missing expected constructor signature")
-      endif()
-    endif()
-
-    if(NOT skip_wrapping)
-      # Skip wrapping if object has a virtual pure method
-      # "x3b" is the unicode for semicolon
-      set(regex "virtual[\\w\\n\\s\\*\\(\\)]+\\=[\\s\\n]*(0|NULL)[\\s\\n]*\\x3b")
-      ctkMacroWrapPythonQt_reSearchfile(${PYTHON_EXECUTABLE} ${PYTHON_LIBRARY_PATH}
-                                        ${regex} ${CMAKE_CURRENT_SOURCE_DIR}/${FILE} is_matching)
-      if(is_matching)
-        set(skip_wrapping TRUE)
-        ctkMacroWrapPythonQt_log("${FILE}: skipping - Contains a virtual pure method")
-      endif()
-    endif()
-
     # if we should wrap it
     # if we should wrap it
-    IF (NOT skip_wrapping)
+    if(NOT skip_wrapping)
 
 
       # compute the input filename
       # compute the input filename
       if(IS_ABSOLUTE FILE)
       if(IS_ABSOLUTE FILE)
@@ -225,9 +151,6 @@ macro(ctkMacroWrapPythonQt WRAPPING_NAMESPACE TARGET SRCS_LIST_NAME SOURCES IS_W
   set(wrap_int_dir generated_cpp/${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}/)
   set(wrap_int_dir generated_cpp/${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}/)
   #message("wrap_int_dir:${wrap_int_dir}")
   #message("wrap_int_dir:${wrap_int_dir}")
 
 
-  # Create intermediate output directory
-  execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${wrap_int_dir})
-
   set(wrapper_init_cpp_filename ${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp)
   set(wrapper_init_cpp_filename ${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp)
   set(wrapper_init_cpp_file ${CMAKE_CURRENT_BINARY_DIR}/${wrap_int_dir}${wrapper_init_cpp_filename})
   set(wrapper_init_cpp_file ${CMAKE_CURRENT_BINARY_DIR}/${wrap_int_dir}${wrapper_init_cpp_filename})
 
 
@@ -245,15 +168,13 @@ macro(ctkMacroWrapPythonQt WRAPPING_NAMESPACE TARGET SRCS_LIST_NAME SOURCES IS_W
       ${CTK_CMAKE_DIR}/ctkScriptWrapPythonQt_Light.cmake
       ${CTK_CMAKE_DIR}/ctkScriptWrapPythonQt_Light.cmake
       ${CTK_CMAKE_DIR}/ctkMacroWrapPythonQtModuleInit.cpp.in
       ${CTK_CMAKE_DIR}/ctkMacroWrapPythonQtModuleInit.cpp.in
     COMMAND ${CMAKE_COMMAND}
     COMMAND ${CMAKE_COMMAND}
-      -DPYTHONQTGENERATOR_EXECUTABLE:FILEPATH=${PYTHONQTGENERATOR_EXECUTABLE}
       -DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE}
       -DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE}
       -DPYTHON_LIBRARY_PATH:PATH=${PYTHON_LIBRARY_PATH}
       -DPYTHON_LIBRARY_PATH:PATH=${PYTHON_LIBRARY_PATH}
+      -DWRAPPING_SCRIPT:FILEPATH=${CTK_CMAKE_DIR}/ctkWrapPythonQt.py
       -DWRAPPING_NAMESPACE:STRING=${WRAPPING_NAMESPACE}
       -DWRAPPING_NAMESPACE:STRING=${WRAPPING_NAMESPACE}
       -DTARGET:STRING=${TARGET}
       -DTARGET:STRING=${TARGET}
       -DSOURCES:STRING=${SOURCES_TO_WRAP_ARG}
       -DSOURCES:STRING=${SOURCES_TO_WRAP_ARG}
-      -DOUTPUT_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}
-      -DWRAP_INT_DIR:STRING=${wrap_int_dir}
-      -DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}
+      -DOUTPUT_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/${wrap_int_dir}
       -DHAS_DECORATOR:BOOL=${HAS_DECORATOR}
       -DHAS_DECORATOR:BOOL=${HAS_DECORATOR}
       -P ${CTK_CMAKE_DIR}/ctkScriptWrapPythonQt_Light.cmake
       -P ${CTK_CMAKE_DIR}/ctkScriptWrapPythonQt_Light.cmake
     COMMENT "PythonQt Wrapping - Generating ${wrapper_init_cpp_filename}"
     COMMENT "PythonQt Wrapping - Generating ${wrapper_init_cpp_filename}"

+ 1 - 1
CMake/ctkMacroWrapPythonQtModuleInit.cpp.in

@@ -1,4 +1,4 @@
-// Configured by cmake macro @CMAKE_CURRENT_LIST_FILENAME@
+// Configured by cmake macro @CMAKE_CURRENT_LIST_FILE@
 
 
 #include <PythonQt.h>
 #include <PythonQt.h>
 #include <Python.h>
 #include <Python.h>

+ 35 - 198
CMake/ctkScriptWrapPythonQt_Light.cmake

@@ -23,18 +23,13 @@
 #
 #
 
 
 #
 #
-# Depends on:
-#  CTK/CMake/ctkMacroWrapPythonQt.cmake
-#
-
-#
 # This script should be invoked either as a CUSTOM_COMMAND
 # This script should be invoked either as a CUSTOM_COMMAND
 # or from the command line using the following syntax:
 # or from the command line using the following syntax:
 #
 #
-#    cmake -DWRAPPING_NAMESPACE:STRING=org.commontk -DTARGET:STRING=MyLib
+#    cmake -DWRAPPING_SCRIPT:FILEPATH=/path/to/ctkWrapPythonQt.py
+#          -DWRAPPING_NAMESPACE:STRING=org.commontk -DTARGET:STRING=MyLib
 #          -DSOURCES:STRING="file1^^file2"
 #          -DSOURCES:STRING="file1^^file2"
-#          -DWRAP_INT_DIR:STRING=subir/subir/
-#          -DOUTPUT_DIR:PATH=/path  -DQT_QMAKE_EXECUTABLE:PATH=/path/to/qt/qmake
+#          -DOUTPUT_DIR:PATH=/path
 #          -DPYTHON_EXECUTABLE:FILEPATH=/path/to/python
 #          -DPYTHON_EXECUTABLE:FILEPATH=/path/to/python
 #          -DPYTHON_LIBRARY_PATH:PATH=/path/to/pythonlib
 #          -DPYTHON_LIBRARY_PATH:PATH=/path/to/pythonlib
 #          -DHAS_DECORATOR:BOOL=True
 #          -DHAS_DECORATOR:BOOL=True
@@ -47,221 +42,63 @@
 #   It will contain the list of class and the constructor signature that will be wrapped.
 #   It will contain the list of class and the constructor signature that will be wrapped.
 #
 #
 
 
-set(verbose 0)
-
-#
-# Convenient function allowing to log the reason why a given class hasn't been wrapped
-# If verbose=1, it will also be displayed on the standard output
-#
-function(log msg)
-  if(verbose)
-    message(${msg})
-  endif()
-  file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/ctkScriptWrapPythonQt_Light_log.txt" "${msg}\n")
-endfunction()
-
-#
-# Convenient function allowing to invoke re.search(regex, string) using the given interpreter.
-# Note that is_matching will be set to True if there is a match
-#
-function(reSearchFile python_exe python_library_path regex file is_matching)
-
-  set(python_cmd "import re\; f = open('${file}', 'r')\;
-res = re.search\(\"${regex}\", f.read(), re.MULTILINE\)\;
-if res == None: print \"FALSE\"
-else: print \"TRUE\"
-")
-  #message("python_cmd: ${python_cmd}")
-
-  if(WIN32)
-    set(ENV{PATH} ${python_library_path};$ENV{PATH})
-  elseif(APPLE)
-    set(ENV{DYLD_LIBRARY_PATH} ${python_library_path}:$ENV{DYLD_LIBRARY_PATH})
-  else()
-    set(ENV{LD_LIBRARY_PATH} ${python_library_path}:$ENV{LD_LIBRARY_PATH})
-  endif()
-
-  execute_process(
-    COMMAND ${python_exe} -c ${python_cmd};
-    RESULT_VARIABLE result
-    OUTPUT_VARIABLE output
-    ERROR_VARIABLE error
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-    )
-
-  if(result)
-    message(FATAL_ERROR "reSearchFile - Problem with regex: ${regex}\n${error}")
-  endif()
-  #message(${output})
-  set(is_matching ${output} PARENT_SCOPE)
-
-endfunction()
-
-if(NOT DEFINED CMAKE_CURRENT_LIST_DIR)
-  get_filename_component(CMAKE_CURRENT_LIST_DIR ${CMAKE_CURRENT_LIST_FILE} PATH)
-endif()
-if(NOT DEFINED CMAKE_CURRENT_LIST_FILENAME)
-  get_filename_component(CMAKE_CURRENT_LIST_FILENAME ${CMAKE_CURRENT_LIST_FILE} NAME)
-endif()
-
 # Check for non-defined var
 # Check for non-defined var
-foreach(var WRAPPING_NAMESPACE TARGET SOURCES WRAP_INT_DIR HAS_DECORATOR)
+foreach(var WRAPPING_NAMESPACE TARGET SOURCES HAS_DECORATOR)
   if(NOT DEFINED ${var})
   if(NOT DEFINED ${var})
     message(FATAL_ERROR "${var} not specified when calling ctkScriptWrapPythonQt")
     message(FATAL_ERROR "${var} not specified when calling ctkScriptWrapPythonQt")
   endif()
   endif()
 endforeach()
 endforeach()
 
 
 # Check for non-existing ${var}
 # Check for non-existing ${var}
-foreach(var QT_QMAKE_EXECUTABLE OUTPUT_DIR PYTHON_EXECUTABLE PYTHON_LIBRARY_PATH)
+foreach(var WRAPPING_SCRIPT OUTPUT_DIR PYTHON_EXECUTABLE PYTHON_LIBRARY_PATH)
   if(NOT EXISTS ${${var}})
   if(NOT EXISTS ${${var}})
     message(FATAL_ERROR "Failed to find ${var}=\"${${var}}\" when calling ctkScriptWrapPythonQt")
     message(FATAL_ERROR "Failed to find ${var}=\"${${var}}\" when calling ctkScriptWrapPythonQt")
   endif()
   endif()
 endforeach()
 endforeach()
 
 
-# Clear log file
-file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ctkScriptWrapPythonQt_Light_log.txt" "")
-
-# Convert wrapping namespace to subdir
-string(REPLACE "." "_" WRAPPING_NAMESPACE_UNDERSCORE ${WRAPPING_NAMESPACE})
-
 # Convert ^^ separated string to list
 # Convert ^^ separated string to list
 string(REPLACE "^^" ";" SOURCES "${SOURCES}")
 string(REPLACE "^^" ";" SOURCES "${SOURCES}")
 
 
-foreach(FILE ${SOURCES})
-
-  # what is the filename without the extension
-  get_filename_component(TMP_FILENAME ${FILE} NAME_WE)
-
-  set(includes
-    "${includes}\n#include \"${TMP_FILENAME}.h\"")
-
-  # Extract classname - NOTE: We assume the filename matches the associated class
-  set(className ${TMP_FILENAME})
-  #message(STATUS "FILE:${FILE}, className:${className}")
-
-  # Extract parent classname
-  set(parentClassName)
-
-  if("${parentClassName}" STREQUAL "")
-    # Does constructor signature is of the form: myclass()
-    set(regex "[^~]${className}[\\s\\n]*\\([\\s\\n]*\\)")
-    reSearchfile(${PYTHON_EXECUTABLE} ${PYTHON_LIBRARY_PATH} ${regex} ${FILE} is_matching)
-    if(is_matching)
-      set(parentClassName "No")
-      log("${TMP_FILENAME} - constructor of the form: ${className}\(\)")
-    endif()
-  endif()
-
-  if("${parentClassName}" STREQUAL "")
-    # Does constructor signature is of the form: myclass(QObject * parent ...)
-    set(regex "${className}[\\s\\n]*\\([\\s\\n]*QObject[\\s\\n]*\\*[\\s\\n]*\\w+[\\s\\n]*(\\=[\\s\\n]*(0|NULL)|,.*\\=.*\\)|\\))")
-    reSearchfile(${PYTHON_EXECUTABLE} ${PYTHON_LIBRARY_PATH} ${regex} ${FILE} is_matching)
-    if(is_matching)
-      set(parentClassName "QObject")
-      log("${TMP_FILENAME} - constructor of the form: ${className}\(QObject * parent ... \)")
-    endif()
-  endif()
-
-  if("${parentClassName}" STREQUAL "")
-    # Does constructor signature is of the form: myclass(QWidget * parent ...)
-    set(regex "${className}[\\s\\n]*\\([\\s\\n]*QWidget[\\s\\n]*\\*[\\s\\n]*\\w+[\\s\\n]*(\\=[\\s\\n]*(0|NULL)|,.*\\=.*\\)|\\))")
-    reSearchfile(${PYTHON_EXECUTABLE} ${PYTHON_LIBRARY_PATH} ${regex} ${FILE} is_matching)
-    if(is_matching)
-      set(parentClassName "QWidget")
-      log("${TMP_FILENAME} - constructor of the form: ${className}\(QWidget * parent ... \)")
-    endif()
-  endif()
-
-  # Generate PythonQtWrapper class
-  if("${parentClassName}" STREQUAL "QObject" OR "${parentClassName}" STREQUAL "QWidget")
-
-    set(pythonqtWrappers
-      "${pythonqtWrappers}
-//-----------------------------------------------------------------------------
-class PythonQtWrapper_${className} : public QObject
-{
-Q_OBJECT
-public:
-public Q_SLOTS:
-  ${className}* new_${className}(${parentClassName}*  parent = 0)
-    {
-    return new ${className}(parent);
-    }
-  void delete_${className}(${className}* obj) { delete obj; }
-};
-")
-
-  elseif("${parentClassName}" STREQUAL "No")
-
-    set(pythonqtWrappers
-      "${pythonqtWrappers}
-//-----------------------------------------------------------------------------
-class Q_DECL_EXPORT PythonQtWrapper_${className} : public QObject
-{
-Q_OBJECT
-public:
-public Q_SLOTS:
-  ${className}* new_${className}()
-    {
-    return new ${className}();
-    }
-  void delete_${className}(${className}* obj) { delete obj; }
-};
-")
+if(WIN32)
+  set(ENV{PATH} ${PYTHON_LIBRARY_PATH};$ENV{PATH})
+elseif(APPLE)
+  set(ENV{DYLD_LIBRARY_PATH} ${PYTHON_LIBRARY_PATH}:$ENV{DYLD_LIBRARY_PATH})
+else()
+  set(ENV{LD_LIBRARY_PATH} ${PYTHON_LIBRARY_PATH}:$ENV{LD_LIBRARY_PATH})
+endif()
 
 
-  else() # Case parentClassName is empty
+set(verbose 0)
 
 
-    message(WARNING "ctkScriptWrapPythonQt_Light - Problem wrapping ${FILE}")
+set(extra_args)
+if(verbose)
+  set(extra_args --extra-verbose)
+endif()
 
 
+execute_process(
+  COMMAND ${PYTHON_EXECUTABLE} ${WRAPPING_SCRIPT}
+    --target=${TARGET} --namespace=${WRAPPING_NAMESPACE} --output-dir=${OUTPUT_DIR} ${extra_args}
+    ${SOURCES}
+  RESULT_VARIABLE result
+  OUTPUT_VARIABLE output
+  ERROR_VARIABLE error
+  OUTPUT_STRIP_TRAILING_WHITESPACE
+  )
+if(NOT result EQUAL 0)
+  message(FATAL_ERROR "Failed to wrap target: ${TARGET}\n${output}\n${error}")
+else()
+  if(verbose)
+    message(${output})
   endif()
   endif()
+endif()
 
 
-  # Generate code allowing to register the class metaobject and its associated "light" wrapper
-  set(registerclasses "${registerclasses}
-  PythonQt::self()->registerClass(
-    &${className}::staticMetaObject, \"${TARGET}\",
-    PythonQtCreateObject<PythonQtWrapper_${className}>);\n")
-
-endforeach()
-
-# Write master include file
-file(WRITE ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}0.h "//
-// File auto-generated by cmake macro ctkScriptWrapPythonQt_Light
-//
-
-#ifndef __${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}0_h
-#define __${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}0_h
-
-#include <QWidget>
-${includes}
-${pythonqtWrappers}
-#endif
-")
-
-# Write wrapper header
-file(WRITE ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp "//
-// File auto-generated by cmake macro ctkScriptWrapPythonQt_Light
-//
-
-#include <PythonQt.h>
-#include \"${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}0.h\"
-
-void PythonQt_init_${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}(PyObject* module)
-{
-  Q_UNUSED(module);
-  ${registerclasses}
-}
-")
+# Convert wrapping namespace to subdir
+string(REPLACE "." "_" WRAPPING_NAMESPACE_UNDERSCORE ${WRAPPING_NAMESPACE})
 
 
-# Configure 'ctkMacroWrapPythonQtModuleInit.cpp.in' replacing TARGET and
+# Configure 'ctkMacroWrapPythonQtModuleInit.cpp.in' using TARGET, HAS_DECORATOR and
 # WRAPPING_NAMESPACE_UNDERSCORE.
 # WRAPPING_NAMESPACE_UNDERSCORE.
 configure_file(
 configure_file(
   ${CMAKE_CURRENT_LIST_DIR}/ctkMacroWrapPythonQtModuleInit.cpp.in
   ${CMAKE_CURRENT_LIST_DIR}/ctkMacroWrapPythonQtModuleInit.cpp.in
   ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_module_init.cpp
   ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_module_init.cpp
   )
   )
 
 
-# Since file(WRITE ) doesn't update the timestamp - Let's touch the files
-execute_process(
-  COMMAND ${CMAKE_COMMAND} -E touch
-    ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp
-  )
 
 

+ 228 - 0
CMake/ctkWrapPythonQt.py

@@ -0,0 +1,228 @@
+
+import errno
+import os
+import re
+from string import Template
+
+PYTHONQT_WRAPPER_WITH_PARENT = Template("""
+//-----------------------------------------------------------------------------
+class PythonQtWrapper_${className} : public QObject
+{
+Q_OBJECT
+public:
+public Q_SLOTS:
+  ${className}* new_${className}(${parentClassName}*  parent = 0)
+    {
+    return new ${className}(parent);
+    }
+  void delete_${className}(${className}* obj) { delete obj; }
+};
+""")
+
+PYTHONQT_WRAPPER_WITHOUT_PARENT = Template("""
+//-----------------------------------------------------------------------------
+class PythonQtWrapper_${className} : public QObject
+{
+Q_OBJECT
+public:
+public Q_SLOTS:
+  ${className}* new_${className}()
+    {
+    return new ${className}();
+    }
+  void delete_${className}(${className}* obj) { delete obj; }
+};
+""")
+
+def _mkdir_p(path):
+    """See """
+    try:
+        os.makedirs(path)
+    except OSError as exc:
+        if exc.errno == errno.EEXIST and os.path.isdir(path):
+            pass
+        else: raise
+
+def ctk_wrap_pythonqt(target, namespace, output_dir, input_files, extra_verbose):
+    if extra_verbose:
+        print("target: %s" % target)
+        print("namespace: %s" % namespace)
+        print("output_dir: %s" % output_dir)
+        print("input_files: %s" % input_files)
+    
+    _mkdir_p(output_dir)
+    
+    includes = []
+    pythonqtWrappers = []
+    registerclasses = []
+    namespace = namespace.replace('.', '_')
+    
+    for input_file in input_files:
+        filename = os.path.basename(input_file)
+        if extra_verbose:
+            print("Wrapping %s" % filename)
+       
+        # what is the filename without the extension
+        filename_we = os.path.splitext(filename)[0]
+        
+        includes.append('#include "%s.h"' % filename_we)
+
+        # Extract classname - NOTE: We assume the filename matches the associated class
+        className = filename_we
+        if extra_verbose:
+            print("\tclassName:%s" % className)
+
+        # Extract parent classname
+        parentClassName = None
+
+        # Read input files
+        with open(input_file) as f:
+            content = f.read()
+            
+            # Skip wrapping if file do NOT contain Q_OBJECT
+            if 'Q_OBJECT' not in content:
+                if extra_verbose:
+                    print("\tskipping - No Q_OBJECT macro")
+                continue
+
+            # Skip wrapping if constructor doesn't match:
+            #    my_class()
+            #    my_class(QObject* newParent ...)
+            #    my_class(QWidget* newParent ...)
+            # Constructor with either QWidget or QObject as first parameter
+            regex = r"[^~]%s[\s\n]*\([\s\n]*((QObject|QWidget)[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\)|\)))" % className
+            res = re.search(regex, content, re.MULTILINE)
+            if res is None:
+                if extra_verbose:
+                    print("\tskipping - Missing expected constructor signature")
+                continue
+     
+            # Skip wrapping if object has a virtual pure method
+            # "x3b" is the unicode for semicolon
+            regex = r"virtual[\w\n\s\*\(\)]+\=[\s\n]*(0|NULL)[\s\n]*\x3b"
+            res = re.search(regex, content, re.MULTILINE)
+            if res is not None:
+                if extra_verbose:
+                    print("skipping - Contains a virtual pure method")
+                continue
+
+            if parentClassName is None:
+                # Does constructor signature is of the form: myclass()
+                regex = r"[^~]%s[\s\n]*\([\s\n]*\)" % className
+                res = re.search(regex, content, re.MULTILINE)
+           
+                if res is not None:
+                    parentClassName = ""
+                    if extra_verbose:
+                        print("\tconstructor of the form: %s()" % className)
+
+            if parentClassName is None:
+                # Does constructor signature is of the form: myclass(QObject * parent ...)
+                regex = r"%s[\s\n]*\([\s\n]*QObject[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\))" % className
+                res = re.search(regex, content, re.MULTILINE)
+                if res is not None:
+                    parentClassName = "QObject"
+                    if extra_verbose:
+                        print("\tconstructor of the form: %s(QObject * parent ... )" % className)
+
+            if parentClassName is None:
+                # Does constructor signature is of the form: myclass(QWidget * parent ...)
+                regex = r"%s[\s\n]*\([\s\n]*QWidget[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\))" % className
+                res = re.search(regex, content, re.MULTILINE)
+                if res is not None:
+                    parentClassName = "QWidget"
+                    if extra_verbose:
+                        print("\tconstructor of the form: %s(QWidget * parent ... )" % className)
+                        
+        # Generate PythonQtWrapper class
+        if parentClassName == "QObject" or parentClassName == "QWidget":
+            pythonqtWrappers.append(
+                PYTHONQT_WRAPPER_WITH_PARENT.substitute(className = className, parentClassName = parentClassName))
+            
+        elif parentClassName == "":
+            pythonqtWrappers.append(PYTHONQT_WRAPPER_WITHOUT_PARENT.substitute(className = className))
+
+        else: # Case parentClassName is None
+            raise Exception("Problem wrapping %s" % input_file)
+        
+        # Generate code allowing to register the class metaobject and its associated "light" wrapper
+        registerclasses.append(
+        Template("""
+  PythonQt::self()->registerClass(
+  &${className}::staticMetaObject, "${target}",
+  PythonQtCreateObject<PythonQtWrapper_${className}>);
+  """).substitute(className = className, target = target))
+
+    output_header = output_dir + "/" + namespace + "_" + target + "0.h"
+    if extra_verbose:
+        print("output_header: %s" % output_header)
+    # Write master include file
+    with open(output_header, "w") as f:
+        f.write(Template(
+"""
+//
+// File auto-generated by ctkWrapPythonQt.py
+//
+
+#ifndef __${namespace}_${target}0_h
+#define __${namespace}_${target}0_h
+
+#include <QWidget>
+${includes}
+${pythonqtWrappers}
+#endif
+""").substitute(namespace = namespace, target = target, includes = '\n'.join(includes), pythonqtWrappers = '\n'.join(pythonqtWrappers)))
+
+    output_cpp = output_dir + "/" + namespace + "_" + target + "_init.cpp"
+    if extra_verbose:
+        print("output_cpp: %s" % output_cpp)
+    with open(output_cpp , "w") as f:
+        # Write wrapper header
+        f.write(Template(
+"""
+//
+// File auto-generated by ctkWrapPythonQt.py
+//
+
+#include <PythonQt.h>
+#include "${namespace}_${target}0.h"
+
+void PythonQt_init_${namespace}_${target}(PyObject* module)
+{
+  Q_UNUSED(module);
+  ${registerclasses}
+}
+""").substitute(namespace = namespace, target = target, registerclasses = '\n'.join(registerclasses)))
+
+if __name__ == '__main__':
+    from optparse import OptionParser
+    usage = "usage: %prog [options] <output_file> <input_file> [<input_file1> [...]]"
+    parser = OptionParser(usage=usage)
+    parser.add_option("-t", "--target",
+                      dest="target", action="store", type="string",
+                      help="Name of the associated library")
+    parser.add_option("-n", "--namespace",
+                      dest="namespace", action="store", type="string",
+                      help="Wrapping namespace")
+    parser.add_option("--output-dir",
+                      dest="output_dir", action="store", type="string",
+                      help="Output directory")
+    parser.add_option("-v", "--verbose",
+                      dest="verbose", action="store_true",
+                      help="Print verbose information")
+    parser.add_option("--extra-verbose",
+                      dest="extra_verbose", action="store_true",
+                      help="Print extra verbose information")
+
+    (options, args) = parser.parse_args()
+
+    #if len(args) < 2:
+    #    parser.error("arguments '%s' are required !" % '<output_file> <input_file>')
+
+    if options.extra_verbose:
+        options.verbose = True
+
+    ctk_wrap_pythonqt(options.target, options.namespace, options.output_dir, args, options.extra_verbose)
+      
+    if options.verbose:
+        print("Wrapped %d files" % len(args))