00001
00002 """
00003 Copyright (c) 2012,
00004 Systems, Robotics and Vision Group
00005 University of the Balearican Islands
00006 All rights reserved.
00007
00008 Redistribution and use in source and binary forms, with or without
00009 modification, are permitted provided that the following conditions are met:
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 copyright
00013 notice, this list of conditions and the following disclaimer in the
00014 documentation and/or other materials provided with the distribution.
00015 * Neither the name of Systems, Robotics and Vision Group, University of
00016 the Balearican Islands nor the names of its contributors may be used to
00017 endorse or promote products derived from this software without specific
00018 prior written permission.
00019
00020 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00021 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00022 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00023 DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
00024 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00025 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00026 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00027 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00028 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00029 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00030 """
00031
00032
00033 try:
00034 import os
00035 import sys
00036
00037 import argparse
00038 import gtk
00039 import xdot
00040 import StringIO
00041 import fnmatch
00042 from xml.etree import ElementTree
00043 import roslib.packages
00044 except ImportError:
00045
00046 rospy.loginfo((os.linesep * 2).join(["An error found importing one module:",
00047 str(sys.exc_info()[1]), "You need to install it", "Stopping..."]))
00048 sys.exit(-2)
00049
00050 def find_launchfiles(directory, pattern='*.launch'):
00051 for root, dirs, files in os.walk(directory):
00052 for basename in files:
00053 if fnmatch.fnmatch(basename, pattern):
00054 filename = os.path.join(root, basename)
00055 yield root, basename, filename
00056
00057 def check_launchfiles(directory, pattern='*.launch'):
00058 for root, dirs, files in os.walk(directory):
00059 for basename in files:
00060 if fnmatch.fnmatch(basename, pattern):
00061 return True;
00062 return False;
00063
00064 def prepare_file():
00065 g = StringIO.StringIO()
00066 g.write('digraph G {\n rankdir=LR \n')
00067 return g
00068
00069 def show_graph(g,package = 0):
00070 drawable = False
00071 g.write('}')
00072 if len(g.getvalue())>26:
00073 drawable = True
00074 window = xdot.xdot.DotWindow()
00075 window.set_dotcode(g.getvalue())
00076 if package:
00077 window.set_title('Package {0}'.format(package))
00078 window.connect('destroy', gtk.main_quit)
00079 g.close()
00080 return drawable
00081
00082 def start_subgraph(g,boxname):
00083 g.write(' subgraph "{0}"'.format(boxname))
00084 g.write(' {\n')
00085 g.write(' label = "{0}";\n'.format(boxname))
00086
00087 def write_information(g,node_name,node_args=0):
00088 if node_args==0:
00089 for name in node_name:
00090 g.write('"{0}_node" [shape=Mrecord,label="{0}",style="filled",fillcolor="khaki"]\n'.format(name))
00091 else:
00092 g.write('"{0}" [shape=none, margin=0, label=<\n'.format(node_name))
00093 g.write('<table border="0" cellborder="1" cellpadding="4">\n')
00094 g.write('<tr><td BGCOLOR="greenyellow"><b>{0}</b></td></tr>\n'.format(node_name))
00095 if node_args:
00096 g.write('<tr><td BGCOLOR="lemonchiffon">Arguments:</td></tr>\n')
00097 for (arg, arg_def) in node_args:
00098 g.write('<tr><td ALIGN="LEFT" BGCOLOR="lemonchiffon">{0} = {1}</td></tr>\n'.format(arg,arg_def))
00099 g.write('</table>>];\n')
00100
00101 def write_connection(g,origin,destination, isNode=False):
00102 if not isNode:
00103 g.write(' "{0}" -> "{1}"\n'.format(origin,destination))
00104 elif isNode:
00105 g.write(' "{0}" -> "{1}_node"\n'.format(origin,destination))
00106
00107 def get_args_from_parser():
00108 parser = argparse.ArgumentParser(description='Draws graph from launchfiles')
00109 parser.add_argument('--pkg', metavar='package', help='ROS packages you want to inspect',nargs='+')
00110 return parser.parse_args()
00111
00112 def draw_folder(g,folder):
00113 working_directory = folder
00114 if check_launchfiles(folder):
00115 for root,filename,filepath in find_launchfiles(folder):
00116 if root != working_directory:
00117 working_directory = root
00118 f = open(filepath,'rt')
00119 try:
00120 tree = ElementTree.parse(f)
00121 node_name = os.path.splitext(filename)[0]
00122 it = tree.iter()
00123
00124 node_args = []
00125 node_includes = []
00126 node_nodes = []
00127 for node in it:
00128 if node.tag == "arg":
00129
00130 arg_name = node.attrib.get('name')
00131 default_value = node.attrib.get('default')
00132 value = node.attrib.get('value')
00133 if_clause = node.attrib.get('if')
00134 if default_value:
00135
00136 node_args.append((arg_name,default_value))
00137 elif not value:
00138
00139 node_args.append((arg_name,"REQUIRED"))
00140 elif if_clause:
00141 nodelet_args = value.split()[1]
00142 if node.tag == "include":
00143
00144 incl_ext = os.path.basename(node.attrib.get('file'))
00145 incl = os.path.splitext(incl_ext)[0]
00146 node_includes.append(incl)
00147 if node.tag == "node":
00148 node_type = node.attrib.get('type')
00149 if node_type == "nodelet":
00150 node_type = node.attrib.get('args')
00151 if len(node_type.split())>1:
00152 node_type = node_type.split()[1]
00153 if node_type[len(node_type)-1] == ")":
00154 node_type = nodelet_args
00155 node_nodes.append(node_type)
00156 else:
00157 node_nodes.append(node_type)
00158
00159 write_information(g,node_name,node_args)
00160 write_information(g,node_nodes)
00161 for dest in node_nodes:
00162 write_connection(g,node_name,dest,True)
00163 for dest in node_includes:
00164 write_connection(g,node_name,dest)
00165 except ElementTree.ParseError:
00166 rospy.loginfo("Error parsing {}".format(filepath))
00167 return g
00168 else:
00169 rospy.loginfo("[W]\tNo launchfile was found in {0}".format(os.path.basename(folder)))
00170
00171 def xml_process(packages = 0):
00172 g = prepare_file()
00173 if not packages:
00174 working_directory = os.getcwd();
00175 draw_folder(g,working_directory)
00176 else:
00177 for pkg in packages:
00178 directory = roslib.packages.get_pkg_dir(pkg)
00179 draw_folder(g,directory)
00180 return g
00181
00182 def main():
00183 args = get_args_from_parser()
00184 g = xml_process(args.pkg)
00185 if show_graph(g):
00186 gtk.main()
00187
00188 if __name__ == "__main__":
00189 main()