7 symlinks2dot - Generate a graph in dot(1) format representing the symlink-target relations among the given files
12 use Cwd qw
/getcwd realpath/;
19 %shape_by_type = (qw
/symlink diamond dir box missing none normal oval/);
25 # reduce multiple slashes
27 # strip trailing slash
28 $path =~ s{(.)/$}{$1}g;
35 # reduce self-dir elements
36 $path =~ s{/\.(/|$)}{/}g;
37 # reduce root-parent elements
38 1 while $path =~ s{^/\.\.(/|$)}{/};
44 return normalize_path_2
(normalize_path_1
(shift));
50 if(-l
$path){ return "symlink" }
51 elsif(-d
$path){ return "dir" }
52 elsif(!-e
$path){ return "missing" }
53 else { return "normal" }
60 my @elems = split /\//, $path;
61 for my $depth (1 .. ($#elems-1))
63 push @ancestors, join '/', @elems[0..$depth];
68 sub add_symlink_target
73 my $target = readlink $path;
77 $target_abs = $target;
81 $target_abs = dirname
($path) . '/' . $target;
83 $target_abs = normalize_path
($target_abs);
84 $hashref->{$target_abs} = 1 unless exists $hashref->{$target_abs};
90 if($path !~ /^\//) { $path = getcwd . '/' . $path; }
91 $path = normalize_path($path);
92 $paths_to_traverse{$path} = 1;
93 $paths_given_at_cli{$path} = 1;
100 while(grep {$paths_to_traverse{$_}} keys %paths_to_traverse)
102 for my $path (grep {$paths_to_traverse{$_}} keys %paths_to_traverse)
104 for my $ancestor (get_ancestors($path))
106 my $basename = basename($ancestor);
107 if($basename eq '..')
109 # we don't get here via symlinked dirs
, because we stop walking through the path
when a
symlink is found
, see below
.
110 # so it's safe to go back the same path where we got here.
111 $ancestor =~ s{/[^/]+/\.\.$}{};
114 $node{$ancestor} = {} unless exists $node{$ancestor};
118 add_symlink_target
($ancestor, \
%paths_to_traverse);
119 $soft_links{$ancestor} = 1;
124 if($paths_given_at_cli{$path})
126 $node{$path}->{'emphasis'} = 1;
131 add_symlink_target
($path, \
%paths_to_traverse);
134 $paths_to_traverse{$path} = 0;
142 print "digraph \"symlinks\" {
146 node [fontsize=10,width=.1,height=.1];";
148 for my $path (keys %node)
150 $node{$path}->{'basename'} = basename
($path);
153 for my $path (sort {$node{$a}->{'basename'} cmp $node{$b}->{'basename'}} keys %node)
155 my $basename = $node{$path}->{'basename'};
156 my $shape = $shape_by_type{gettype
($path)};
158 if($node{$path}->{'emphasis'}) { $attrs .= ",style=\"bold\""; }
159 print "\"$path\" [label=\"$basename\",shape=\"$shape\"$attrs];"
165 my @same_rank_nodes = grep {scalar(split /\//, $_) == $level} keys %node;
166 last if scalar @same_rank_nodes == 0;
167 if(scalar @same_rank_nodes > 1)
169 print " { rank=\"same\";";
170 for my $path (@same_rank_nodes)
179 for my $child (keys %solid_edge)
181 my $parent = $solidedge{$child};
183 if(!-l
$child and !-e
$child) { $attrs .= ",style=\"dashed\""; }
184 print "\"$parent\" -> \"$child\" [color=\"black\"$attrs]";
187 for my $symlink_node_path (keys %soft_links)
189 my $target_node_path = $soft_links{$symlink_node_path};
191 #TODOif($target !~ /^\// or !$node{$target_abs_path}->{'is_physical'}) {
192 $attrs .= ",label=\"$target\"";
194 if(!-e
$symlink_node_path) { $color = "red"; }
195 print "\"$symlink_abs_path\" -> \"$target_node_path\" [color=\"$color\",fontcolor=\"$color\"$attrs]";