dot revision 260678
1#!/bin/sh 2#- 3# Copyright (c) 2012-2013 Devin Teske 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26# 27# $FreeBSD: stable/10/usr.sbin/bsdconfig/dot/dot 260678 2014-01-15 07:49:17Z dteske $ 28# 29############################################################ INCLUDES 30 31# Prevent common.subr from auto initializing debugging (this is not an inter- 32# active utility that requires debugging; also `-d' has been repurposed). 33# 34DEBUG_SELF_INITIALIZE=NO 35 36BSDCFG_SHARE="/usr/share/bsdconfig" 37. $BSDCFG_SHARE/common.subr || exit 1 38f_dprintf "%s: loading includes..." "$0" 39 40BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="dot" 41f_include_lang $BSDCFG_LIBE/include/messages.subr 42f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr 43 44f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm && 45 pgm="${ipgm:-$pgm}" 46 47############################################################ CONFIGURATION 48 49# 50# Location of bsdconfig(8) 51# 52BSDCONFIG=/usr/sbin/bsdconfig 53 54############################################################ GLOBALS 55 56# 57# Options 58# 59SHOW_GRAPH_LABEL_DATE=1 60SHOW_INCLUDES=1 61SHOW_CMDLINE=1 62 63############################################################ FUNCTIONS 64 65# begin_nodelist $shape $color $fillcolor $style 66# 67# Create a new multi-node list rendering nodes in a specific style described by 68# the arguments passed. 69# 70begin_nodelist() 71{ 72 local shape="$1" color="$2" fillcolor="$3" style="$4" 73 74 printf "\tnode [\n" 75 [ "$shape" ] && 76 printf '\t\tshape = "%s",\n' "$shape" 77 [ "$color" ] && 78 printf '\t\tcolor = "%s",\n' "$color" 79 [ "$fillcolor" ] && 80 printf '\t\tfillcolor = "%s",\n' "$fillcolor" 81 [ "$style" ] && 82 printf '\t\tstyle = "%s",\n' "$style" 83 printf "\t] {\n" 84} 85 86# print_node $node [$attributes ...] 87# 88# Print a node within a multi-node list. 89# 90print_node() 91{ 92 local node="$1" 93 94 shift 1 # node 95 96 case "$node" in 97 edge) printf '\t\t%s' "$node" ;; 98 *) printf '\t\t"%s"' "$node" ;; 99 esac 100 101 if [ $# -gt 0 ]; then 102 echo -n ' [' 103 while [ $# -gt 0 ]; do 104 printf " %s" "$1" 105 shift 1 106 [ $# -gt 0 ] && echo -n "," 107 done 108 echo -n " ]" 109 fi 110 111 echo ";" 112} 113 114# print_node2 $node $node [$attributes ...] 115# 116# Print a directed node-node connection within a multi-node list. 117# 118print_node2() 119{ 120 local node1="$1" node2="$2" 121 122 shift 2 # node1 node2 123 124 printf '\t\t"%s" -> "%s"' "$node1" "$node2" 125 126 if [ $# -gt 0 ]; then 127 echo -n ' [' 128 while [ $# -gt 0 ]; do 129 printf " %s" "$1" 130 shift 1 131 [ $# -gt 0 ] && echo -n "," 132 done 133 echo -n " ]" 134 fi 135 136 echo ";" 137} 138 139# end_nodelist 140# 141# Close a multi-node list. 142# 143end_nodelist() 144{ 145 printf "\t};\n" 146} 147 148############################################################ MAIN 149 150# Incorporate rc-file if it exists 151[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc" 152 153# 154# Process command-line arguments 155# 156while getopts cdhi flag; do 157 case "$flag" in 158 i) SHOW_INCLUDES= ;; 159 d) SHOW_GRAPH_LABEL_DATE= ;; 160 c) SHOW_CMDLINE= ;; 161 h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;; 162 esac 163done 164shift $(( $OPTIND - 1 )) 165 166cd $BSDCFG_LIBE || f_die # Pedantic 167 168# 169# Get a list of menu programs 170# 171menu_program_list= 172for file in [0-9][0-9][0-9].*/INDEX; do 173 menu_program_list="$menu_program_list $( 174 tail -r "$file" | awk -v item="${file%%/*}" ' 175 /^[[:space:]]*menu_program="/ { 176 sub(/^.*="/, "") 177 sub(/"$/, "") 178 if ( ! $0 ) next 179 if ( $0 !~ "^/" ) sub(/^/, item "/") 180 print; exit 181 }' 182 )" 183done 184 185# 186# Get a list of submenu programs 187# 188submenu_program_list= 189for menu_program in $menu_program_list; do 190 case "$menu_program" in 191 [0-9][0-9][0-9].*/*) : fall-through ;; 192 *) continue # No sub-menus we can process 193 esac 194 195 submenu_program_list="$submenu_program_list $( 196 awk -v menu_program="$menu_program" \ 197 -v item="${menu_program%%/*}" \ 198 ' 199 /^menu_selection="/ { 200 sub(/.*\|/, "") 201 sub(/"$/, "") 202 if ( ! $0 ) next 203 if ( $0 !~ "^/" ) 204 sub(/^/, item "/") 205 if ( $0 == menu_program ) next 206 print 207 } 208 ' "${menu_program%%/*}/INDEX" 209 )" 210done 211 212# 213# Get a list of command-line programs 214# 215cmd_program_list= 216for file in */INDEX; do 217 cmd_program_list="$cmd_program_list $( 218 awk -v item="${file%%/*}" ' 219 /^menu_selection="/ { 220 sub(/.*\|/, "") 221 sub(/"$/, "") 222 223 if ( ! $0 ) next 224 225 if ( $0 !~ "^/" ) 226 sub(/^/, item "/") 227 228 print 229 } 230 ' $file 231 )" 232done 233 234# 235# [Optionally] Calculate list of include files 236# 237if [ "$SHOW_INCLUDES" ]; then 238 print_includes_awk=' 239 BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" } 240 ( $0 ~ regex ) { sub(regex, ""); print } 241 ' # END-QUOTE 242 243 # 244 # Build list of files in which to search for includes 245 # 246 file_list=$( 247 for file in \ 248 $BSDCONFIG \ 249 $menu_program_list \ 250 $submenu_program_list \ 251 $cmd_program_list \ 252 ; do 253 [ -e "$file" ] && echo $file 254 done | sort -u 255 ) 256 257 # 258 # Build list of includes used by the above files 259 # 260 include_file_list= 261 for file in $file_list; do 262 include_file_list="$include_file_list $( 263 awk "$print_includes_awk" $file 264 )" 265 done 266 267 # 268 # Sort the list of includes and remove duplicate entries 269 # 270 include_file_list=$( 271 for include_file in $include_file_list; do 272 echo "$include_file" 273 done | sort -u 274 ) 275 276 # 277 # Search previously-discovered include files for further includes 278 # 279 before="$include_file_list" 280 while :; do 281 for file in $include_file_list; do 282 include_file_list="$include_file_list $( 283 awk "$print_includes_awk" $BSDCFG_SHARE/$file 284 )" 285 done 286 287 # 288 # Sort list of includes and remove duplicate entries [again] 289 # 290 include_file_list=$( 291 for include_file in $include_file_list; do 292 echo "$include_file" 293 done | sort -u 294 ) 295 296 [ "$include_file_list" = "$before" ] && break 297 before="$include_file_list" 298 done 299fi 300 301# 302# Add script.subr to includes if it exists 303# 304[ -f $BSDCFG_SHARE/script.subr ] && 305 include_file_list="$include_file_list script.subr" 306 307# 308# Start the directional-graph (digraph) output 309# 310printf 'strict digraph "" { // Empty name to prevent SVG Auto-Tooltip\n' 311label_format="$msg_graph_label_with_command" 312[ "$SHOW_GRAPH_LABEL_DATE" ] && 313 label_format="$msg_graph_label_with_command_and_date" 314lang="${LANG:-$LC_ALL}" 315printf "\n\tlabel = \"$label_format\"\n" \ 316 "${lang:+LANG=${lang%%[$IFS]*} }bsdconfig $pgm${ARGV:+ $ARGV}" \ 317 "$( date +"%c %Z" )" 318 319# 320# Print graph-specific properties 321# 322printf '\n\t/*\n\t * Graph setup and orientation\n\t */\n' 323printf '\tlabelloc = top;\t\t// display above label at top of graph\n' 324printf '\trankdir = LR;\t\t// create ranks left-to-right\n' 325printf '\torientation = portrait;\t// default\n' 326printf '\tratio = fill;\t\t// approximate aspect ratio\n' 327printf '\tcenter = 1;\t\t// center drawing on page\n' 328 329# 330# Perform edge-concentration when displaying a lot of information 331# 332# NOTE: This is disabled because dot(1) version 2.28.0 (current) and older have 333# a bug that causes a crash when rankdir = LR and concentrate = true 334# 335# NOTE: Do not re-enable until said bug is fixed in some future revision. 336# 337#[ "$SHOW_INCLUDES" -a "$SHOW_CMDLINE" ] && 338# printf '\tconcentrate = true;\t// enable edge concentrators\n' 339 340# 341# Print font details for graph/cluster label(s) 342# 343printf '\n\t/*\n\t * Font details for graph/cluster label(s)\n\t */\n' 344printf '\tfontname = "Times-Italic";\n' 345printf '\tfontsize = 14;\n' 346 347# 348# Print default node attributes 349# 350printf '\n\t/*\n\t * Default node attributes\n\t */\n' 351printf '\tnode [\n' 352printf '\t\tfontname = "Times-Roman",\n' 353printf '\t\tfontsize = 12,\n' 354printf '\t\twidth = 2.5, // arbitrary minimum width for all nodes\n' 355printf '\t\tfixedsize, // turn minimum width into exact width\n' 356printf '\t];\n' 357 358# 359# Print top-level item(s) 360# 361printf '\n\t/*\n\t * bsdconfig(8)\n\t */\n' 362shape=circle color=black fillcolor=yellow style=filled 363begin_nodelist "$shape" "$color" "$fillcolor" "$style" 364print_node "bsdconfig" "fontname = \"Times-Bold\"" "fontsize = 16" 365end_nodelist 366 367# 368# Print menus 369# 370printf '\n\t/*\n\t * Menu items\n\t */\n' 371shape=box color=black fillcolor=lightblue style=filled 372begin_nodelist "$shape" "$color" "$fillcolor" "$style" 373for menu_program in $menu_program_list; do 374 print_node "$menu_program" "label = \"${menu_program#*/}\"" 375done 376end_nodelist 377 378# 379# Print sub-menus 380# 381printf '\n\t/*\n\t * Sub-menu items\n\t */\n' 382shape=box color=black fillcolor=lightblue style=filled 383begin_nodelist "$shape" "$color" "$fillcolor" "$style" 384for submenu_program in $submenu_program_list; do 385 print_node "$submenu_program" "label = \"${submenu_program#*/}\"" 386done 387end_nodelist 388 389# 390# Print menu relationships 391# 392printf '\n\t/*\n\t * Menu item relationships\n\t */\n' 393shape=box color=black fillcolor=lightblue style=filled edge_color=blue 394begin_nodelist "$shape" "$color" "$fillcolor" "$style" 395print_node edge "penwidth = 5.0" "style = bold" "color = $edge_color" 396for menu_program in $menu_program_list; do 397 print_node2 "bsdconfig" "$menu_program" 398done 399end_nodelist 400 401# 402# Print sub-menu relationships 403# 404printf '\n\t/*\n\t * Sub-menu item relationships\n\t */\n' 405shape=box color=black fillcolor=lightblue style=filled edge_color=blue 406begin_nodelist "$shape" "$color" "$fillcolor" "$style" 407# Lock sub-menu headport to the West (unless `-c' was passed) 408[ "$SHOW_CMDLINE" -o ! "$SHOW_INCLUDES" ] && print_node edge "headport = w" 409print_node edge "style = bold" "color = $edge_color" 410for submenu_program in $submenu_program_list; do 411 for menu_program in $menu_program_list; do 412 case "$menu_program" in 413 [0-9][0-9][0-9].*/*) : fall-through ;; 414 *) continue # Not a menu item 415 esac 416 417 # Continue if program directories do not match 418 [ "${menu_program%%/*}" = "${submenu_program%%/*}" ] || 419 continue 420 421 print_node2 "$menu_program" "$submenu_program" 422 break 423 done 424done 425end_nodelist 426 427# 428# [Optionally] Print include files 429# 430if [ "$SHOW_INCLUDES" ]; then 431 printf '\n\t/*\n\t * Include files\n\t */\n' 432 shape=oval color=black fillcolor=white style=filled 433 begin_nodelist "$shape" "$color" "$fillcolor" "$style" 434 printf '\t\tconstraint = false;\n' 435 for include_file in $include_file_list; do 436 print_node "$include_file" \ 437 "label = \"${include_file##*/}\"" 438 done 439 end_nodelist 440fi 441 442# 443# [Optionally] Print f_include() usage/relationships 444# 445if [ "$SHOW_INCLUDES" ]; then 446 printf '\n\t/*\n\t * Include usage\n\t */\n' 447 shape=oval color=black fillcolor=white style=filled edge_color=grey 448 begin_nodelist "$shape" "$color" "$fillcolor" "$style" 449 print_node edge "style = dashed" "color = $edge_color" 450 #print_node edge "label = \"\\T\"" "fontsize = 9" 451 # NOTE: Edge labels are buggy on large graphs 452 file_list=$( 453 for file in \ 454 $BSDCONFIG \ 455 $menu_program_list \ 456 $submenu_program_list \ 457 $cmd_program_list \ 458 $include_file_list \ 459 ; do 460 [ -f "$BSDCFG_SHARE/$file" ] && 461 echo $BSDCFG_SHARE/$file 462 [ -e "$file" ] && echo $file 463 done | sort -u 464 ) 465 for file in $file_list; do 466 # Skip binary files and text files that don't use f_include() 467 grep -qlI f_include $file || continue 468 469 awk \ 470 -v file="${file#$BSDCFG_SHARE/}" \ 471 -v bsdconfig="$BSDCONFIG" \ 472 ' 473 BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" } 474 ( $0 ~ regex ) { 475 sub(regex, "") 476 if ( file == bsdconfig ) sub(".*/", "", file) 477 printf "\t\t\"%s\" -> \"%s\";\n", $0, file 478 } 479 ' $file 480 done | sort 481 end_nodelist 482fi 483 484# 485# Print command-line shortcuts 486# 487if [ "$SHOW_CMDLINE" ]; then 488 printf '\n\t/*\n\t * Command-line shortcuts\n\t */\n' 489 shape=parallelogram color=black fillcolor=lightseagreen style=filled 490 begin_nodelist "$shape" "$color" "$fillcolor" "$style" 491 for file in */INDEX; do 492 awk -v item="${file%%/*}" ' 493 /^menu_selection="/ { 494 sub(/^.*="/, "") 495 sub(/\|.*/, "") 496 printf "\t\t\"bsdconfig %s\"", $0 497 printf " [ label = \"%s\" ];\n", $0 498 } 499 ' $file 500 done 501 end_nodelist 502fi 503 504# 505# Print command-line shortcut relationships 506# 507if [ "$SHOW_CMDLINE" ]; then 508 printf '\n\t/*\n\t * Command-line shortcut relationships\n\t */\n' 509 shape=box color=black fillcolor=lightseagreen style=filled 510 begin_nodelist "$shape" "$color" "$fillcolor" "$style" 511 print_node edge "headport = w" "weight = 100.0" 512 print_node edge "style = bold" "color = $fillcolor" 513 for file in */INDEX; do 514 awk -v item="${file%%/*}" \ 515 -v node_fillcolor="$node_fillcolor" \ 516 -v edge_color="$edge_color" \ 517 ' 518 /^menu_selection="/ { 519 sub(/^.*="/, "") 520 sub(/"$/, "") 521 522 if ( ! $0 ) next 523 524 split($0, menusel, "|") 525 if ( menusel[2] !~ "^/" ) 526 sub(/^/, item "/", menusel[2]) 527 528 printf "\t\t\"bsdconfig %s\" -> \"%s\";\n", 529 menusel[1], menusel[2] 530 } 531 ' $file 532 done 533 end_nodelist 534fi 535 536# 537# Print clusters 538# 539bgcolor_bsdconfig="lightyellow" 540bgcolor_includes="gray98" 541bgcolor_menuitem="aliceblue" 542bgcolor_shortcuts="honeydew" 543printf '\n\t/*\n\t * Clusters\n\t */\n' 544printf '\tsubgraph "cluster_bsdconfig" {\n' 545printf '\t\tbgcolor = "%s";\n' "$bgcolor_bsdconfig" 546printf '\t\tlabel = "bsdconfig(8)";\n' 547printf '\t\ttooltip = "bsdconfig(8)";\n' 548print_node "bsdconfig" 549if [ "$SHOW_INCLUDES" ]; then 550 printf '\t\tsubgraph "cluster_includes" {\n' 551 printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_includes" 552 printf '\t\t\tlabel = "%s";\n' "$msg_includes" 553 554 for include_file in $include_file_list; do 555 echo $include_file 556 done | awk -v bgcolor="$bgcolor_bsdconfig" ' 557 BEGIN { created = 0 } 558 function end_subgraph() { printf "\t\t\t};\n" } 559 ( $0 !~ "/" ) { 560 if ( ! created ) 561 { 562 printf "\t\t\tsubgraph \"%s\" {\n", 563 "cluster_bsdconfig_includes" 564 printf "\t\t\t\tbgcolor = \"%s\";\n", bgcolor 565 printf "\t\t\t\tlabel = \"bsdconfig\";\n" 566 created++ 567 } 568 printf "\t\t\t\t\"%s\";\n", $1 569 } 570 END { created && end_subgraph() }' 571 572 for include_file in $include_file_list; do 573 echo $include_file 574 done | awk ' 575 BEGIN { created = 0 } 576 function end_subgraph() { printf "\t\t\t};\n" } 577 ( $0 ~ "/" ) { 578 include_dir_tmp = $1 579 sub("/[^/]*$", "", include_dir_tmp) 580 gsub(/[^[:alnum:]_]/, "_", include_dir_tmp) 581 582 if ( created && include_dir != include_dir_tmp ) 583 { 584 end_subgraph() 585 created = 0 586 } 587 588 if ( ! created ) 589 { 590 include_dir = include_dir_tmp 591 printf "\t\t\tsubgraph \"cluster_%s_includes\" {\n", 592 include_dir 593 printf "\t\t\t\tbgcolor = \"white\";\n" 594 printf "\t\t\t\tlabel = \"%s\";\n", include_dir 595 created++ 596 } 597 598 printf "\t\t\t\t\"%s\";\n", $1 599 } 600 END { created && end_subgraph() }' 601 602 printf '\t\t};\n' 603fi 604end_nodelist 605for INDEX in */INDEX; do 606 menu_title= 607 menu_help= 608 f_include_lang "$INDEX" 609 610 item="${INDEX%%/*}" 611 printf '\tsubgraph "cluster_%s" {\n' "$item" 612 613 case "$item" in 614 [0-9][0-9][0-9].*) bgcolor="$bgcolor_menuitem" ;; 615 *) bgcolor="$bgcolor_shortcuts" 616 esac 617 printf '\t\tbgcolor = "%s";\n' "$bgcolor" 618 if [ "$menu_title" ]; then 619 printf '\t\tlabel = "%s\\n\\"%s\\"";\n' "$item" "$menu_title" 620 else 621 printf '\t\tlabel = "%s";\n' "$item" 622 fi 623 printf '\t\ttooltip = "%s";\n' "${menu_help:-$item}" 624 625 program_list=$( 626 for program in \ 627 $menu_program_list \ 628 $submenu_program_list \ 629 $cmd_program_list \ 630 ; do 631 echo "$program" 632 done | sort -u 633 ) 634 for program in $program_list; do 635 case "$program" in "$item"/*) 636 print_node "$program" "label = \"${program#*/}\"" 637 esac 638 done 639 640 if [ "$SHOW_INCLUDES" ]; then 641 item_include_list= 642 [ -d "$item/include" ] && 643 item_include_list=$( find "$item/include" -type f ) 644 item_include_list=$( 645 for item_include in $item_include_list; do 646 for include_file in $include_file_list; do 647 [ "$item_include" = "$include_file" ] || 648 continue 649 echo "$item_include"; break 650 done 651 done 652 ) 653 if [ "$item_include_list" ]; then 654 printf '\t\tsubgraph "cluster_%s_includes" {\n' "$item" 655 printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_includes" 656 printf '\t\t\tlabel = "%s";\n' "$msg_includes" 657 fi 658 for item_include in $item_include_list; do 659 printf '\t\t\t"%s";\n' "$item_include" 660 done 661 [ "$item_include_list" ] && printf '\t\t};\n' 662 fi 663 664 if [ "$SHOW_CMDLINE" ]; then 665 printf '\t\tsubgraph "cluster_%s_shortcuts" {\n' "$item" 666 printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_shortcuts" 667 printf '\t\t\tlabel = "%s";\n' "$msg_shortcuts" 668 awk '/^menu_selection="/ { 669 sub(/^.*="/, "") 670 sub(/\|.*/, "") 671 printf "\t\t\t\"bsdconfig %s\";\n", $0 672 }' "$INDEX" 673 printf '\t\t};\n' 674 fi 675 676 end_nodelist 677done 678 679printf '\n};\n' 680 681################################################################################ 682# END 683################################################################################ 684