#!/usr/bin/perl 
# dim ( Directory IMage ) v. 1.1
# Copyright 2001 by Matt Chisholm - matt@theory.org
# 
# changelog:
# v. 1.0 first release
# v. 1.1 sep 19 2001 added:
#        -s display file-size 
#        -b display in bytes
#        -k display in kilobytes
#        -m display in megabytes
#        added support for rendering multiple directories in a single command 
#
# TODO: 
#        -d date support
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

use strict;
#use Term::ReadKey;
use Getopt::Std;
use vars qw( $color $DEBUG $opt_a $opt_l $opt_h $opt_s $opt_k $opt_b $opt_m);
$DEBUG = 0;

&main();

sub main {
  getopts('alhsbkm');
  $color = Color->new();                                # read colors
  my @dirs = ();
  if ($opt_b or $opt_k or $opt_m ) { $opt_s = 1; }      # display size...

  map { push @dirs, $_ if ($_ !~ m/^-/); } (@ARGV);     # eliminate - opts
  
  if ($opt_h) { help() };

  if ($#dirs == -1) {                                   # no dirs listed
    showdir( "." );
  } elsif ($#dirs == 0) {                               # one dir listed
    showdir( $dirs[0] ) ;
  } else {
    foreach my $dir (@dirs) {                           # multiple dirs listed
      print "$dir:\n";
      showdir( $dir );
    }
  }
} 

sub help {
  print "dim ( Directory IMage ) v. 1.1
Copyright 2001 by Matt Chisholm - matt\@theory.org

-a list all files
-h show this help 
-l list mode (for future development) - currently lists total # of files
-s display file-size intelligently
-b display file-size in bytes
-k display file-size in kilobytes
-m display file-size in megabytes
";
exit;
}

sub showdir {
  my $dir = shift;
  
  opendir DIR, $dir;                                      # read directory
  my @dir = readdir( DIR ); 
  closedir DIR;
  
  if ($opt_l) {print "total ", $#dir-1, "\n";}
  if ($#dir<2) { exit; }
  
  if($DEBUG){ print "@dir", join ( ", ", @dir ), "\n" ;}
  
  my @file = ();
  foreach $_ (0..$#dir) {                                 # prune directory
    if (!(($dir[$_] =~ m/.+\~$/) or ($dir[$_] =~ m/^\./)) or ( $opt_a )) { 
      push @file, $dir[$_];
    }
  }

  if($DEBUG){ print "@file", join ( ", ", @file ), "\n";}
  
  my $tabl = Table->new( \@file, $dir, " ", "" );         # make table

  if($DEBUG){ $tabl->printt();  print "-"x 80, "\n";}

  $tabl->collapsetable();                                 # collapse table
  my $t = $tabl->printt();
  if($DEBUG){ print $tabl->{c}, "chars / entry\n"; }
  print render( $t, $#{$t}, (($tabl->{c}+2)*$tabl->{e})); # render table to terminal
}

sub termsize {
  # This lifted from /usr/lib/perl5/5.005/i386-linux/Term/ReadKey.pm
  # For some reason not all systems have this module...
  my @results;
  my($prog) = "resize";
                
  # Workaround for Solaris path sillyness
  if(-f "/usr/openwin/bin/resize") { $prog = "/usr/openwin/bin/resize"}
  
  my($resize) = scalar(`$prog`);
  if($resize =~ /COLUMNS\s*=\s*(\d+)/ or 
     $resize =~ /setenv\s+COLUMNS\s+'?(\d+)/)  {
     $results[0] = $1;
     if( $resize =~ /LINES\s*=\s*(\d+)/ or
	 $resize =~ /setenv\s+LINES\s+'?(\d+)/) {
	 $results[1] = $1;
      } else {
	@results = ();
      }
   } else {
     @results = ();
   }
  return @results;
}

sub render {
#  $DEBUG = 1;
  my ($list, $lh, $lw) = @_;
  my ($sw, $sh) = termsize();

#  my ($sw, $sh, undef, undef) = Term::ReadKey::GetTerminalSize STDOUT; 
#  my $sw = $ENV{COLUMNS};
#  my $sh = $ENV{LINES};
#  my $sw = `echo \$COLUMNS\n`;
#  my $sh = `echo \$LINES\n`;
 
  my $return;

  if($DEBUG){ print "screen width $sw line width $lw\n"; }

  if ($lh > $sh ) {          # if the table to render is longer than the screen
    my $cols = $sw / $lw ;
    my $cram = 0;
    if($DEBUG){ print "cols: $cols\n"; }
    $cols = int( $cols );
    if ($cols == 0) { $cols = 1; $cram = 1;}

    my $rows = int( $lh / $cols )+1;    
    if($DEBUG){ print "$cols columns\t$rows rows\n"; }
    
    my @start = ();
    
    foreach $_ ( 0 .. $cols ) {
      push @start, ($_*$rows);
    }
    if($DEBUG){ print join( ", ", @start), "\n"; }

    foreach my $sr ( 0 .. $rows-1 ) {
      if($DEBUG){ $return .= $sr; }
      foreach my $sc ( @start ) {
	$return .= $list->[$sr+$sc];
      }
      if ($cram) { $return =~ s/\ +\n$/\n/; } # try to cram the render into small space
      $return .= "\n";
    }
  } else {
    map { s/\ +$//g; $return .= $_."\n" ; } @{$list};
  } 
  return $return;
}
 
sub size {
  my $s = shift;

  if ($opt_b) {                # list bytes
    return $s."b";
  } elsif ($opt_k) {           # list kilobytes
    $s = int( $s / 1000 );
    return $s."k";
  } elsif ($opt_m) {           # list megabytes
    $s = int( $s / 1000000 );
    return $s."m";
  }

  my $l = length( $s );        # list sizes intelligently
  if ($l <= 2){                # Xb, XXb
    return $s."b";
  } elsif ( $l <= 3 ){         # .Xk
    $s = int( $s / 100 );    
    return ".".$s."k";
  } elsif ( $l <= 5 ){         # Xk, XXk
    $s = int( $s / 1000 );
    return $s."k";
  } elsif ( $l <= 6 ){         # .Xm
    $s = int( $s / 100000 ); 
    return ".".$s."m"; 
  } else {                     # Xm, XXm, XXXm, &c. 
    $s = int( $s / 1000000 );
    return $s."m";
  }
}


package Color;    

sub new {
  my $pkg = shift; 
  my @col = split ":", $ENV{LS_COLORS};
  my %col= ();
  map {
    my ($pat, $col) = split "=", $_;
    $col{$pat} = $col;
    if($main::DEBUG){print "$pat => $col\t" };
  } @col;

  bless \%col, $pkg;
}

sub ize {                  
  my ($self, $name) = @_;
  if($main::DEBUG){print "\"$name->{e}\" ==> ",$self->{"*.".$name->{e}},"\n",$name->{n}.".".$name->{e}, "\t";}
  my $path = $name->path();
  if ( -d $path ) { return $self->{di}; }
  if ( -l $path ) { return $self->{ln}; }
  if ( -p $path ) { return $self->{pi}; }
  if ( -S $path ) { return $self->{so}; }
  if ( -b $path ) { return $self->{bd}; }
  if ( -c $path ) { return $self->{cd}; }
#  if (( -l $path ) and (!( -e $path ))) { return $self->{or}; }
  if ( -x $path ) { return $self->{ex}; }
  return $self->{"*.".$name->{e}};
}

package Table;

sub new { 
  my ($pkg, $list, $dir, $itemsep, $linesep) = @_;
  my $i = 0;
  my $cellsize = 0;
  my $names = ();
  my $exts = ();
  my %names = ();
  my %exts = ();
  my $tabl = ();

  if (!defined($itemsep)) { $itemsep = " "; }                 # item separator
  if (!defined($linesep)) { $linesep = "|\n"; }               # line separator

  foreach $_ ( @{$list} ) {
    $_ = Name->new( $_, $dir);
    $names{$_->{n}} = 1;
    $exts{$_->{e}}  = 1;
    if ($_->{l} > $cellsize){ $cellsize = $_->{l};}#calculates longest filename
    if($main::DEBUG){ print "new elt $_->{n}, $_->{e}\n"; }
  }

  if($main::DEBUG){ print "cell size=$cellsize\n";}

# No longer necessary 
#  delete $names{""};                                     # eliminates .dotfiles 

  map { $names{$_} = $i ++; } (sort keys %names);        #  sorts names
  $names = $i;                                           # counts names 
  $i = 0;
  map { $exts{$_} = $i ++; } (sort keys %exts);          #  sorts extensions
  $exts = $i;                                            # counts extensions

  if($main::DEBUG){  print %names, "\n"; print %exts, "\n";}

  foreach $_ ( @{$list} ) {          
    $tabl->[ $names{$_->{n}} ][ $exts{$_->{e}} ] = $_;   # creates table 
    if($main::DEBUG){ print  $tabl->[ $names{$_->{n}} ][ $exts{$_->{e}} ];}
  }

  bless {
	 t => $tabl,
	 n => $names,
	 e => $exts,
	 c => $cellsize,
	 l => $linesep,
	 i => $itemsep,
	 d => $dir,
	}, $pkg; 
}

sub collapsetable {
  my $self = shift;
  my $i = 1;         # must start from 1 to avoid trying to collapse row 0 and col 0 
  my $maxname = 0;
  my $maxext  = 0;

  # the collapse-table loop alternates between collapsing a row, and a column. 
  # there may be more intelligent ways to do this.
  while (( $i <= $self->{e} ) or ( $i <= $self->{n} )) { 
    if ($i <= $self->{n} ) {
      my $j = $self->collapsename( $i );                  # collapse row i 
      if ($j > $maxname) { $maxname = $j ; }
    }
    if ($i <= $self->{e} ) {
      my $j = $self->collapseext( $i );                   # collapse col i
      if ($j > $maxext) { $maxext = $j ; }
    }
    $i++;
  }
  if($main::DEBUG){ print "$maxname, $maxext\n";}
  $self->{n} = $maxname + 1;
  $self->{e} = $maxext + 1;
}

sub collapsename {
  my ($self, $i) = @_;
  my $name = $self->calcnamefill( $i );                   # determine filled entries in row i
  my $j = $i;
  while ($self->nameempty( $j-1, $name ) and ($j > 0) ) { # look back to find empty previous rows
    $j-- ;
  } 
  if ( $j < $i ) {                                        # move filled entries in row i to row j 
    $self->movename( $i, $j, $name ); 
  }
  return $j;
}

sub collapseext {
  my ($self, $i) = @_;
  my $ext = $self->calcextfill( $i );                     # determine filled entries in col i
  my $j = $i;
  while ($self->extempty( $j-1, $ext ) and ($j > 0) ) {   # look back to find empty previous cols
    $j-- ;
  }
  if ( $j < $i ) {                                        # move filled entries in col i to col j
    $self->moveext( $i, $j, $ext ); 
  }
  return $j;
}

sub calcnamefill {
  my ( $self, $i ) = @_;
  my $r = 0;
  my @list = ();
  while ( $r <= $self->{e} ) {
    if ( defined( $self->{t}->[$i][$r] )) {
      push @list, $r;
    }
    $r++;
  }
  return \@list;
}

sub calcextfill {
  my ($self, $i) = @_;
  my $r = 0;
  my @list = ();
  while ( $r <= $self->{n} ) {
    if ( defined( $self->{t}->[$r][$i] )) {
      push @list, $r;
    }
    $r++;
  }
  return \@list;
}

sub nameempty {
  my ($self, $i, $list) = @_;
  foreach my $r ( @{$list} ) {
    if ( defined( $self->{t}->[$i][$r] )) { return 0; }
  }
  return 1;
}

sub extempty { 
  my ($self, $i, $list) = @_;
  foreach my $r ( @{$list} ) {
    if ( defined( $self->{t}->[$r][$i] )) { return 0; }
  }
  return 1;
}

sub movename {
  my ($self, $i, $j, $list) = @_;
  foreach my $r ( @{$list} ) {
    $self->{t}->[$j][$r] = $self->{t}->[$i][$r] ;
    $self->{t}->[$i][$r] = undef;
  }
}
    
sub moveext {
  my ($self, $i, $j, $list) = @_;
  foreach my $r ( @{$list} ) {
    $self->{t}->[$r][$j] = $self->{t}->[$r][$i] ;
    $self->{t}->[$r][$i] = undef;
  }
}

sub printt {
  my $self = shift;
  my $i = 0;   my $j = 0;
  my @return = ();

  if($main::DEBUG){  print $self->{e}, $self->{n},$self->{c},"\n";}
  while ($j < $self->{n} ) {
    my $return;
    if($main::DEBUG){ print "$j ";}
    while ($i < $self->{e} ) {
      if($main::DEBUG){print "$i ";}
      if (defined ( $self->{t}->[$j][$i] )){
	my $item = $self->{t}->[$j][$i];
	my $out  = $item->printn();
	$return .= $out . " "x(($self->{c})-$item->{l});
      } else {
	$return .= (" "x($self->{c}));
      }
      $return .= $self->{i};
      $i++;
    }
    $i = 0;
    $return .= $self->{l};#"\n";
    push @return, $return;
    $j++;
  }
  return \@return;
}

sub printt2 {
  my $self = shift;
  my $i = 0;   my $j = 0;
  my $return; 
  if($main::DEBUG){  print $self->{e}, $self->{n},$self->{c},"\n";}
  while ($j < $self->{n} ) {
    if($main::DEBUG){ print "$j ";}
    while ($i < $self->{e} ) {
      if($main::DEBUG){print "$i ";}
      if (defined ( $self->{t}->[$j][$i] )){
	my $item = $self->{t}->[$j][$i];
	my $out  = $item->printn();
	$return .= $out . " "x(($self->{c})-($item->{l}));
      } else {
	$return .= (" "x($self->{c}));
      }
      $return .= $self->{i};
      $i++;
    }
    $i = 0;
    $return .= $self->{l};
    $j++;
  }
  return $return;
}

package Name;

sub new {
  my ($pkg, $str, $dir ) = @_;
  my @dots = split /\./, $str;
  my $name = shift @dots; 
  my $ext = pop @dots;
  my $color = undef;
  if (@dots) {
    $name .= ".".join( ".", @dots );
  }
  if($main::DEBUG){ print "new name: $name, $ext\n"};
  my $size = " ".main::size((stat( $dir."/".$str ))[7]) if ($main::opt_s);

  bless {
	 n => $name, 
	 e => $ext, 
	 c => $color,
	 l => length($str)+length($size),
	 d => $dir,
	 s => $size
	}, $pkg;
}

sub name {
  my $self = shift;
  if ( defined $self->{e}) {
    return $self->{n}.".".$self->{e}
  } else {
    return $self->{n};
  }
}

sub path { 
  my $self = shift;
  return $self->{d}."/".$self->name();
}

sub printn {
  my $self = shift;
  my $ret = "\033[".$main::color->ize( $self ).";9m".$self->name()."\033[0m".$self->{s};
  return $ret;
}



syntax highlighted by Code2HTML, v. 0.9.1