]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - usr.sbin/bsdconfig/dot/dot
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / usr.sbin / bsdconfig / dot / dot
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 #
34 DEBUG_SELF_INITIALIZE=NO
35
36 BSDCFG_SHARE="/usr/share/bsdconfig"
37 . $BSDCFG_SHARE/common.subr || exit 1
38 f_dprintf "%s: loading includes..." "$0"
39
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
43
44 f_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 #
52 BSDCONFIG=/usr/sbin/bsdconfig
53
54 ############################################################ GLOBALS
55
56 #
57 # Options
58 #
59 SHOW_GRAPH_LABEL_DATE=1
60 SHOW_INCLUDES=1
61 SHOW_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 #
70 begin_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 #
90 print_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 #
118 print_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 #
143 end_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 #
156 while 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
163 done
164 shift $(( $OPTIND - 1 ))
165
166 cd $BSDCFG_LIBE || f_die # Pedantic
167
168 #
169 # Get a list of menu programs
170 #
171 menu_program_list=
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="/ {
176                                 sub(/^.*="/, "")
177                                 sub(/"$/, "")
178                                 if ( ! $0 ) next
179                                 if ( $0 !~ "^/" ) sub(/^/, item "/")
180                                 print; exit
181                         }'
182         )"
183 done
184
185 #
186 # Get a list of submenu programs
187 #
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
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         )"
210 done
211
212 #
213 # Get a list of command-line programs
214 #
215 cmd_program_list=
216 for 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         )"
232 done
233
234 #
235 # [Optionally] Calculate list of include files
236 #
237 if [ "$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
300 fi
301
302 #
303 # Start the directional-graph (digraph) output
304 #
305 printf 'strict digraph "" { // Empty name to prevent SVG Auto-Tooltip\n'
306 label_format="$msg_graph_label_with_command"
307 [ "$SHOW_GRAPH_LABEL_DATE" ] &&
308         label_format="$msg_graph_label_with_command_and_date"
309 lang="${LANG:-$LC_ALL}"
310 printf "\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 #
317 printf '\n\t/*\n\t * Graph setup and orientation\n\t */\n'
318 printf '\tlabelloc = top;\t\t// display above label at top of graph\n'
319 printf '\trankdir = LR;\t\t// create ranks left-to-right\n'
320 printf '\torientation = portrait;\t// default\n'
321 printf '\tratio = fill;\t\t// approximate aspect ratio\n'
322 printf '\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 #
338 printf '\n\t/*\n\t * Font details for graph/cluster label(s)\n\t */\n'
339 printf '\tfontname = "Times-Italic";\n'
340 printf '\tfontsize = 14;\n'
341
342 #
343 # Print default node attributes
344 #
345 printf '\n\t/*\n\t * Default node attributes\n\t */\n'
346 printf '\tnode [\n'
347 printf '\t\tfontname = "Times-Roman",\n'
348 printf '\t\tfontsize = 12,\n'
349 printf '\t\twidth = 2.5, // arbitrary minimum width for all nodes\n'
350 printf '\t\tfixedsize = true, // turn minimum width into exact width\n'
351 printf '\t];\n'
352
353 #
354 # Print top-level item(s)
355 #
356 printf '\n\t/*\n\t * bsdconfig(8)\n\t */\n'
357 shape=circle color=black fillcolor=yellow style=filled
358 begin_nodelist "$shape" "$color" "$fillcolor" "$style"
359 print_node "bsdconfig" "fontname = \"Times-Bold\"" "fontsize = 16"
360 end_nodelist
361
362 #
363 # Print menus
364 #
365 printf '\n\t/*\n\t * Menu items\n\t */\n'
366 shape=box color=black fillcolor=lightblue style=filled
367 begin_nodelist "$shape" "$color" "$fillcolor" "$style"
368 for menu_program in $menu_program_list; do
369         print_node "$menu_program" "label = \"${menu_program#*/}\""
370 done
371 end_nodelist
372
373 #
374 # Print sub-menus
375 #
376 printf '\n\t/*\n\t * Sub-menu items\n\t */\n'
377 shape=box color=black fillcolor=lightblue style=filled
378 begin_nodelist "$shape" "$color" "$fillcolor" "$style"
379 for submenu_program in $submenu_program_list; do
380         print_node "$submenu_program" "label = \"${submenu_program#*/}\""
381 done
382 end_nodelist
383
384 #
385 # Print menu relationships
386 #
387 printf '\n\t/*\n\t * Menu item relationships\n\t */\n'
388 shape=box color=black fillcolor=lightblue style=filled edge_color=blue
389 begin_nodelist "$shape" "$color" "$fillcolor" "$style"
390 print_node edge "penwidth = 5.0" "style = bold" "color = $edge_color"
391 for menu_program in $menu_program_list; do
392         print_node2 "bsdconfig" "$menu_program"
393 done
394 end_nodelist
395
396 #
397 # Print sub-menu relationships
398 #
399 printf '\n\t/*\n\t * Sub-menu item relationships\n\t */\n'
400 shape=box color=black fillcolor=lightblue style=filled edge_color=blue
401 begin_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"
404 print_node edge "style = bold" "color = $edge_color"
405 for 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
419 done
420 end_nodelist
421
422 #
423 # [Optionally] Print include files
424 #
425 if [ "$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
435 fi
436
437 #
438 # [Optionally] Print f_include() usage/relationships
439 #
440 if [ "$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
477 fi
478
479 #
480 # Print command-line shortcuts
481 #
482 if [ "$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
497 fi
498
499 #
500 # Print command-line shortcut relationships
501 #
502 if [ "$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
529 fi
530
531 #
532 # Print clusters
533 #
534 bgcolor_bsdconfig="lightyellow"
535 bgcolor_includes="gray98"
536 bgcolor_menuitem="aliceblue"
537 bgcolor_shortcuts="honeydew"
538 printf '\n\t/*\n\t * Clusters\n\t */\n'
539 printf '\tsubgraph "cluster_bsdconfig" {\n'
540 printf '\t\tbgcolor = "%s";\n' "$bgcolor_bsdconfig"
541 printf '\t\tlabel = "bsdconfig(8)";\n'
542 printf '\t\ttooltip = "bsdconfig(8)";\n'
543 print_node "bsdconfig"
544 end_nodelist
545 if [ "$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() }'
599 fi
600 for 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
672 done
673
674 printf '\n}\n'
675
676 ################################################################################
677 # END
678 ################################################################################