The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# I know lowercase names are reserved for pragma's, but other programs do this
# such as perlbrew and dzil. It makes loading this program for testing very easy
# with C<use lib 'bin'; require fetchware; fetchware->import();>, and it
# bypasses a limitation in dzil regarding creating the POD properly.

package fetchware;

# ABSTRACT: Fetchware is a package manager for source code distributions.
use strict;
use warnings;

# Enable Perl 6 knockoffs, and use 5.10.1, because smartmatching and other
# things in 5.10 were changed in 5.10.1+.
use 5.010001;

# Use Getopt::Long for options parsing beyond fetchware's simple commands such
# as install, new, uninstall, help, and so on.
use Getopt::Long qw(:config bundling pass_through);
###BUGALERT### This breaks App::Fetchware's encapsulation, and screws up its API
#fix this bug by extracting the fetchwarefile without relying on start() having
#already created the temp directory!!!!!!
use App::Fetchware qw(parse_directory_listing);
use App::Fetchware::Config qw(config __clear_CONFIG config_replace);
use App::Fetchware::Util qw(:UTIL);
use Test::Fetchware 'create_test_fetchwarefile';
use Archive::Tar;
use File::Copy qw(mv cp);
use File::Spec::Functions qw(curdir catdir catfile catpath tmpdir splitpath
    splitdir rel2abs abs2rel updir file_name_is_absolute);
use Cwd 'cwd';
use File::Path qw(make_path remove_tree);
use Term::UI;
use Term::ReadLine;
use Perl::OSType 'is_os_type';
use File::HomeDir;
use File::Find 'find';
use File::Temp 'tempfile';
use Fcntl qw(SEEK_SET);
use Path::Class;
use Text::Wrap 'wrap';
use Data::Dumper;
use Fcntl ':flock';
use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
use Sub::Mage;
use URI::Split qw(uri_split uri_join);

# Setup exports, which are only meant to ease testing.
use Exporter 'import';
our %EXPORT_TAGS  = (
    TESTING => [qw(
        parse_fetchwarefile    
        create_fetchware_package
        fetchware_database_path
        determine_fetchware_package_path
        extract_fetchwarefile
        copy_fpkg_to_fpkg_database
        cmd_install
        cmd_uninstall
        cmd_look
        cmd_list
        cmd_upgrade
        cmd_upgrade_all
        cmd_new
        cmd_clean
        name_program
        opening_message
        get_lookup_url
        download_lookup_url
        analyze_lookup_listing
        add_mirrors
        add_verification
        determine_mandatory_options 
        determine_filter_option
        append_to_fetchwarefile
        prompt_for_other_options
        append_options_to_fetchwarefile
        edit_manually
        check_fetchwarefile
        ask_to_install_now_to_test_fetchwarefile
    )]
);
our @EXPORT_OK = @{$EXPORT_TAGS{TESTING}};

our $verbose = 0;
our $quiet = 0;
our $dry_run = 0;

# Be a modulino, so I can "use fetchware" in my test suite, so I can test
# bin/fetchware normally like any other perl module.
run() unless caller();

sub run {
    # Set up a %SIG handler for CTRL-C or CTRL-Z on Windows.
    # And a %SIG handler for QUIT, which is CTRL-\
    #
    # Define a $parent_pid, so I can compare it to $$ (the current pid) to
    # see if I'm the child or the parent inside the sig handler to act
    # accordingly.
    my $parent_pid = $$;
    #
    # Be sure to prepend the first message that's printed with a newline to
    # ensure that it's printed on a brand new fresh line.
    @SIG{qw(INT TERM QUIT)} = sub {
        my $sig = shift;
        # Avoid a silly race condition where both the parent and the child both
        # try to run this code at the same time resulting in the one closing the
        # file and deleting the tempdir() before the other one resulting in
        # strange undefined warnings.
        #
        if ($parent_pid == $$) {
            msg <<EOM;
\nSignal [$sig] received. Cleaning up Fetchware.
EOM
            vmsg <<EOM;
Any temporary files that fetchware may have created will be deleted by
File::Temp's END block.
EOM

            cleanup_tempdir();
        }

        # Exit failure, because fetchware failed to properly install your
        # software while it was running, because the signal it received forced
        # it to exit prematurely making it questionable if fetchware succeeded
        # in properly and completely completing the actions you specified on the
        # command line and/or in a Fetchwarefile.
        exit 1;
    };

    vmsg 'Parsing command line options using Getopt::Long';

    GetOptions(
        # $VERSION is managed by dzil; therefore, I use eval to access it at
        # run time instead of compile time, so that I can test fetchware without
        # running dzil test.
        'version|V' => sub { eval 'say "Fetchware version $VERSION"; '; exit 0},
        'help|h|?' => \&help,
        'verbose|v' => \$verbose,
        'quiet|q' => \$quiet,
        ###BUGALERT### dry-run functionality is *not* implemented!!!
        #'dry-run|d' => \$dry_run,
    );


    # Getopt::Long is *only* used to determine dash and double dash style options
    # such as -v, --verbose, --help, -h, -?, etc....
    #
    # Below the first argument to fetchware is used to determine what fetchware
    # does.  If nothing is specified then help is printed.
    ###BUGALERT### Add a loop around @ARGV to support multiple Fetchwarefiles
    #or fetchware packages ending in .fpkg.
    eval { # Trap any fatal errors.
        vmsg 'Entering main eval{} block to trap errors.';
        ###BUGALERT### Should trapped exceptions with this eval cause fetchware
        #to cd to $original_cwd and then exit, so that the File::Temp's END
        #block can delete fetchware's source dir???
        # Or fetchware could print the path of this source dir and close, and
        # tell the user that they can clean it up with fetchware clean??
        # Also, add cmdline options to control what to do when this happens???
        vmsg 'Determining which command to run based on command line options.';
        my $command = shift @ARGV;
        if ($command eq 'install') {
            cmd_install($command eq @ARGV);
        } elsif ($command eq 'uninstall') {
            cmd_uninstall($command eq @ARGV);
        } elsif ($command eq 'new') {
            cmd_new($command eq @ARGV);
        } elsif ($command eq 'upgrade') {
            cmd_upgrade($command eq @ARGV);
        } elsif ($command eq 'upgrade-all') {
            cmd_upgrade_all($command eq @ARGV);
        } elsif ($command eq 'list') {
            cmd_list($command eq @ARGV);
        } elsif ($command eq 'look') {
            cmd_look($command eq @ARGV);
        } elsif ($command eq 'clean') {
            cmd_clean($command eq @ARGV);
        } elsif ($command eq 'help') {
            cmd_help(@ARGV);
        } else {
            cmd_help(@ARGV);
        }
        # Exit success, because if any of the main subroutines run into any
        # problems they die() exceptions, which get caught in eval above, and
        # warn()ed below, and fetchware exits 1 for failure.
        vmsg 'Fetchware ran successfully! Exiting with status of 0 for success!';
        exit 0;
    };
    # If a fatal error was thrown print it to STDERR and exit indicating failure.
    if ($@) {
        msg <<EOM;
Fetchware threw an exception! Exiting with an exit status of 1 for failure.
EOM
        warn $@;
        exit 1;
    }
}





###BUGALERT### cmd_install() does *not* actually do this. Consider implementing
#it.
#If no filename was
#provided or the filename doesn't exist then, cmd_install() calls new() to create
#and install a new fetchware package.


sub cmd_install {
    # These variables must be shared back to the parent from the child using
    # pipe_{write,read}_newline().
    my $P_build_path;
    ###BUGALERT### After verifying basic functionality of cmd_install wrap
    #subroutine contents in a for my $filename (pop @ARGV) loop to try to
    #install all given arguments that arn't command line options as parsed by
    #GetOpt::Long.
    ### Add this loop in run(), so there is just one loop to test.
    my $filename = shift;

    msg "Starting fetchware install to install [$filename]";
    
    my $output;
    if (defined($filename) and -e $filename) {

    # If a fpkg extract out the Fetchwarefile into a scalar, and if not a
    # fetchware package to go ahead and open for reading only the Fetchware
    # right now while we're perhaps still root, so we can be sure we can
    # still access it.
    my $fetchwarefile;
    if ($filename =~ /\.fpkg$/) {
        $fetchwarefile = extract_fetchwarefile($filename);
        vmsg <<EOM;
Extracting out Fetchwarefile from [$filename] to [$$fetchwarefile]
EOM
    } else {
        my $fh = safe_open($filename, <<EOD);
fetchware: Fetchware failed to open the filename you specified to fetchware
install [$filename]. The OS error was [$!].
EOD
        vmsg "Opened file [$filename] for slurping.";
        # Add a \ to turn the slurped scalar into a scalar ref for calling
        # parse_fetchwarefile() properly.
        $fetchwarefile = \do {local $/; <$fh>};
        vmsg  "Slurped [$filename] into fetchware: [$$fetchwarefile]";
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";


    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

        # Drop privs, so only install() and  end() are called with root perms
        $output = drop_privs(
        sub {
            my $write_pipe = shift;

            # Run the App::Fetchware API subroutines to do everything to install
            # the program, but be mindful of drop_privs() requiring this coderef
            # to use write_dropprivs_pipe() to communicate needed changes back to
            # the parent process, for example, $P_build_path--the parent needs to
            # chdir() to that directory before it tries to execute install().

            ###BUGALERT### install installs no matter if the program is already
            #installed!!! Change this to parse out the package from the
            #download_urlif possible, compare with the one in the fetchware
            #package database, and call exit right here if the current version
            #is already installed unless of course --force is used!!!
            my $download_url = lookup();

            my $package_path = download($temp_dir, $download_url);

            ###BUGALERT### Add support for caching the key files gpg creates to
            #the fetchwarefile, and for actually using them later on inside the
            #fpkg.
            verify($download_url, $package_path);

            $P_build_path = unarchive($package_path);

            build($P_build_path);

            # Tell the parent, root, process the values of the variables the
            # child calculated in this coderef, and write them across this pipe
            # back to the parent
            write_dropprivs_pipe($write_pipe, $P_build_path);
        }, config('user')
        ); # End drop_privs().

        # Read from the pipe the child, the drop_privs()ed process, writes to to
        # read the necessary values that correspond to the variables that the
        # child must communicate back to the parent, so the parent can continue
        # processing as though no fork()ing or priv dropping took place.
        ($P_build_path)
            = read_dropprivs_pipe($output);

        install($P_build_path);

        vmsg "Creating Fetchware package from [@{[cwd()]}].";
        my $fetchware_package_path
            =
            create_fetchware_package($fetchwarefile, cwd());
        vmsg "Created fetchware package at [$fetchware_package_path].";

        vmsg 'Installing created fetchware package to fetchware database.';
        my $installed_fetchware_package_path
            = copy_fpkg_to_fpkg_database($fetchware_package_path);
        vmsg <<EOM;
Installed created fetchware package to [$installed_fetchware_package_path]
EOM

        end();

        # Return the path of the created and installed fetchware package.
        return $installed_fetchware_package_path;
    } else {
        ###BUGALERT### Replace with warn for proposed for loop above to work???
        die <<EOD;
fetchware: You called fetchware install incorrectly. You must also specify
either a Fetchwarefile or a fetchware package that ends with [.fpkg].
EOD
    }
}




sub cmd_uninstall {

    my $uninstall_package_path = shift;

    msg "Uninstalling specified package [$uninstall_package_path]";

    my $fetchware_package_path
        = determine_fetchware_package_path($uninstall_package_path);
    vmsg <<EOM;
Determined the path of this package in the fetchware database to be
[$fetchware_package_path].
EOM

    # Extract out the $fetchwarefile from the $fetchware_package_path.
    my $fetchwarefile;
    if ($fetchware_package_path =~ /\.fpkg$/) {
        $fetchwarefile
            =
            extract_fetchwarefile($fetchware_package_path, cwd());
        vmsg <<EOM;
Extracting out Fetchwarefile from [$fetchware_package_path] to [$fetchwarefile]
EOM
    } else {
        die <<EOD;
fetchware: The option you provided to uninstall is not a currently installed
fetchware package. Please rerun uninstall after determining the proper name for
the already installed fetchware package. To see a list of already installed
fetchware packages please try fetchware's list command: fetchware list
EOD
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

    # "Download" the package using File::Copy's cp().
    my $package_path;
    if (cp($fetchware_package_path, $temp_dir)) {
        # Determine the output file that cp() used.
        ###BUGALERT### Open the file for cp(), and provide cp() with a
        #filehandle to write the data to to ensure the filename is exactly
        #what it needs to be.
        $package_path = catfile($temp_dir,
            file($fetchware_package_path)->basename());
    } else {
        die <<EOD;
fetchware: Fetchware failed to copy the file [$fetchware_package_path] to the
destination directory [$temp_dir]. OS error [$!].
EOD
    }


    vmsg "Copied installed package to temporary directory at [$package_path]";

    my $build_path = unarchive($package_path);

    uninstall($build_path);

    end();

    vmsg 'Uninstalling fetchware package from fetchware database.';
    uninstall_fetchware_package_from_database($fetchware_package_path);

    msg "Uninstalled fetchware package [$uninstall_package_path].";
    # Return the name of the uninstalled package's full path fetchware's
    # database.
    return $fetchware_package_path;
}



###BUGALERT### Move cmd_new() before install()?????
###BUGALERT### Print out fetchware's assumptions it makes about what FTP & hTTP
#lookup_url's look like, versionstring's assumptions, timestamp's assumptions,
#verify's assumptions, and so on. If not here in new() at least do it in the
#POD documentation.
###BUGALERT### Support ~/.Fetchwarefile, or whatever File::HomeDir wants it to
#be. Test if ~/.Fetchwarefile exists, if it does do nothing, but if it does not
#exist then prompt the user to fill one out!!!

############BUGALERT########################BUGALERT##################
############BUGALERT########################BUGALERT##################
###BUGALERT### Modify analyze_lookup_listing() to print the directory listing
#for the user to peruse, and have the user choose what program they want to
#install from the listing. Then use that as the basis for the filter option.
#httpd-2.4.1.tar.bz2 would simply be m/(\w+?)[.-_\d]+?/ And $1 is the filter
#option. If the match fails to the existing manual crap.
############BUGALERT########################BUGALERT##################
############BUGALERT########################BUGALERT##################

###BUGALERT### Add a command line option to fetchware new that allows users to
#only edit a blank file (Have helpful comments about what options are must
#haves), and then have fetchware ask the user if they would like to then install
#that new Fetchwarefile.
###BUGALERT### Rename lookup_url to main_mirror or master_mirror or author_mirror
#or something to better implicitly explain what it is.
###BUGALERT### Add the check to see if there is a MIRRORS file or some similar
#regex, and if so parse it, and auto add it to the list of mirrors? Is this
#really feasible?


sub cmd_new {

    # These are the variables that the child must share back with the parent.
    my $P_program_name = shift; # The child might change or define it again.
    my $P_fetchwarefile;

    # Drop privs, so only install() is called with root permissions
    my $output = drop_privs( sub {
        my $write_pipe = shift;

        my $term = Term::ReadLine->new('Fetchware new');

        my $now = localtime;
        ###BUGALERT### Only App::Fetchware based Fetchwarefile's are supported now
        #will have to add some ifs and code to support others.
        $P_fetchwarefile = <<EOF;
use App::Fetchware;
# Auto generated $now by fetchware's new command.
# However, feel free to edit this file if fetchware's new command's
# autoconfiguration is not enough.
# 
# Please look up fetchware's documentation of its configuration file syntax at
# perldoc App::Fetchware, and only if its configuration file syntax is not
# malleable enough for your application should you resort to customizing
# fetchware's behavior. For extra flexible customization see perldoc
# App::Fetchware.
EOF
        ###INSANEFEATUREENHANCEMENT### Prompt for name of program, and do a fuzzy 
        #search on CPAN for that program under
        #App::Fetchware::FetchwarefileX::UpCasedProgName. Consider using the meta
        #CPAN API. And if it exists ask user if they wanna use that one instead of
        #autogening one.
        #
        #Perhaps create a 'fetchwarefile' command to download and look at
        #fetchwarefiles from CPAN, and then install them, and/or perhaps upload
        #them pausing to ask for the user's PAUSE credentials!!!!!!!!!

        opening_message();

        # Ask user for name of program unless the user provided one at command
        # line such as fetchware new <programname>.
        $P_program_name //= name_program($term);
        vmsg "Determined name of your program to be [$P_program_name]";

        append_options_to_fetchwarefile({program => $P_program_name},
            \$P_fetchwarefile);
        vmsg "Appended program [$P_program_name] configuration option to Fetchwarefile";

        my $lookup_url = get_lookup_url($term);
        vmsg "Asked user for lookup_url [$lookup_url] from user.";

        # Add $lookup_url to $P_fetchwarefile.
        append_options_to_fetchwarefile({lookup_url => $lookup_url},
            \$P_fetchwarefile);
        vmsg "Appended lookup_url [$lookup_url] configuration option to Fetchwarefile";

        vmsg "Downloaded lookup_url [$lookup_url]";
        my $filename_listing = download_lookup_url($term, $lookup_url);
        vmsg "Downloaded lookup_url's directory listing";
        vmsg Dumper($filename_listing);

        analyze_lookup_listing($term, $filename_listing, $lookup_url,
            \$P_fetchwarefile);

        ###BUGALERT### Ask to parrallelize make with make_options???
        ###BUGALERT### Verify prefix is writable by current user, who will
        #presumably be the user who will install the package now and later.
        ###BUGALERT### Ask user for a prefix if their running nonroot???
        vmsg 'Prompting for other options that may be needed.';
        my %options = prompt_for_other_options($term);
        vmsg 'User entered the following options.';
        vmsg Dumper(\%options);

        # Append all other options to P_fetchwarefile.
        append_options_to_fetchwarefile(\%options, \$P_fetchwarefile);
        vmsg 'Appended all other options listed above to Fetchwarefile.';

        edit_manually($term, \$P_fetchwarefile);
        vmsg <<EOM;
Asked user if they would like to edit their generated Fetchwarefile manually.
EOM

        # Tell the parent, root, process the values of the variables the
        # child calculated in this coderef, and write them across this pipe
        # back to the parent
        write_dropprivs_pipe($write_pipe, $P_program_name, $P_fetchwarefile);
    }, config('user'),
    # cmd_new() does not want or need the directory that drop_privs() creates
    # for the child so that the child can write files inside the parent's
    # temporary directory that was created with start().
    SkipTempDirCreation => 1
    ); # End drop_privs() back to root now!

    # Use our own term in the parent process. Writing the Term::ReadLine object
    # across the pipe makes no sense, so just make your own in the parent
    # process.
    my $term = Term::ReadLine->new('Fetchware new');

    # Read from the pipe the child, the drop_privs()ed process, writes to to
    # read the necessary values that correspond to the variables that the
    # child must communicate back to the parent, so the parent can continue
    # processing as though no fork()ing or priv dropping took place.
    ($P_program_name, $P_fetchwarefile) = read_dropprivs_pipe($output);

    my $fetchware_package_path =
        ask_to_install_now_to_test_fetchwarefile($term, \$P_fetchwarefile,
            $P_program_name);

    return $fetchware_package_path;
}







sub name_program {
    my $term = shift;
    my $what_a_program_is = <<EOM;
A program configuration directive simply names your program.

In the future it may access existing repositories to see if a Fetchwarefile
has already been created for that program, and use that one instead of creating
a new one, but for now it only names your program, so you can easily tell what
program the Fetchwarefile is supposed to download and install.
EOM

    my $program_name = $term->get_reply(
        prompt => q{What name is your program called? },
        print_me => $what_a_program_is,
    );

    return $program_name;
}



sub opening_message {

    my $opening_message = <<EOM;
Fetchware's new command is reasonably sophisticated, and is smart enough to
determine based on the lookup_url you provide if it can autogenerate a
Fetchwarefile for you. If Fetchware cannot, then it will ask you more
questions regarding the information it requires to be able to build a
installable fetchware package for you. After that, fetchware will ask you if
you would like to edit the Fetchwarefile, fetchware has created for you in an
editor. If you say yes, fetchware will open a editor for you, but if you say
no, fetchware will skip the custom editing. Next, fetchware will create a test
Fetchwarefile for you, and ask you if you would like to test it by trying to
install it now. If you say yes, fetchware will install it, and if you say no,
then fetchware will print the location of the Fetchwarefile it created for
you to later use to install your application.
EOM

    # Just print the opening message.
    print $opening_message;
}



sub get_lookup_url {
    my $term = shift;


    # prompt for lookup_url.
    my $lookup_url = $term->get_reply(
        print_me => <<EOP,
Fetchware's heart and soul is its lookup_url. This is the configuration option
that tells fetchware where to check if a new version of fetchware is released.

How to determine your application's lookup_url:
    1. Go to your application's Web site.
    2. Determine the download link for the latest version and copy it with
       CTRL-C or right-click it and select "copy".
    3. Paste the download link into your browser's URL Location Bar.
    4. Delete the filename from the location by starting at the end and deleting
       everything to the left until you reach a slash '/'.
       * ftp://a.url/downloads/program.tar.gz -> ftp://a.url/downloads/
    5. Press enter to access the directory listing on your Application's mirror
       site.
    6. If the directory listing in either FTP or HTTP format is displayed in
       your browser, then Fetchware's default, built-in lookup fuctionality will
       probably work properly. Copy and paste this URL into the prompt below, and
       Fetchware will download and analyze your lookup_url to see if it will work
       properly. If you do not end up with a browser directory listing, then
       please see Fetchware's documentation using perldoc App::Fetchware.
EOP
        prompt => q{What is your application's lookup_url? },
        allow => qr!(ftp|http|file)://!);

    return $lookup_url;
}



sub download_lookup_url {
    my $term = shift;
    my $lookup_url = shift;

    my $filename_listing;
    eval {
        # Use no_mirror_download_dirlist(), because the regular one uses
        # config(qw(lookup_url mirror)), which is not known yet.
        my $directory_listing = no_mirror_download_dirlist($lookup_url);

        # Create a fake lookup_url, because parse_directory_listing() uses it to
        # determine the type of *_filename_listing() subroutine to call.
        config(lookup_url => $lookup_url);

        $filename_listing = parse_directory_listing($directory_listing);

        __clear_CONFIG();

        # Fix the most annoying bug that ever existed in perl.
        # http://blog.twoshortplanks.com/2011/06/06/unexceptional-exceptions-in-perl-5-14/
        1;
    } or do {
        my $lookup_url_failed_try_again = <<EOF;
fetchware: the lookup_url you provided failed because of :
[$@]
Please try again. Try the steps outlined above to determine what your program's
lookup_url should be. If you cannot figure out what it should be please see
perldoc App::Fetchware for additional hints on how to choose a lookup_url.
EOF
        $lookup_url = get_lookup_url($term, $lookup_url_failed_try_again);

        eval {
            # Use no_mirror_download_dirlist(), because the regular one uses
            # config(qw(lookup_url mirror)), which is not known yet.
            my $dir_list = no_mirror_download_dirlist($lookup_url);

            # Create a fake lookup_url, because parse_directory_listing() uses
            # it to determine the type of *_filename_listing() subroutine to
            # call.
            config(lookup_url => $lookup_url);

            $filename_listing = parse_directory_listing($dir_list);

            __clear_CONFIG();
        # Fix the most annoying bug that ever existed in perl.
        # http://blog.twoshortplanks.com/2011/06/06/unexceptional-exceptions-in-perl-5-14/
        1;
        } or do {
            die <<EOD;
fetchware: run-time error. The lookup_url you provided [$lookup_url] is not a
usable lookup_url because of the error below:
[$@]
Please see perldoc App::Fetchware for troubleshooting tips and rerun
fetchware new.
EOD
        };
    };

    return $filename_listing;
}



sub analyze_lookup_listing {
    my $term = shift;
    # $filename_listing is an array of [$filename, $timestamp] arrays.
    my $filename_listing = shift;
    my $lookup_url = shift;
    my $fetchwarefile = shift;
    
    determine_mandatory_options($term, $filename_listing, $lookup_url,
        $fetchwarefile);
    determine_filter_option($term, $filename_listing, $fetchwarefile);
}



sub determine_mandatory_options {
    my $term = shift;
    # $filename_listing is an array of [$filename, $timestamp] arrays.
    my $filename_listing = shift;
    # add_verification() needs $lookup_url.
    my $lookup_url = shift;
    my $fetchwarefile = shift;

    # program and lookup_url were defined earlier, so the only other mandatory
    # options are at least one mirror (but more are allowed, and some way to
    # verify downloads.
    add_mirrors($term, $filename_listing, $fetchwarefile);
    add_verification($term, $filename_listing, $lookup_url, $fetchwarefile);
}



sub add_mirrors {
    my ($term, $filename_listing, $fetchwarefile) = @_;

    my @options;

    my $mirror = $term->get_reply(
        print_me => <<EOP,
Fetchware requires you to please provide a mirror. This mirror is required,
because most software authors prefer users download their software packages from
a mirror instead of from the authors main download site, which your lookup_url
should point to.

The mirror should be a URL in standard browser format such as [ftp://a.mirror/].
FTP, HTTP, and local file:// mirrors are supported. All other formats are not
supported.
EOP
        prompt => 'Please enter the URL of your mirror: ',
        allow => qr!^(ftp|http|file)://!,
    );

    # Append mirror to $fetchwarefile.
    append_options_to_fetchwarefile({mirror => $mirror}, $fetchwarefile);

    if (
        $term->ask_yn(
        print_me => <<EOP,
In addition to the one required mirror that you must define in order for
fetchware to function properly, you may specify additonal mirros that fetchware
will use if the mirror you've already specified is unreachable or download
attempts using that mirror fail.
EOP
        prompt => 'Would you like to add any additional mirrors? ',
        default => 'n',
        )
    ) {
        # Prompt for first mirror outside loop, because if you just hit enter or
        # type done, then the above text will be appended to your fetchwarefile,
        # but you'll be able to skip actually adding a mirror.
        my $first_mirror = $term->get_reply(
                prompt => 'Type in URL of mirror or done to continue: ',
                allow => qr!^(ftp|http|file)://!,
            );
            push @options, 'mirror', $first_mirror;

            # Append $first_mirror to $fetchwarefile.
            append_options_to_fetchwarefile({mirror => $first_mirror}, $fetchwarefile);

        while (1) {
            my $mirror_or_done = $term->get_reply(
                prompt => 'Type in URL of mirror or done to continue: ',
                default => 'done',
                allow => qr!(^(ftp|http|file)://)|done!,
            );
            if ($mirror_or_done eq 'done') {
                last;
            } else {
                # Append $mirror_or_done to $fetchwarefile.
                append_options_to_fetchwarefile({mirror => $mirror_or_done}, $fetchwarefile);
            }
        }
    }
}



sub add_verification {
    my ($term, $filename_listing, $lookup_url, $fetchwarefile) = @_;

    my @options;

    my %available_verify_methods;
    # Determine what types of verification are available.
    for my $file_and_timestamp (@$filename_listing) {
        if ($file_and_timestamp->[0] =~ /\.(asc|sig|sign)$/) {
            $available_verify_methods{gpg}++;
        } elsif ($file_and_timestamp->[0] =~ /\.sha1?$/) {
            $available_verify_methods{sha1}++;
        } elsif ($file_and_timestamp->[0] =~ /\.md5$/) {
            $available_verify_methods{md5}++;
        }
    }

    my $verify_configed_flag = 0;
    #If gpg is available prefer it over the others.
    if (exists $available_verify_methods{gpg}
            and defined $available_verify_methods{gpg}
            and $available_verify_methods{gpg} > 0
    ) {
        msg <<EOM;
gpg digital signatures found. Using gpg verification.
EOM
        push @options, verify_method => 'gpg';

        # Search for a KEYS file to use to import the author's keys.
        if (grep {$_->[0] eq 'KEYS'} @$filename_listing) {
            msg <<EOM;
KEYS file found using lookup_url. Adding gpg_keys_url to your Fetchwarefile.
EOM
            # Add 'KEYS' or '/KEYS' to $lookup_url's path.
            my ($scheme, $auth, $path, $query, $fragment) =
                uri_split($lookup_url);
            $path = catfile($path, 'KEYS');
            $lookup_url = uri_join($scheme, $auth, $path, $query, $fragment);

            push @options, gpg_keys_url => $lookup_url;
            $verify_configed_flag++;
        } else {
            msg <<EOM;
KEYS file *not* found!
EOM
            # Since autoconfiguration of KEYS failed, try asking the user if
            # they would like to import the author's key themselves into their
            # own keyring and have fetchware use that.
            if (
                $term->ask_yn(prompt =>
q{Would you like to import the author's key yourself after fetchware completes? },
                    default => 'n',
                    print_me => <<EOP,
Automatic KEYS file discovery failed. Fetchware needs the author's keys to
download and import into its own keyring, or you may specify the option
user_keyring, which if true will cause fetchware to use the user who runs
fetchware's keyring instead of fetchware's own keyring. But you, the user, needs
to import the author's keys into your own gpg keyring. You can do this now in a
separate shell, or after you finish configuring this Fetchwarefile. Just run the
command [gpg --import <name of file>].
EOP
                )
            ) {
                push @options, user_keyring => 'On';

                $verify_configed_flag++;
            }

            # And if the user does not want to, then fallback to sha1 and/or md5
            # if they're defined, which is done below.
        }
    }
    
    
    # Only try sha1 and md5 if gpg failed.
    unless ($verify_configed_flag == 1) {
        if (exists $available_verify_methods{sha1}
                and defined $available_verify_methods{sha1}
                and $available_verify_methods{sha1} > 0
        ) {
            msg <<EOM;
SHA1 checksums found. Using SHA1 verification.
EOM
            push @options, verify_method => 'sha1';
        } elsif (exists $available_verify_methods{md5}
                and defined $available_verify_methods{md5}
                and $available_verify_methods{md5} > 0
        ) {
            msg <<EOM;
MD5 checksums found. Using MD5 verification.
EOM
            push @options, verify_method => 'md5';
        } else {
            # Print a huge long nasty warning even include links to news stories
            # of mirrors actually getting hacked and serving malware, which
            # would be detected and prevented with proper verification enabled.

            # Ask user if they would like to continue installing fetchware even if
            # verification fails, and then enable the verify_failure_ok option.
            if (
                $term->ask_yn(prompt => <<EOP,
Would you like fetchware to ignore the fact that it is unable to verify the
authenticity of any downloads it makes? Are you ok with possibly downloading
viruses, worms, rootkits, or any other malware, and installing it possibly even
as root? 
EOP
                    default => 'n',
                    print_me => <<EOP,
Automatic verification of your fetchware package has failed! Fetchware is
capable of ignoring the error, and installing software packages anyway using its
verify_failure_ok configuration option. However, installing software packages
without verifying that they have not been tampered with could allow hackers to
potentially install malware onto your computer. Don't think this is *not*
possible or do you think its extremely unlikely? Well, it's actually
surprisingly common:
    1.  http://arstechnica.com/security/2012/09/questions-abound-as-malicious-phpmyadmin-backdoor-found-on-sourceforge-site/
    Discusses how a mirror for sourceforge was hacked, and the phpMyAdmin
    software package on that mirror was modified to spread malware.
    2.  http://www.geek.com/news/major-open-source-code-repository-hacked-for-months-says-fsf-551344/
    Discusses how FSF's gnu.org ftp download site was hacked.
    3.  http://arstechnica.com/security/2012/11/malicious-code-added-to-open-source-piwik-following-website-compromise/
    Discusses how Piwiki's wordpress software was hacked, and downloads of
    Piwiki had malicious code inserted into them.
    4. http://www.theregister.co.uk/2011/03/21/php_server_hacked/
    Discusses how php's wiki.php.org server was hacked yielding credentials to
    php's source code repository.
Download mirrors *do* get hacked. Do not make the mistake, and think that it is
not possible. It is possible, and it does happen, so please properly configure
your Fetchwarefile to enable fetchware to verify that the downloaded software is
the same what the author uploaded.
EOP
                )
            ) {
                # If the user is ok with not properly verifying downloads, then
                # ignore the failure, and install anyway.
                push @options, verify_failure_ok => 'On';
            } else {
                # Otherwise, throw an exception.
                die <<EOD;
fetchware: Fetchware *must* be able to verify any software packages that it
downloads. The Fetchwarefile that you were creating could not do this, because
you failed to specify how fetchware can verify its downloads. Please rerun
fetchware new again, and this time be sure to specify a gpg_keys_url, specify
user_keyring to use your own gpg keyring, or answer yes to the question
regarding adding verify_failure_ok to your Fetchwarefile to make failing
verificaton acceptable to fetchware.
EOD
            }
        }
    }

    # Append @options to $fetchwarefile.
    append_options_to_fetchwarefile({@options}, $fetchwarefile);
}



sub determine_filter_option {
    my $term = shift;
    # $filename_listing is an array of [$filename, $timestamp] arrays.
    my $filename_listing = shift;
    my $fetchwarefile = shift;
    msg <<EOS;
Analyzing the lookup_url you provided to determine if fetchware can use it to
successfully determine when new versions of your software are released.
EOS

    my $filter;
    if (grep {$_->[0] =~ /^(CURRENT|LATEST)[_-]IS[_-].+/} @$filename_listing) {
        # There is only one version in the lookup_url directory listing, so
        # I do not need a filter option.
        msg <<EOS;
* The lookup_url you gave fetchware includes a CURRENT_IS or a LATEST_IS file
that tells fetchware and regular users what the latest version is. Because of
this we can be reasonable sure that a filter option is not needed, so I'll skip
asking for one. You can provide one later if you need to provide one, when
fetchware prompts you for any custom options you may want to use.
EOS
    } else {
        # There is a CURRENT_IS_<ver_num> or LATEST_IS_<ver_num> file that tells
        # you what the latest version is.
###BUGALERT### Why is this line in both sections of the if statement??? Inside
#this else block means that a CURRENT_IS or LATEST-IS was *not* found??? Fix
#this!!!!!!
        msg <<EOS;
* The directory listing of your lookup_url has a CURRENT_IS_<ver_num> or
LATEST_IS_<ver_num> file that specifies the latest version, which means that
your program's corresponding Fetchwarefile does not need a filter option. If you
still would like to provide one, you can do so later on, when fetchware allows
you to define any additional configuration options.
EOS
        my $what_a_filter_is = <<EOA;
Fetchware needs you to provide a filter option, which is a pattern that fetchware
compares each file in the directory listing of your lookup_url to to determine
which version of your program to install.

Directories will have other junk files in them or even completely different
programs that could confuse fetchware, and even potentially cause it to install
a different program. Therefore, you should also add the program name to the
begining of your filter. For example if you program is apache, then your filter
should include the name of apache on mirror sites, which is actually:
httpd

For example, Apache's lookup_url has three versions in the same lookup_url
directory listing. These are 2.4, 2.2, and 2.0. Without the filter option
fetchware would choose the highest, which would be 2.4, which is the latest
version. However, you may want to stick with the older and perhaps more stable
2.2 version of apache. Therefore, you'll need to tell fetchware this by using
by adding the version number to your filter:
httpd-2.2
will result in fetchware filtering the results of its lookup check through your
filter of httpd-2.2 causing fetchware to choose the latest version from the 2.2
stable branch instead of the higher version numbered 2.4 or 2.0 legacy releases.
Note the use of the dash, which is used in the filename to separate the 'httpd'
name part from the '2.2' version part.

Note: fetchware accepts any valid perl regular expresion as an acceptable
filter option, but that should only be needed for advanced users. See perldoc
fetchware.
EOA
        # Prompt for the needed filter option.
        $filter = $term->get_reply(
            prompt => <<EOP,
[Just press enter or return to skip adding a filter option]
What does fetchware need your filter option to be? 
EOP
            print_me => $what_a_filter_is,
        );
        ###BUGALERT### Consider Adding a loop around checking the filter option
        #that runs determine_lookup_url() using the provided filter option, and
        #then asking the user if that is indeed the correct filter option, and
        #if not ask again and try it again unit it succeeds or user presses
        #ctrl-c|z.
    }

    # Append $filter to $fetchwarefile.
    append_options_to_fetchwarefile({filter => $filter}, $fetchwarefile)
        if defined $filter;
}



sub append_to_fetchwarefile {
    my ($fetchwarefile,
        $config_file_option,
        $config_file_value,
        $description) = @_;

    die <<EOD if ref($fetchwarefile) ne 'SCALAR';
fetchware: run-time error. You called append_to_fetchwarefile() with a
fetchwarefile argument that is not a scalar reference. Please add the need
backslash reference operator to your call to append_to_fetchwarefile() and try
again.
EOD


    # Only add a $description if we were called 
    if (defined $description) {
        # Append a double newline for easier reading, but only when we print a
        # new $description, which implies we're switching to a new configuration
        # option.
        $$fetchwarefile .= "\n\n";

        # Append a newline to $description if it doesn't have one already.
        $description .= "\n" unless $description =~ /\n$/;
        # Change wrap() to wrap at 80 columns instead of 76.
        local $Text::Wrap::columns = 81;
        # Use Text::Wrap's wrap() to split $description up
        $$fetchwarefile .= wrap('# ', '# ', $description);
    }

    # This simple chunk of regexes provide trivial and buggy support for
    # ONEARRREFs. This support simly causes fetchware to avoid adding any
    # characters that are needed for proper Perl syntax if the user has provided
    # those characters for us.
    if ($config_file_value =~ /('|")/) {
        $$fetchwarefile .= "$config_file_option $config_file_value";

        if ($config_file_value =~ /[^;]$/) {
            $$fetchwarefile .= ";"; 
        } elsif ($config_file_value =~ /[^\n]$/) {
            $$fetchwarefile .= "\n";
        }
    } else { 
        $$fetchwarefile .= "$config_file_option '$config_file_value';\n";
    }
}



sub prompt_for_other_options {
    my $term = shift;

    my %option_description = (
        temp_dir => {
            prompt => <<EOP,
What temp_dir configuration option would you like? 
EOP
            print_me => <<EOP
temp_dir is the directory where fetchware creates a temporary directory that
stores all of the temporary files it creates while it is building your software.
The default directory is /tmp on Unix systems and C:\\temp on Windows systems.
EOP
        },
        user => {
            prompt => <<EOP,
What user configuration option would you like? 
EOP
            print_me => <<EOP
user specifies what user fetchware will drop priveleges to on Unix systems
capable of doing so. This allows fetchware to download files from the internet
with user priveleges, and not do anything as the administrative root user until
after the downloaded software package has been verified as exactly the same as
the author of the package intended it to be. If you use this option, the only
thing that is run as root is 'make install' or whatever this package's
install_commands configuratio option is.
EOP
        },
        prefix => {
            prompt => <<EOP,
What prefix configuration option would you like? 
EOP
            print_me => <<EOP
prefix specifies the base path that will be used to install this software. The
default is /usr/local, which is acceptable for most unix users. Please note that
this difective only works for software packages that use GNU AutoTools, software
that uses ./configure --prefix=<your prefix will go here> to change the prefix.
EOP
        },
        configure_options => {
            prompt => <<EOP,
What configure_options configuration option would you like? 
EOP
            print_me => <<EOP
configure_options specifies what options fetchware should add when it configures
this software package for you. A list of possible options can be obtained by
running unarchiving the software package that corresponds to this Fetchwarefile,
and running the command './configure --help'. These options vary from software
package to software package. Please note that this option only works for GNU
AutoTools based software distributions, ones that use ./configure to configure
the software.
EOP
        },
        make_options => {
            prompt => <<EOP,
What make_options configuration option would you like? 
EOP
            print_me => <<EOP
make_options specifies what options fetchware will pass to make when make is run
to compile, perhaps test, and install your software package. They are simpley
added after make is called. An example is '-j 4', which will cause make to
execute 4 jobs simultaneously. A reasonable rule of thumb is to set make's -j
argument to two times as many cpu cores your computer has as compiling programs
is sometimes IO bound instead of CPU bound, so you can get away with running
more jobs then you have cores.
EOP
        },
###BUGALERT### Create a config sub called build_system that takes args like
#AutoTools, cmake, MakeMaker, Module::Build, and so on that will use the default
#build commands of whatever system this option specifies.
        build_commands => {
            prompt => <<EOP,
What build_commands configuration option would you like? 
EOP
            print_me => <<EOP
build_commands specifies what commands fetchware will run to compile your
software package. Fetchware's default is simply 'make', which is good for most
programs. If you're software package uses something other than fetchware's
default of GNU AutoTools, then you may need to change this configuration option
to specify what you would like instead. Specify multiple build commands in
single quotes with a comma between them:
'./configure', 'make'
EOP
        },
        install_commands => {
            prompt => <<EOP,
What install_commands configuration option would you like? 
EOP
            print_me => <<EOP
install_commands specifies what commands fetchware will run to install your
software package. Fetchware's default is simply 'make install', which is good
for most programs. If you're software package uses something other than
fetchware's default of GNU AutoTools, then you may need to change this
configuration option to specify what you would like instead. Specify multiple
build commands in single quotes with a comma between them:
'make test', 'make install'
EOP
        },
        uninstall_commands => {
            prompt => <<EOP,
What uninstall_commands configuration option would you like?
EOP
            print_me => <<EOP,
uninstall_commands specifes what commands fetchware will run to uninstall your
software pacakge. The default is 'make uninstall,' which works for some GNU
AutoTools packages, but not all. If your software package does not have a 'make
uninstall' make target, but it has some other command that can uninstall it,
then please specify it using uninstall_commands so fetchware can uninstall it. 
EOP

        },
        lookup_method => {
            prompt => <<EOP,
What lookup_method configuration option would you like? 
EOP
            print_me => <<EOP
lookup_method specifies what how fetchware determines if a new version of your
software package is available. The available algorithms are 'timstamp' and
'versionstring'. 'timestamp' uses the timestamp listed in the FTP or HTTP
listing, and uses the software package that is the newest by filesystem
timestamp. The 'versionstring' algorithm uses the filename of the files in the
FTP or HTTP listing. It parses out the version information, sorts it highest to
lowest, and then picks the highest version of your software package. The default
is try 'timestamp' and if that doesn't work, then try 'versionstring'.
EOP
        },
        gpg_keys_url => {
            prompt => <<EOP,
What gpg_keys_url configuration option would you like? 
EOP
            print_me => <<EOP
gpg_keys_url specifies a url similar to lookup_url in that it should specify a
directory instead a specific file. It is used to download KEYS files, which
contain your program author's gpg keys to import into gpg.
EOP
        },
        gpg_sig_url => {
            prompt => <<EOP,
What gpg_sig_url configuration option would you like? 
EOP
            print_me => <<EOP
gpg_sig_url specifies a url similar to lookup_url in that it should specify a
directory instead a specific file. It is used to download gpg signatures to
verify your software package.
EOP
        },
        sha1_url => {
            prompt => <<EOP,
What sha1_url configuration option would you like? 
EOP
            print_me => <<EOP
sha1_url specifies a url similar to lookup_url in that it should specify a
directory instead of a specific file. It is separate from lookup_url, because
you should download software from mirrors, but checksums from the original
vendor's server, because checksums are easily replaced on a mirror by a hacker
if the mirror gets hacked.
EOP
        },
        md5_url => {
            prompt => <<EOP,
What md5_url configuration option would you like? 
EOP
            print_me => <<EOP,
md5_url specifies a url similar to lookup_url in that it should specify a
directory instead of a specific file. It is separate from lookup_url, because
you should download software from mirrors, but checksums from the original
vendor's server, because checksums are easily replaced on a mirror by a hacker
if  the mirror gets hacked.
EOP
        },
        verify_method => {
            prompt => <<EOP,
What verify_method configuration option would you like? 
EOP
            print_me => <<EOP,
verify_method specifies what method of verification fetchware should use to
ensure the software you have downloaded has not been tampered with. The default
is to try gpg verification, then sha1, and then finally md5, and if they all
fail an error message is printed and fetchware exits, because if your software
package cannot be verified, then it should not be installed. This configuration
option allows you to remove the warnings by specifying a specific way of
verifying your software has not been tampered with. To disable verification set
the 'verify_failure_ok' configuration option to true.
EOP
        },
###BUGALERT### replace no_install config su with a command line option that
#would be the opposite of --force???
# Nah! Leave it! Just create a command line option for it too!
        no_install => {
            prompt => <<EOP,
Would you like to enable the no_install configuration option? 
EOP
            ###BUGALERT### no_install is not currently implemented properly!!!
            print_me => <<EOP
no_install is a true or false option, whoose acceptable values include 1
or 0, true or falue, On or Off. It's default value is false, but if you enable
it, then fetchware will not install your software package, and instead it will
simply download, verify, and build it. And then it will print out the full path
of the directory it built your software package in.
EOP
            ###BUGALERT### Add support for a check regex, so that I can ensure
            #that what the user enters will be either true or false!!!
        },
        verify_failure_ok => {
            prompt => <<EOP,
Would you like to enable the verify_failure_ok configuration option? 
EOP
            print_me => <<EOP
verify_failure_ok is a true or false option, whoose acceptable values include 1
or 0, true or falue, On or Off. It's default value is false, but if you enable
it, then fetchware will not print an error message and exit if verification
fails for your software package. Please note that you should never use this
option, because it makes it possible for fetchware to install source code that
may have been tampered with.
EOP
        },
        users_keyring => {
            prompt => <<EOP,
Would you like to enable users_keyring configuration option? 
EOP
            print_me => <<EOP
users_keyring when enabled causes fetchware to use the user who calls
fetchware's gpg keyring instead of fetchware's own gpg keyring. Useful for
source code distributions that do not provide an easily accessible KEYS file.
Just remember to import the author's keys into your gpg keyring with gpg
--import.
EOP
        },
    );
    my %answered_option;

    if (
        $term->ask_yn(prompt =>
        q{Would you like to add extra configuration options to your fetchwarefile?},
        default => 'n',
        print_me => <<EOP,
Fetchware has many different configuration options that allow you to control its
behavior, and even change its behavior if needed to customize fetchware for any
possible source code distribution.

If you think you need to add configuration options please check out perldoc
fetchware for more details on fetchware and its Fetchwarefile configuration
options.

If this is your first package your creating with Fetchware or you're creating a
package for a new program for the first time, you should skip messing with
fetchware's more flexible options, and just give the defaults a chance.
EOP
        )
    ) {
        my @options = keys %option_description;
        my @config_file_options_to_provide = $term->get_reply(
            print_me => <<EOP,
Below is a listing of Fetchware's available configuration options.
EOP
            prompt => <<EOP,
Please answer with a space seperated list of the number before the configuration
file options that you would like to add to your configuration file? 
EOP
            choices => \@options,
            multi => 1,
        );


        for my $config_file_option (@config_file_options_to_provide) {
            $answered_option{$config_file_option} = $term->get_reply(
                print_me => $option_description{$config_file_option}->{print_me},
                prompt => $option_description{$config_file_option}->{prompt},
            );
        }
    }
    return %answered_option;
}



sub append_options_to_fetchwarefile {
    my ($options, $fetchwarefile) = @_;

    my %config_file_description = (
        program => <<EOA,
program simply names the program the Fetchwarefile is responsible for
downloading, building, and installing.
EOA
        filter => <<EOA,
filter specifies a program name and/or version number that tells fetchware
which program and or which version of a program you want fetchware to install.
This is *only* needed in cases where there are multiple programs and or
multiple versions of the same program in the directory lookup_url specifies.
EOA
        temp_dir => <<EOA,
temp_dir specifies what temporary directory fetchware will use to download and
build this program.
EOA
        user => <<EOA,
user specifes a user that fetchware will drop priviledges to when fetchware
downloads and builds your software. It will then switch back to root privs, if
run as root, and install your software system wide. This does not work on
Windows.
EOA
        fetchware_database_path => <<EOA,
fetchware_database_path specifies an alternate path for fetchware to use to
store the fetchware package that 'fetchware install' creates, and that
'fetchware upgrade' uses to upgrade this fetchware package.
EOA
        prefix => <<EOA,
prefix specifies what base path your software will be installed under. This
only works for software that uses GNU AutoTools to configure itself, it uses
./configure.
EOA
        configure_options => <<EOA,
configure_options specifes what options fetchware should pass to ./configure
when it configures your software. This option only works for software that
uses GNU AutoTools.
EOA
        make_options => <<EOA,
make_options specifes what options fetchware should pass to make when make is
run to build and install your software.
EOA
        build_commands => <<EOA,
build_commands specifies what commands fetchware should execute to build your
software.
EOA
        install_commands => <<EOA,
install_commands specifies what commands fetchware should execute to install
your software.
EOA
        uninstall_commands => <<EOA,
uninstall_commands specifies what commands fetchware should execute to uninstall
your software.
EOA
        lookup_url => <<EOA,
lookup_url specifes the url that fetchware uses to determine what what
versions of your program are available. It should point to a directory listing
instead of a specific file.
EOA
        lookup_method => <<EOA,
lookup_method specifies how fetchware determines what version of your program
to install. The default is the 'timestamp' algorithm, and then to try the
'versionstring' algorithm if 'timestamp' fails. lookup_method specifies which
one you would like to use. Only the strings 'timestamp' and 'versionstring'
are allowed options.
EOA
        gpg_keys_url => <<EOA,
gpg_keys_url specifies the url that fetchware will use to download the author's
KEYS file that it uses for gpg verification.
EOA
        gpg_sig_url => <<EOA,
gpg_sig_url specifies the url that fetchware uses to download digital
signatures of this program. They're files that usually end .asc.
EOA
        sha1_url => <<EOA,
sha1_url specfies the url that fetchware uses to download sha1sum files of
this program. This url should be the program's main download site instead of a
mirror, because a hacked mirror could alter the sha1sum on that mirror.
EOA
        md5_url => <<EOA,
md5_url specfies the url that fetchware uses to download md5sum files of
this program. This url should be the program's main download site instead of a
mirror, because a hacked mirror could alter the md5sum on that mirror.
EOA
        verify_method => <<EOA,
verify_method specifes a specific method that fetchware should use to verify
your program. This method can be 'gpg', 'sha1', or 'md5'.
EOA
        no_install => <<EOA,
no_install specifies that this software should not be installed. Instead, the
install step is skipped, and fetchware prints to STDOUT where it downloaded,
verified, and built your program. no_install must be a true or false value.
EOA
        verify_failure_ok => <<EOA,
verify_failure_ok specifies that fetchware should not stop installing your
software and terminate with an error message if fetchware fails to verify your
software. You should never set this to true. Doing so could cause fetchware to
install software that may have been compromised, or had malware inserted into
it. Never use this option unless the author or maintainer of this program does
not gpg sign or checksum his software.
EOA
        user_keyring => <<EOA,
users_keyring if enabled causes fetchware to use the user's own gpg keyring
instead of fetchware's own keyring.
EOA
        mirror => <<EOA
The mirror configuration option provides fetchware with alternate servers to
try to download this program from. This option is used when the server
specified in the url options in this file is unavailable or times out.
EOA
    );

    # Ensure %description_seen keeps track across calls. NOTE: that this will
    # cause bugs if more than one Fetchwarefile is created with cmd_new. But as
    # long as only one Fetchwarefile is created per fetchware run, this
    # limitation is ok.
    state %description_seen;
    for my $opt (keys %$options) {
        if (defined $config_file_description{$opt}) {
            # If the description has not been written to the $fetchwarefile yet,
            # then include it.
            unless (exists $description_seen{$opt}
                and defined $description_seen{$opt}
                and $description_seen{$opt} > 0 
            ) {
                append_to_fetchwarefile($fetchwarefile, $opt, $options->{$opt},
                    $config_file_description{$opt});
            # Otherwise avoid duplicating the description.
            } else {
                append_to_fetchwarefile($fetchwarefile, $opt, $options->{$opt});
            }
            vmsg <<EOM;
Appended [$opt] configuration option [$options->{$opt}] to Fetchwarefile.
EOM
        } else {
            die <<EOD;
fetchware: append_options_to_fetchwarefile() was called with \$options that it
does not support having a description for. Please call
append_options_to_fetchwarefile() with the correct \$options, or add the new,
missing option to append_options_to_fetchwarefile()'s internal
%config_file_description hash. \$options was:
@{[Dumper(\%$options)]}
EOD
        }
        # Increment this for each time each $opt is written to the
        # $fetchwarefile to ensure that only on the very first time the $opt
        # is written to the $fetchwarefile that its description is also
        # written.
        $description_seen{$opt}++;
    }
}



sub edit_manually {
    my ($term, $fetchwarefile) = @_;

    if (
        $term->ask_yn(
        print_me => <<EOP,
Fetchware has now asked you all of the needed questions to determine what it
thinks your new program's Fetchwarefile should look like. But it's not perfect,
and perhaps you would like to tweak it manually. If you would like to edit it
manually in your favorite editor, answer 'yes', and if you want to skip this just
answer 'no', or just press <Enter>.

If you would like to cancel any edits you have made, and use the automagically
generated Fetchwarefile, just delete the entire contents of the file, and save
an empty file.
EOP
            prompt => q{Would you like to edit your automagically generated Fetchwarefile manually? },
        default => 'n',
        )
    ) {
        my ($fh, $fetchwarefile_filename) =
            tempfile('Fetchwarefile-XXXXXXXXX', TMPDIR => 1);
        print $fh $$fetchwarefile;

        close $fh;

        # Ask what editor to use if EDITOR environment variable is not set.
        my $editor = $ENV{EDITOR} || do {
            $term->get_reply(prompt => <<EOP,
What text editor would you like to use? 
EOP
                print_me => <<EOP
The Environment variable EDITOR is not set. This is used by fetchware and other
programs to determine what program fetchware should use to edit your
Fetchwarefile. Please enter what text editor you would like to use. Examples
include: vim, emacs, nano, pico, or notepad.exe (on Windows).
EOP
            );
        };

        run_prog($editor, $fetchwarefile_filename);
        # NOTE: fetchware will "block" during the above call to run_prog(), and
        # wait for the user to close the editor program.

        # If the edited Fetchwarefile does not have a file size of zero.
        if (not -z $fetchwarefile_filename) {
            my $fh = safe_open($fetchwarefile_filename, <<EOD);
fetchware: run-time error. fetchware can't open the fetchwarefile you edited
with your editor after you edited it. This just shouldn't happen. Possible race
condition or weird bug. See perldoc fetchware.
EOD
            $$fetchwarefile = do { local $/; <$fh> }; # slurp fetchwarefile
        } else {
            msg <<EOM;
You canceled any custom editing of your fetchwarefile by writing an empty file
to disk.
EOM
        }
    }
    return $fetchwarefile;
}



sub check_fetchwarefile {
    my $fetchwarefile = shift;

    msg <<EOM;
Checking your Fetchwarefile to ensure it has all of the mandatory configuration
options properly configured.
EOM

    vmsg <<EOM;
The generated fetchwarefile is [
$$fetchwarefile
]
EOM

    # %checkers are either an error message, which will be printed when an error
    # message is the value, or a coderef(), which is called with each line, and
    # is responsible for throwing its own exception or printing any warnings
    # itself.
    my %checkers = (
        program => <<EOE,
fetchware: The Fetchwarefile fetchware generated for you does not have a program
configuration option. Please add a program configuration file such as
[program 'My Program';] The generated Fetchwarefile was [
$$fetchwarefile
]
EOE
        lookup_url => <<EOE,
fetchware: The Fetchwarefile fetchware generated for you does not have a
lookup_url configuration option. Please add a lookup_url configuration file such
as [lookup_url 'My Program';] The generated Fetchwarefile was [
$$fetchwarefile
]
EOE
        mirror => <<EOE,
fetchware: The Fetchwarefile fetchware generated for you does not have a mirror
configuration option. Please add a mirror configuration file such as
[mirror 'My Program';] The generated Fetchwarefile was [
$$fetchwarefile
]
EOE
        verification => sub {
            my $fetchwarefile = shift;

            if ($$fetchwarefile =~ /^\s*gpg_keys_url\s+/m) {
                return 'Success';
            } elsif ($$fetchwarefile =~ /^\s*user_keyring\s+/m) {
                return 'Success';
            } else {
                msg <<EOM;
Warning: gpg verification is *not* enabled. Please switch to gpg verification if
possible, because it is more secure against hacked 3rd party mirrors.
EOM
            }

            if ($$fetchwarefile
                =~ /^\s*verify_failure_ok\s+'?(?i:On|1|True)/m) {
                msg <<EOM;
Warning: verify_failure_ok has been enabled! This means downloads are not
verified to ensure that they have not been tampered with by 3rd parties! Mirrors
do sometimes get hacked, so please enabled verification if possible.
EOM
            }

        },
    );

    for my $check (sort keys %checkers) {
        if (not ref $checkers{$check}) {
            # Throw the specified exception if $check is not found at the
            # beginning of a line. Use //m to turn on multiline mode, so that
            # perl will search the whole fetchwarefile at once not line by line.
            die $checkers{$check} if not $$fetchwarefile =~ /^\s*$check\s+/m
        } elsif (ref $checkers{$check} eq 'CODE') {
            $checkers{$check}->($fetchwarefile);
        } else {
            die <<EOD;
fetchware: Internal Error! Wrongly formatted %checker was specified to the
internal fetchware new subroutine, check_fetchwarefile(). This just shouldn't
happen! This is a bug, so please report it.
EOD
        }
    }
    # Return success!
    return 'Fetchwarefile syntax correct!';
}



sub ask_to_install_now_to_test_fetchwarefile {
    my ($term, $fetchwarefile, $program_name) = @_;


    vmsg <<EOM;
Determining if user wants to install now or just save their Fetchwarefile.
EOM

    # If the user wants to install their new Fetchwarefile.
    if (
        $term->ask_yn(
        print_me => <<EOP,
It is recommended that fetchware go ahead and install the program based on the
Fetchwarefile that fetchware has created for you. If you don't want to install
it now, then enter 'no', but if you want to test your Fetchwarefile now, and
install it, then please enter 'yes' or just press <Enter>.
EOP
        prompt => q{Would you like to install the program you just created a Fetchwarefile for? },
        default => 'y',
        )
    ) {

        # Create a temp Fetchwarefile to store the autogenerated configuration.
        my ($fh, $fetchwarefile_filename)
            =
            tempfile("fetchware-$$-XXXXXXXXXXXXXX", TMPDIR => 1, UNLINK => 1);
        print $fh $$fetchwarefile;
        # Close the temp file to ensure everything that was written to it gets
        # flushed from caches and actually makes it to disk.
        close $fh;

        vmsg <<EOM;
Saved Fetchwarefile temporarily to [$fetchwarefile_filename].
EOM

        my $fetchware_package_path = cmd_install($fetchwarefile_filename);
        ###BUGALERT### Call cmd_install() inside an eval that will catch any
        #problems that come up, and suggest how to fix them???
        #Is that really doable???
        vmsg <<EOM;
Copied Fetchwarefile package to fetchware database [$fetchware_package_path].
EOM
        msg 'Installed Fetchware package to fetchware database.';
        return $fetchware_package_path;
    # Else the user just wants to save the Fetchwarefile somewhere.
    } else {
        my $fetchwarefile_filename = $program_name . '.Fetchwarefile';

        # Get a name for the Fetchwarefile that does not already exist.
        if (-e $fetchwarefile_filename) {
            while (1) {
                $fetchwarefile_filename = $term->get_reply(prompt => <<EOP,
What would you like your new Fetchwarefile's filename to be? 
EOP                 
                    print_me => <<EOP
Fetchware by default uses the program name you specified at the beginning of
running fetchware new plus a '.Fetchwarefile' extension to name your
Fetchwarefile. But his file already exists, so you'll have to pick a new
filename that does not currently exist.
EOP
                );
                last unless -e $fetchwarefile_filename;
            }
        }
        vmsg <<EOM;
Determine Fetchwarefile name to be [$fetchwarefile_filename].
EOM

    ###BUGALERT### Replace >, create or delete whole file and replace it with
    #what I write now, with >> for append to file if it already exists????
        my $fh = safe_open($fetchwarefile_filename, <<EOD, MODE => '>');
fetchware: failed to open your new fetchwarefile because of os error
[$!]. This really shouldn't happen in this case. Probably a bug, or a weird race
condition.
EOD
        print $fh $$fetchwarefile;

        close $fh;

        msg "Saved Fetchwarefile to [$fetchwarefile_filename].";
        return $fetchwarefile_filename;
    }
}





###BUGALERT### Add a config sub for a command to run after upgrade.
#C<after_upgrade_commands> that will allow you to restart apache or whatever
#after you've upgraded it, so that the newest version is running after you
#upgrade, because otherwise the currently running version won't have whatever
#suecurity fixes that might have been in the previous release.
sub cmd_upgrade {
    my $upgrade_name = shift;

    my ($P_file_listing_0_0,
        $P_download_url_basename,
        $P_upgrade_name_basename,
        $P_build_path);

    ###BUGALERT### the or --force cmdline option will skip the checking of
    #version numbers, and the one lookup() says to download will be installed
    #regardless.
    msg "Upgrading installed fetchware package [$upgrade_name].";

    my $fetchware_package_path = determine_fetchware_package_path($upgrade_name);
    vmsg <<EOM;
Determined already installed fetchware package's path to be [$fetchware_package_path].
EOM

    # Parse out the Fetchwarefile from the fetchware package stored in the
    # fetchware database directory.
    my $fetchwarefile;
    if ($fetchware_package_path =~ /\.fpkg$/) {
        $fetchwarefile
            = extract_fetchwarefile($fetchware_package_path);            
        vmsg "Extracted Fetchwarefile temporarily into [$fetchwarefile]";
    } else {
        die <<EOD;
fetchware: fetchware upgrade failed to extract the Fetchwarefile from the
fetchware package that should be stored in fetchware's database.
EOD
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

    # Drop privs, so only install() is called with root permissions
    my $output = drop_privs(
    sub {
        my $write_pipe = shift;

        ###BUGALERT### Have lookup() replace the timestamp of what we should
        #download too to make upgrade() be able to use the lookup_by_timestamp
        #algorithm too, which is a better default anyway.
        my $download_url = lookup();

        my $P_download_url_basename = file($download_url)->basename();
        my $P_upgrade_name_basename =
            file( $fetchware_package_path)->basename();
        vmsg <<EOM;
Shortened the new download url [$P_download_url_basename] and the installed
package's [$P_upgrade_name_basename] into just their basenames.
EOM
        # Strip trailing garbage to normalize their names, so that they can be
        # compared to each other.
        ###BUGALERT### This comparision is quite fragile. Figure out a better way to
        #do this!!!
        $P_upgrade_name_basename =~ s/\.fpkg$//;
        $P_download_url_basename
            =~ s/(\.(?:zip|tgz|tbz|txz|fpkg)|(?:\.tar\.(gz|bz2|xz|Z)?))$//;
        vmsg <<EOM;
Striped the new download url [$P_download_url_basename] and the installed
package's [$P_upgrade_name_basename] of their file extensions.
EOM

        # Transform both competing filenames into a string of version numbers.
        my @file_listing = map { [$_, (join '', split /\D+/, $_)] }
            $P_download_url_basename, $P_upgrade_name_basename;
        die <<EOD if grep { not defined $_->[1] } @file_listing;
fetchware: Of the installed filename [$P_upgrade_name_basename] and the
potential replacement filename [$P_download_url_basename] fetchware cannot tell
which one is newer, and if a new package is available. Please report this as a
bug, so I can make fetchware's upgrade functionality more robust.
EOD
        vmsg 'Transformed competing filenames into a string of version numbers.';
        vmsg Dumper(\@file_listing);
        @file_listing = sort { $b->[1] <=> $a->[1] } @file_listing;
        # If the highest sorted, "latest version," filename is the same as the one
        # lookup() lookedup, then a new version is available, and we should install
        # it.
        # Clean up 
        $P_file_listing_0_0 = $file_listing[0][0];

        if ($P_file_listing_0_0 eq $P_download_url_basename
            # Make cmd_upgrade() not upgrade when the ver
            and $P_file_listing_0_0 ne $P_upgrade_name_basename) {
            msg 'New version available upgrading now.';

            my $package_path = download($temp_dir, $download_url);

            ###BUGALERT### Add support for caching the key files gpg creates to the
            #fetchwarefile, and for actually using them later on inside the fpkg.
            verify($download_url, $package_path);

            $P_build_path = unarchive($package_path);
            build($P_build_path);
        } # End If new version available.

        # Tell the parent, root, process the values of the variables the
        # child calculated in this coderef, and write them across this pipe
        # back to the parent
        #
        # Note: how @file_listing is the last one pipe_{write,read}_newline() do
        # *not* support arrays, but I'm faking it by using Perl's list
        # semantics--you know how perl will read from an array until its end
        # when you call a subroutine, and it will write a list to a array when
        # you assign from a subroutine in list context.
        write_dropprivs_pipe($write_pipe,
            $P_download_url_basename,
            $P_upgrade_name_basename,
            $P_file_listing_0_0,
            $P_build_path);
    }, config('user')
    ); # End drop_privs()


    # Read from the pipe the child, the drop_privs()ed process, writes to to
    # read the necessary values that correspond to the variables that the
    # child must communicate back to the parent, so the parent can continue
    # processing as though no fork()ing or priv dropping took place.
    #
    # Uses and abuses Perl's list calling and and returning semantics to
    # fakes support for writting an array (@file_listing)
    ($P_download_url_basename,
    $P_upgrade_name_basename,
    $P_file_listing_0_0,
    $P_build_path) = read_dropprivs_pipe($output);

    # Test if a new version is available again due to drop_priv() ending
    # half way through this if statement.
    if ($P_file_listing_0_0 eq $P_download_url_basename
        # Make cmd_upgrade() not upgrade when the ver
        and $P_file_listing_0_0 ne $P_upgrade_name_basename) {
        install($P_build_path);

        my $updated_fetchware_package_path
            =
            create_fetchware_package($fetchwarefile, cwd());
        vmsg <<EOM;
Created a new fetchware package for the newly installed upgraded fetchware
package [$updated_fetchware_package_path].
EOM

        # Note: I must uninstall the current package before I copy over the new
        # one, because uninstall_fetchware_package_from_database() requires a
        # simple name that it looks up in the fetchware database. It does not
        # accept a full path to something to delete for a full fetchware package
        # name that would support multiple versions being in the fetchware
        # package database at the same time.
        ###BUGALERT### The above Note is a ridiculous bug. Fix it!
        vmsg 'Uninstalled the old fetchware package from the fetchware database.';
        uninstall_fetchware_package_from_database($fetchware_package_path);

        my $installed_fetchware_package_path
            = copy_fpkg_to_fpkg_database($updated_fetchware_package_path);
        vmsg <<EOM;
Installed new fetchware package to fetchware package database
[$installed_fetchware_package_path].
EOM

        end();

        # Return the path of the created and installed fetchware package.
        return $installed_fetchware_package_path;
    } else {
        msg <<EOM;
The latest version [$P_download_url_basename] is the same as the currently
installed version [$P_upgrade_name_basename]. So no upgrade is needed. 
EOM
        # Clean up temp dir.
        end();

        # Return success! An upgrade isn't needed, because the latest version
        # has been installed.
        return 'No upgrade needed.';
    }
}



sub cmd_upgrade_all {
    # Does *not* drop_privs(), because it calls cmd_upgrade(), which does, and
    # it does not make any real sense to do it in cmd_upgrade_all(), because all
    # it does is glob the fetchware_database_path(), and pass each element
    # of that list to cmd_upgrade() to do the actual upgrading.
    die <<EOD if @_;
fetchware: fetchware's upgrade-all command takes no arguments. Instead, it
simply loops through fetchware's package database, and upgrades all already
installed fetchware packages. Please rerun fetchware upgrade-all without any
arguments to upgrade all already installed packages, or run fetchware help for
usage instructions.
EOD

    msg 'Upgrading all installed fetchware packages.';

    my $fetchware_db_glob = catfile(fetchware_database_path(), '*');

    my @upgraded_packages;
    for my $fetchware_package (glob $fetchware_db_glob) {
        vmsg 'Looping over list of installed fetchware packages.';
        ###BUGALERT### subize the 2 lines below, because I do this more than
        #once.
        # Strip each member of the fetchwarefile database down to just its name
        # without any path garbage or fetchware package file extension, because
        # cmd_upgrade() only accepts arguments of this format, and I do not want
        # users to be able to provide a fetchware package as an argument to
        # the fetchware upgrade command. I only want it capable of looking them
        # up from its database.
        $fetchware_package = file($fetchware_package)->basename();
        $fetchware_package =~ s/\.fpkg$//;
        ###BUGALERT### Spit out a warning for anything in
        #fetchware_database_path() that does not end .fpkg, which should be
        #here.
        vmsg "Upgrading installed fetchware package [$fetchware_package]";
        push @upgraded_packages, cmd_upgrade($fetchware_package);
    }

    ###BUGALERT### push the fetchware pacakge name and its cmd_upgrade() return
    #value into a hash, and then return it or msg() it, to tell the user what
    #was upgraded and what was not.
    # Return 'No upgrade needed.' only if every package that was upgraded
    # returned 'No upgrade needed.'.
    if ( (grep { $_ eq 'No upgrade needed.'}
            @upgraded_packages) eq @upgraded_packages) {
        msg 'No packages need to be upgraded.';
        return 'No upgrade needed.';
    # Return a list of all packages that are not 'No upgrade needed.', which
    # should not be returned.
    } else {
        my @upgraded_packages = grep { $_ ne 'No upgrade needed.' }
            @upgraded_packages;
        msg 'Packages were upgraded to newer versions:';
        msg Dumper(\@upgraded_packages);
        return @upgraded_packages;
    }
}




###BUGALERT### Fix the bug that prevents look from check for an installed
#package first, then a filename or fetchwarefile.
sub cmd_look {
    my $filename = shift;

    my $P_look_path;

    my $fetchwarefile;
    if ($filename =~ /\.fpkg$/) {
        $fetchwarefile = extract_fetchwarefile($filename);
        vmsg <<EOM;
Extracting out Fetchwarefile from [$filename] to [$fetchwarefile]
EOM
    } else {
        my $fh = safe_open($filename, <<EOD);
fetchware: Fetchware failed to open the filename you specified to fetchware
install [$filename]. The OS error was [$!].
EOD
        vmsg "Opened file [$filename] for slurping.";
        # Add a \ to turn the slurped scalar into a scalar ref for calling
        # parse_fetchwarefile() properly.
        $fetchwarefile = \do {local $/; <$fh>};
        vmsg  "Slurped [$filename] into fetchware: [$fetchwarefile]";
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    #
    # Call start() with an option to have it keep the temp dir, and not
    # have File::Temp clean it up with an END handler.
    my $temp_dir = start(KeepTempDir => 1);

    # Drop privs to match up with cmd_install()'s behavior.
    my $output = drop_privs(
    sub {
        my $write_pipe = shift;

        msg 'Downloading and unarchiving specified distribution.';

        ###BUGALERT### Fails to support looking up installed packages in fetchware
        #database. This test will prevent them from running.
        if (defined($filename) and -e $filename) {

            my $download_url = lookup();

            my $package_path = download(cwd(), $download_url);

            ###BUGALERT### Add support for caching the key files gpg creates to
            #the fetchwarefile, and for actually using them later on inside the
            #fpkg.
            verify($download_url, $package_path);

            my $build_path = unarchive($package_path);

            # end() is *not* run, because the point of look is to lookup,
            # download, and unarchive, and then actually "look" at the files,
            # and running end() would delete them.

            $P_look_path =  catfile($temp_dir, $build_path);
            msg <<EOM;
Your package's contents are at [$P_look_path]. Please run [fetchware clean] to
delete these files and any other files fetchware may have left behind when you
are finished looking inside this package.
EOM

            # Tell the parent, root, process the values of the variables the
            # child calculated in this coderef, and write them across this pipe
            # back to the parent
            write_dropprivs_pipe($write_pipe, $P_look_path);
        } else {
            ###BUGALERT### Replace with warn for proposed for loop above to work?
            die <<EOD;
fetchware: You called fetchware look incorrectly. You must specify
either a Fetchwarefile or a fetchware package that ends with [.fpkg].
EOD
        }

        # Does not need to execute anything as root, because cmd_look() does not
        # install anything or even call end(), because the suer is supposed to
        # look at its output in the tempdir it prints out.
    }, config('user')
    ); # End drop_privs()

    # Read from the pipe the child, the drop_privs()ed process, writes to to
    # read the necessary values that correspond to the variables that the
    # child must communicate back to the parent, so the parent can continue
    # processing as though no fork()ing or priv dropping took place.
    ($P_look_path)
        = read_dropprivs_pipe($output);

    return $P_look_path;
}



sub cmd_list {
    my @installed_packages = glob catfile(fetchware_database_path(), '*');
    
    if (@installed_packages == 0) {
        msg 'No fetchware packages are currently installed.';
        return;
    }

    msg 'Listing all currently installed packages:';
    for my $fetchware_package (@installed_packages) {
        # Clean up $fetchware_package.
        $fetchware_package = file($fetchware_package)->basename();
        $fetchware_package =~ s/\.fpkg$//;

        msg $fetchware_package;
    }
}




###BUGALERT### It could parse all installed Fetchwarefile's to obtain a listing
#of all temp_dirs that are used, and clean them as well!!!!
###BUGALERT### Use --force to parse all temp_dir's in installed packages, and
#clean them too?? Let it receive an arg to a dir to clean of fetchware crap???
sub cmd_clean {
    # If user specified no specific directories to clean, then clean the default
    # system tmpdir().
    my @fetchware_temp_dirs = scalar @_ ? @_ : tmpdir();

    my @globbed_fetchware_temp_dirs;

    # Build a list of fetchware temporary directories across tmpdir() and any
    # user provided paths on the command line.
    for my $fetchware_temp_dir (@fetchware_temp_dirs) {
        # What the user specified or tmpdir() must be a directory.
        die <<EOD if not -d $fetchware_temp_dir;
fetchware: The specified directory [$fetchware_temp_dir] is not a directory or
does not exist. Please only specify directories that exist, and ones you have
read and write permission in. OS error [$!].
EOD

        # Store all of the fetchware-* temp dirs in @globbed_fetchware_temp_dirs
        # for later processing.
        for my $fetchware_file_or_dir (
            glob(catfile($fetchware_temp_dir, 'fetchware-*')),
            glob(catfile($fetchware_temp_dir, 'Fetchwarefile-*'))
        ) {
            # If it's a directory add it to the queue of directories to delete
            # below.
            if (-d $fetchware_file_or_dir) {
                push @globbed_fetchware_temp_dirs, $fetchware_file_or_dir;
            # If it's just a file just delete right away.
            } else {
                ###BUGALERT### Should I check if the current user has perms to
                #delete the file before deleting it? What about root? Should
                #root delete all files found even for other users? I'll go with
                #the Unix default of just doing the operation, and dealing with
                #the error message you receive to avoid the complexity of
                #checking perms. Furthermore, what about Unix ACLs and Windows'
                #ACL style perms? It's not worth dealing with that hassel.
                unlink $fetchware_file_or_dir or die <<EOD;
fetchware: Failed to unlink file [$fetchware_file_or_dir]. OS error [$!].
EOD
                    vmsg <<EOM;
fetchware clean found and deleted file [$fetchware_file_or_dir].
EOM
            }
        }
    }

    msg "fetchware clean found no fetchware temporary directories to clean"
        if @globbed_fetchware_temp_dirs < 1;

    # Holds the number of directories that had errors when they were
    # deleted.
    my $num_remove_tree_errors = 0;
    # Number of directories remove_tree removed successfully.
    my $num_remove_tree_successes = 0;


    # Loop over fetchware temp dirs, and delete the ones that are not locked.
    for my $temp_dir (@globbed_fetchware_temp_dirs) {
        # Try to lock the 'fetchware.sem' semaphore lock file

        # I annoying must open the file before I can see if I can lock it or
        # not.
        my $sem_lock_file = catfile($temp_dir, 'fetchware.sem');
        my $fh_sem;
        if (open $fh_sem, '>', $sem_lock_file) {
            vmsg "Successfully created [fetchware.sem] semaphore lock file.";
        } else {
            # Test if the lockfile has the same owner uid as this running perl
            # process, and if they differ skip deleting this one, because we
            # lack the perms to do it anyway.
            if ($> != (stat($sem_lock_file))[4]) {
                msg "Skipping file [$sem_lock_file], because a different user created it.";
                next;
            } else {
                die <<EOD;
App-Fetchware-Util: Failed to create [$sem_lock_file] semaphore lock file! This
should not happen, because fetchware is creating this file in a brand new
directory that only fetchware should be accessing. You simply shouldn't see this
error unless some one is messing with fetchware, or perphaps there actually is a
bug? I don't know, but this just shouldn't happen. It's so hard to trigger it to
happen, it can't easily be tested in fetchware's test suite. OS error [$!].
EOD
            }
        }
        # Now flock 'fetchware.sem.' This should
        # Use LOCK_NB so flock won't stupidly wait forever and ever until 
        # he lock becomes available.
        # If flock fails, don't die! Instead, just skip deleting this
        # fetchware temporary directory, and go on to the next one.
        unless (flock $fh_sem, LOCK_EX | LOCK_NB) {
            # Flock failed, something else has the lock, print message, and skip
            # this directory, and go on to the next one.
            msg <<EOM;
[$temp_dir] locked by another fetchware process. Skipping.
EOM
            next;
        }

        # Delete the whole $tempdir. Use error and result for File::Path's
        # experimental error handling, and set safe to true to avoid borking the
        # filesystem. This might be run as root, so it really could screw up
        # your filesystem big time! So set safe to true to avoid doing so.
        remove_tree($temp_dir, {
            error => \my $err,
            result => \my $res,
            safe => 1} );

        # Parse remove_tree()'s insane error handling system. It's expirimental,
        # but it's been experimental forever, so I can't see it changing.
        if (@$err) {
            $num_remove_tree_errors++;
            for my $diag (@$err) {
                my ($file, $message) = %$diag;
                if ($file eq '') {
                    msg "general error: $message\n";
                } else {
                    ###BUGALERT### Make a wmsg() that does the same thing
                    #msg()does except it prints the message to STDERR
                    #instead of STDOUT.
                    ###BUGALERT### Perhaps a wvmsg() too?
                    msg "problem unlinking $file: $message\n";
                }
            }
        } else {
            msg "No errors encountered\n";
        }

        if (@$res) {
            $num_remove_tree_successes = @$res;
            vmsg "unlinked [$_]" for @$res;
        }
    }

    # Summarize success or failure for user, so he doesn't have to dig
    # through a bunch of error messages to see if it worked right.
    msg <<EOM if $num_remove_tree_errors > 0;
fetchware clean had [$num_remove_tree_errors] directories give errors.
EOM
    msg <<EOM if $num_remove_tree_successes > 0;
fetchware clean successfully deleted [$num_remove_tree_successes] directories. 
EOM

}



sub cmd_help {
	print <<'HELP';

fetchware is a package manager for source code distributions. It gives you the
ability to install, uninstall, and even upgrade your source code distributions
just like you can with your binary packages using yum, apt-get, or slackpkg.

To create a new package just use fetchware's "new" command such as:
	$ fetchware new
And then answer the questions as best you can while fetchware takes your
answers and creates a Fetchwarefile for you. If your program's needs seem to
exceed the ability of fetchware's q&a configuration see perldoc App::Fetchware
for instructions on manual Fetchwarefile configuration.

USAGE:
	fetchware new|install|uninstall|upgrade|upgrade-all|list|look|clean|help
		[--help|-h|-?|--version|-V|--verbose|-v|--quiet|-q]
		package-name

COMMANDS:
    new - creates a new Fetchwarefile for use with fetchware.
	install - installs a fetchware package, which is a .tar.gz ending with
		.fpkg, which includes the source code distribution unmodified,
		but with an included Fetchwarefile. See perldoc fetchware.
	uninstall - uninstalls a fetchware package.
	upgrade - upgrades a fetchware package if a newer version is available.
	upgrade-all - upgrades *all* installed fetchware packages.
    list - lists all installed fetchware packages.
    look - downloads and unarchives a fetchware package for viewing.
    clean - deletes any left over messes caused by fetchware in your tempdir.
	help - prints this help message
OPTIONS:
	--help|-h|-? - prints this help message.
	--version|-V - prints a version message.
	--verbose|-v - prints additional logging information.
	--quiet|-q - prints *no* logging invormation. Determine success or
		failure with fetchware's exit status. 0 = success. Non-zero = failure.

For more information see perldoc fetchware and perldoc App::Fetchware.
HELP

    ###BUGALERT###Consider actually adding dry run functionality.
    #--dry-run|-d - turns on dry run functionality causing fetchware to not
    #actually download or install or create any packages.
	exit 0;
}







sub parse_fetchwarefile {
    my $fetchwarefile = shift;

    # Arg $fetchwarefile must be a SCALAR ref.
    die <<EOD unless ref($fetchwarefile) eq 'SCALAR';
fetchware: parse_fetchwarefile() was called with the wrong arguments. It only
accepts and scalar references of the text of your fetchwarefile.
EOD

    # Ensure the $fetchwarefile has a use App::Fetchware somewhere in it. And be
    # sure to support fetchware extensions such as App::FetchwareX::HTMLPageSync.
    die <<EOD unless $$fetchwarefile =~ /^\s*use\s+App::FetchwareX?(::)?/m;
fetchware: The fetchwarefile you provided did not have a [use App::Fetchware]
line in it. This line is required, because it is an important part of how
fetchware uses Perl for its configuration file. Your fetchware file was.
[$$fetchwarefile]
EOD

    # Do the potentially evil eval. No Safe compartment or use ops is used. This
    # is one gigantic security hole; however, it is also how fetchware works :)
    #
    # safe_open() is used to ensure that the file the user provides is "safe" to
    # use, and is the limit of fetchware's safty features.
    eval $$fetchwarefile;

    die <<EOD if $@;
fetchware: run-time error. fetchware failed to execute the Fetchwarefile
[$$fetchwarefile] you provieded on the command line or that was packaged
with your Fetchware package (*.fpkg). The error was [$@].
EOD


    # Ensure that the specified App::Fetchware implementation exports the proper
    # subroutines.
    my %api_subs = (
        start => 1,
        lookup => 1,
        download => 1,
        verify => 1,
        unarchive => 1,
        build => 1,
        install => 1,
        uninstall => 1,
        end => 1,
    );

    # Determine if all of the @api_subs are in sublist, the list of all subs in
    # the current package.
    # Code adapted from Perl Cookbook pg. 129.
    my (%union, %intersection);
    for my $element (keys %api_subs, sublist()) {
        $union{$element}++ && $intersection{$element}++;
    }

    unless ( (grep {exists $api_subs{$_} and exists $intersection{$_}
                and $api_subs{$_} eq $intersection{$_}} keys %api_subs) == 9) {
        my @missing_api_subs;
        for my $api_sub (keys %api_subs) {
            if (not exists $intersection{$api_sub}
                or not defined $intersection{$api_sub}
                or ($intersection{$api_sub} == 0)
            ) {
                push @missing_api_subs, $api_sub;
            }
        }
        die <<EOD;
fetchware: The App::Fetchware module you choose in your fetchwarefile does not
properly export the necessary subroutines fetchware needs it to. These include:
start(), lookup(), download(), verify, unarchive(), build(), install(),
uninstall(), and end().
The missing subroutines are [@missing_api_subs].
EOD
    }

    return 'Evaled config file successfully';
}



sub create_fetchware_package {
    my ($fetchwarefile,
        $unarchived_package_path,
        $dir_for_new_fpkg) = @_;

    # chdir() to my cwd's parent directory, because my cwd is currently on linux
    # /tmp/fetchware-kd883ejfe/program-1.2, and I need the program-1.2 part to
    # be in the archive's @file_list.
    my $previous_cwd = cwd();
    my $new_dir = dir(cwd())->parent();
    chdir($new_dir) or die <<EOD;
fetchware: run-time error. Fetchware failed to change it's working directory to
[$new_dir] from [$previous_cwd]. The os error was [$!].
EOD


    # Turn something like /tmp/fetchware-djdjkd8382/package-1.2/Fetchware (with
    # the "Fetchwarefile" filename only sometimes being there) into just
    # "package-1.2"
    my $pc = dir($unarchived_package_path);
    my $last_dir = $pc->dir_list(-1, 1);
    my $fetchware_package_name = "$last_dir.fpkg";

    # The dir the new fpkg goes in is the current working directory, or a user
    # provided alternate path to store it in.
    $dir_for_new_fpkg //= cwd();
    # Calculate the full absolute path of the fetchware package I create below.
    my $fetchware_package_full_path
        =
        catfile($dir_for_new_fpkg, $fetchware_package_name);

    # Determine @file_list, because Archive::Tar does not just automatically
    # include *all* files like bin/tar does.
    my @file_list;
    find(sub {
            push @file_list, $File::Find::name;
        }, $unarchived_package_path);

    # Convert absolute filenames into relative filenames, because Archive::Tar
    # will use the exact filenames that you provide, so I need to remove the
    # unneeded machine specific paths from the paths that will be stored in the
    # fetchware package.
    $_ = abs2rel($_) for @file_list;

    my $tar = Archive::Tar->new();

    # Add the $fetchwarefile to the new fetchware package.
    # 
    # Create a Archive::Tar::File object to represent the Fetchwarefile without
    # bothering to write it to disk, or use the Fetchwarefile, which may or may
    # not already be on the disk.
    #
    # Be sure to deref $fetchwarefile, becauses it's passed in as a ref.
    my $tar_fetchwarefile
        =
        Archive::Tar::File->new(data => './Fetchwarefile', $$fetchwarefile)
            or die <<EOD;
fetchware: Failed to create a Archive::Tar::File object to represent your
Fetchwarefile
[$fetchwarefile] 
Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    $tar->add_files($tar_fetchwarefile) or die <<EOD;
fetchware: Failed to add your Fetchwarefile to fetchware's internal Archive::Tar
object. Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    # Add all of the other files to the Fetchware package.
    $tar->add_files(@file_list) or die <<EOD;
fetchware: Failed to add all of your program's files to fetchware's internal
Archvie::Tar object. Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    $tar->write($fetchware_package_full_path, COMPRESS_GZIP) or die <<EOD;
fetchware: Failed to write Archive::Tar's in-memeory tar file to disk.
Archive::Tar error [@{[Archive::Tar->error()]}].
EOD


    # chdir() back to original directory.
    chdir($previous_cwd) or die <<EOD;
fetchware: run-time error. Fetchware failed to change its working directory from
[@{[cwd()]}] to [$previous_cwd]. The os error was [$!].
EOD

    # Return a fullpath version of $fetchware_package_name.
    return $fetchware_package_full_path;
}



sub fetchware_database_path {
    # If user specifically specifies their own fetchware database path in their
    # fetchwarefile use it instead of the default one.
    my $fetchware_database_path;
    if (defined config('fetchware_db_path')) {
        $fetchware_database_path = config('fetchware_db_path');
    } elsif (defined $ENV{FETCHWARE_DATABASE_PATH}) {
        $fetchware_database_path = $ENV{FETCHWARE_DATABASE_PATH};
    } elsif (is_os_type('Unix', $^O)) {
        # If we're effectively root use a "system" directory.
        if ($> == 0) {
            # Fetchware is modeled slightly after Slackware's package manager,
            # which keeps its package database under /var/log/packages.
            $fetchware_database_path = '/var/log/fetchware';
        # else use a "user" directory.
        } else {
            $fetchware_database_path
                =
                File::HomeDir->my_dist_data('fetchware', { create => 1 });
        }
    } elsif ($^O eq "MSWin32") {
        # Load main Windows module to use to see if we're Administrator or not.
        BEGIN {
            if ($^O eq "MSWin32")
            {
                require Win32;
                Win32->import();  # assuming you would not be passing arguments to "use Module"
            }
        }
        if (Win32::IsAdminUser()) {
            # Is this an appropriate default?
            $fetchware_database_path = 'C:\Fetchware'; 
        } else {
            $fetchware_database_path
                =
                File::HomeDir->my_dist_data('fetchware' , { create => 1 });
        }
    # Fall back on File::HomeDir's recommendation if not "Unix" or windows.
    ###BUGALERT### Is this appropriate for Mac OSX???? /Fetchware perhaps?????
    } else {
         $fetchware_database_path
            =
            File::HomeDir->my_dist_data('fetchware', { create => 1 });
    }
    vmsg <<EOM;
Determined fetchware database path to be: [$fetchware_database_path]
EOM
    return $fetchware_database_path;
}



sub determine_fetchware_package_path {
    my $fetchware_package = shift;
my ($package, $filename, $line) = caller;
    my $fetchware_db_glob = catfile(fetchware_database_path(), '*');

    my @fetchware_package_filenames
        =
        grep /$fetchware_package/, glob $fetchware_db_glob;

    die <<EOD if @fetchware_package_filenames == 0;
fetchware: Fetchware failed to determine the fetchware package that is
associated with the argument that you provided to fetchware
[$fetchware_package]. In this case, fetchware only allows arguments for
fetchware packages that have already been installed. Please run fetchware list
to obtain a list of installed packages to choose from.
EOD

    ###BUGALERT### Use Term::UI, and output a numbered list for the user to
    #choose from using a prompt, and then rerun upgrade with that argument.
    if (@fetchware_package_filenames > 1) {
        # Print beginning of message to STDERR.
        warn <<EOW;
fetchware: Too many installed packages match the argument you provided to the
upgrade command. Your argument was [$fetchware_package], and the multiple
results it returned were:
EOW

        # Print modified array values to STDERR.
        for (@fetchware_package_filenames) {
            warn file($_)->basename(), "\n";
        }

        # Print closing of message to STDERR.
        die <<EOD;
Choose which package from the list above you want to upgrade, and rerun
fetchware upgrade using it as the argument for the package you want to upgrade.
EOD
    }

    # Return the first and only result.
    return $fetchware_package_filenames[0];
}



sub extract_fetchwarefile {
    my ($fetchware_package_path) = @_;

    # safe_open() the fetchware package path, which ends with .fpkg, but it
    # actually a .tar.gz.
    my $fh = safe_open($fetchware_package_path, <<EOD);
fetchware: run-time error. fetchware failed to open the Fetchwarefile you
specified on the command line [$fetchware_package_path]. Please check
permissions and try again. See perldoc App::Fetchware. OS error [$!].
EOD

    # Create a temporary file to write the ungzipped file to.
    my ($output_fh, $gunzipped_path) = tempfile("fetchware-$$-XXXXXXXXXXX",
        TMPDIR => 1, UNLINK => 1); 

    gunzip($fh => $output_fh) or die <<EOD;
fetchware: IO::Uncompress::Gunzip::gunzip failed to un gzip
[$fetchware_package_path]. Gunzip's error [$GunzipError].
EOD

    my $tar = Archive::Tar->new();

    # seek the $output_fh back to its beginning, so tar can reuse it.
    seek $output_fh, 0, SEEK_SET;

    # read in the same output filehandle that gunzip() wrote the uncompressed tar
    # file to. This prevents any race conditions, and other users from messing
    # with our version of the open file.
    $tar->read($output_fh) or die <<EOD;
fetchware: Archive::Tar failed to read in the gunziped file [$gunzipped_path]
that was previously gziped as [$fetchware_package_path].
Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    my $fetchwarefile = $tar->get_content('./Fetchwarefile')
        or die <<EOD;
fetchware: run-time error. fetchware failed to extract your fetchware package's
Fetchwarefile from the argument you specified on the command line [@ARGV].
Archive::Tar error [@{[$tar->error()]}]. Please see perldoc App::Fetchware.
EOD


    # Return a scalar ref of the $fetchwarefile that makes up the Fetchwarefile.
    # Do not
    return \$fetchwarefile;
}



sub copy_fpkg_to_fpkg_database {
    my $fetchware_package_path = shift;

    my $fetchware_db_path = fetchware_database_path();

    unless (-e $fetchware_db_path) {
        # Just use make_path() from File::Path to avoid having to check if
        # directories that contain the fetchware db directory have been created
        # or not. I doubt /var and /var/log won't exist on *nix systems, but
        # they probably don't on Mac OSX, which is kinda *nix.
        make_path($fetchware_db_path) or die <<EOD;
fetchware: run-time error. fetchware failed to create the directory that it
needs to store its database of installed packages in [$fetchware_db_path].
Library function error [$@].
EOD
    }
    cp($fetchware_package_path, $fetchware_db_path) or die <<EOD;
fetchware: run-time error. fetchware failed to copy the specified
fetchware package path [$fetchware_package_path] to [$fetchware_db_path]. Please
see perldoc App::Fetchware.
EOD
    
    # Return the full path to the fetchware package that has been copied.
    my $fetchware_package_path_basename
        = dir($fetchware_package_path)->basename();
    return catfile($fetchware_db_path, $fetchware_package_path_basename);
}



sub uninstall_fetchware_package_from_database {
    my $uninstall_package_name = shift;

    # Don't make preexisting absolute paths absolute again.
    $uninstall_package_name
        =
        catfile(fetchware_database_path(), $uninstall_package_name)
            unless file_name_is_absolute($uninstall_package_name);

    unlink $uninstall_package_name
        or die <<EOD;
fetchware: Fetchware successfully uninstalled the fetchware package you
requested [$uninstall_package_name], but it failed to also delete the
corresponding fetchware package from its database Os error [$!].
EOD
}



1;

=pod

=head1 NAME

fetchware - Fetchware is a package manager for source code distributions.

=head1 VERSION

version 1.003

=head1 SYNOPSIS

=head2 Manpage synopsis.

    fetchware [-v | --verbose] [-q | --quiet] [-h | -? | --help]
              [-V | --version] <command> [<filenames | paths | Fetchwarfiles>]

=head2 L<Create a new fetchware package.|/new>

    fetchware new <name of program>

    ... Read the printed explanations...

    ... And answer the questions fetchware asks you appropriately and then press
    enter.

=head2 L<Install a new fetchware package.|/install>

    fetchware install name-of-program.Fetchwarefile

    # And you can use a .fpkg fetchware package instead of a Fetchwarefile if
    # you have one.
    fetchware install name-of-program.fpkg

=head2 L<Upgrade a specific fetchware package.|/upgrade>

    fetchware upgrade <name of already installed program>

    # Use fetchware list to see a list of already installed programs.
    fetchware list

=head2 L<Upgrade B<all> installed fetchware packages.|/upgrade-all>

    fetchware upgrade-all

=head2 L<Uninstall an installed fetchware package.|/uninstall>

    # Requires a "uninstall" make target, or customization of its Fetchwarefile
    # to specify what specific C<uninstall_commands> will uninstall this package.

=head2 L<List all installed fetchware packages.|/list>

    fetchware list 

    # Pipe to grep if you want to search for something specific.
    fetchware list | grep <something specific>

=head2 L<"Look" inside a fetchware package.|/look>

    fetchware look <name-of-program.fpkg> | <name-of-program.Fetchwarefile>

=head2 Put this in your /etc/cron.daily to make fetchware check for updates every night

    #!/bin/sh
    # Update all already installed fetchware packages.
    fetchware upgrade-all

=head2 Or use crontab -e to put this in a user crontab if you don't want to fetchware system wide

    # Check for updates using fetchware every night at 2:30AM.
    # Minute   Hour   Day of Month     Month          Day of Week     Command    
    # (0-59)  (0-23)     (1-31)  (1-12 or Jan-Dec) (0-6 or Sun-Sat)
        30      2          *              *               *           fetchware upgrade-all

=head1 DESCRIPTION

Fetchware is a package manager for source code distributions. It takes advantage
of the fact that coincidentially I<most> source code distributions follow the same
conventions. Most use FTP and HTTP mirrors. Most use AutoTools or at least just
a few commands that you execute in sequence to configure, build, and install the
program.

Fetchware harnesses these conventions to create a powerful and flexible package
manager for source code distributions. It includes a simple, powerful, and
flexible configuration syntax stored in files called C<Fetchwarefile>s. These
C<Fetchwarefile>s specify the required mandatory configuration options,
C<program>, C<lookup>, C<mirror>, and a method of verifying your program. And
they also specify any additional optional configuration options.

To create a new Fetchwarefile to install a source code distribution use the
L<fetchware new|/new> command. It will ask you a bunch of questions, and based
on your answers and fetchware's assumptions fetchware will automagically create
a new Fetchwarefile for you. Then it will ask if you would like fetchware to
install it for you.

If your source code distribution exceeds fetchware's new command's capabilities,
then see the section L<MANUALLY CREATING A App::Fetchware FETCHWAREFILE> in
L<App::Fetchware>. It details how to create a Fetchwarefile manually in a text
editor of your choice.

Fetchware's commands are described next followed by its options. Following that
is the section L<HOW FETCHWARE WORKS>, which describes in some detail how
Fetchware does its magic, and documents how it all fits together.

See L<App::Fetchware> for more information on fetchware's Fetchwarefile syntax:

=over

=item *

L<App::Fetchware/"MANUALLY CREATING A App::Fetchware FETCHWAREFILE"> - Describes
how to create a appropriate Fetchwarefile manually using a text editor. This
can be skipped. You should try fetchware's L<new command|/new> first.

=item *

L<App::Fetchware/"USING YOUR App::Fetchwarefile WITH FETCHWARE"> - Shows how to
use your newly created fetchwarefile with fetchware.

=item *

L<App::Fetchware/"App::Fetcwhare'S FETCHWAREFILE CONFIGURATION OPTIONS"> - Details
all of fetchware's configuration options that you can use to further customize
your Fetchwarefile.

=item *

L<App::Fetchware/"FURTHER CUSTOMIZING YOUR FETCHWAREFILE"> - Shows you how to use
embed Perl inside your Fetchwarefile to change fetchware's behavior as needed
to make fetchware work with programs that use different conventions and
assumptions that fetchware makes.

=item *

L<App::Fetchware/"CREATING A FETCHWARE EXTENSION"> - Details how to replace the
module that implements fetchware's behavior, App::Fetchware, with a completely
different module implementing completely different behavior. These fetchware
extensions can even be shared with everyone else on CPAN. See
L<App::FetchwareX::HTMLPageSync> for an example.

=back

=head1 COMMANDS

Each command maps to one operation a package manager can do. C<install>,
C<upgrade>, and C<uninstall>. There is also C<new> to create new Fetchwarefiles
without bothering with a text editor. And fetchware's way of upgrading all
packages with C<upgrade-all>. Fetchware can also list its installed packages
with C<list>. And C<look> is similar to Perl's original CPAN client's look
command that downloads and unarchives the package, so you can "look" at it.

=head2 new

    fetchware new <name of program>

C<new> asks you a bunch of questions, and uses the answers you provide in
addition to the contents of the directory listng fetchware downloads based on
the C<lookup_url> you give fetchware, to create a Fetchwarefile for you with all
the mandatory options filled in. It also gives you the opportunity to add any
additional options that you may want to use. C<new> also gives you a chance to
edit the Fetchwarefile it created for you manually in your editor. Set the
C<EDITOR> environment variable to pick which editor to use, or leave it empty,
and fetchware will ask you what editor you would like to use.

C<new> finishes by asking if you would like fetchware to go ahead and install
the Fetchwarefile it has just created for you. If you say yes, then fetchware
will install it, or if you say no, fetchware will skip installing it for you,
and print out the path to the Fetchwarefile it just created for you.

You can install that Fetchwarefile later with:

    fetchware install path/to/your/some-program.Fetchwarefile

For more details about fetchware's configuration files Fetchwarefiles see
L<App::Fetchware/CREATING A App::Fetchware FETCHWAREFILE>

=head2 install

    fetchware install <path to program.Fetchwarefile>

    fetchware install <path to program.fpkg>

C<install> parses the given Fetchwarefile or uses the embeded Fetchwarefile
inside the fetchware package you specify. Then C<install> I<install>s your
program as you specified in your Fetchwarefile. 

By default executes the commands:

=over

=item 1. C<./configure>

=item 2. C<make>

=item 3. C<make install>

=back

You can use the Fetchwarefile configuraton options C<build_commands> to specify
alternate commands to build the program replacing C<./configure> and C<make>,
and you can also specify the C<install_commands> to replace C<make install> with
some other command or commands that install your program.

    ...
    # build_commands and install_commands Fetchwarefile example.
    build_commands './Configure', 'make';

    install_commands 'make test', 'make install';

    ...

See L<App::Fetchware/"App::Fetchware'S FETCHWAREFILE CONFIGURATION OPTIONS> for
more details on these configuration options.

=head2 upgrade

    fetchware upgrade <already installed fetchware package>

C<upgrade> only upgrades already installed fetchware packages. You cannot
upgrade a Fetchwarefile only an already installed fetchware package. To see a
list of already installed fetchware packages run C<fetchware list>, or pipe it
through L<grep(1)>

    fetchware list | grep <keyword>

=head2 upgrade-all

    fetchware upgrade-all

C<upgrade-all> takes no arguments. Instead, it loops over the list of installed
programs C<fetchware list> and runs C<upgrade> on each one to upgrade all
currently installed programs.

=head2 uninstall

C<uninstall> removes all components of a currently installed program.
Afterwards, that program won't show up in a C<fetchware list> anymore.

=over

=item B<WARNING>

C<uninstall> is only capable of uninstalling programs that maintain a
C<uninstall> make target. For example, C<ctags> has a C<make uninstall>, while
Apache does not; therefore, without a prefix, fetchware can uninstall ctags, but
it cannot uninstall Apache.

The easiest way to be able to uninstall a program you install with fetchware
that does not have a C<make uninstall> is to use the C<prefix> configuration
option to use a separate prefix that everything is installed into this
directory. Then you could specify a custom C<uninstall_commands> that would
delete everything in that directory:

    # Set prefix so apache can be easily uninstalled.
    prefix '/usr/local/apache';

    # Set uninstall_commands to delete everything in the prefix directory when
    # apache is uninstalled.
    uninstall_commands 'rm -r /usr/local/apache';

Then when you uninstall apache, fetchware deletes its associated files, which
may include your Web site's Web files, so back them up before hand if you need to
keep them.

The other way around this limitation is to use one of the following programs
that use a cool C<LD_PRELOAD> trick to watch what files C<make install> or its
equivelent copy, and where they are copied to. Then these files are put into
some sort of vendor-specific package such as apt-get or rpm.

=over

=item L<checkinstall|http://www.debian-administration.org/articles/147>

Run like C<checkinstall make install> will detect what files are copied where
during installation, and will create a slackware, debian, or redhat package
based on this information.

=item L<paco|http://paco.sourceforge.net/>

Provides very similar functionality to fetchware, but lacks fetchware's lookup
and verify mechanisms. Includes its own package management functionality.

=back

=back

As far a fetchware one day supporting some sort of hack like checkinstall or
paco use, I'm against it. I'd prefer everyone just adding a C<make uninstall> to
their Makefiles. But it is on my todo list, and I may add similar functionality
in the future, but I'll make no promises. Until then consider using the
C<prefix> and C<uninstall_commands> hack.

=head2 list

    fetchware list

    fetchware list | grep <what are you looking for?>

C<list> just prints out the names of all fetchware packages that have been
installed. It takes no arguments, and currently does not support listing only
packages that match a certain criteria. However, you can just pipe it to
L<grep(1)> to using a regex to limit which packages you're looking for.

=head2 look

    fetchware look <package name>

C<look> looks up the specified program using your C<lookup_url>, downloads it,
verifies it, and unarchives it. Then it prints out the location of the
unarchived program, so you can take a look at its code, or install it manually
if you would like to.

=head2 clean

    fetchware clean

C<clean> deletes all fetchware temporary files and directories to clean up your
system temporary directory.

You can also specify one or more arguments to C<fetchware clean> to specify what
directories you want fetchware to search for fetchware's left over temp files to
clean up.

=head2 help

Prints out a brief screen full of help messages reminding you of fetchware's
command-line syntax.

=head1 OPTIONS

Fetchware's configuration file options are detailed below.

Most of its options are stored in its configuration file. If none of these
options suite what you need fetchware to do, consider using its Fetchwarefile
to meet your needs. See
L<App::Fetchware/"App::Fetchware'S FETCHWAREFILE CONFIGURATION OPTIONS>

=head2 -v or --verbose

    fetchware -v install <some-program.Fetchwarefile>

Fetchware's -v or --verbose option turns on verbose logging, which prints to
STDOUT additional information regarding what fetchware is doing and how
fetchware does it.

If you have any problems with your Fetchwarefile, then you could turn on verbose
mode to have fetchware log additional messages to STDOUT to aid in debugging
your Fetchwarefile.

=head2 -q or --quite

    fetchware -q upgrade <some-program>

The -q or --quite option tells fetchware to B<not> log anything at all.
Fetchware will even prevent any commands it runs from printing output to your
terminal's STDOUT to avoid cluttering up your screen.

Any warnings or error messages are still printed to STDERR.

To determine if fetchware succeeded or failed you can test its exit status:

    fetchware -q upgrade <some-program>

    echo $?
    0

Fetchware exits 0 for success and non-zero for failure.

=head2 -V or --version

Prints out a short message and says what version of fetchware is running.

=head2 -h or -? or --help

Prints out a brief screen full of help messages reminding you of fetchware's
command-line syntax.

=head1 HOW FETCHWARE WORKS

Fetchware works by having fetchware, the C<bin/fetchware> file and fetchware
Perl package, do all of the "package manager" stuff: 

=over

=item *

Creating fetchware packages (create_fetchware_package())

=item *

Copying fetchware packages to the fetchware database
(copy_fpkg_to_fpkg_database())

=item *

Creating and managing the fetchware database
(determine_fetchware_package_path(), extract_fetchwarefile(),
and fetchware_database_path())

=item *

uninstalling installed packages from the fetchware database
(uninstall_fetchware_package_from_database())

=back

Fetchware I<delegates> all of the specifics on how to install, upgrade, and
uninstall the fetchware packages that fetchware manages to App::Fetchware or a
App::Fetchware extension:

=over

=item *

Lookup to see if a new version is available (lookup())

=item *

Downloading the archive (download())

=item *

Verifying that the downloaded file is the same one the author uploaded (verify())

=item *

Unarchiving the package (unarchive())

=item *

Building and installing it (build() and install())

=item *

Uninstalling any already installed fetchware package (uninstall())

=item *

Some before and after hooks (start() and end()).

=back

=head2 How fetchware's commands work

Fetchware's commands work by using fetchware's API, described in the section
L<INTERNAL LIBRARY SUBROUTINES>, to manage the package manager stuff. And
fetchware I<delegates> the heavy lifting of the steps needed to install,
upgrade, and uninstall fetchware packages to L<App::Fetchware> or a
L<App::Fetchware extension|App::Fetchware/"CREATING A FETCHWARE EXTENSION">.

=over

=item new

C<new> just asks the user a bunch of questions, and gives them an opportunity to
answer questions. Then it uses your answers to generate a Fetchwarefile for you,
so that you don't have to mess with creating one manually in a text editor.

=item install

Fetchware's install runs whatever fetchware API subroutines it needs to use, see
the section L<INTERNAL LIBRARY SUBROUTINES> for more. Then, install() will parse
a user provided Fetchwarefile or a Fetchwarefile fetchware finds in a fetchware
package. The act of parsing the Fetchwarefile will import the App::Fetchware API
subroutines into fetchware's namespace. This gives fetchware access to
App::Fetchwares API or whatever extension may have been used. Then, the API
subroutines are run providing whatever arguments they need and storing whatever
their important return values may be in a variable to probably later be given to
a later API subroutine as an argument.

=item upgrade

Upgrade just cleverly calls the same subroutine that implementes install. And if
the version install determines is the lastes is the same as the version that is
already installed, then fetchware does not bother installing it again.

=item uninstall

Uninstall parses the Fetcwharefile of the installed pacakge you specified. Then
it runs whatever C<uninstall_commands> you specified or the default,
C<make uninstall> if you specified none. Then the installed package is deleted
from the fetchware database.

=item list

List just globs all files in the fetchware database directory as returned by
fetchware_database_path(), and prints them to STDOUT. It does not let you
specify a Perl regex, or a keyword or anything yet, because I'm currently unsure
about the security ramifications of doing so. This feature may be added in the
future.

=item look

look just does the first part of install(). It parses whatever Fetchwarefile it
gets passed to it, then it does the start(), lookup(), download(), verify(), and
unarchive() parts of install(). Then look prints the path of this directory, and
exits.

=item clean

Clean just deletes all fetchware temp files and directories in the system
temp_dir. These files and directories all start with C<fetchware-*> or
C<Fetchwarefile-*>.

=item help

Just prints a simple, shirt, concise help message.

=back

=head2 How fetchware interfaces with App::Fetchware

Fetchware interfaces with App::Fetchware using the parse_fetchwarefile() API
subroutine. This subroutine simply eval()'s your Fetchwarefile and traps any
errors, and then rethrows that exception adding a helpful message about what
happened in addition to passing along the original problem from Perl.

The act of eval()ing your Fetchwarefile causes Perl to parse and execute as it
would any other Perl program. Only because its inside an eval any subroutines
that are imported are imported in the the caller of eval()'s package. In this
case fetchware.

Fetchware takes advantage of this by requiring all Fetchwarefile's to have a
C<use App::Fetchware...;> line. This line is what imports the default imports of
App::Fetchware into fetchware, which include App::Fetchware's API subroutines.

=head2 How fetchware intefaces with a fetchware extension

As explained above parse_fetchwarefile() eval()'s your Fetchwarefile, and this
causes Perl to parse and execute it. And any imports are imported into the
caller's package, which is fetchware.

That's how fetchware receives App::Fetchware's API subroutines, and it is also
how fetchware receives a fetchware extensions API subroutines, the fetchware
extension is simply use()d inside your Fetchwarefile instead of the default one
of App::Fetchware. Instead of:

    use App::Fetchware;

You would write:

    use App::FetchwareX::HTMLPageSync;

To use the fetchware extension HTMLPageSync.

=head1 INTERNAL SUBROUTINES IMPLEMENTING FETCHWARE COMMANDS

Below are all of subroutines that implement fetchware's main command line
options such as C<fetchware install> or C<fetchware new> and so on. These main
subroutines are called based on the options you pass to fetchware from the
command line.

=head2 cmd_install()

    my $installed_fetchware_package_path = cmd_install($filename|@ARGV)

cmd_install() implements fetchware's install command, which installs a package
based on the specified Fetchwarefile or fetchware package.

=head2 cmd_uninstall()

    my $uninstall_package_path = cmd_uninstall($uninstall_package_path|@ARGV);

Uninstalls the given package. Note the given package does B<not> have to be an
exact match, but it does have to be unique if you have two versions of the same
software installed such as httpd-2.2 and httpd-2.4. In that case you'd have to
specify the version number as well.

=over

=item LIMITATION

cmd_uninstall() unlike cmd_install() does not accept Fetchwarefiles as an
argument to uninstall a fetchware package! Instead, you must provide the name
and perhaps the name and version number of an already installed software
package. For a list of such package names just run C<fetchware list> to list all
installed fetchware packages.

=back

=over

=item NOTICE

cmd_uninstall() does B<not> call drop_privs() to drop privileges, because it
needs root privileges to copy the installed fetchware package from the system
level fetchware package database, and it needs root to actually be able to
delete files in system level directories.

=back

=head2 cmd_new()

    my $fetchware_package_path = cmd_new($program_name);

cmd_new() implements fetchware's new command. See
L<App::Fetchware/CREATING A App::Fetchware FETCHWAREFILE> for detailed
documentation for the specifics of the new command. This chunk of POD is about
its implementation. cmd_new() calls a bunch of helper subroutines that implement
the algorithm fetchware uses to build new Fetchwarefiles automagically for the
user. The algorithm is dead stupid:

=over

=item 1. Ask for lookup_url & download it.

=item 2. Analyze the contents of the output from the lookup_url.

=item 3. Build the Fetchwarefile according to the output.

=item 4. Ask other questions as needed.

=back

cmd_new() uses Term::UI, which in turn uses Term::ReadLine to implement the
character based question and anwser wizard interface.

cmd_new() also asks the user if they would like fetchware to build and install
their new program based on their newly created Fetchwarefile. If they answer
yes, it builds and installs it, and if not, cmd_new() returns the path to the
created Fetchwarefile for them.

=head2 cmd_new() API REFERENCE

Below are the API routines that cmd_new() uses to create the question and answer
interface for helping to build new Fetchwarefiles and fetchware packages.

=head3 name_program();

    my $program_name = name_program($term);

Asks the user to provide a name for the program that will that corresponds to
Fetchwarefile's C<program> configuration subroutine. This directive is currently
not used for much, but might one day become a default C<filter> option, or might
be used in msg() output to the user for logging.

=head3 opening_message();

    opending_message();

Prints new()'s opening message. It takes no parameters including the message to
be printed; instead, it simply prints the message only it can access to
C<STDOUT>.

=head3 get_lookup_url()

    my $lookup_url = get_lookup_url($term);

Uses $term argument as a L<Term::ReadLine>/L<Term::UI> object to interactively
explain what a lookup_url is, and to ask the user to provide one and press
enter.

=head3 download_lookup_url()

    my $filename_listing = download_lookup_url($term, $lookup_url);

Attempts to download the lookup_url the user provides. Returns it after parsing
it using parse_directory_listing() from L<App::Fetchware> that lookup() itself
uses.

=head3 analyze_lookup_listing()

    analyze_lookup_listing($term, $filename_listing, $lookup_url, $fetchwarefile);

Calls numerous subroutines to analyze the $filename_listing to determine what
mandatory configuration file options should be, and if any optional
configuration file options may be needed such as C<filter>.

The helper subroutines that analyze_lookup_listing() calls must follow the
proper API. These helper subroutines are called, and are expected to use their
$fetchwarefile argument to call append_options_to_fetchwarefile() directly to
directly add their options to the user's $fetchwarefile.

=head3 determine_mandatory_options()

    my $filter = determine_mandatory_options($term, $filename_listing, $lookup_url);

Analyzes $filename_listing and asks the user the necessary questions to
determine what the values to fetchware's mandatory configuration options should
be. These B<mandatory> configuration options are: C<program>, C<lookup_url>,
C<mirror>. And some method of verifying any archives that fetchware downloads
using one or more of C<gpg_keys_url>, C<sha1_url> or C<md5_url>, or using
C<verify_failure_ok> to disable verifying downloaded archives, which is B<not>
recommended.

Note: C<program> and C<lookup_url> were defined earlier, so
determine_mandatory_options() only determines mirrors and how to verify
downloads. It uses add_mirrors() and add_verification() to do this.

=head3 add_mirrors()

    add_mirrors($term, $filename_listing, $fetchwarefile);

Asks the user to specify at least one mirror to use to download their archives.
It also reiterates to the user that the C<lookup_url> should point to the
author's original download site, and B<not> a 3rd party mirror, because md5sums,
sha1sums, and gpg signatures should B<only> be downloaded from the author's
download site to avoid them being modified by a hacked 3rd party mirror. While
C<mirror> should be configured to point to a 3rd party mirror to lessen the load
on the author's offical download site.

After the user enters at least one mirror, add_mirrors() asks the user if they
would like to add any additional mirrors, and it adds them if the user specifies
them.

add_mirrors() then uses append_options_to_fetchwarefile() to add these options to
the user's $fetchwarefile.

=head3 add_verification()

    add_verification($term, $filename_listing, $lookup_url, $fetchwarefile);

Parses $filename_listing to determine what type of verification is available.
Prefering gpg, but falling back on sha1, and then md5 if gpg is not available.

If the type is gpg, then add_verification() will ask the user to specify a
C<gpg_keys_url>, which is required for gpg, because fetchware needs to be able
to import the needed keys to be able to use those keys to verify package
downloads. If this URL is not provided by the author, then add_verification()
will ask the user if they would like to import the author's key into their own
gpg public keyring. If they would, then add_verification() will use the
C<user_keyring> C<'On'> option to use the user's public keyring instead of
fetchware's own keyring. And if the user does not want to use their own gpg
public keyring, then add_verification will fall back to sha1 or md5 setting
C<verify_method> to sha1 or md5 as needed.

Also, adds a gpg_keys_url option if a C<KEYS> file is found in
$filename_listing.

If no verification methods are available, fetchware will print a big nasty
warning message, and offer to use C<verify_failure_ok> to make such a failure
cause fetchware to continue installing your software.

Adds all of the determined options to $fetchwarefile using
append_options_to_fetchwarefile().

=head3 determine_filter_option()

    determine_filter_option($term, $filename_listing, $fetchwarefile);

Analyzes $filename_listing and asks the user whatever questions are needed by
fetchware to determine if a C<filter> configuration option is needed, and if it
is what it should be. C<filter> is simply a perl regex that the list of files
that fetchware downloads is checked against, and only files that match this
regex will fetchware consider to be the latest version of the software package
that you want to install. The C<filter> option is needed, because some mirrors
will have multiple software packages in the same directory or multitple
different versions of one piece of software in the same directory. An example
would be Apache, which has Apache versions 2.0, 2.2, and 2.4 all in the same
directory. The C<filter> option is how you differentiate between them.

If a filter option was provided append_options_to_fetchwarefile() is used to
append the provided filter option to the user's $fetchwarefile.

=head3 append_to_fetchwarefile()

    append_to_fetchwarefile(\$fetchwarefile, $config_file_option, $config_file_value, $description)

Turns $description into a comment as described below, and then appends it to the
$fetchwarefile. Then $config_file_option and $config_file_value are also
appended inside proper Fetchwarefile syntax.

$description is split into strings 78 characters long, and printed with C<# >
prepended to make it a proper comment so fetchware skips parsing it.

$description is optional. If you do not include it when you call
append_to_fetchwarefile(), then append_to_fetchwarefile() will not add the
provided description.

=over

=item NOTE
Notice the backslash infront of the $fetchwarefile argument above. It is there,
because the argument $fetchwarefile must be a reference to a scalar.

=back

=head3 prompt_for_other_options()

    prompt_for_other_options($term);

Asks user if they would like to add any other options to their Fetchwarefile. If
they answer no, then everything else this subroutine does is skipped. If they
answerer yes, then information helping them decide what to do is printed as
needed. They are asked to input space separated list of configuration options
they would like to customize. Then for each option they specify, a helpful
message is printed that will help them determine what they should provide for
that option, and then they input what they would like to answer for that option.

The user's answers are tallied up in a hash that is returned as a list instead of
as a hash reference.

=head3 append_options_to_fetchwarefile()

    append_options_to_fetchwarefile(\%options, \$fetchwarefile);

Takes a hash ref of name value fetchwarefile configuration options and a
scalar ref representing your Fetchwarefile, and then it appends your
fetchwarefile configuration options to the provided $fetchwarefile scalar ref.
It also prepends a description of that specific fetchwarefile option.

If you provide an configuration option for which
append_options_to_fetchwarefile() does not have a preexisting description for in
its internal hash, %config_file_description, append_options_to_fetchwarefile()
will throw an exception.

append_options_to_fetchwarefile() uses  append_to_fetchwarefile() to do the
manipulation of your $fetchwarefile.

=head3 edit_manually()

    $fetchwarefile = edit_manually($term, \$fetchwarefile);

edit_manually() asks the user if they would like to edit the specified
$fetchwarefile manually. If the user answers no, then nothing is done. But if
the user answers yes, then fetchware will open their favorit editor either using
the C<$ENV{EDITOR}> environment variable, or fetchware will ask the user what
editor they would like to use. Then this editor, and a temporary fetchwarefile
are opened, and the user can edit their Fetchwarefile as they please. If they
are not satisfied with their edits, and wan to undo them, they can delete the
entire file, and write a size 0 file, which will cause fetchware to ignore the
file they edited. If the write a file with a size greater than 0, then the file
the user wrote, will be used as their Fetchwarefile.

=head3 check_fetchwarefile()

    check_fetchwarefile($fetchwarefile);

Ensures that the fetchwarefile that fetchware new creates has all of the
necessary configuration options that fetchware requires. These are:

=over

=item * program - A name for your Fetchwarefile. If they're all called
Fetchwarefile how do you tell them apart?

=item * lookup_url - need a url to determine what the latest version is.

=item * mirror - need a 3rd party mirror to avoid drowning the main mirror with
exceess requests.

=item * Some method of verification. Checks for gpg_keys_url, and if it or
user_keyring are not found, then a warning is given. Gpg is the only recommended
way of verifying downloads.

=back

Throws an exception if any of the conditions above are not met.

=head3 ask_to_install_now_to_test_fetchwarefile()

    my $fetchware_package_path = ask_to_install_now_to_test_fetchwarefile($term, \$fetchwarefile, $program_name);
    my $fetchwarefile_filename = ask_to_install_now_to_test_fetchwarefile($term, \$fetchwarefile, $program_name);

This subroutine asks the user if they want to install the Fetchwarefile that
this subroutine has been called with. If they say yes, then the Fetchwarefile is
passed on to cmd_install() to do all of the installation stuff. If they say no,
then fetchware saves the file to C<"$program_name.Fetchwarefile"> or
ask_to_install_now_to_test_fetchwarefile() will ask the user where to save the
file until the user picks a filename that does not exist.

=over 
NOTE: ask_to_install_now_to_test_fetchwarefile() has an infinite loop in it! It
asks the user forever until they provide a filename that doesn't exist. Should a
limit be placed on this? Should it only ask just once?

=back

If you answer yes to install your Fetchwarefile, then
ask_to_install_now_to_test_fetchwarefile() will return the full path to the
fetchware package that has been installed.

=head2 cmd_upgrade()

    my $installed_fetchware_package_path = cmd_upgrade($upgrade_name);
    'No upgrade needed.' = cmd_upgrade($upgrade_name);

Subroutine implementing Fetchware's upgrade command. This subroutine and command
upgrade one and only one package that must be specified on the command line as
well.

=head2 cmd_upgrade_all()

    my @upgraded_packages = cmd_upgrade_all();
    'No upgrade needed.' = cmd_upgrade_all();

Implements the C<fetchware upgrade-all> command, which upgrades all installed
packages simply by looping over the fetchware database and running cmd_upgrade()
on each one.

Returns a list of the packages that were upgraded or the string
'No upgrade needed.' if no packages were upgraded.

=head2 cmd_look()

    my $look_path = cmd_look($filename);

Looks up the latest version of the specified Fetchwarefile or fetchware package,
and downloads, verifies, and unarchives the specified source code distribution,
and then prints out the location of this archive.

=over

=item LIMITATION

cmd_look() unarchive's the desired source code distribution into the same sort
of temporary directory that fetchware itself uses during regular installs or
upgrades. This cannot be changed, but after fetchware creates this directory it
outputs its path, so that you can cd to it, and do whatever you need to it. You
could also move it to where you want it to be as well. Remember to delete the
fetchware-$PID-randomeletters style directory that it was stored in, or just run
fetchware clean when you are finished working with it.

=back

=head2 cmd_list()

    cmd_list();

Lists B<all> of the packages fetchware has stored in its
fetchware_database_path().

=over

=item LIMITATION

There is no ability to limit this listing with a
regex currently, so just pipe it to grep for now. Obviously in the future this
ability could be added, but I'm currently unclear about its security
ramifications. So for now, I'll hold out until I study what ack does.

=back

=head2 cmd_clean()

    cmd_clean(@ARGV);

cmd_clean() implements fetchware's clean command, which deletes any left over
fetchware temporary directories from your system's temorary directory. It
cleverly uses locking to ensure that cmd_clean() does B<not> delete a temporary
directory that is still being used by a running fetchware process.

cmd_clean() also deletes any temporary files that Fetchware uses that are
regular files not directories. These start with either C<fetchware-*> or
C<Fetchwarefile-*> for Fetchwarefiles cmd_new() creates for the user.

flock() is used along with C<LOCK_{EX,NB}> from L<Fcntl>. C<LOCK_EX> gets an
exclusive lock (only current process who got lock can access the file, and
C<LOCK_NB>, which does a non-blocking attempt to get a lock returning success at
getting the lock or not getting the lock immediately. flock() is used on a semaphore
file called C<fetchware.sem> it is a useless empty file, that is only used for
locking each fetchware temporary directory.

flock() is used, because if the fetchware process using the lock closes the
file or the process dies, exits itself, or is killed even sith C<SIGKILL>, the
lock is released automatically by the OS and/or system libraries.

cmd_clean() simply attempts to get a lock, and if it does it deletes that
particular fetchware temporary directory. If it fails to get the exclusive lock,
then it probably means that that fetchware temporary directory is still being
used by another fetchware process, so that directory is skipped.

create_tempdir() and cleanup_tempdir() create and lock the fetchware semaphore
lock file, and close and unlock it as they are executed by start() and end().

cmd_clean() via @ARGV, which run() calls it with, takes the arguments it
receives as paths to whatever temporary directories it should clean.

=head2 cmd_help()

    cmd_help();

Prints a help message to C<STDOUT> listing usage, all command options, and
examples.

And then C<exit()>s with an exit status of 0 indicating success.

=head1 INTERNAL LIBRARY SUBROUTINES

Below are the helper subroutines used by install(), uninstall(), new(), and so
on.

=head2 parse_fetchwarefile()

    'Evaled config file successfully' = parse_fetchwarefile(\$fetchwarefile);

Eval's the \$fetchwarefile to effectively "parse" it.

The only checking for the $fetchwarefile it does is that it is a scalar ref, and
that it has at least one line beginning with C<use App::Fetchware>.

Returns true on success and dies with an error message if it fails.

=head2 create_fetchware_package()

    # Most uses should just use this.
    my $fetchware_package_full_path
        =
        create_fetchware_package($fetchwarefile, $unarchived_package_path);


    # But some uses in test suites thanks to safe_open() need to be able to
    # specify where they should write the new fetchware package's path to.
    my $fetchware_package_full_path
        =
        create_fetchware_package($fetchwarefile,
            $unarchived_package_path
            $path_to_new_fpkg);

Creates a fetchware package, ending in .fpkg, using $unarchived_package_path, as
the directory to archive. Also, adds the C<Fetchwarefile> stored in the
scalar $fetchwarefile argument to the fethware package that is created.

You can specify an optional $dir_for_new_fpkg, which will be a directory where
create_fetchware_package() will write the new fetchware package to.

Returns the full pathname to the fetchware package that was created.

=head2 fetchware_database_path()

    my $fetchware_database_path = fetchware_database_path();

Returns the correct path for the fetchware package database based on operating
system and if super user or not.

Also, supports user customizable fetchware database paths via the
C<FETCHWARE_DATABASE_PATH> environment variable, and the
C<fetchware_database_path> Fetchwarefile configuration file. If both are
specified C<fetchware_database_path> is prefered over
C<FETCHWARE_DATABASE_PATH>.

=head2 determine_fetchware_package_path()

    my $fetchware_package_filename = determine_fetchware_package_path($fetchware_package);

Looks up the $fetchware_package in C<fetchware_database_path()>, and returns the
full path to that given $fetchware_package.

=over 
=item NOTE
determine_fetchware_package_path() could potentially come up with more than one
result if you have multiple versions of apache or other similarly named packages
installed at the same time. If this happens an exception is thrown asking the
user to specify a more specific name to query the fetchware database with.

=back

=head2 extract_fetchwarefile()

    my $fetchwarefile = extract_fetchwarefile($fetchware_package_path);

Extracts out the Fetchwarefile of the provided fetchware package as specified by
$fetchware_package_path, and returns the content of the Fetchwarefile as a
scalar reference. Throws an exception if it it fails.

=head2 copy_fpkg_to_fpkg_database()

    my $fetchware_package_path = copy_fpkg_to_fpkg_database($fetchwarefile_path);

Installs (just copies) the specified fetchware package to the fetchware
database, which is /var/log/fetchware on UNIX, C:\FETCHWARE on Windows with
root or Administrator. All others are whatever L<File::HomeDir> says. For Unix
or Unix-like systems such as linux, L<File::HomeDir> will put your own user
fetchware database independent of the system-wide one in C</var/log/fetchware>
in C<~/.local/share/Perl/dist/fetchware/>. This correctly follows some sort of
standard. XDG or FreeDesktop perhaps?

Creates the directory the fetchware database is stored in if it does not already
exist.

Returns the full path of the copied fetchware package.

=head2 uninstall_fetchware_package_from_database()

    my uninstall_fetchware_package_from_database($uninstall_package_name);

Deletes the specified $uninstall_package_name from the fetchware package
database. Throws an exception on error.

=head1 MOTIVATION

While sysadmining I liked to install my own compiled from source versions of
popular programs like Apache, MySQL, or Perl without threading. However, doing
so means that you have to manually recompile everytime a new security hole comes
out, which is annoyingly frequent for Apache. So, fetchware was created to bring
the power of package management to source code distributions.

=head1 THE FETCHWARE PACKAGE

Like other package managers, fetchware has its own package format:

=over

=item *

It ends with a C<.fpkg> file extension.

=item *

The package path, the location of the unarchived downloaded program, is simply
archived again using L<Archive::Tar>, and compressed with gzip.

=item *

But before the package path is archived the currently used Fetchwarefile is
copied into the current directory, so that it is included with your fetchware
package:

    ./Fetchwarefile
    httpd-2.2.x
    httpd-2.2.x/README
    httpd-2.2.x/INSTALL
    ....

=back

This simple package format was chosen instead of using a native package format
such as a MS C<.msi> package, slackware format, rpm format, C<.deb> format, and
so on. Thanks to distros like Gentoo and Arch, there are even more formats now.
Also, each version of BSD has its own package format, and each version of
commerical UNIX has its own package format too. ...It was easier to create a new
format, then deal with all of the existing ones.

This custom package format is unique, bare bones, and retains all of the power
that installing the software from source manaully gives you.

=over

=item *

Simple, and retains backward compatibility with manual installation.

=item *

The package format includes the source code, so it can be recompiled if you
move the fetchware package to an architecture different than the one it was
compiled on.

=item *

You can specify whatever configure and build options you want, so you're not
stuck with whatever your distro's package maintainer has chosen.

=back

=head1 FAQ

=head2 How does fetchware's database work?

The design of fetchware's database was copied after Slackware's package database
design. In Slackware each package is a file in C</var/log/packages>, an
example: C</var/log/packages/coreutils-8.14-x86_64_slack13.37>. And inside that
file is a list of files, whoose names are the locations of all of the files that
this Slackware package installed. This format is really simple and flexible.

Fetchware's database is simply the directory C</var/log/fetchware> (on Unix when
run as root), or whatever File::HomeDir recommends. When packages are installed
the final version of that package that ends with C<.fpkg> is copied to your
fetchware database path. So after you install apache your fetchware database
will look like:

    ls /var/log/fetchware
    httpd-2.4.3.fpkg

It's not a real database or anything cool like that. It is simply a directory
containting a list of fetchware packages that have been installed. However, this
directory is managed by fetchware, and should not be messed with unless you are
sure of what you are doing.

=head2 What exactly is a fetchware package?

A fetchware package is a gziped tar archive with its file extension changed to
C<.fpkg>. This archive consists of the package that was downloaded in addition
to your Fetchwarefile. For example.

    tar tvf httpd-2.4.3.fpkg
    ./Fetchwarefile
    httpd-2.4.3/README
    httpd-2.4.3/...
    ...

See the section L<THE FETCHWARE PACKAGE> to see all of the cool things you can
do with them.

=head1 ERRORS

As with the rest of App::Fetchware, fetchware does not return any
error codes; instead, all errors are die()'d if it's fetchware's
error, or croak()'d if its the caller's fault.

=head1 CAVEATS

=over

=item WINDOWS COMPATIBILITY

Fetchware was written on Linux and tested by its author B<only> on Linux.
However, it should work on popular Unixes without any changes. But it has B<not>
been ported or tested on Windows yet, so it may work, or parts of it may work,
but some might not. However, I have used File::Spec and Path::Class to support
path and file manipulation accross all Perl-supported platorms,so that code
should work on Windows. I intend to add Windows support, and add tests for Windows
in the future, but for now it is unsupported, but may work. This is likely to
improve in the future.

=back

=head1 AUTHOR

David Yingling <deeelwy@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by David Yingling.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

__END__











###BUGALERT###Should I implement dryrun functionality???
#=head2 -d or --dryrun
#
#Just prints out what fetchware will do when you run fetchware. No external
#commands are run, and fetchware itself doesn't read or create any files
#including any temporary directories.
#
####BUGALERT### dryrun functionality is not implemnted.
## Should be easy to implement in run_prog(), and create a sub like
## skip_all_unless_release_testing() to test for -d.

###BUGALERT###What about -f and --force too??????
#=head2 -f or --force
#
####BUGALERT### Do I want or need --force???
#



###BUGALERT###Actually add better examples of these awesome features and more
#details.
##TODO##These unique qualities give fetchware unique abilites that other package
##TODO##managers don't have.
##TODO##
##TODO##=head2 Deploying with Fetchware Packages
##TODO##
##TODO##Just like how you can create a repository for custom rpms or C<.deb>s, you can
##TODO##do the same with fetchware packages, which brings with it the full power of your
##TODO##software's build environment, and fetchware's Fetchwarefile's support for
##TODO##embeding Perl giving you great flexibility.
##TODO##
##TODO#####BUGALERT### Actually give a useful example of this!
##TODO##
##TODO##=head2 Cross-platform Deployment with Fetchware Packages
##TODO##
##TODO##Unlike other package formats, fetchware's is cross-platform, and supports the
##TODO##same platforms, the software's build environment does. You can take advantage of
##TODO##this to deploy your software as a fetchware package across whatever number of
##TODO##architectures your software's build system supports.
##TODO##
##TODO##Note, it will have to be built on each platform when the package is installed.
##TODO##
##TODO#####BUGALERT### Give more details about this!
##TODO##
##TODO##=head2 Deploying to Systems without a Build Environment
##TODO##
##TODO#####BUGALERT### This doesn't actually work now!!!
##TODO##
##TODO##Fetchware is flexible enough, using the C<no_rebuild> and C<no_lookup>
##TODO##configuration options, to be configured and compiled on one computer, and then
##TODO##B<only> installed on any additional servers that install that fetchware package.
##TODO##
##TODO##So, instead of needing gcc, devel versions of libraries, and make, the servers
##TODO##you deploy your fetchware package on compile using these options will only need
##TODO##make installed instead of an entire build environment (Actually, they'll need
##TODO##whatever commands your Fetchwarefile's C<install_commands> uses, which is
##TODO##C<make install> by default.
##TODO##
##TODO#####BUGALERT### Actually implemente this cool idea, and fix docs to say that to
##TODO###actually lookup and actually build the package use the --force option, which
##TODO###also needs to be implemented.









###BUGALERT### Actually implement croak or more likely confess() support!!!