#!/usr/pkg/bin/perl

=begin metadata

Name: words
Description: find words which can be made from a string of letters
Author: Ronald J Kimball, rjk-perl@tamias.net
License: perl

=end metadata

=cut

use strict;

use File::Basename qw(basename dirname);
use File::Spec qw();
use Getopt::Std qw(getopts);
use PerlPowerTools;

use constant EX_SUCCESS => 0;
use constant EX_FAILURE => 1;

use vars qw($opt_w $opt_m);

my $VERSION = '1.3';
my $Program = basename($0);

getopts('m:w:') or usage();
usage() unless @ARGV;
my $letters = shift;
if (@ARGV) {
    warn "$Program: extra argument: '$ARGV[0]'\n";
    usage();
}

my $wordlist;
if (defined $opt_w) {
    $wordlist = $opt_w;
} else {
    $wordlist = File::Spec->catfile(dirname($0), 'wordlist');
}

my $minlen;
if (defined $opt_m) {
    if ($opt_m !~ m/\A[0-9]+\Z/) {
        warn "$Program: <min-length> must be a whole number\n";
        exit EX_FAILURE;
    }
    $minlen = $opt_m;
} else {
    $minlen = 0;
}

if (-d $wordlist) {
    warn "$Program: '$wordlist' is a directory\n";
    exit EX_FAILURE;
}
my $dict;
$wordlist = get_dictionary_path($wordlist);
unless (open $dict, '<', $wordlist) {
    warn "$Program: unable to open '$wordlist': $!\n";
    exit EX_FAILURE;
}

$| = 1;

$letters = lc $letters;              # convert to lowercase
$letters =~ tr/a-z//cd;              # strip non-letter characters

my %letters;
foreach (split(//, $letters)) {      # store letter counts
    $letters{$_}++;
}

my($word);
WORD:
while (defined($word = <$dict>)) {
                                     # for each word in list
    chomp($word);

    next WORD if (length($word) < $minlen);
                                     # verify length
    next WORD if ($word =~ /[^$letters]/o);
                                     # verify letters used
                                     # comments also skipped here

    my %word;
    foreach (split(//, $word)) {     # verify letter counts
        $word{$_}++;
        next WORD if ($word{$_} > $letters{$_});
    }

    print "$word\n";                 # success - print word
}
if ($!) {
    warn "$Program: failed to read '$wordlist': $!\n";
    exit EX_FAILURE;
}
unless (close $dict) {
    warn "$Program: failed to close '$wordlist': $!\n";
    exit EX_FAILURE;
}
exit EX_SUCCESS;

sub usage {
    warn "usage: $Program [-w <word-file>] [-m <min-length>] <letters>\n";
    exit EX_FAILURE;
}

__END__

=pod

=head1 NAME

words - find words which can be made from a string of letters

=head1 SYNOPSIS

B<words> [B<-w> I<word-file>] [B<-m> I<min-length>] I<letters>

=head1 DESCRIPTION

B<words> prints all the uncapitalized words in the word list that can
be made from the string of letters.  Each letter can appear in a word
once for each time it appears in the string.

=head2 OPTIONS

B<words> accepts the following options:

=over 4

=item B<-w> I<word-file>

By default, B<words> looks for a word-file named 'wordlist' in the
same directory as the executable.  Use the B<-w> option to specify the
path to an alternate word list.  If no word file found, attempt to use
a system dictionary wordlist instead.

=item B<-m> I<min-length>

By default, B<words> prints words of any length that can be made from
the string of letters.  Use the B<-m> option to specify the minimum
length of each word.  B<words> will skip any words that are less than
the specified length.

=back

=head1 FILES

=over 4

=item F<wordlist>

The list of words, found with the executable.

For a comprehensive word list, the author recommends the ENABLE word
list, with more than 172,000 words, which can be found at
http://personal.riverusers.com/~thegrendel/software.html.

=back

=head1 BUGS

This implementation of B<words> has no known bugs.

=head1 AUTHOR

This implementation of B<words> in Perl was written by Ronald J Kimball,
I<rjk-perl@tamias.net>.

=head1 COPYRIGHT and LICENSE

This program is copyright 1999 by Ronald J Kimball.

This program is free and open software.  You may use, modify, or
distribute this program (and any modified variants) in any way you
wish, provided you do not restrict others from doing the same.

=cut

