#!/bin/sh #- # Copyright (c) 2012 Devin Teske # All Rights Reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $FreeBSD$ # ############################################################ INCLUDES BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="dot" f_include_lang $BSDCFG_LIBE/include/messages.subr f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr ipgm=$( f_index_menu_selection $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ) [ $? -eq $SUCCESS -a "$ipgm" ] && pgm="$ipgm" ############################################################ CONFIGURATION # # Locataion of bsdconfig(8) # BSDCONFIG=/usr/sbin/bsdconfig ############################################################ GLOBALS # # Options # SHOW_GRAPH_LABEL_DATE=1 SHOW_INCLUDES=1 SHOW_CMDLINE=1 ############################################################ FUNCTIONS # begin_nodelist $shape $color $fillcolor $style # # Create a new multi-node list rendering nodes in a specific style described by # the arguments passed. # begin_nodelist() { local shape="$1" color="$2" fillcolor="$3" style="$4" printf "\tnode [\n" [ "$shape" ] && printf '\t\tshape = "%s",\n' "$shape" [ "$color" ] && printf '\t\tcolor = "%s",\n' "$color" [ "$fillcolor" ] && printf '\t\tfillcolor = "%s",\n' "$fillcolor" [ "$style" ] && printf '\t\tstyle = "%s",\n' "$style" printf "\t] {\n" } # print_node $node [$attributes ...] # # Print a node within a multi-node list. # print_node() { local node="$1" shift 1 # node case "$node" in edge) printf '\t\t%s' "$node";; *) printf '\t\t"%s"' "$node";; esac if [ $# -gt 0 ]; then echo -n ' [' while [ $# -gt 0 ]; do printf " %s" "$1" shift 1 [ $# -gt 0 ] && echo -n "," done echo -n " ]" fi echo ";" } # print_node2 $node $node [$attributes ...] # # Print a directed node-node connection within a multi-node list. # print_node2() { local node1="$1" node2="$2" shift 2 # node1 node2 printf '\t\t"%s" -> "%s"' "$node1" "$node2" if [ $# -gt 0 ]; then echo -n ' [' while [ $# -gt 0 ]; do printf " %s" "$1" shift 1 [ $# -gt 0 ] && echo -n "," done echo -n " ]" fi echo ";" } # end_nodelist # # Close a multi-node list. # end_nodelist() { printf "\t};\n" } ############################################################ MAIN # Incorporate rc-file if it exists [ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc" # # Process command-line arguments # while getopts cdhi flag; do case "$flag" in i) SHOW_INCLUDES=;; d) SHOW_GRAPH_LABEL_DATE=;; c) SHOW_CMDLINE=;; h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm";; esac done shift $(( $OPTIND - 1 )) cd $BSDCFG_LIBE || f_die 1 "$msg_directory_not_found" "$BSDCFG_LIB" # # Get a list of menu programs # menu_program_list= for file in [0-9][0-9][0-9].*/INDEX; do menu_program_list="$menu_program_list $( tail -r "$file" | awk -v item="${file%%/*}" ' /^[[:space:]]*menu_program="/ { sub(/^.*="/, "") sub(/"$/, "") if ( ! $0 ) next if ( $0 !~ "^/" ) sub(/^/, item "/") print; exit }' )" done # # Get a list of submenu programs # submenu_program_list= for menu_program in $menu_program_list; do case "$menu_program" in [0-9][0-9][0-9].*/*) : fall-through ;; *) continue # No sub-menus we can process esac submenu_program_list="$submenu_program_list $( awk -v menu_program="$menu_program" \ -v item="${menu_program%%/*}" \ ' /^menu_selection="/ { sub(/.*\|/, "") sub(/"$/, "") if ( ! $0 ) next if ( $0 !~ "^/" ) sub(/^/, item "/") if ( $0 == menu_program ) next print } ' "${menu_program%%/*}/INDEX" )" done # # Get a list of command-line programs # cmd_program_list= for file in */INDEX; do cmd_program_list="$cmd_program_list $( awk -v item="${file%%/*}" ' /^menu_selection="/ { sub(/.*\|/, "") sub(/"$/, "") if ( ! $0 ) next if ( $0 !~ "^/" ) sub(/^/, item "/") print } ' $file )" done # # [Optionally] Calculate list of include files # if [ "$SHOW_INCLUDES" ]; then print_includes_awk=' BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" } ( $0 ~ regex ) { sub(regex, ""); print } ' # END-QUOTE # # Build list of files in which to search for includes # file_list=$( for file in \ $BSDCONFIG \ $menu_program_list \ $submenu_program_list \ $cmd_program_list \ ; do [ -e "$file" ] && echo $file done | sort -u ) # # Build list of includes used by the above files # include_file_list= for file in $file_list; do include_file_list="$include_file_list $( awk -v file="$file" -v item="${file%%/*}" \ "$print_includes_awk" $file )" done # # Sort the list of includes and remove duplicate entries # include_file_list=$( for include_file in $include_file_list; do echo "$include_file" done | sort -u ) # # Search previously-discovered include files for further includes # for file in $include_file_list; do include_file_list="$include_file_list $( awk -v file="$file" -v item="${file%%/*}" \ "$print_includes_awk" $BSDCFG_SHARE/$file )" done # # Sort the list of includes and remove duplicate entries [again] # include_file_list=$( for include_file in $include_file_list; do echo "$include_file" done | sort -u ) fi # # Start the directional-graph (digraph) output # printf 'strict digraph "" { // Empty name to prevent SVG Auto-Tooltip\n' label_format="$msg_graph_label_with_command" [ "$SHOW_GRAPH_LABEL_DATE" ] && label_format="$msg_graph_label_with_command_and_date" lang="${LANG:-$LC_ALL}" printf "\n\tlabel = \"$label_format\"\n" \ "${lang:+LANG=${lang%%[$IFS]*} }bsdconfig $pgm${ARGV:+ $ARGV}" \ "$( date +"%c %Z" )" # # Print graph-specific properties # printf '\n\t/*\n\t * Graph setup and orientation\n\t */\n' printf '\tlabelloc = top;\t\t// display above label at top of graph\n' printf '\trankdir = LR;\t\t// create ranks left-to-right\n' printf '\torientation = portrait;\t// default\n' printf '\tratio = fill;\t\t// approximate aspect ratio\n' printf '\tcenter = 1;\t\t// center drawing on page\n' # # Perform edge-concentration when displaying a lot of information # # NOTE: This is disabled because dot version 2.28.0 (current) and older have a # bug that causes a crash when rankdir = LR and concentrate = true # # NOTE: Do not re-enable until said bug is fixed in some future revision. # #[ "$SHOW_INCLUDES" -a "$SHOW_CMDLINE" ] && # printf '\tconcentrate = true;\t// enable edge concentrators\n' # # Print font details for graph/cluster label(s) # printf '\n\t/*\n\t * Font details for graph/cluster label(s)\n\t */\n' printf '\tfontname = "Times-Italic";\n' printf '\tfontsize = 14;\n' # # Print default node attributes # printf '\n\t/*\n\t * Default node attributes\n\t */\n' printf '\tnode [\n' printf '\t\tfontname = "Times-Roman",\n' printf '\t\tfontsize = 12,\n' printf '\t\twidth = 2.5, // arbitrary minimum width for all nodes\n' printf '\t\tfixedsize, // turn minimum width into exact width\n' printf '\t];\n' # # Print top-level item(s) # printf '\n\t/*\n\t * bsdconfig(8)\n\t */\n' shape=circle color=black fillcolor=yellow style=filled begin_nodelist "$shape" "$color" "$fillcolor" "$style" print_node "bsdconfig" "fontname = \"Times-Bold\"" "fontsize = 16" end_nodelist # # Print menus # printf '\n\t/*\n\t * Menu items\n\t */\n' shape=box color=black fillcolor=lightblue style=filled begin_nodelist "$shape" "$color" "$fillcolor" "$style" for menu_program in $menu_program_list; do print_node "$menu_program" "label = \"${menu_program#*/}\"" done end_nodelist # # Print sub-menus # printf '\n\t/*\n\t * Sub-menu items\n\t */\n' shape=box color=black fillcolor=lightblue style=filled begin_nodelist "$shape" "$color" "$fillcolor" "$style" for submenu_program in $submenu_program_list; do print_node "$submenu_program" "label = \"${submenu_program#*/}\"" done end_nodelist # # Print menu relationships # printf '\n\t/*\n\t * Menu item relationships\n\t */\n' shape=box color=black fillcolor=lightblue style=filled edge_color=blue begin_nodelist "$shape" "$color" "$fillcolor" "$style" print_node edge "penwidth = 5.0" "style = bold" "color = $edge_color" for menu_program in $menu_program_list; do print_node2 "bsdconfig" "$menu_program" done end_nodelist # # Print sub-menu relationships # printf '\n\t/*\n\t * Sub-menu item relationships\n\t */\n' shape=box color=black fillcolor=lightblue style=filled edge_color=blue begin_nodelist "$shape" "$color" "$fillcolor" "$style" # Lock sub-menu headport to the West (unless `-c' was passed) [ "$SHOW_CMDLINE" -o ! "$SHOW_INCLUDES" ] && print_node edge "headport = w" print_node edge "style = bold" "color = $edge_color" for submenu_program in $submenu_program_list; do for menu_program in $menu_program_list; do case "$menu_program" in [0-9][0-9][0-9].*/*) : fall-through ;; *) continue # Not a menu item esac # Continue if program directories do not match [ "${menu_program%%/*}" = "${submenu_program%%/*}" ] || continue print_node2 "$menu_program" "$submenu_program" break done done end_nodelist # # [Optionally] Print include files # if [ "$SHOW_INCLUDES" ]; then printf '\n\t/*\n\t * Include files\n\t */\n' shape=oval color=black fillcolor=white style=filled begin_nodelist "$shape" "$color" "$fillcolor" "$style" printf '\t\tconstraint = false;\n' for include_file in $include_file_list; do print_node "$include_file" \ "label = \"${include_file##*/}\"" done end_nodelist fi # # [Optionally] Print f_include() usage/relationships # if [ "$SHOW_INCLUDES" ]; then printf '\n\t/*\n\t * Include usage\n\t */\n' shape=oval color=black fillcolor=white style=filled edge_color=grey begin_nodelist "$shape" "$color" "$fillcolor" "$style" print_node edge "style = dashed" "color = $edge_color" print_node edge "label = \"\\T\"" "fontsize = 9" file_list=$( for file in \ $BSDCONFIG \ $menu_program_list \ $submenu_program_list \ $cmd_program_list \ $include_file_list \ ; do [ -f "$BSDCFG_SHARE/$file" ] && echo $BSDCFG_SHARE/$file [ -e "$file" ] && echo $file done | sort -u ) for file in $file_list; do # Skip binary files and text files that don't use f_include() grep -qlI f_include $file || continue awk \ -v file="${file#$BSDCFG_SHARE/}" \ -v bsdconfig="$BSDCONFIG" \ ' BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" } ( $0 ~ regex ) { sub(regex, "") if ( file == bsdconfig ) sub(".*/", "", file) printf "\t\t\"%s\" -> \"%s\";\n", $0, file } ' $file done | sort end_nodelist fi # # Print command-line shortcuts # if [ "$SHOW_CMDLINE" ]; then printf '\n\t/*\n\t * Command-line shortcuts\n\t */\n' shape=parallelogram color=black fillcolor=lightseagreen style=filled begin_nodelist "$shape" "$color" "$fillcolor" "$style" for file in */INDEX; do awk -v item="${file%%/*}" ' /^menu_selection="/ { sub(/^.*="/, "") sub(/\|.*/, "") printf "\t\t\"bsdconfig %s\"", $0 printf " [ label = \"%s\" ];\n", $0 } ' $file done end_nodelist fi # # Print command-line shortcut relationships # if [ "$SHOW_CMDLINE" ]; then printf '\n\t/*\n\t * Command-line shortcut relationships\n\t */\n' shape=box color=black fillcolor=lightseagreen style=filled begin_nodelist "$shape" "$color" "$fillcolor" "$style" print_node edge "headport = w" "weight = 100.0" print_node edge "style = bold" "color = $fillcolor" for file in */INDEX; do awk -v item="${file%%/*}" \ -v node_fillcolor="$node_fillcolor" \ -v edge_color="$edge_color" \ ' /^menu_selection="/ { sub(/^.*="/, "") sub(/"$/, "") if ( ! $0 ) next split($0, menusel, "|") if ( menusel[2] !~ "^/" ) sub(/^/, item "/", menusel[2]) printf "\t\t\"bsdconfig %s\" -> \"%s\";\n", menusel[1], menusel[2] } ' $file done end_nodelist fi # # Print clusters # bgcolor_bsdconfig="lightyellow" bgcolor_includes="gray98" bgcolor_menuitem="aliceblue" bgcolor_shortcuts="honeydew" printf '\n\t/*\n\t * Clusters\n\t */\n' printf '\tsubgraph "cluster_bsdconfig" {\n' printf '\t\tbgcolor = "%s";\n' "$bgcolor_bsdconfig" printf '\t\tlabel = "bsdconfig(8)";\n' printf '\t\ttooltip = "bsdconfig(8)";\n' print_node "bsdconfig" if [ "$SHOW_INCLUDES" ]; then printf '\t\tsubgraph "cluster_includes" {\n' printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_includes" printf '\t\t\tlabel = "%s";\n' "$msg_includes" for include_file in $include_file_list; do echo $include_file done | awk -v bgcolor="$bgcolor_bsdconfig" ' BEGIN { created = 0 } function end_subgraph() { printf "\t\t\t};\n" } ( $0 !~ "/" ) { if ( ! created ) { printf "\t\t\tsubgraph \"%s\" {\n", "cluster_bsdconfig_includes" printf "\t\t\t\tbgcolor = \"%s\";\n", bgcolor printf "\t\t\t\tlabel = \"bsdconfig\";\n" created++ } printf "\t\t\t\t\"%s\";\n", $1 } END { created && end_subgraph() }' for include_file in $include_file_list; do echo $include_file done | awk ' BEGIN { created = 0 } function end_subgraph() { printf "\t\t\t};\n" } ( $0 ~ "/" ) { include_dir_tmp = $1 sub("/[^/]*$", "", include_dir_tmp) gsub(/[^[:alnum:]_]/, "_", include_dir_tmp) if ( created && include_dir != include_dir_tmp ) { end_subgraph() created = 0 } if ( ! created ) { include_dir = include_dir_tmp printf "\t\t\tsubgraph \"cluster_%s_includes\" {\n", include_dir printf "\t\t\t\tbgcolor = \"white\";\n" printf "\t\t\t\tlabel = \"%s\";\n", include_dir created++ } printf "\t\t\t\t\"%s\";\n", $1 } END { created && end_subgraph() }' printf '\t\t};\n' fi end_nodelist for INDEX in */INDEX; do menu_title= menu_help= f_include_lang "$INDEX" item="${INDEX%%/*}" printf '\tsubgraph "cluster_%s" {\n' "$item" case "$item" in [0-9][0-9][0-9].*) bgcolor="$bgcolor_menuitem";; *) bgcolor="$bgcolor_shortcuts" esac printf '\t\tbgcolor = "%s";\n' "$bgcolor" if [ "$menu_title" ]; then printf '\t\tlabel = "%s\\n\\"%s\\"";\n' "$item" "$menu_title" else printf '\t\tlabel = "%s";\n' "$item" fi printf '\t\ttooltip = "%s";\n' "${menu_help:-$item}" program_list=$( for program in \ $menu_program_list \ $submenu_program_list \ $cmd_program_list \ ; do echo "$program" done | sort -u ) for program in $program_list; do case "$program" in "$item"/*) print_node "$program" "label = \"${program#*/}\"" esac done if [ "$SHOW_INCLUDES" ]; then item_include_list= [ -d "$item/include" ] && item_include_list=$( find "$item/include" -type f ) item_include_list=$( for item_include in $item_include_list; do for include_file in $include_file_list; do [ "$item_include" = "$include_file" ] || continue echo "$item_include"; break done done ) if [ "$item_include_list" ]; then printf '\t\tsubgraph "cluster_%s_includes" {\n' "$item" printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_includes" printf '\t\t\tlabel = "%s";\n' "$msg_includes" fi for item_include in $item_include_list; do printf '\t\t\t"%s";\n' "$item_include" done [ "$item_include_list" ] && printf '\t\t};\n' fi if [ "$SHOW_CMDLINE" ]; then printf '\t\tsubgraph "cluster_%s_shortcuts" {\n' "$item" printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_shortcuts" printf '\t\t\tlabel = "%s";\n' "$msg_shortcuts" awk '/^menu_selection="/ { sub(/^.*="/, "") sub(/\|.*/, "") printf "\t\t\t\"bsdconfig %s\";\n", $0 }' "$INDEX" printf '\t\t};\n' fi end_nodelist done printf '\n};\n' ################################################################################ # END ################################################################################