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$ 28# 29############################################################ INCLUDES 30 31# Prevent common.subr from auto initializing debugging (this is not an inter- 32# active utility so does not require 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 $BSDCFG_SHARE/script.subr \ 253 ; do 254 [ -e "$file" ] && echo $file 255 done | sort -u 256 ) 257 258 # 259 # Build list of includes used by the above files 260 # 261 include_file_list= 262 for file in $file_list; do 263 include_file_list="$include_file_list $( 264 awk "$print_includes_awk" $file 265 )" 266 done 267 268 # 269 # Sort the list of includes and remove duplicate entries 270 # 271 include_file_list=$( 272 for include_file in $include_file_list; do 273 echo "$include_file" 274 done | sort -u 275 ) 276 277 # 278 # Search previously-discovered include files for further includes 279 # 280 before="$include_file_list" 281 while :; do 282 for file in $include_file_list; do 283 include_file_list="$include_file_list $( 284 awk "$print_includes_awk" $BSDCFG_SHARE/$file 285 )" 286 done 287 288 # 289 # Sort list of includes and remove duplicate entries [again] 290 # 291 include_file_list=$( 292 for include_file in $include_file_list; do 293 echo "$include_file" 294 done | sort -u 295 ) 296 297 [ "$include_file_list" = "$before" ] && break 298 before="$include_file_list" 299 done 300fi 301 302# 303# Start the directional-graph (digraph) output 304# 305printf 'strict digraph "" { // Empty name to prevent SVG Auto-Tooltip\n' 306label_format="$msg_graph_label_with_command" 307[ "$SHOW_GRAPH_LABEL_DATE" ] && 308 label_format="$msg_graph_label_with_command_and_date" 309lang="${LANG:-$LC_ALL}" 310printf "\n\tlabel = \"$label_format\"\n" \ 311 "${lang:+LANG=${lang%%[$IFS]*} }bsdconfig $pgm${ARGV:+ $ARGV}" \ 312 "$( date +"%c %Z" )" 313 314# 315# Print graph-specific properties 316# 317printf '\n\t/*\n\t * Graph setup and orientation\n\t */\n' 318printf '\tlabelloc = top;\t\t// display above label at top of graph\n' 319printf '\trankdir = LR;\t\t// create ranks left-to-right\n' 320printf '\torientation = portrait;\t// default\n' 321printf '\tratio = fill;\t\t// approximate aspect ratio\n' 322printf '\tcenter = 1;\t\t// center drawing on page\n' 323 324# 325# Perform edge-concentration when displaying a lot of information 326# 327# NOTE: This is disabled because dot(1) version 2.28.0 (current) and older have 328# a bug that causes a crash when rankdir = LR and concentrate = true 329# 330# NOTE: Do not re-enable until said bug is fixed in some future revision. 331# 332#[ "$SHOW_INCLUDES" -a "$SHOW_CMDLINE" ] && 333# printf '\tconcentrate = true;\t// enable edge concentrators\n' 334 335# 336# Print font details for graph/cluster label(s) 337# 338printf '\n\t/*\n\t * Font details for graph/cluster label(s)\n\t */\n' 339printf '\tfontname = "Times-Italic";\n' 340printf '\tfontsize = 14;\n' 341 342# 343# Print default node attributes 344# 345printf '\n\t/*\n\t * Default node attributes\n\t */\n' 346printf '\tnode [\n' 347printf '\t\tfontname = "Times-Roman",\n' 348printf '\t\tfontsize = 12,\n' 349printf '\t\twidth = 2.5, // arbitrary minimum width for all nodes\n' 350printf '\t\tfixedsize = true, // turn minimum width into exact width\n' 351printf '\t];\n' 352 353# 354# Print top-level item(s) 355# 356printf '\n\t/*\n\t * bsdconfig(8)\n\t */\n' 357shape=circle color=black fillcolor=yellow style=filled 358begin_nodelist "$shape" "$color" "$fillcolor" "$style" 359print_node "bsdconfig" "fontname = \"Times-Bold\"" "fontsize = 16" 360end_nodelist 361 362# 363# Print menus 364# 365printf '\n\t/*\n\t * Menu items\n\t */\n' 366shape=box color=black fillcolor=lightblue style=filled 367begin_nodelist "$shape" "$color" "$fillcolor" "$style" 368for menu_program in $menu_program_list; do 369 print_node "$menu_program" "label = \"${menu_program#*/}\"" 370done 371end_nodelist 372 373# 374# Print sub-menus 375# 376printf '\n\t/*\n\t * Sub-menu items\n\t */\n' 377shape=box color=black fillcolor=lightblue style=filled 378begin_nodelist "$shape" "$color" "$fillcolor" "$style" 379for submenu_program in $submenu_program_list; do 380 print_node "$submenu_program" "label = \"${submenu_program#*/}\"" 381done 382end_nodelist 383 384# 385# Print menu relationships 386# 387printf '\n\t/*\n\t * Menu item relationships\n\t */\n' 388shape=box color=black fillcolor=lightblue style=filled edge_color=blue 389begin_nodelist "$shape" "$color" "$fillcolor" "$style" 390print_node edge "penwidth = 5.0" "style = bold" "color = $edge_color" 391for menu_program in $menu_program_list; do 392 print_node2 "bsdconfig" "$menu_program" 393done 394end_nodelist 395 396# 397# Print sub-menu relationships 398# 399printf '\n\t/*\n\t * Sub-menu item relationships\n\t */\n' 400shape=box color=black fillcolor=lightblue style=filled edge_color=blue 401begin_nodelist "$shape" "$color" "$fillcolor" "$style" 402# Lock sub-menu headport to the West (unless `-c' was passed) 403[ "$SHOW_CMDLINE" -o ! "$SHOW_INCLUDES" ] && print_node edge "headport = w" 404print_node edge "style = bold" "color = $edge_color" 405for submenu_program in $submenu_program_list; do 406 for menu_program in $menu_program_list; do 407 case "$menu_program" in 408 [0-9][0-9][0-9].*/*) : fall-through ;; 409 *) continue # Not a menu item 410 esac 411 412 # Continue if program directories do not match 413 [ "${menu_program%%/*}" = "${submenu_program%%/*}" ] || 414 continue 415 416 print_node2 "$menu_program" "$submenu_program" 417 break 418 done 419done 420end_nodelist 421 422# 423# [Optionally] Print include files 424# 425if [ "$SHOW_INCLUDES" ]; then 426 printf '\n\t/*\n\t * Include files\n\t */\n' 427 shape=oval color=black fillcolor=white style=filled 428 begin_nodelist "$shape" "$color" "$fillcolor" "$style" 429 printf '\t\tconstraint = false;\n' 430 for include_file in $include_file_list; do 431 print_node "$include_file" \ 432 "label = \"${include_file##*/}\"" 433 done 434 end_nodelist 435fi 436 437# 438# [Optionally] Print f_include() usage/relationships 439# 440if [ "$SHOW_INCLUDES" ]; then 441 printf '\n\t/*\n\t * Include usage\n\t */\n' 442 shape=oval color=black fillcolor=white style=filled edge_color=grey 443 begin_nodelist "$shape" "$color" "$fillcolor" "$style" 444 print_node edge "style = dashed" "color = $edge_color" 445 #print_node edge "label = \"\\T\"" "fontsize = 9" 446 # NOTE: Edge labels are buggy on large graphs 447 file_list=$( 448 for file in \ 449 $BSDCONFIG \ 450 $menu_program_list \ 451 $submenu_program_list \ 452 $cmd_program_list \ 453 $include_file_list \ 454 ; do 455 [ -f "$BSDCFG_SHARE/$file" ] && 456 echo $BSDCFG_SHARE/$file 457 [ -e "$file" ] && echo $file 458 done | sort -u 459 ) 460 for file in $file_list; do 461 # Skip binary files and text files that don't use f_include() 462 grep -qlI f_include $file || continue 463 464 awk \ 465 -v file="${file#$BSDCFG_SHARE/}" \ 466 -v bsdconfig="$BSDCONFIG" \ 467 ' 468 BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" } 469 ( $0 ~ regex ) { 470 sub(regex, "") 471 if ( file == bsdconfig ) sub(".*/", "", file) 472 printf "\t\t\"%s\" -> \"%s\";\n", $0, file 473 } 474 ' $file 475 done | sort 476 end_nodelist 477fi 478 479# 480# Print command-line shortcuts 481# 482if [ "$SHOW_CMDLINE" ]; then 483 printf '\n\t/*\n\t * Command-line shortcuts\n\t */\n' 484 shape=parallelogram color=black fillcolor=lightseagreen style=filled 485 begin_nodelist "$shape" "$color" "$fillcolor" "$style" 486 for file in */INDEX; do 487 awk -v item="${file%%/*}" ' 488 /^menu_selection="/ { 489 sub(/^.*="/, "") 490 sub(/\|.*/, "") 491 printf "\t\t\"bsdconfig %s\"", $0 492 printf " [ label = \"%s\" ];\n", $0 493 } 494 ' $file 495 done 496 end_nodelist 497fi 498 499# 500# Print command-line shortcut relationships 501# 502if [ "$SHOW_CMDLINE" ]; then 503 printf '\n\t/*\n\t * Command-line shortcut relationships\n\t */\n' 504 shape=box color=black fillcolor=lightseagreen style=filled 505 begin_nodelist "$shape" "$color" "$fillcolor" "$style" 506 print_node edge "headport = w" "weight = 100.0" 507 print_node edge "style = bold" "color = $fillcolor" 508 for file in */INDEX; do 509 awk -v item="${file%%/*}" \ 510 -v node_fillcolor="$node_fillcolor" \ 511 -v edge_color="$edge_color" \ 512 ' 513 /^menu_selection="/ { 514 sub(/^.*="/, "") 515 sub(/"$/, "") 516 517 if ( ! $0 ) next 518 519 split($0, menusel, "|") 520 if ( menusel[2] !~ "^/" ) 521 sub(/^/, item "/", menusel[2]) 522 523 printf "\t\t\"bsdconfig %s\" -> \"%s\";\n", 524 menusel[1], menusel[2] 525 } 526 ' $file 527 done 528 end_nodelist 529fi 530 531# 532# Print clusters 533# 534bgcolor_bsdconfig="lightyellow" 535bgcolor_includes="gray98" 536bgcolor_menuitem="aliceblue" 537bgcolor_shortcuts="honeydew" 538printf '\n\t/*\n\t * Clusters\n\t */\n' 539printf '\tsubgraph "cluster_bsdconfig" {\n' 540printf '\t\tbgcolor = "%s";\n' "$bgcolor_bsdconfig" 541printf '\t\tlabel = "bsdconfig(8)";\n' 542printf '\t\ttooltip = "bsdconfig(8)";\n' 543print_node "bsdconfig" 544end_nodelist 545if [ "$SHOW_INCLUDES" ]; then 546 for include_file in $include_file_list; do 547 echo $include_file 548 done | awk \ 549 -v bgcolor="$bgcolor_bsdconfig" \ 550 -v msg_subroutines="$msg_subroutines" \ 551 ' 552 BEGIN { created = 0 } 553 function end_subgraph() { printf "\t};\n" } 554 ( $0 !~ "/" ) { 555 if ( ! created ) 556 { 557 printf "\tsubgraph \"%s\" {\n", 558 "cluster_bsdconfig_includes" 559 printf "\t\tbgcolor = \"%s\";\n", bgcolor 560 printf "\t\tlabel = \"bsdconfig %s\";\n", 561 msg_subroutines 562 created++ 563 } 564 printf "\t\t\"%s\";\n", $1 565 } 566 END { created && end_subgraph() } 567 ' # END-QUOTE 568 569 for include_file in $include_file_list; do 570 echo $include_file 571 done | awk -v msg_subroutines="$msg_subroutines" ' 572 BEGIN { created = 0 } 573 function end_subgraph() { printf "\t};\n" } 574 ( $0 ~ "/" ) { 575 include_dir_tmp = $1 576 sub("/[^/]*$", "", include_dir_tmp) 577 gsub(/[^[:alnum:]_]/, "_", include_dir_tmp) 578 579 if ( created && include_dir != include_dir_tmp ) 580 { 581 end_subgraph() 582 created = 0 583 } 584 585 if ( ! created ) 586 { 587 include_dir = include_dir_tmp 588 printf "\tsubgraph \"cluster_%s_includes\" {\n", 589 include_dir 590 printf "\t\tbgcolor = \"thistle\";\n" 591 printf "\t\tlabel = \"%s %s\";\n", include_dir, 592 msg_subroutines 593 created++ 594 } 595 596 printf "\t\t\"%s\";\n", $1 597 } 598 END { created && end_subgraph() }' 599fi 600for INDEX in */INDEX; do 601 menu_title= 602 menu_help= 603 f_include_lang "$INDEX" 604 605 item="${INDEX%%/*}" 606 printf '\tsubgraph "cluster_%s" {\n' "$item" 607 608 case "$item" in 609 [0-9][0-9][0-9].*) bgcolor="$bgcolor_menuitem" ;; 610 *) bgcolor="$bgcolor_shortcuts" 611 esac 612 printf '\t\tbgcolor = "%s";\n' "$bgcolor" 613 if [ "$menu_title" ]; then 614 printf '\t\tlabel = "%s\\n\\"%s\\"";\n' "$item" "$menu_title" 615 else 616 printf '\t\tlabel = "%s";\n' "$item" 617 fi 618 printf '\t\ttooltip = "%s";\n' "${menu_help:-$item}" 619 620 program_list=$( 621 for program in \ 622 $menu_program_list \ 623 $submenu_program_list \ 624 $cmd_program_list \ 625 ; do 626 echo "$program" 627 done | sort -u 628 ) 629 for program in $program_list; do 630 case "$program" in "$item"/*) 631 print_node "$program" "label = \"${program#*/}\"" 632 esac 633 done 634 635 if [ "$SHOW_INCLUDES" ]; then 636 item_include_list= 637 [ -d "$item/include" ] && 638 item_include_list=$( find "$item/include" -type f ) 639 item_include_list=$( 640 for item_include in $item_include_list; do 641 for include_file in $include_file_list; do 642 [ "$item_include" = "$include_file" ] || 643 continue 644 echo "$item_include"; break 645 done 646 done 647 ) 648 if [ "$item_include_list" ]; then 649 printf '\t\tsubgraph "cluster_%s_includes" {\n' "$item" 650 printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_includes" 651 printf '\t\t\tlabel = "%s";\n' "$msg_includes" 652 fi 653 for item_include in $item_include_list; do 654 printf '\t\t\t"%s";\n' "$item_include" 655 done 656 [ "$item_include_list" ] && printf '\t\t};\n' 657 fi 658 659 if [ "$SHOW_CMDLINE" ]; then 660 printf '\t\tsubgraph "cluster_%s_shortcuts" {\n' "$item" 661 printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_shortcuts" 662 printf '\t\t\tlabel = "%s";\n' "$msg_shortcuts" 663 awk '/^menu_selection="/ { 664 sub(/^.*="/, "") 665 sub(/\|.*/, "") 666 printf "\t\t\t\"bsdconfig %s\";\n", $0 667 }' "$INDEX" 668 printf '\t\t};\n' 669 fi 670 671 end_nodelist 672done 673 674printf '\n}\n' 675 676################################################################################ 677# END 678################################################################################ 679