in reply to Directory tree explorer with stats reporting

Updated code follows to use File::Spec especially for those who are having problem on non-Unix operating systems; please test & report. Additionally, it binds 'q' to quit the Tk window, and displays the size in more appropriate units (see the BEGIN block).

Update (some minutes later): Explicitly use lstat to avoid the issue of circular|dangling links. It also reduces an extra stat call in alternate branch of if ( -d _ && ! -l _ ) { ... } else{ ... }.

Update (a few hours later): Move $tree->entryconfigure() outside of if ... else; suppress file & directory display if respective count is 0; removed variables which were used to calculate geometry of the window (which was dependent on font used|available and screen resolution (i think)).

Update (a few hours & some minutes later): Sort the enteries; move path stack joining in its own function; make the recursion, in buildSubTree(), to be in alternate branch of if ... else.

Update (next day's evening): Add key binding (enter|return key) to toggle collpase|expansion of a subtree; initially expand the tree of given directory w/ children collapsed; plus some otherwise minor changes.

Sep 19 2008 Update: Added key binding in help text.

#!perl # This is a modified copy of the directory explorer code posted on # PerlMonks by 'GrandFather' at ... # # http://perlmonks.org/index.pl?node_id=535607 # http://perlmonks.org/index.pl?displaytype=print;node_id=535607;repl +ies=1 use warnings; use strict; use Tk; use Tk::Tree; use Tk::Font; use File::Spec; BEGIN { # Tk-related variable(s). our ( $node_sep ) = ( '/' ); sub stack_as_string { return join $node_sep , ref $_[0] ? @{ $_[0] } : @_; } # Number of bytes. my %units = ( 1 => 'B' , 1024 => 'KB' , 1024 * 1024 => 'MB' , 1024 * 1024 * 1024 => 'GB' , 1024 * 1024 * 1024 * 1024 => 'TB' ) ; my @ordered = sort { $a <=> $b } keys %units; # Convert size in bytes to a unit (upto TB) appropriate for the ord +er of # the size. sub size_in_xbyte { my ( $size ) = @_; return 'UNKNOWN SIZE' unless defined $size; my $factor = $ordered[0]; foreach my $u ( @ordered ) { $size < $u and last; $factor = $u; } # Use number of bytes (B) as is (knowing that $factor will be 1). my $format = $factor != $ordered[0] ? '%0.1f %s' : '%0d %s'; return sprintf $format, $size / $factor, $units{$factor}; } } our ( $node_sep ); my $tree_start = 1; my $path = shift; ShowHelp() unless defined $path; # In File::Spec pod, there is no constructor method noted, nor is 'si +mple # use' defined for which functional forms of methods are available. +Missing # also is a list of exported function. my $fspec = 'File::Spec'; $path = $fspec->canonpath($path); ShowHelp( -2 , "Error finding folder $path\n\n" ) unless -d $path; my $main = MainWindow->new( '-title' => "Folder stats for $path" ); # Adjust as you like. $main->geometry( '900x700' ); my $tree = $main->ScrlTree ( '-font' => $main->Font( 'systemfont' ) , '-itemtype' => 'text' , '-separator' => $node_sep # Once, having scrollbars-only-when-needed-option, 'o', did not m +ake the # scrollbars appear when the content overflows display area (Free +BSD # 6-STABLE & Tk-804.027, Tk 8.4.11.2). Now (Sun Mar 12 13:18:18 U +TC 2006) # optional scrollbars are behaving as advertised. And i do not k +now # why|how! , '-scrollbars' => 'osow' ) ->pack( '-fill' => 'both' , '-expand' => 1 ) ; my @node_stack = ($tree_start); my $node = $tree_start; $tree->add( $node , '-text' => $path ); my ( $subDirCount , $subFileCount , $subTotalSize ) = buildSubTree( $tree , $path , \@node_stack ); $tree->entryconfigure ( $node , '-text' => annotate( $path , $subTotalSize , $subDirCount , $subFile +Count ) ); $tree->autosetmode; collapse_nodes( $tree ); $tree->open( $node ); $main->bind( '<KeyPress-q>' , sub { exit ; } ); $tree->bind( '<KeyPress-Return>' , \&toggle_tree_expand ); $tree->focus; MainLoop; # Key binding function to expand|collapse a subtree. sub toggle_tree_expand { my $root = $tree->info( 'selection' ); return unless defined $root && length $root; my @nodes = $tree->info( 'children' , $root ); return unless scalar @nodes; # $tree->open() does not (sometimes) expands a tree, but $tree->clo +se() does # collapse an open()'d tree. # -- # Handle both open & close cases for now. my ( $meth ); foreach ( @nodes ) { $meth = $tree->info( 'hidden' , $nodes[-1] ) ? 'show' : 'hide'; $tree->setmode( $root , $meth eq 'show' ? 'close' : 'open' ); $tree->$meth( 'entry' , $_ ); } $tree->update; } sub collapse_nodes { my ( $tree , $node , $collapse ) = @_; my @subnodes = $tree->info( 'children' , $node ); return unless scalar @subnodes; foreach my $n ( @subnodes ) { collapse_nodes( $tree , $n , 1 ); $tree->hide( 'entry' , $n ) if $collapse; } $tree->setmode( $node , 'open' ) if $node; } sub buildSubTree { my ( $tree , $path , $stack ) = @_ ; my ( $dirCount , $fileCount , $sizeTotal ) = (0) x3 ; push @$stack , $tree_start; opendir my $DH , $path or die "Cannot open $path: $!\n"; foreach my $dir ( sort readdir $DH ) { next if $dir eq $fspec->updir or $dir eq $fspec->curdir; # Don't know what will happen if catfile() is substitued w/ catdi +r() # on non-Unix operating systems. my $path = $fspec->catfile( $path , $dir ); my $node = stack_as_string( $stack ); $tree->add( $node , '-text' => $path ); my ( $size , $dirs , $files ); # Use lstat to avoid chasing circular|dangling symbolic links. lstat $path; if ( ! -d _ ) { ++$fileCount; $size = -s _; $sizeTotal += $size if defined $size; } else { ( $dirs , $files , $size ) = buildSubTree( $tree , $path , $stac +k ); $dirCount += $dirs + 1; $fileCount += $files; $sizeTotal += $size; } $tree->entryconfigure ( $node , '-text' => annotate( $path , $size , $dirs , $files ) ); ++$stack->[-1]; } closedir $DH or die "Cannot close $path: $!\n"; pop @$stack; return ( $dirCount , $fileCount , $sizeTotal ); } sub annotate { my ( $path , $size , $dirs , $files ) = @_; return join q// , $path , "\t(" , size_in_xbyte( $size ) , ( map { !$_->[0] ? () : sprintf ', %s %s%s' , @{$_} , count_to_plural_suffix( $_-> +[0] ) } [ $dirs , 'dir' ] , [ $files , 'file' ] ) , ')' ; } sub count_to_plural_suffix { return $_[0] > 1 ? 's' : ''; } sub ShowHelp { my $exitValue = 0; $exitValue = shift if defined $_[0] and $_[0] =~ /^[-+]?\d+$/; print $_ while $_ = shift; print <<HELP; FolderStats scans a directory tree starting at the folder given on the command line and generates an explorer like tree giving folder content stats such as number of files, their total size, and the count and sizes of sub-folders. Note that the statistics are not dynamically updated as files and folders are altered on disk. Usage: FolderStats <root folder> Key Binding: q: quits the program. Enter: expands or collapses a tree. Up, Down: Vertically move up or down. arrows: Left, Right: Move one level up or down. arrows: HELP exit( $exitValue || -1 ); }

Replies are listed 'Best First'.
Re^2: Directory tree explorer with stats reporting
by GrandFather (Saint) on Mar 12, 2006 at 01:58 UTC

    Thanks for the improvements. A couple of minor things:

    count_to_plural_suffix would be better return $_[0] != 1 ? 's' : ''; In English - 0 apples, 1 apple, many apples.

    The new display size is nice for general purpose, but for the application that catalysed the code I needed the exact number of bytes. Just shows, you can't write code to suit everyone. :)


    DWIM is Perl's answer to Gödel

      I personally like seeing "0 X" than to "0 Xs" where things are being counted like in this case. (Or, just do away w/ the count_to_plural_suffix). But as you said, "you can't...".

      I have been myself in situation where i wanted to see the exact number of bytes (or rather in 512-blocks), so no argument there. (I suppose "more appropriate units" caused your response?:)

      This is my first experience w/ Tk, otherwise i would have added some more key bindings (namely rebuilding the tree on request) and options to (interactively) modify the display. Slowly, but surely, one day ...

        "0 apple" isn't conventional English usage. In Russian useage I understand that there are three plural cases - perhaps you could redo the user interface in Russian? :)

        Not sure I noticed "more appropriate units", not to the point of objecting anyway. More comment concerning units was a "more than one way to use it" type comment. I'll likely change my version to insert comas however to make the numbers more readable. Perhaps we need to add a configuration menu?

        I use Tk in a fairly light weight way fairly often for simple GUI apps such as this. I guess I didn't spend much more than an hour on the original code before I posted it - post early and often they say.


        DWIM is Perl's answer to Gödel