3 # Copyright (c) 2012-2013 Devin Teske
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
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.
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
29 ############################################################ INCLUDES
31 # Prevent common.subr from auto initializing debugging (this is not an inter-
32 # active utility that requires debugging; also `-d' has been repurposed).
34 DEBUG_SELF_INITIALIZE=NO
36 BSDCFG_SHARE="/usr/share/bsdconfig"
37 . $BSDCFG_SHARE/common.subr || exit 1
38 f_dprintf "%s: loading includes..." "$0"
40 BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="dot"
41 f_include_lang $BSDCFG_LIBE/include/messages.subr
42 f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
44 ipgm=$( f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" )
45 [ $? -eq $SUCCESS -a "$ipgm" ] && pgm="$ipgm"
47 ############################################################ CONFIGURATION
50 # Location of bsdconfig(8)
52 BSDCONFIG=/usr/sbin/bsdconfig
54 ############################################################ GLOBALS
59 SHOW_GRAPH_LABEL_DATE=1
63 ############################################################ FUNCTIONS
65 # begin_nodelist $shape $color $fillcolor $style
67 # Create a new multi-node list rendering nodes in a specific style described by
68 # the arguments passed.
72 local shape="$1" color="$2" fillcolor="$3" style="$4"
76 printf '\t\tshape = "%s",\n' "$shape"
78 printf '\t\tcolor = "%s",\n' "$color"
80 printf '\t\tfillcolor = "%s",\n' "$fillcolor"
82 printf '\t\tstyle = "%s",\n' "$style"
86 # print_node $node [$attributes ...]
88 # Print a node within a multi-node list.
97 edge) printf '\t\t%s' "$node" ;;
98 *) printf '\t\t"%s"' "$node" ;;
101 if [ $# -gt 0 ]; then
103 while [ $# -gt 0 ]; do
106 [ $# -gt 0 ] && echo -n ","
114 # print_node2 $node $node [$attributes ...]
116 # Print a directed node-node connection within a multi-node list.
120 local node1="$1" node2="$2"
122 shift 2 # node1 node2
124 printf '\t\t"%s" -> "%s"' "$node1" "$node2"
126 if [ $# -gt 0 ]; then
128 while [ $# -gt 0 ]; do
131 [ $# -gt 0 ] && echo -n ","
141 # Close a multi-node list.
148 ############################################################ MAIN
150 # Incorporate rc-file if it exists
151 [ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
154 # Process command-line arguments
156 while getopts cdhi flag; do
159 d) SHOW_GRAPH_LABEL_DATE= ;;
161 h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
164 shift $(( $OPTIND - 1 ))
166 cd $BSDCFG_LIBE || f_die 1 "$msg_directory_not_found" "$BSDCFG_LIB"
169 # Get a list of menu programs
172 for 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="/ {
179 if ( $0 !~ "^/" ) sub(/^/, item "/")
186 # Get a list of submenu programs
188 submenu_program_list=
189 for 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
195 submenu_program_list="$submenu_program_list $(
196 awk -v menu_program="$menu_program" \
197 -v item="${menu_program%%/*}" \
199 /^menu_selection="/ {
205 if ( $0 == menu_program ) next
208 ' "${menu_program%%/*}/INDEX"
213 # Get a list of command-line programs
216 for file in */INDEX; do
217 cmd_program_list="$cmd_program_list $(
218 awk -v item="${file%%/*}" '
219 /^menu_selection="/ {
235 # [Optionally] Calculate list of include files
237 if [ "$SHOW_INCLUDES" ]; then
239 BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" }
240 ( $0 ~ regex ) { sub(regex, ""); print }
244 # Build list of files in which to search for includes
250 $submenu_program_list \
253 [ -e "$file" ] && echo $file
258 # Build list of includes used by the above files
261 for file in $file_list; do
262 include_file_list="$include_file_list $(
263 awk "$print_includes_awk" $file
268 # Sort the list of includes and remove duplicate entries
271 for include_file in $include_file_list; do
277 # Search previously-discovered include files for further includes
279 before="$include_file_list"
281 for file in $include_file_list; do
282 include_file_list="$include_file_list $(
283 awk "$print_includes_awk" $BSDCFG_SHARE/$file
288 # Sort list of includes and remove duplicate entries [again]
291 for include_file in $include_file_list; do
296 [ "$include_file_list" = "$before" ] && break
297 before="$include_file_list"
302 # Add script.subr to includes if it exists
304 [ -f $BSDCFG_SHARE/script.subr ] &&
305 include_file_list="$include_file_list script.subr"
308 # Start the directional-graph (digraph) output
310 printf 'strict digraph "" { // Empty name to prevent SVG Auto-Tooltip\n'
311 label_format="$msg_graph_label_with_command"
312 [ "$SHOW_GRAPH_LABEL_DATE" ] &&
313 label_format="$msg_graph_label_with_command_and_date"
314 lang="${LANG:-$LC_ALL}"
315 printf "\n\tlabel = \"$label_format\"\n" \
316 "${lang:+LANG=${lang%%[$IFS]*} }bsdconfig $pgm${ARGV:+ $ARGV}" \
320 # Print graph-specific properties
322 printf '\n\t/*\n\t * Graph setup and orientation\n\t */\n'
323 printf '\tlabelloc = top;\t\t// display above label at top of graph\n'
324 printf '\trankdir = LR;\t\t// create ranks left-to-right\n'
325 printf '\torientation = portrait;\t// default\n'
326 printf '\tratio = fill;\t\t// approximate aspect ratio\n'
327 printf '\tcenter = 1;\t\t// center drawing on page\n'
330 # Perform edge-concentration when displaying a lot of information
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
335 # NOTE: Do not re-enable until said bug is fixed in some future revision.
337 #[ "$SHOW_INCLUDES" -a "$SHOW_CMDLINE" ] &&
338 # printf '\tconcentrate = true;\t// enable edge concentrators\n'
341 # Print font details for graph/cluster label(s)
343 printf '\n\t/*\n\t * Font details for graph/cluster label(s)\n\t */\n'
344 printf '\tfontname = "Times-Italic";\n'
345 printf '\tfontsize = 14;\n'
348 # Print default node attributes
350 printf '\n\t/*\n\t * Default node attributes\n\t */\n'
352 printf '\t\tfontname = "Times-Roman",\n'
353 printf '\t\tfontsize = 12,\n'
354 printf '\t\twidth = 2.5, // arbitrary minimum width for all nodes\n'
355 printf '\t\tfixedsize, // turn minimum width into exact width\n'
359 # Print top-level item(s)
361 printf '\n\t/*\n\t * bsdconfig(8)\n\t */\n'
362 shape=circle color=black fillcolor=yellow style=filled
363 begin_nodelist "$shape" "$color" "$fillcolor" "$style"
364 print_node "bsdconfig" "fontname = \"Times-Bold\"" "fontsize = 16"
370 printf '\n\t/*\n\t * Menu items\n\t */\n'
371 shape=box color=black fillcolor=lightblue style=filled
372 begin_nodelist "$shape" "$color" "$fillcolor" "$style"
373 for menu_program in $menu_program_list; do
374 print_node "$menu_program" "label = \"${menu_program#*/}\""
381 printf '\n\t/*\n\t * Sub-menu items\n\t */\n'
382 shape=box color=black fillcolor=lightblue style=filled
383 begin_nodelist "$shape" "$color" "$fillcolor" "$style"
384 for submenu_program in $submenu_program_list; do
385 print_node "$submenu_program" "label = \"${submenu_program#*/}\""
390 # Print menu relationships
392 printf '\n\t/*\n\t * Menu item relationships\n\t */\n'
393 shape=box color=black fillcolor=lightblue style=filled edge_color=blue
394 begin_nodelist "$shape" "$color" "$fillcolor" "$style"
395 print_node edge "penwidth = 5.0" "style = bold" "color = $edge_color"
396 for menu_program in $menu_program_list; do
397 print_node2 "bsdconfig" "$menu_program"
402 # Print sub-menu relationships
404 printf '\n\t/*\n\t * Sub-menu item relationships\n\t */\n'
405 shape=box color=black fillcolor=lightblue style=filled edge_color=blue
406 begin_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"
409 print_node edge "style = bold" "color = $edge_color"
410 for 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
417 # Continue if program directories do not match
418 [ "${menu_program%%/*}" = "${submenu_program%%/*}" ] ||
421 print_node2 "$menu_program" "$submenu_program"
428 # [Optionally] Print include files
430 if [ "$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##*/}\""
443 # [Optionally] Print f_include() usage/relationships
445 if [ "$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
456 $submenu_program_list \
460 [ -f "$BSDCFG_SHARE/$file" ] &&
461 echo $BSDCFG_SHARE/$file
462 [ -e "$file" ] && echo $file
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
470 -v file="${file#$BSDCFG_SHARE/}" \
471 -v bsdconfig="$BSDCONFIG" \
473 BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" }
476 if ( file == bsdconfig ) sub(".*/", "", file)
477 printf "\t\t\"%s\" -> \"%s\";\n", $0, file
485 # Print command-line shortcuts
487 if [ "$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="/ {
496 printf "\t\t\"bsdconfig %s\"", $0
497 printf " [ label = \"%s\" ];\n", $0
505 # Print command-line shortcut relationships
507 if [ "$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" \
518 /^menu_selection="/ {
524 split($0, menusel, "|")
525 if ( menusel[2] !~ "^/" )
526 sub(/^/, item "/", menusel[2])
528 printf "\t\t\"bsdconfig %s\" -> \"%s\";\n",
529 menusel[1], menusel[2]
539 bgcolor_bsdconfig="lightyellow"
540 bgcolor_includes="gray98"
541 bgcolor_menuitem="aliceblue"
542 bgcolor_shortcuts="honeydew"
543 printf '\n\t/*\n\t * Clusters\n\t */\n'
544 printf '\tsubgraph "cluster_bsdconfig" {\n'
545 printf '\t\tbgcolor = "%s";\n' "$bgcolor_bsdconfig"
546 printf '\t\tlabel = "bsdconfig(8)";\n'
547 printf '\t\ttooltip = "bsdconfig(8)";\n'
548 print_node "bsdconfig"
549 if [ "$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"
554 for include_file in $include_file_list; do
556 done | awk -v bgcolor="$bgcolor_bsdconfig" '
557 BEGIN { created = 0 }
558 function end_subgraph() { printf "\t\t\t};\n" }
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"
568 printf "\t\t\t\t\"%s\";\n", $1
570 END { created && end_subgraph() }'
572 for include_file in $include_file_list; do
575 BEGIN { created = 0 }
576 function end_subgraph() { printf "\t\t\t};\n" }
579 sub("/[^/]*$", "", include_dir_tmp)
580 gsub(/[^[:alnum:]_]/, "_", include_dir_tmp)
582 if ( created && include_dir != include_dir_tmp )
590 include_dir = include_dir_tmp
591 printf "\t\t\tsubgraph \"cluster_%s_includes\" {\n",
593 printf "\t\t\t\tbgcolor = \"white\";\n"
594 printf "\t\t\t\tlabel = \"%s\";\n", include_dir
598 printf "\t\t\t\t\"%s\";\n", $1
600 END { created && end_subgraph() }'
605 for INDEX in */INDEX; do
608 f_include_lang "$INDEX"
611 printf '\tsubgraph "cluster_%s" {\n' "$item"
614 [0-9][0-9][0-9].*) bgcolor="$bgcolor_menuitem" ;;
615 *) bgcolor="$bgcolor_shortcuts"
617 printf '\t\tbgcolor = "%s";\n' "$bgcolor"
618 if [ "$menu_title" ]; then
619 printf '\t\tlabel = "%s\\n\\"%s\\"";\n' "$item" "$menu_title"
621 printf '\t\tlabel = "%s";\n' "$item"
623 printf '\t\ttooltip = "%s";\n' "${menu_help:-$item}"
628 $submenu_program_list \
634 for program in $program_list; do
635 case "$program" in "$item"/*)
636 print_node "$program" "label = \"${program#*/}\""
640 if [ "$SHOW_INCLUDES" ]; then
642 [ -d "$item/include" ] &&
643 item_include_list=$( find "$item/include" -type f )
645 for item_include in $item_include_list; do
646 for include_file in $include_file_list; do
647 [ "$item_include" = "$include_file" ] ||
649 echo "$item_include"; break
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"
658 for item_include in $item_include_list; do
659 printf '\t\t\t"%s";\n' "$item_include"
661 [ "$item_include_list" ] && printf '\t\t};\n'
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="/ {
671 printf "\t\t\t\"bsdconfig %s\";\n", $0
681 ################################################################################
683 ################################################################################