Skip to content

Instantly share code, notes, and snippets.

@b-adkins
Last active December 21, 2015 21:29
Show Gist options
  • Save b-adkins/6368918 to your computer and use it in GitHub Desktop.
Save b-adkins/6368918 to your computer and use it in GitHub Desktop.
Script that generates include graphs from Robot Operating System .launch files.
digraph graphname {
"sim_stage.launch" -> "environment_stage.launch";
"sim_stage.launch" -> "sim_to_map_stage.launch";
"navigation_stage.launch" -> "asdf.launch";
"nav_stack_stage.launch" -> "navigation_stage.launch";
"nav_stack_stage.launch" -> "tf_robot_base2rel_teleop_wasd.launch";
"environment_stage.launch" -> "asdf_node.xml";
"environment_stage.launch" -> "move_base_stage.xml";
"run_all_stage.launch" -> "sim.launch";
"run_all_stage.launch" -> "sim_stage.launch";
"run_all_stage.launch" -> "nav_stack_stage.launch";
"run_all_stage.launch" -> "teleop_keyboard.launch";
"run_all_stage.launch" -> "view_sim.launch";
"tf_robot_base2rel.launch" -> "kinect.launch";
"tf_robot_base2rel.launch" -> "tf_base_link_to_laser.launch";
"tf_robot_base2rel_teleop_wasd.launch" -> "teleop_wasd.launch";
"tf_robot_base2rel_teleop_wasd.launch" -> "tf_robot_base2rel.launch";
}
Display the source blob
Display the rendered blob
Raw
#!/bin/bash
##
# Generates dot graphs launch files/directory given.
#
# @author Bea Adkins
# @date 08-27-2013
#
## Default file regex.
DEFAULT_PATTERN='.*'
usage()
{
echo "Generates include graphs from ROS .launch files."
echo "USAGE: $0 [flags] package path(s)"
echo "Parameters:"
echo " package ROS package."
echo " path File/directory path relative to package. File patterns"
echo " supported (e.g. adsf.launch, *, run*.launch)."
echo "Flags:"
echo " -f Forces $0 to overwrite outfile."
echo " -o outfile Name of output file."
echo " -e pattern Regex (in quotes) to restrict matching files. Default: '$DEFAULT_PATTERN'."
}
#
# Parse arguments
#
# Default values
OUT_FILE=graph.pdf
PATTERN=$DEFAULT_PATTERN
FORCE=false
# Parse flags
while getopts o:e:f option; do
case $option in
f) FORCE=true;;
o) OUT_FILE=$OPTARG;;
e) PATTERN=$OPTARG;;
\?) echo "Invalid option: -$OPTARG." >&2; exit 1;;
:) echo "Option -$OPTARG requires an argument." >&2; exit 1;;
esac
done
# Shift flags out of argument list
shift $(expr $OPTIND - 1)
# Insufficient positional arguments
if [ $# -lt 2 ]; then
usage
exit 0
fi
#
# Determine paths.
#
# Find absolute package path
PACKAGE=$1
PACKAGE_ROOT=$(rospack find $PACKAGE)
# Abort if not found. Assumes rospack prints its own error message.
if [ -z "$PACKAGE_ROOT" ]; then
exit 1
fi
# Get file pattern.
LAUNCH_PATH=$PACKAGE_ROOT/$2
# Prepare scratch files
TMP_PATH=/tmp/$(basename $OUT_FILE).dot
TMP_PATH_2=/tmp/$(basename $OUT_FILE)_uniq.dot
rm -f $TMP_PATH $TMP_PATH_2 # Silently clean up, if needed.
#
# Find .launch files.
#
# File exists and is directory, start with files matching pattern in directory
if [ -d "$LAUNCH_PATH" ]; then
FILES=$(ls -d -1 $LAUNCH_PATH/*.* | grep "$PATTERN")
# File pattern/file.
else
FILES=$(echo "$LAUNCH_PATH" | grep "$PATTERN")
fi
# Debugging for argument/file path parsing.
# echo "Package: $PACKAGE"
# echo "Package root: $PACKAGE_ROOT"
# echo "Launch path: $LAUNCH_PATH"
# echo "Output file: $OUT_FILE"
# echo "Pattern: $PATTERN"
# echo "Files:"
# for FILE in $FILES; do
# echo "$FILE"
# done
# exit 0
# No matching files found, fail.
if [ -z "$FILES" ]; then
# Generate error message.
MSG="No files found matching '$LAUNCH_PATH'"
# if [ "$PATTERN" != "$DEFAULT_PATTERN" ]; then
MSG="$MSG and pattern '$PATTERN'"
# fi
MSG="$MSG."
echo $MSG
exit 1
fi
# Output file exists, abort if not forced.
if ! $FORCE && [ -f $OUT_FILE ]; then
echo "Output file '$OUT_FILE' exists!"
exit 1
fi
#
# Recursively outputs the includes of this file and its children as DOT graph edges.
#
# @param ROS package of file. Only used for printing.
# @param Single absolute file path.
# @param Output file.
#
extract_dot_edges()
{
#
# Check parameters
#
# Needs one parameter
if [ $# -lt 3 ]; then
echo "Insufficient parameters. Given:$#. Needed: 3. Parameters: $@"
return
fi
# Parse parameters, executing file path.
PACKAGE_NAME=$1
FILE_PATH="$2"
OUT_PATH=$3
# File doesn't exist, return empty
if [ ! -f $FILE_PATH ]; then
return
fi
#
# Extract includes.
#
# 1) grep extracts <include> tags
# 2) sed strips <!-- XML comments -->
# 3) sed extracts 'file' attribute of <include> tags
# 4) sed gets package from "find" commands, placing it in front of the path
#
INCLUDES=$(grep "<include.*>" $FILE_PATH | sed -ne '/<!--/ { :c; /-->/! { N; b c; }; /-->/s/<!--.*-->//g }; /^ *$/!p;' | sed 's/ *<include\( \+.*\)* \+file=["\x27]\(.*\)["\x27]\( \+.*\)*\/*>/\2/' | sed 's!\$(find \([a-zA-Z0-9_]\+\))[/\\]!\1::!g' )
# Dot edges: parent -> child
for INCLUDE in $INCLUDES; do
echo " \"$PACKAGE_NAME::$(basename $FILE_PATH)\" -> \"$(basename $INCLUDE)\";" >> "$OUT_PATH"
done
# Escape condition: no includes found
if [ -z "$INCLUDES" ]; then
return
fi
# For each include recursively call self
for INCLUDE in $INCLUDES; do
INCLUDE_TOK=$(echo $INCLUDE | tr ':' ' ') # Tokenize include by ':'
INCLUDE_PACKAGE_NAME=$(echo $INCLUDE_TOK | cut -f1 -d\ -)
INCLUDE_PATH=$(rospack find $INCLUDE_PACKAGE_NAME)/$(echo $INCLUDE_TOK | cut -f2 -d\ -)
extract_dot_edges "$INCLUDE_PACKAGE_NAME" "$INCLUDE_PATH" "$OUT_PATH"
done
}
# Start the traversing the tree(s).
for FILE in $FILES; do
extract_dot_edges $PACKAGE $FILE $TMP_PATH
done
# Remove duplicates
sort -u $TMP_PATH > $TMP_PATH_2
#
# Print graph boilerplate
#
echo 'digraph graphname {' > $TMP_PATH
echo '}' | cat $TMP_PATH_2 - >> $TMP_PATH
# Generate PDF
dot -Tpdf $TMP_PATH -o $OUT_FILE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment