doxygenator.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # Software License Agreement (BSD License)
00003 #
00004 # Copyright (c) 2008, Willow Garage, Inc.
00005 # All rights reserved.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions
00009 # are met:
00010 #
00011 #  * Redistributions of source code must retain the above copyright
00012 #    notice, this list of conditions and the following disclaimer.
00013 #  * Redistributions in binary form must reproduce the above
00014 #    copyright notice, this list of conditions and the following
00015 #    disclaimer in the documentation and/or other materials provided
00016 #    with the distribution.
00017 #  * Neither the name of Willow Garage, Inc. nor the names of its
00018 #    contributors may be used to endorse or promote products derived
00019 #    from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 # POSSIBILITY OF SUCH DAMAGE.
00033 #
00034 # Revision $Id$
00035 
00036 from distutils.version import StrictVersion
00037 from subprocess import Popen, PIPE
00038 import shutil
00039 import tempfile
00040 import shutil 
00041 import roslib.msgs
00042 import roslib.srvs
00043 import roslib.rospack 
00044 
00045 from rosdoc.rdcore import *
00046 
00047 ## @param ext str: extension (msg or srv)
00048 ## @param type str: type name
00049 ## @param text str: full text definition of type
00050 def _msg_srv_tmpl(ext, type, text):
00051     tmpl = '<h2>%s.%s</h2><div style="border: 1px solid #333;"><p style="font-family: monospace;">'%(type, ext)
00052     for line in text.split('\n'):
00053         parts = line.split('#')
00054         if len(parts) > 1:
00055             tmpl = tmpl + parts[0]+'<font color="blue">#%s</font><br/>'%('#'.join(parts[1:]))
00056         else:
00057             tmpl = tmpl + "%s<br />"%parts[0]
00058     return tmpl+"</p></div>"
00059 
00060 ## create include files for messages and services
00061 def generate_msg_srv_includes(package, tmp, to_delete):
00062     for ext, list_types, spec_file in [('msg', roslib.msgs.list_msg_types, roslib.msgs.msg_file),\
00063                                        ('srv', roslib.srvs.list_srv_types, roslib.srvs.srv_file)]:
00064         for type_ in list_types(package, False):
00065             with open(spec_file(package, type_), 'r') as f:
00066                 p = os.path.join(tmp, '%s.%s.html'%(type_, ext))
00067                 with open(p, 'w') as html_file:
00068                     to_delete.append(p)
00069                     html_file.write(_msg_srv_tmpl(ext, type_, f.read()))
00070 
00071 ## @param package str: package name
00072 ## @param rd_config dict: rosdoc configuration parameters for this doxygen build
00073 ## @param m Manifest : package manifest
00074 ## @param html_dir str: directory to store doxygen files                    
00075 def create_package_template(package, rd_config, m, path, html_dir,
00076                             header_filename, footer_filename, example_path):
00077     # TODO: replace with general purpose key/value parser/substitution
00078 
00079     # set defaults for overridable keys
00080     file_patterns = '*.c *.cpp *.h *.cc *.hh *.hpp *.py *.dox *.java'
00081     excludes = '%s/build/'%path
00082     exclude_patterns = ''
00083 
00084     # last one wins
00085     for e in m.get_export('doxygen', 'excludes'):
00086         # prepend the packages path
00087         excludes = '%s/%s'%(path, e)
00088 
00089     # last one wins        
00090     for e in m.get_export('doxygen', 'file-patterns'):
00091         file_patterns = e
00092 
00093     # rd_config take precedence. default to empty dir so vars logic below works
00094     if not rd_config:
00095         rd_config = {}
00096        
00097     include_path = roslib.rospack.rospackexec(['cflags-only-I',package])
00098 
00099     # example path is where htmlinclude operates
00100     dvars = { '$INPUT':  path, '$PROJECT_NAME': package,
00101               '$EXAMPLE_PATH': "%s %s"%(path, example_path),
00102               '$EXCLUDE_PROP': rd_config.get('exclude', excludes),
00103               '$FILE_PATTERNS': rd_config.get('file_patterns', file_patterns),
00104               '$EXCLUDE_PATTERNS': rd_config.get('exclude_patterns', ''),
00105               '$HTML_OUTPUT': os.path.abspath(html_dir),
00106               '$HTML_HEADER': header_filename, '$HTML_FOOTER': footer_filename,
00107               '$OUTPUT_DIRECTORY': html_dir,
00108               '$INCLUDE_PATH': include_path,
00109 
00110               '$JAVADOC_AUTOBRIEF': rd_config.get('javadoc_autobrief', 'NO'),
00111               '$MULTILINE_CPP_IS_BRIEF': rd_config.get('multiline_cpp_is_brief', 'NO'),
00112               '$TAB_SIZE': rd_config.get('tab_size', '8'),
00113               '$ALIASES': rd_config.get('aliases', ''),
00114               '$EXAMPLE_PATTERNS': rd_config.get('example_patterns', ''),
00115               '$IMAGE_PATH': rd_config.get('image_path', path), #default to $INPUT
00116               '$EXCLUDE_SYMBOLS': rd_config.get('exclude_symbols', ''),
00117               }
00118     return instantiate_template(doxy_template, dvars)
00119 
00120 ## Processes manifest for package and then generates templates for
00121 ## header, footer, and manifest include file
00122 ## @param package: package to create templates for
00123 ## @type  package: str
00124 ## @param rd_config: rosdoc configuration dictionary
00125 ## @type  rd_config: dict
00126 ## @param path: file path to package
00127 ## @type  path: str
00128 ## @param m: package manifest or None
00129 ## @type  m: manifest.Manifest
00130 ## @return: header, footer, manifest
00131 ## @rtype: (str, str, str)
00132 def load_manifest_vars(ctx, rd_config, package, path, docdir, package_htmldir, m):
00133     author = license = description = status = notes = li_vc = li_url = brief = ''
00134     
00135     # by default, assume that packages are on wiki
00136     home_url = 'http://ros.org/wiki/%s'%package
00137 
00138     if rd_config:
00139         if 'homepage' in rd_config:
00140             home_url = rd_config['homepage']
00141             
00142     project_link = '<a href="%s">%s</a>'%(home_url, package)
00143     if m is not None:
00144         license = m.license or ''
00145         author = m.author or ''
00146         description = m.description or ''
00147         status = m.status or ''
00148         notes = m.notes or ''
00149 
00150         if m.brief:
00151             brief = ": "+m.brief
00152 
00153         li_url = '<li>Homepage: <a href=\"%s\">%s</a></li>'%(m.url, m.url)
00154         if m.versioncontrol:
00155             vcurl = m.versioncontrol.url
00156             li_vc = '<li>Version Control (%s): <a href="%s">%s</a></li>'%(m.versioncontrol.type, vcurl, vcurl)
00157     else:
00158         print "no manifest [%s]"%(package)
00159 
00160     # include links to msgs/srvs
00161     msgs = roslib.msgs.list_msg_types(package, False)
00162     srvs = roslib.srvs.list_srv_types(package, False)
00163         
00164     return {'$package': package,
00165             '$projectlink': project_link, '$license': license,
00166             '$description': description, '$brief': brief,
00167             '$author': author, '$status':status, 
00168             '$notes':notes, '$li_vc': li_vc, '$li_url': li_url,
00169             }
00170 
00171 ## utility to write string data to files and handle unicode 
00172 def _write_to_file(f, tmpl):
00173     try:
00174         if type(tmpl) == str:
00175             f.write(tmpl)
00176         else: #iso-8859-1 is the declared encoding of doxygen
00177             f.write(tmpl.encode('utf-8'))
00178         f.flush()
00179     except:
00180         print "ERROR, f[%s], tmpl[%s]"%(f, tmpl)
00181         raise
00182     
00183 def get_doxygen_version():
00184     try:
00185         command = ['doxygen', '--version']
00186         version = Popen(command, stdout=PIPE, stderr=PIPE).communicate()[0].strip()
00187     except:
00188         version = None
00189     return version
00190         
00191 # #3870: doxygen changed their template file syntax in a 'patch'
00192 # version.  It's a bit ugly to inline this on import, but this entire
00193 # generator needs to be rewritten.
00194 def header_template_name():
00195     doxygen_version = get_doxygen_version()
00196     # doxygen not available
00197     if doxygen_version is None:
00198         return None
00199     doxygen_version_splitted = doxygen_version.split('.')
00200     major = doxygen_version_splitted[0]
00201     minor = doxygen_version_splitted[1]
00202     patch = doxygen_version_splitted[2]
00203     if len(doxygen_version_splitted) > 3:
00204         build = doxygen_version_splitted[3]
00205     # > 1.7.3 doxygen changed the template syntax
00206     if StrictVersion('%s.%s.%s'%(major, minor, patch)) > StrictVersion('1.7.3'):
00207         return 'header-1.7.4.html'
00208     else:
00209         return 'header.html'
00210 
00211 
00212 def run_doxygen(package, doxygen_file, quiet=False):
00213     try:
00214         command = ['doxygen', doxygen_file]
00215         if quiet:
00216             Popen(command, stdout=PIPE, stderr=PIPE).communicate()
00217         else:
00218             print "doxygen-ating %s [%s]"%(package, ' '.join(command))
00219             Popen(command, stdout=PIPE).communicate()            
00220     except OSError, (errno, strerr):
00221         #fatal        
00222         print """\nERROR: It appears that you do not have doxygen installed.
00223 If you are on Ubuntu/Debian, you can install doxygen by typing:
00224 
00225    sudo apt-get install doxygen
00226 """
00227         sys.exit(1) 
00228 
00229 ## Main entrypoint into creating doxygen files
00230 ## @return [str]: list of directories in which documentation was generated (aka the list of successful packages)
00231 def generate_doxygen(ctx):
00232     quiet = ctx.quiet
00233 
00234     #TODO: generate_doxygen shouldn't receive packages in external_docs
00235     
00236     #TODO: success is now supposed to be the listed of generated
00237     #artifacts. There is some discontinuity here if the cwd is
00238     #changed.
00239     
00240     # setup temp directory
00241     example_path = tempfile.mkdtemp(prefix='rosdoc_doxygen')
00242 
00243     success = []
00244     
00245     dir = ctx.docdir
00246     # dictionary mapping packages to paths
00247     packages = ctx.packages
00248     # list of packages that we are documenting
00249     doc_packages = ctx.doc_packages
00250     external_docs = ctx.external_docs
00251     rd_configs = ctx.rd_configs
00252     manifests = ctx.manifests
00253 
00254     tmpls = [header_template, footer_template, manifest_template]
00255     try:
00256         for package, path in packages.iteritems():
00257             if not package in doc_packages or \
00258                    not ctx.has_builder(package, 'doxygen'):
00259                 continue
00260 
00261             # the logic for the doxygen builder is different from
00262             # others as doxygen is the default builder if no config is
00263             # declared
00264             rd_config = rd_configs.get(package, None)
00265             if rd_config:
00266                 # currently only allow one doxygen build per package. This is not inherent, it
00267                 # just requires rewriting higher-level logic
00268                 rd_config = [d for d in ctx.rd_configs[package] if d['builder'] == 'doxygen'][0]
00269 
00270             # Configuration (all are optional)
00271             #
00272             # name: Documentation set name (e.g. C++ API)
00273             # output_dir: Directory to store files (default '.')
00274             # file_patterns: override FILE_PATTERNS
00275             # excludes: override EXCLUDES
00276             
00277             # doxygenator currently does some non-doxygen work.
00278             # pkg_doc_dir is the pointer to the directory for these non-doxygen
00279             # tools. html_dir is the path for doxygen
00280             pkg_doc_dir = html_path(package, ctx.docdir)
00281                 
00282             # compute the html directory for doxygen
00283             html_dir = html_path(package, ctx.docdir)
00284             if rd_config and 'output_dir' in rd_config:
00285                 html_dir = os.path.join(html_dir, rd_config['output_dir'])
00286 
00287             # have to makedirs for external packages
00288             if not os.path.exists(pkg_doc_dir):
00289                 os.makedirs(pkg_doc_dir)
00290                     
00291             files = []
00292             try:
00293                 header_file = tempfile.NamedTemporaryFile('w+')
00294                 footer_file = tempfile.NamedTemporaryFile('w+')
00295                 doxygen_file = tempfile.NamedTemporaryFile('w+')
00296                 manifest_file = open(os.path.join(example_path, 'manifest.html'), 'w')
00297                 files = [header_file, footer_file, manifest_file, doxygen_file]
00298                 to_delete = [manifest_file]
00299 
00300                 # create the doxygen templates and wiki header
00301 
00302                 generate_msg_srv_includes(package, example_path, to_delete)
00303 
00304                 # - instantiate the templates
00305                 manifest_ = manifests[package] if package in manifests else None
00306 
00307                 vars = load_manifest_vars(ctx, rd_config, package, path, dir, html_dir, manifest_)
00308                 header, footer, manifest_html = [instantiate_template(t, vars) for t in tmpls]
00309 
00310                 if package not in external_docs:
00311                     doxy = \
00312                         create_package_template(package, rd_config, manifest_,
00313                                                 path, html_dir,
00314                                                 header_file.name, footer_file.name,
00315                                                 example_path)
00316                     for f, tmpl in zip(files, [header, footer, manifest_html, doxy]):
00317                         _write_to_file(f, tmpl)
00318                     # doxygenate
00319                     run_doxygen(package, doxygen_file.name, quiet=quiet)
00320                         
00321                 # support files (stylesheets)
00322                 dstyles_in = os.path.join(ctx.template_dir, 'doxygen.css')
00323                 dstyles_css = os.path.join(html_dir, 'doxygen.css')
00324                 shutil.copyfile(dstyles_in, dstyles_css)
00325 
00326                 success.append(package)
00327             except Exception as e:
00328                 print >> sys.stderr, "ERROR: Doxygen of package [%s] failed: %s"%(package, str(e))
00329             finally:
00330                 for f in files:
00331                     f.close()
00332     finally:
00333         shutil.rmtree(example_path)
00334     return success
00335 
00336 
00337 doxy_template = load_tmpl('doxy.template')
00338 
00339 header_template_filename = header_template_name()
00340 if header_template_filename is None:
00341     raise Exception("Doxygen is not installed.  Please install it to continue.")
00342 else:
00343     header_template = load_tmpl(header_template_filename)
00344     footer_template = load_tmpl('footer.html')
00345     manifest_template = load_tmpl('manifest.html')


rosdoc
Author(s): Ken Conley/kwc@willowgarage.com
autogenerated on Sun Oct 5 2014 23:30:30