#!/usr/bin/perl
use strict;
use warnings;

# listdir_seq
# This script lists the files in the specified directory
# but shows numerical sequences as one item.
# E.g. the files file1.txt, file2.txt, file3.txt would show as file[1-3].txt
# Cameron Hayne (macdev@hayne.net)  September 2004
#                                   bug fix: September 2007

# If no command-line argument, use the current directory
my $dirname = scalar(@ARGV) >= 1 ? $ARGV[0]: ".";

# function declarations
sub read_directory($);
sub group_files(@);
sub print_groups(%);

MAIN:
{
    my @filenames = read_directory($dirname);
    print_groups(group_files(@filenames));
}
exit;


#----- FUNCTIONS -----

sub read_directory($)
{
    my ($dirname) = @_;

    my @filenames = ();
    opendir(DIR, $dirname) or die "can't opendir $dirname: $!";
    while (defined(my $filename = readdir(DIR)))
    {
        next if $filename =~ /^\.\.?$/;     # skip . and ..
        push(@filenames, $filename);
    }
    closedir(DIR);
    return @filenames;
}

sub group_files(@)
{
    my (@filenames) = @_;

    my %groups = ();
    foreach my $name (@filenames)
    {
        my ($start, $end, $number);
        if ($name =~ /^(.*\D)(\d+)(\D*)$/)
        {
            $start = $1;
            $number = $2;
            $end = $3;
        }
        else # filenames without a number in them
        {
            $start = $name;
            $end = "";
        }

        my $key = "$start$end";
        my $group = $groups{$key};
        unless ($group)
        {
            my @numbers = ();
            $group =
            {
                start    => $start,
                end      => $end,
                numbers  => \@numbers,
                noNumber => 0,
            };
            $groups{$key} = $group;
        }

        if (defined($number))
        {
            my $numbers_ref = $group->{numbers};
            push(@$numbers_ref, $number);
        }
        else
        {
            $group->{noNumber}++;
        }
    }

    return %groups;
}

sub get_number_ranges(@)
{
    my (@numbers) = @_;

    my @ranges = ();
    my $low = undef;
    my $prev = undef;
    my $range = undef;
    foreach my $n (sort {$a <=> $b} @numbers)
    {
        if (!defined($prev) or $n != ($prev + 1))
        {
            push(@ranges, $range) if defined($range);
            $low = $n;
            $range = "$n";
        }
        else
        {
            $range = "[$low-$n]";
        }
        $prev = $n;
    }

    push(@ranges, $range) if defined($range);
    return @ranges;
}

sub print_groups(%)
{
    my (%groups) = @_;

    foreach my $key (sort keys %groups)
    {
        my $group = $groups{$key};
        my $start = $group->{start};
        my $end = $group->{end};
        my $numbers_ref = $group->{numbers};

        if ($group->{noNumber})
        {
            print "$start$end\n";
        }

        if (defined($numbers_ref))
        {
            my @ranges = get_number_ranges(@$numbers_ref);
            foreach my $range (@ranges)
            {
                print "$start$range$end\n";
            }
        }
    }
}

