#!/usr/bin/perl
#
# A Simple helper script to allow (only) globbing via sudo (ie no shell expansion
# etc in the privileged context
#

use strict;
use filetest qw(access);
use List::Util qw(sum);
use Getopt::Long qw(:config no_ignore_case bundling);
use Pod::Usage;
use File::Glob qw(:bsd_glob);
use Cwd qw(abs_path);
use Data::Dumper;

# Glob Flags (& to defref because constants are actually subroutines)
my %FLAGS = (
	&GLOB_MARK   => 0,
	&GLOB_NOCASE => 0,
	&GLOB_BRACE  => 1,
	&GLOB_TILDE  => 1,
	&GLOB_NOSORT => 0
);

# Result filetype tests
my %TESTS = (
	'-F' => undef, # Regular file
	'-F' => undef, # Diretory
	'-L' => undef, # Symbolic link
	'-S' => undef, # Socket
	'-P' => undef, # Named pipe (fifo)
	'-C' => undef, # Block Special (device) file
	'-B' => undef, # Character Special (device) file 
);

my %OPTS = (
	'realpath'  => 0,
	'separator' => "\n"
);

# No easy way to dynamically reference file tests since they are
# operators rather than functions, so just build a dispatch table
# of subroutines.
my $FILETEST = {
	'-F' => sub { return -f $_[0]; },
	'-D' => sub { return -d $_[0]; },
	'-L' => sub { return -l $_[0]; },
	'-S' => sub { return -S $_[0]; },
	'-P' => sub { return -p $_[0]; },
	'-B' => sub { return -b $_[0]; },
	'-C' => sub { return -c $_[0]; }
};


GetOptions(
	# Glob options
	'm|mark!'         => \$FLAGS{&GLOB_MARK},
        'i!'              => \$FLAGS{&GLOB_NOCASE},
	'case!'           => sub { $FLAGS{&GLOB_NOCASE} = $_[1] ? 0 : 1; },
	'b|brace|braces!' => \$FLAGS{&GLOB_BRACE},
	't|tilde|tildes!' => \$FLAGS{&GLOB_TILDE},
	's|sort!'         => sub { $FLAGS{&GLOB_NOSORT} = $_[1] ? 0 : 1; },

	# File type tests
	'F|file|files!'            => \$TESTS{'-F'},
	'D|directory|directories!' => \$TESTS{'-D'},
	'L|symlink|symlinks!'      => \$TESTS{'-L'},
	'S|socket|sockets!'        => \$TESTS{'-S'},
	'P|named-pipe|named-pipes|fifo|fifos'
	                           => \$TESTS{'-P'},
	'B|block|block-special'    => \$TESTS{'-B'},
	'C|char|char-special'      => \$TESTS{'-C'},

	# Other options
	'r|realpath'   => \$OPTS{'realpath'},
	'0|null'       => sub { $OPTS{'separator'} = "\0"; },
	'd|separator=s'  => \$OPTS{'separator'},
	'h|help|usage' => sub { pod2usage(-verbose=>2, -exitval=>2); }

) or die('Invalid options');


{ 
	my $_GLOB_FLAGS = sum( grep { $FLAGS{$_} } (keys %FLAGS) );
	

	my @tests = grep { defined $TESTS{$_} } (keys %TESTS);

	my @matches = ();
	foreach (@ARGV) {
		push(@matches, bsd_glob($_, $_GLOB_FLAGS & ~GLOB_NOSORT));
	}

	# Filter by filetest(s)
	foreach my $test (@tests) {
		@matches = grep { $FILETEST->{$test}->($_) == $TESTS{$test} } @matches;
	}

	# Realpath resolution?
	if ($OPTS{'realpath'}) {
		@matches = map { abs_path($_) } @matches;
	}

	# Sort the results?
	if ($FLAGS{&GLOB_NOSORT}) {
		@matches = sort @matches;
	}

	print join($OPTS{'separator'}, @matches);
	print $OPTS{'separator'} if @matches;
}
	
__END__

=head1 NAME

glob - expand shell globs

=head1 SYNOPSIS

glob [options] [tests] [--] [pattern ...]

Options:

  -m|--[no-]mark      Mark directory results by appending a trailing slash
  -i|--[no-]case      Match glob patterns case insensitively
  -b|--[no-]brace[s]  Perform brace expansion on patterns (eg a{a,b} => aa ab)
  -t|--[no-]tilde[s]  Perform tilde (~ or ~<username>) expansion on patterns
  -s|--[no-]sort      Sort the results
  -r|--realpath       Resolve symbolic links etc in the results 
  -0|--null           Set the output separator to null (\0) instead of newline
  -d|--separator=     Set the output separator to the specified string
  -h|--help|--usage   Display this help message then exit

Tests:

  -F|--[no]-file[s]                       Include or exclude regular files
  -D|--[no]director(y|ies)                Include or exclude directories
  -L|--[no-]symlink[s]                    Include or exclude symbolic links
  -P|--[no-]named-pipe[s]|--[no-]fifo[s]  Include or exclude named pipe (fifo) files
  -B|--[no-]block[-special]               Include or exclude block device files
  -C|--[no-]char[-special]                Include or exclude character device files

=head1 DESCRIPTION

B<glob> will expand shell patterns provided on the command line and print any matching path names separated by newlines

=cut
