#!/usr/bin/perl
##
## du(1) clone: Estimate regular file usage, and file, dir and misc count
## Copyright (c) 2008-2013 SATOH Fumiyasu @ OSS Technology Corp., Japan
##               <http://www.OSSTech.co.jp/>
##
## License: GNU General Public License version 3
##

use strict;
use warnings;
use IO::File;
use File::stat;
use File::Find;
use Getopt::Long qw(:config gnu_getopt no_ignore_case no_auto_abbrev);

my $file_usage_total =
my $file_count_total =
my $dir_count_total =
my $symlink_count_total =
my $misc_count_total = 0.0;
my $file_count;
my $file_usage;
my $dir_count;
my $symlink_count;
my $misc_count;

my @exclude_files = ();
my @exclude_dirs = ();
my @exclude_symlinks = ();
my @exclude_miscs = ();

my @prune_dirs = ();

my $cmd_usage = "Usage: $0 [OPTIONS] [DIR ...]

Options:
 --exclude-file-re REGEX
 --exclude-dir-re REGEX
 --exclude-symlinks-re REGEX
 --exclude-misc-re REGEX
 --prune-dir-re REGEX
";

my %opt = ();
{
  ## Trap warning messages from Getopt::Long
  local($SIG{'__WARN__'}) = sub {
    my ($msg) = shift(@_);
    chomp($msg);
    STDERR->print("$0: ERROR: $msg\n");
  };
  GetOptions(
    'exclude-file-re=s' =>	sub {
      push(@exclude_files, qr($_[1]));
    },
    'exclude-dir-re=s' =>	sub {
      push(@exclude_dirs, qr($_[1]));
    },
    'exclude-symlinks-re=s' =>	sub {
      push(@exclude_symlinks, qr($_[1]));
    },
    'exclude-misc-re=s' =>	sub {
      push(@exclude_miscs, qr($_[1]));
    },
    'prune-dir-re=s' =>	sub {
      push(@prune_dirs, qr($_[1]));
    },
    'help' => sub {
      print $cmd_usage;
      exit(0);
    }
  ) || exit(1);
}

STDOUT->autoflush(1);

my $dir_iter = (@ARGV > 0) ?
  sub { return shift(@ARGV); } :
  sub { my $dir = STDIN->getline; defined($dir) && chomp($dir); return $dir; };

print sprintf("%16s %12s %12s %8s %8s NAME\n",
  'FILE USAGE', 'FILES', 'DIRECTORIES', 'SYMLINKS', 'MISC'
);

my $du = sub {
  my $name = $_;

  if (-l $name) {
    unless (grep {$name =~ /$_/} @exclude_symlinks) {
      $symlink_count++;
    }
  }
  elsif (-f $name) {
    unless (grep {$name =~ /$_/} @exclude_files) {
      my $stat = stat($name) || die "stat failed: $File::Find::name: $!\n";
      $file_usage += $stat->size;
      $file_count++;
    }
  }
  elsif (-d $name) {
    unless (grep {$name =~ /$_/} @exclude_dirs) {
      $dir_count++;
    }
    if (grep {$name =~ /$_/} @prune_dirs) {
      $File::Find::prune = 1;
    }
  }
  else {
    unless (grep {$name =~ /$_/} @exclude_miscs) {
      $misc_count++;
    }
  }
};

while (defined(my $dir = $dir_iter->())) {
  $file_usage = $file_count = $dir_count = $symlink_count = $misc_count = 0.0;
  File::Find::find($du, $dir);
  $file_usage_total += $file_usage;
  $file_count_total += $file_count;
  $dir_count_total += $dir_count;
  $symlink_count_total += $symlink_count;
  $misc_count_total += $misc_count;

  print sprintf("%16.0f %12.0f %12.0f %8.0f %8.0f $dir\n",
    $file_usage, $file_count, $dir_count, $symlink_count, $misc_count, $dir
  );
}

print sprintf("%16.0f %12.0f %12.0f %8.0f %8.0f TOTAL\n",
  $file_usage_total, $file_count_total,
  $dir_count_total, $symlink_count_total, $misc_count_total
);

