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