#/usr/bin/perl use strict; use warnings; use File::Basename qw/basename/; use Getopt::Long; use vars qw/$nodes $max_depth $VERSION/; $VERSION = "1.0"; sub give_help { my $err = shift; if ($err) { chomp $err; $err = "Err: $err\n\n"; print $err; } print <. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Version $VERSION. sizetree - create HTML trees of file/directory tree sizes Usage: sizetree.pl [OPTIONS] -d DIRECTORY [-d DIRECTORY ...] -d DIRECTORY --directory DIR Specify an additional directory to be scanned. -nf --nofiles Do not include individual files in the diagram. -m MAXDEPTH Specify the maximum directory depth to be included --maxdepth MAX in the diagram. -h --help Display this help screen. -o FILE --output FILE Specify output filename. -nr --noroot Do not include virtual scan root in diagram. -j Join scan directories into virtual scan root. --join (Overrides -nr) HERE exit; } sub dec_to_hex { my $dec = int shift; my $hex = ''; my @digits = (0..9, 'A'..'F'); while ($dec) { $hex .= $digits[($dec % 16)]; $dec = int($dec/16); } $hex = reverse $hex; return $hex; } sub sum { my $sum = 0; $sum += $_ for @_; return $sum; } sub commify { my $int = shift; my $sep = shift || '.'; my @ary = reverse split //, $int; $int = ''; while (@ary) { $int .= (length $int > 0 ? $sep : '') . join('', splice @ary,0,3); } $int = reverse $int; return $int; } sub recurse_tree { my $file = shift; give_help("An error occurred getting file stats.") if not defined $file; my $depth = shift; $depth ||= 1; $max_depth = $depth if $depth > $max_depth; $nodes++; if (-f $file) { return [basename($file), -s $file]; } elsif (-d $file) { opendir my $dh, $file or give_help("Error opening '$file': $!"); my $ary = [basename($file), 0]; while (my $f = readdir $dh) { next if $f =~ /^\.\.?$/; push @$ary, recurse_tree($file.'/'.$f, $depth+1); $ary->[1] += $ary->[-1][1]; } closedir $dh; return $ary; } else { give_help("Something's not a directory nor a file. ($file)"); } } sub recurse_sort { my $tree = shift; ref $tree eq 'ARRAY' or return; my @head = splice @$tree, 0, 2; @$tree = sort {$b->[1] <=> $a->[1]} @$tree; map {recurse_sort($_)} @$tree; unshift @$tree, $head[1]; unshift @$tree, $head[0]; } sub walk_tree { my $tree = shift; my $callback = shift; my $depth = shift; my $cur_node = 0; my $walker; $walker = sub { my $tree = shift; my $callback = shift; my $depth = shift; $cur_node++; $depth ||= 1; $callback->($tree->[0], $tree->[1], $depth, $cur_node, (@$tree==2?0:1)); return if @$tree == 2; foreach (2..$#{$tree}) { $walker->($tree->[$_], $callback, $depth+1); } }; $walker->($tree, $callback, $depth); } my $dirs_only = 0; my $max_depth_to_display = 0; my $help_required = 0; my $output_filename = 'sizetree.html'; my $do_not_display_root = 0; my $join_directories = 0; my @dirs; GetOptions( 'd|dir|directory=s' => \@dirs, 'nf|nofiles' => \$dirs_only, 'm|maxdepth=i' => \$max_depth_to_display, 'h|help|?' => \$help_required, 'o|output=s' => \$output_filename, 'nr|noroot' => \$do_not_display_root, 'j|join' => \$join_directories, # implies root ); if ($help_required) { give_help(); } if (not @dirs) { give_help("You need to specify at least one search root directory."); } open my $fh, '>', $output_filename or give_help("Error opening output file: $!"); print $fh <<'HERE'; Directory Size Index HERE use Data::Dumper; my $global_data = ['/', 0]; $nodes = 0; $max_depth = 1; foreach my $dir (@dirs) { my $data = recurse_tree($dir); recurse_sort $data; push @$global_data, $data; } $max_depth++; print "Nodes: $nodes\nMaximum depth: $max_depth\n"; $global_data->[1] = sum( map {$_->[1]} @{$global_data}[2..$#{$global_data}] ); recurse_sort $global_data; my $largest = $global_data->[1]; my $callback = sub { my $name = shift; my $size = shift; my $cur_depth = shift; my $cur_node = shift; my $is_dir = shift; return if $dirs_only and not $is_dir; return if $max_depth_to_display and $max_depth_to_display < $cur_depth; my $width = $cur_depth * 15; my $columns = $cur_depth*2 - 1; my $cols_left = $max_depth*2-$columns; my $class_other = ' class="other"'; my $class_dir = ' class="dir"'; my $class_file = ' class="file"'; my $class = ($is_dir?$class_dir:$class_file); my $red = int( 255 * ($size/$largest)**.2 ); my $green = 255 - $red - 20; $green = 0 if $green < 0; $green = sprintf "%02s", dec_to_hex( $green ); $red = sprintf "%02s", dec_to_hex( $red ); my $bar_width = int( 100*($size/$largest)**.3 ) + 1; my $size_commified = commify($size); print $fh <   $size_commified $name HERE }; if ($join_directories) { $max_depth--; splice @{$global_data}, 2, $#{$global_data}, map { @{$_}[2..$#$_] } @{$global_data}[2..$#{$global_data}]; recurse_sort $global_data; walk_tree( $global_data, $callback, ); } elsif ($do_not_display_root) { $max_depth--; my @sets = @{$global_data}[2..$#{$global_data}]; foreach my $data (@sets) { walk_tree( $data, $callback, ); } } else { walk_tree( $global_data, $callback, ); } print $fh <<'HERE';
HERE