8. Shell Tools

8.1. dir_to_dot.sh - generate dot(1) graph from directory

dir_to_dot.sh - recursive dot(1) graph from directory tree

usage: dir_to_dot.sh [OPTIONS] [directory]

OPTIONS

option args description
-d, –debug   N/I do not execute commands
-h, –help   show this help
–umlx RX[:TYP] N/I extract UML diagrams matching RX
|:todo:| document simple database design with || as separator
|:todo:| hidden color set is not properly defined
|:todo:| add –title, –no-title
|:todo:| activity diagram for link processing
|:todo:| add –link-targets, –no-link-targets
|:todo:| add –no-legend, –legend

(see figure 8.1)

// -*- c -*- // Copyright (C) 2020, Wolfgang Scherer, <Wolfgang.Scherer at gmx.de> // This file is part of Documentation Standard. // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. digraph dirtree { center=1; rankdir=LR; // newrank = true; // rank = source; // rank = sink; // nodesep = 0.5; node [style=filled,fillcolor="#cccccc",shape=box]; node [fontname="FreeSans",fontsize="10"]; edge [style=solid,dir=forward,arrowsize=0.5] edge [fontname="FreeSans",fontsize="20",labelfontname="FreeSans",labelfontsize="9"]; // |:here:| // (shell-command "recursive_dir_to_dot.sh" t) subgraph cluster_outside { style = invisible; TOP [label=< <b>development</b> >,fontcolor="black",fillcolor="lightblue"]; } subgraph cluster_tree { label=< <table bgcolor="transparent" border="0" cellborder="1" cellspacing="0" cellpadding="3"> <tr><td bgcolor="blue"><font color="white"><b> dir-to-dot </b></font></td></tr> </table> >; HERE [label=< <b>dir-to-dot</b> >,fontcolor="white",fillcolor="blue"]; TOP -> HERE [style="invis",dir="none"]; HERE_F0 [label=< _static >,fontcolor="black",fillcolor="green"]; HERE -> HERE_F0; HERE_F1 [label=< stale-link >,fontcolor="black",fillcolor="orange"]; HERE -> HERE_F1; HERE_F2 [label=< <b>shallow</b> >,fontcolor="white",fillcolor="blue"]; HERE -> HERE_F2; HERE_F2_F0 [label=< flat5 >,fontcolor="black",fillcolor="green"]; HERE_F2 -> HERE_F2_F0; HERE_F2_F1 [label=< flat4 >,fontcolor="black",fillcolor="transparent"]; HERE_F2 -> HERE_F2_F1; HERE_F2_F2 [label=< flat2 >,fontcolor="black",fillcolor="transparent"]; HERE_F2 -> HERE_F2_F2; HERE_F2_F3 [label=< flat1 >,fontcolor="black",fillcolor="transparent"]; HERE_F2 -> HERE_F2_F3; HERE_F2 -> HERE [color="deepskyblue",taillabel=< <FONT color="deepskyblue" point-size="6">●●</FONT> >]; HERE_F2 -> HERE_F2 [color="steelblue",label=< <FONT color="steelblue" point-size="6">●</FONT> >]; HERE_F3 [label=< my-picture >,fontcolor="black",fillcolor="green"]; HERE -> HERE_F3; HERE_F4 [label=< Makefile >,fontcolor="black",fillcolor="transparent"]; HERE -> HERE_F4; HERE_F5 [label=< doc >,fontcolor="black",fillcolor="green"]; HERE -> HERE_F5; HERE_F6 [label=< dir_to_dot.svg >,fontcolor="black",fillcolor="transparent"]; HERE -> HERE_F6; HERE_F7 [label=< dir_to_dot.sh >,fontcolor="black",fillcolor="transparent"]; HERE -> HERE_F7; HERE_F8 [label=< dir_to_dot.dot.e >,fontcolor="black",fillcolor="transparent"]; HERE -> HERE_F8; HERE_F9 [label=< dir_to_dot.dot-skip-dots.e >,fontcolor="black",fillcolor="transparent"]; HERE -> HERE_F9; HERE_F10 [label=< dir_to_dot.dot >,fontcolor="black",fillcolor="transparent"]; HERE -> HERE_F10; HERE_F11 [label=< <b>deep</b> >,fontcolor="white",fillcolor="blue"]; HERE -> HERE_F11; HERE_F11_F0 [label=< <b>underground</b> >,fontcolor="white",fillcolor="blue"]; HERE_F11 -> HERE_F11_F0; HERE_F11_F0_F0 [label=< crawler5 >,fontcolor="black",fillcolor="transparent"]; HERE_F11_F0 -> HERE_F11_F0_F0; HERE_F11_F0_F1 [label=< crawler2 >,fontcolor="black",fillcolor="transparent"]; HERE_F11_F0 -> HERE_F11_F0_F1; HERE_F11_F0_F2 [label=< crawler1 >,fontcolor="black",fillcolor="transparent"]; HERE_F11_F0 -> HERE_F11_F0_F2; HERE_F11_F0 -> HERE_F11 [color="deepskyblue",taillabel=< <FONT color="deepskyblue" point-size="6">●●</FONT> >]; HERE_F11_F0 -> HERE_F11_F0 [color="steelblue",label=< <FONT color="steelblue" point-size="6">●</FONT> >]; HERE_F11_F1 [label=< middle5 >,fontcolor="black",fillcolor="transparent"]; HERE_F11 -> HERE_F11_F1; HERE_F11 -> HERE [color="deepskyblue",taillabel=< <FONT color="deepskyblue" point-size="6">●●</FONT> >]; HERE_F11 -> HERE_F11 [color="steelblue",label=< <FONT color="steelblue" point-size="6">●</FONT> >]; HERE_F12 [label=< .zzzz >,fontcolor="black",fillcolor="#dddddd"]; HERE -> HERE_F12; HERE_F13 [label=< .hidden >,fontcolor="black",fillcolor="#dddddd"]; HERE -> HERE_F13; HERE -> TOP [color="deepskyblue",taillabel=< <FONT color="deepskyblue" point-size="6">●●</FONT> >]; HERE -> HERE [color="steelblue",label=< <FONT color="steelblue" point-size="6">●</FONT> >]; subgraph links { label=none; HERE_F2_F0 -> HERE_F2_F2 [style=dashed,color="green",weight=0]; HERE_F3 -> HERE_F10 [style=dashed,color="green",weight=0]; HERE_F0 -> LINK_TRG_0 [style=dashed,color="green",weight=0]; HERE_F1 -> LINK_TRG_1 [style=dashed,color="red",weight=0]; HERE_F5 -> LINK_TRG_4 [style=dashed,color="green",weight=0]; } } subgraph cluster_outside { // rank = max; LINK_TRG_0 [label=< ../_static >,fontcolor="black",fillcolor="palegreen"]; LINK_TRG_1 [label=< ../not-there >,fontcolor="black",fillcolor="#fff09a"]; LINK_TRG_4 [label=< ../doc >,fontcolor="black",fillcolor="palegreen"]; } LEGEND [label=< <table bgcolor="transparent" border="0" cellborder="1" cellspacing="0" cellpadding="3"> <tr><td> <b>node</b> </td> <td> <b>description</b> </td> <td> <b>node</b> </td> <td> <b>description</b> </td></tr> <tr><td bgcolor="blue"> <font color="white">directory</font> </td> <td align="left"> in main directory tree </td> <td bgcolor="green"> <font color="black">symlink</font> </td> <td align="left"> in main directory tree </td></tr> <tr><td bgcolor="lightblue"> <font color="black">directory</font></td> <td align="left"> outside main directory tree </td> <td bgcolor="palegreen"> <font color="black">symlink</font></td> <td align="left"> outside main directory tree </td></tr> <tr><td bgcolor="transparent"> <font color="steelblue" point-size="6">●</font> </td> <td align="left"> hard link <font point-size="14"><b>.</b></font> to current directory </td> <td bgcolor="orange"> <font color="black">stale symlink </font> </td> <td align="left"> in main directory tree </td></tr> <tr><td bgcolor="transparent"> <font color="deepskyblue" point-size="6">●●</font> </td> <td align="left"> hard link <font point-size="14"><b>..</b></font> to parent directory </td> <td bgcolor="#fff09a"> <font color="black">stale symlink </font></td> <td align="left"> outside main directory tree </td></tr> <tr><td bgcolor="transparent"> <font color="black">regular file</font> </td> <td align="left"> in main directory tree </td> <td bgcolor="#dddddd"> <font color="black">hidden file</font> </td> <td align="left"> in main directory tree </td></tr> </table> >,shape=none,fillcolor="transparent"]; TOP -> LEGEND [style=invisible,dir=none,weight=1]; // |:here:| } // (progn (forward-line 1) (snip-insert "dot.t.ide" t t "dot") (insert "")) // :ide-menu: Emacs IDE Main Menu - Buffer @BUFFER@ // . M-x `eIDE-menu' ()(eIDE-menu "z") // :ide: OCCUR-OUTLINE: Sections: `||: sec :||' // . (x-symbol-tag-occur-outline "sec" '("||:" ":||") '("|:" ":|")) // :ide: MENU-OUTLINE: Sections `||: sec :||' // . (x-eIDE-menu-outline "sec" '("||:" ":||") '("|:" ":|")) // :ide: OCCUR-OUTLINE: Default `|||: sec :|||' // . (x-symbol-tag-occur-outline) // :ide: MENU-OUTLINE: Default `|||: sec :|||' // . (x-eIDE-menu-outline) // :ide: +-#+ // . Buffer Outline Sections () // :ide: COMPILE: hamilton_circle_dot_to_cv.py -1 > ...-1-1.cv # exponential // . (let* ((fname (file-name-nondirectory (buffer-file-name))) (base (file-name-sans-extension f))) (save-buffer) (compile (concat "hamilton_circle_dot_to_cv.py -1 " fname " >" base "-1-1.cv"))) // :ide: COMPILE: hamilton_circle_dot_to_cv.py --onto > ...-onto.cv # polynomial // . (let* ((fname (file-name-nondirectory (buffer-file-name))) (base (file-name-sans-extension f))) (save-buffer) (compile (concat "hamilton_circle_dot_to_cv.py --onto " ffname " >" base "-onto.cv"))) // :ide: +-#+ // . Hamiltonian Circle () // :ide: DOT: this file's (generated DOT) as PS // . (let* ((args "") (do-gen nil) (outtype "ps") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (compile (concat (if do-gen gen) " dot -T " outtype " -o " outfile " " dotfile)) (let ((outbuf (find-file-noselect outfile t))) (if outbuf (kill-buffer outbuf)))) // :ide: DOT: this file's (generated DOT) as PDF // . (let* ((args "") (do-gen nil) (outtype "pdf") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (compile (concat (if do-gen gen) " dot -T " outtype " -o " outfile " " dotfile)) (let ((outbuf (find-file-noselect outfile t))) (if outbuf (kill-buffer outbuf)))) // :ide: DOT: this file's (generated DOT) as PNG // . (let* ((args "") (do-gen nil) (outtype "png") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (compile (concat (if do-gen gen) " dot -T " outtype " -o " outfile " " dotfile)) (let ((outbuf (find-file-noselect outfile t))) (if outbuf (kill-buffer outbuf)))) // :ide: DOT: this file's (generated DOT) as SVG // . (let* ((args "") (do-gen nil) (outtype "svg") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (compile (concat (if do-gen gen) " dot -T " outtype " -o " outfile " " dotfile)) (let ((outbuf (find-file-noselect outfile t))) (if outbuf (kill-buffer outbuf)))) // :ide: +-#+ // . Plain Generation () // :ide: DOT2TEX: this file's (generated DOT) // . (let* ((args "") (do-gen nil) (outtype "png") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (compile (concat (if do-gen gen) " ./dot2tex.sh " dotfile))) // :ide: COMPILE: images // . (let ((args "images")) (save-buffer) (compile (concat "make -k " args))) // :ide: INFO: Dot Shapes // . (let ((ref-buffer "*dot-shapes*")) (if (not (get-buffer ref-buffer)) (save-buffer) (shell-command (concat "w3m -dump -cols " (number-to-string (1- (window-width))) " 'http://www.graphviz.org/doc/info/shapes.html'") ref-buffer) (display-buffer ref-buffer t))) // :ide: +-#+ // . Reference / Compile / Tools () // :ide: DOTTY: this file's (generated DOT) // . (let* ((args "") (do-gen nil) (outtype "png") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (shell-command (concat (if do-gen gen) " dotty " dotfile))) // :ide: DOT: this file's (generated DOT) as PNG + display // . (let* ((args "") (do-gen nil) (outtype "png") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (compile (concat (if do-gen gen) " dot -T " outtype " -o " outfile " " dotfile " && display " outfile))) // :ide: DOT: this file's (generated DOT) as PNG + VIEW // . (let* ((args "") (do-gen nil) (outtype "png") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (shell-command (concat (if do-gen gen) " dot -T " outtype " -o " outfile " " dotfile)) (let ((outbuf (find-file-noselect outfile t))) (if outbuf (kill-buffer outbuf))) (view-file-other-window (concat base "." outtype))) // :ide: DOT: this file's (generated DOT) as SVG + VIEW // . (let* ((args "") (do-gen nil) (outtype "svg") (base (file-name-sans-extension (buffer-file-name))) (dotfile (concat base ".dot")) (outfile (concat base "." outtype)) (gen (concat "sh " (buffer-file-name) " " args " >" dotfile ";"))) (save-buffer) (shell-command (concat (if do-gen gen) " dot -T " outtype " -o " outfile " " dotfile)) (let ((outbuf (find-file-noselect outfile t))) (if outbuf (kill-buffer outbuf))) (view-file-other-window (concat base "." outtype))) // :ide: +-#+ // . Generate and view () // // Local Variables: // mode: c // eval: (snip-minor-mode) // snip-show-inactive: t // snip-mode: dot // truncate-lines: t // End: // mode: graphviz-dot

figure 8.1 dir_to_dot.sh generator output

(see figure 8.2)

@startuml /' a0 '/
skinparam padding 1
partition "dir_to_dot" {
start
floating note right
dir_to_dot.sh - recursive dot(1) graph from directory tree

usage: dir_to_dot.sh [OPTIONS] [directory]

OPTIONS
| option          | args     | description                          |
| -o, --outline   |          | only show directories                |
| -s, --skip-dots |          | do not show dot and dot-dot entries  |
| -d, --debug     |          | show directory trace                 |
| -h, --help      |          | show this help                       |
| --umlx          | RX[:TYP] | N/I extract UML diagrams matching RX |
end note
:define colors and formats;
:setup temporary directory for
- file database
- symbolic link database;
:print dot(1) file header;
partition "handle directory" {
:change to directory supplied by argument;
:* determine absolute pathnames for
  - current directory (HERE)
  - parent directory (TOP)
* and record node names in file database
* start subgraph //cluster_outside// (TOP)
* start subgraph //cluster_tree// (HERE);
:list_dir HERE TOP|
}
partition "process symbolic links" {
:analyze and connect symbolic links with their targets
* put symbolic link edges into sbugraph //link//
* put symbolic link nodes outside the directory
  tree into sbugraph //cluster_outside//;
}
:define legend;
:print dot(1) file footer;
:clean up temporary dirctory;
stop
} /' move after //stop//, if there are no subsections '/
@enduml

figure 8.2 dir_to_dot.sh generator script

(see figure 8.3)

@startuml /' a1 '/
skinparam padding 1
partition "list_dir" {
start
floating note right
usage: list_dir [OPTIONS] DIR_NODE PARENT_NODE

OPTIONS
| option          | args     | description                          |
| -o, --outline   |          | only show directories                |
| -s, --skip-dots |          | do not show dot and dot-dot entries  |
end note
    while (for each file in directory) is (do)
        if (file is a special link • or ••?) then (yes)
        partition "process • and ••" {
        if (file is the special link •?) then (yes)
            :connect DIR_NODE to itself
            with edge label •;
        elseif (file is the special link ••?) then (yes)
            :connect current DIR_NODE to
            PARENT_NODE with edge label ••;
        endif
        }
        else
        partition "initialize flags and colors" {
        if (file starts with a dot **.**?) then (yes)
            :* mark file as hidden
            * select hidden color sets;
        endif
        if (file is a symbolic link?) then (yes)
            :determine link target;
            if (link target exists?) then (yes)
                :setup regular link colors;
            else
                :setup stale link colors;
            endif
        elseif (file is directory?) then (yes)
            :setup directory colors;
        else
            :setup regular file colors;
        endif
        }
        partition "print and record file/link target in DB" {
        :* use file name as node label
        * assign node name as FILE_NODE
        * record absolute path + FILE_NODE
          in file database
        * print dot FILE_NODE definition
        * connect FILE_NODE to DIR_NODE;
        if (file is a symbolic link?) then (yes)
            :record absolute path + link target
            path in link database;
        endif
        }
        partition "handle directory recursively" {
        if (file is a directory?) then (yes)
            :* change into subdirectory
            * list_dir FILE_NODE DIR_NODE|
        endif
        }
        endif
    endwhile
    stop
}
@enduml

figure 8.3 list_dir recurvise graph generator