rdcore.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2008, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 #
00033 # Revision $Id$
00034 
00035 import os
00036 import traceback
00037 import sys
00038 from subprocess import Popen, PIPE
00039 import yaml
00040 
00041 import roslib.packages
00042 import roslib.manifest 
00043 import roslib.rospack 
00044 import roslib.stacks
00045 import roslib.stack_manifest
00046 
00047 class RosdocContext(object):
00048     
00049     def __init__(self, name, docdir, package_filters=None, path_filters=None):
00050         self.name = name
00051         self.package_filters = package_filters
00052         self.path_filters = []
00053         if path_filters:
00054             for p in path_filters.split(os.pathsep):
00055                 if not p:
00056                     continue
00057                 if not p.endswith(os.sep):
00058                     p = p + os.sep
00059                 self.path_filters.append(p)
00060         self.docdir = docdir
00061 
00062         # these will be initialized in init()
00063         self.packages = {}
00064         self.stacks = {}        
00065         self.external_docs = {}
00066         self.manifests = {}
00067         self.stack_manifests = {}
00068 
00069         # - generally suppress output
00070         self.quiet = False
00071         # - for profiling
00072         self.timings = {}
00073 
00074         # advanced per-package config
00075         self.rd_configs = {} 
00076 
00077         self.template_dir = None
00078 
00079     def has_builder(self, package, builder):
00080         """
00081         @return: True if package is configured to use builder. NOTE:
00082         if there is no config, package is assumed to define a doxygen
00083         builder
00084         @rtype: bool
00085         """
00086         rd_config = self.rd_configs.get(package, None)
00087         if not rd_config:
00088             return builder == 'doxygen'
00089         if type(rd_config) != list:
00090             sys.stderr.write("WARNING: package [%s] has an invalid rosdoc config\n"%(package))
00091             return False            
00092         try:
00093             return len([d for d in rd_config if d['builder'] == builder]) > 0
00094         except KeyError:
00095             sys.stderr.write("config file for [%s] is invalid, missing required 'builder' key\n"%(package))
00096             return False
00097         except:
00098             sys.stderr.write("config file for [%s] is invalid\n"%(package))
00099             return False
00100             
00101     def should_document(self, package):
00102         """
00103         @return: True if package should be documented
00104         @rtype: bool
00105         """
00106         if not package in self.packages:
00107             return False
00108         # package filters override all 
00109         if self.package_filters:
00110             return package in self.package_filters
00111         # don't document if not in path filters
00112         if self.path_filters:
00113             package_path = self.packages[package]
00114             if not [p for p in self.path_filters if package_path.startswith(p)]:
00115                 return False
00116         # TODO: don't document if not in requested stack
00117         return True
00118 
00119     def init(self):
00120         if not self.quiet:
00121             print "initializing rosdoc context:\n\tpackage filters: %s\n\tpath filters: %s"%(self.package_filters, self.path_filters)
00122         
00123         rosdoc_dir = roslib.packages.get_pkg_dir('rosdoc')
00124         self.template_dir = os.path.join(rosdoc_dir, 'templates')
00125 
00126         # use 'rospack list' to locate all packages and store their paths in a dictionary
00127         rospack_list = roslib.rospack.rospackexec(['list']).split('\n')
00128         rospack_list = [x.split(' ') for x in rospack_list if ' ' in x]
00129 
00130         # I'm still debating whether or not to immediately filter
00131         # these. The problem is that a package that is within the
00132         # filter may reference packages outside that filter. I'm not
00133         # sure if this is an issue or not.
00134         packages = self.packages
00135         for package, path in rospack_list:
00136             packages[package] = path
00137 
00138         # cache all stack manifests due to issue with empty stacks not being noted by _crawl_deps
00139         stack_manifests = self.stack_manifests
00140         rosstack_list = roslib.rospack.rosstackexec(['list']).split('\n')
00141         rosstack_list = [x.split(' ') for x in rosstack_list if ' ' in x]
00142         for stack, path in rosstack_list:
00143 
00144             f = os.path.join(path, roslib.stack_manifest.STACK_FILE)
00145             try:
00146                 stack_manifests[stack] = roslib.stack_manifest.parse_file(f)
00147             except:
00148                 traceback.print_exc()
00149                 print >> sys.stderr, "WARN: stack '%s' does not have a valid stack.xml file, manifest information will not be included in docs"%stack
00150 
00151         self.doc_packages = [p for p in packages if self.should_document(p)]
00152         self._crawl_deps()
00153         
00154     def _crawl_deps(self):
00155         """
00156         Crawl manifest.xml dependencies
00157         """
00158         external_docs = self.external_docs
00159         manifests = self.manifests
00160         rd_configs = self.rd_configs
00161 
00162         stacks = self.stacks = {}
00163 
00164         # keep track of packages with invalid manifests so we can unregister them
00165         bad = []
00166         for package, path in self.packages.iteritems():
00167 
00168             # find stacks to document on demand
00169             if self.should_document(package):
00170                 if not self.quiet:
00171                     print "+package[%s]"%(package)
00172                 stack = roslib.stacks.stack_of(package) or ''
00173                 if stack and stack not in stacks:
00174                     #print "adding stack [%s] to documentation"%stack
00175                     try:
00176                         p = roslib.stacks.get_stack_dir(stack)
00177                         stacks[stack] = p
00178                     except:
00179                         sys.stderr.write("cannot locate directory of stack [%s]\n"%(stack))
00180                 
00181             f = os.path.join(path, roslib.manifest.MANIFEST_FILE)
00182             try:
00183                 manifests[package] = m = roslib.manifest.parse_file(f)
00184 
00185                 if self.should_document(package):
00186                     #NOTE: the behavior is undefined if the users uses
00187                     #both config and export properties directly
00188 
00189                     # #1650 for backwards compatibility, we read the old
00190                     # 'doxymaker' tag, which is deprecated
00191                     #  - this is a loop but we only accept one value
00192                     for e in m.get_export('doxymaker', 'external'):
00193                         external_docs[package] = e
00194                     for e in m.get_export('rosdoc', 'external'):
00195                         external_docs[package] = e
00196 
00197                     # load in any external config files
00198                     # TODO: check for rosdoc.yaml by default
00199                     for e in m.get_export('rosdoc', 'config'):
00200                         try:
00201                             e = e.replace('${prefix}', path)
00202                             config_p = os.path.join(path, e)
00203                             with open(config_p, 'r') as config_f:
00204                                 rd_configs[package] = yaml.load(config_f)
00205                         except Exception as e:
00206                             sys.stderr.write("ERROR: unable to load rosdoc config file [%s]: %s\n"%(config_p, str(e)))
00207                     
00208             except:
00209                 if self.should_document(package):
00210                     sys.stderr.write("WARN: Package '%s' does not have a valid manifest.xml file, manifest information will not be included in docs\n"%(package))
00211                 bad.append(package)
00212 
00213         for b in bad:
00214             if b in self.packages:
00215                 del self.packages[b]
00216         stack_manifests = self.stack_manifests
00217         for stack, path in stacks.iteritems():
00218             if not self.quiet:
00219                 print "+stack[%s]"%(stack)
00220 
00221 def compute_relative(src, target):
00222     s1, s2 = [p.split(os.sep) for p in [src, target]]
00223     #filter out empties
00224     s1, s2 = filter(lambda x: x, s1), filter(lambda x: x, s2)
00225     i = 0
00226     while i < min(len(s1), len(s2)):
00227         if s1[i] != s2[i]:
00228             break
00229         i+=1
00230     rel = ['..' for d in s1[i:]] + s2[i:]
00231     return os.sep.join(rel)
00232 
00233 def html_path(package, docdir):
00234     return os.path.join(docdir, package, 'html')
00235 
00236 
00237 ################################################################################
00238 # TEMPLATE ROUTINES
00239 
00240 _TEMPLATES_DIR = 'templates'
00241 
00242 def load_tmpl(filename):
00243     filename = os.path.join(roslib.packages.get_pkg_dir('rosdoc'), _TEMPLATES_DIR, filename)
00244     if not os.path.isfile(filename):
00245         sys.stderr.write("Cannot locate template file '%s'\n"%(filename))
00246         sys.exit(1)
00247     with open(filename, 'r') as f:
00248         str = f.read()
00249         if not str:
00250             sys.stderr.write("Template file '%s' is empty\n"%(filename))
00251             sys.exit(1)
00252         return str
00253 
00254 def instantiate_template(tmpl, vars):
00255     for k, v in vars.iteritems():
00256         tmpl = tmpl.replace(k, str(v).encode('utf-8'))
00257     return tmpl
00258 


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