The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
##
# name:      Package
# abstract:  The Acmeist Module Package Management Tool
# author:    Ingy döt Net <ingy@cpan.org>
# license:   perl
# copyright: 2011, 2012

use 5.008003;

use Mouse 0.97 ();
use MouseX::App::Cmd 0.08 ();
use Hash::Merge 0.12 ();
use IO::All 0.44 ();
use Template::Toolkit::Simple 0.14 ();
use YAML::XS 0.37 ();

#------------------------------------------------------------------------------#
package Package;

our $VERSION = '0.17';

#------------------------------------------------------------------------------#
package Package::Command;
use App::Cmd::Setup -command;
use Mouse;
extends 'MouseX::App::Cmd::Command';

sub validate_args {}

# Semi-brutal hack to suppress extra options I don't care about.
around usage => sub {
    my $orig = shift;
    my $self = shift;
    my $opts = $self->{usage}->{options};
    @$opts = grep { $_->{name} ne 'help' } @$opts;
    return $self->$orig(@_);
};

#-----------------------------------------------------------------------------#
package Package;
use App::Cmd::Setup -app;
use Mouse;
extends 'MouseX::App::Cmd';

use Module::Pluggable
  require     => 1,
  search_path => [ 'Package::Command' ];
Package->plugins;

#------------------------------------------------------------------------------#
package Package::Command::new;
Package->import( -command );
use Mouse;
extends 'Package::Command';

use Cwd 'abs_path';
use IO::All;
use Template::Toolkit::Simple;

use constant abstract => 'Create new module package directory from template';
use constant usage_desc => 'pkg init --from=<dir> --module=<Name> --to=<dir>';

has dryrun => (
    is => 'ro',
    isa => 'Bool',
    default => 0,
    documentation => 'Create new package but skip git',
);


sub execute {
    my ($self, $opt, $args) = @_;
    my $pkg_name = pop(@$args) || '';
    $self->_args($args);
    
    my $to = io($pkg_name || '.')->absolute;

    $pkg_name ||= do {
        my $cwd = Cwd::cwd;
        $cwd =~ s!.*/!!;
        $cwd;
    };
    $self->_pkg_name($pkg_name);

    my $stash = $self->conf->stash;

    if ($to->exists) {
        die <<"..." if not $to->empty;
$to is not an empty directory.
Can't make a new pkg here.

Either specify the name of a new directory you wish to create,
or name an empty directory, or cd to an empty directory.
...
    }
    else {
        $to->assert->mkdir;
    }
    $to->chdir or die "Can't chdir to $to";

    for my $file (sort keys %{$self->conf->manifest}) {
        if (not $file =~ /%(.*?)%/) {
            $self->write_file($file, $file);
            next;
        }
        my $key = $1;
        my $path = $self->conf->lookup($key)
            or die "Config variable '$key' is not defined.\n";
        (my $f = $file) =~ s/%.*?%/$path/;
        $self->write_file($f, $file);
    }

    if ($stash->{git}{init}) {
        if ($self->dryrun or $to =~ /^.*\/[xyz]{1,5}\d?$/) {
            (my $url = $stash->{git}{origin}) =~
                s/\%pkg\.name\%/$stash->{pkg}{name}/e;
            print <<"...";
New package '$to' successfully created, but GitHub repo creation skipped.

The GitHub info would be:

    origin: $url
    repo:   $pkg_name
    desc:   $stash->{desc}
...
            return;
        }
        system("git init; git add .; git commit -m 'First commit'");
        if (my $url = $stash->{git}{origin}) {
            $url =~ s/\%pkg\.name\%/$stash->{pkg}{name}/e;
            system("git remote add origin $url");
        }
        $self->create_git_repo($pkg_name, $stash->{desc});
    }

    print "New package '$to' successfully created!\n";
}

sub write_file {
    my ($self, $file, $key) = @_;
    my $path = $self->conf->manifest->{$key};
    my $template = io($path)->all;
    my $text = eval {
        tt
        ->path([])
        ->strict(1)
        ->data($self->conf->stash)
        ->render(\$template);
    };
    if ($@) {
        if ($@ =~ /var\.undef error - undefined variable: (\S+)/) {
            die "Config variable '$1' is not defined.\n";
        }
        die $@;
    }
    io($file)->assert->print($text);
    if (-x $path) {
        chmod 0755, $file;
    }
}

#------------------------------------------------------------------------------#
package Package::Command::repo;
Package->import( -command );
use Mouse;
extends 'Package::Command';

use constant abstract => 'Create a new repo on github';
use constant usage_desc => 'pkg repo --from=<dir>';

sub execute {
    my ($self, $opt, $args) = @_;
    die "Directory has no git repo"
        unless -d '.git';

    my $stash = $self->conf->stash;
    for (@$args) {
        if (/^--(\w+)=(.*)/) {
            my ($k, $v) = ($1, $2);
            # $v =~ s/^'(.*)'$/$1/;
            # $v =~ s/^"(.*)"$/$1/;
            $stash->{$k} = $v;
        }
    }

    my $pkg_name = do {
        my $cwd = Cwd::cwd;
        $cwd =~ s!.*/!!;
        $cwd;
    };

    $self->create_git_repo($pkg_name, $stash->{desc});
}

#------------------------------------------------------------------------------#
package Package::Command::listvars;
Package->import( -command );
use Mouse;
extends 'Package::Command';

use constant abstract => 'Print list of config variables from template';
use constant usage_desc => 'pkg listvars --from=<dir>';

sub execute {
    my ($self, $opt, $args) = @_;
    print YAML::XS::Dump($self->conf->stash);
}

#------------------------------------------------------------------------------#
package Package::Command;

has from => (
    is => 'ro',
    isa => 'Str',
    required => 1,
    documentation => 'Pkg template directory',
);

has _conf => (
    is => 'ro',
    lazy => 1,
    reader => 'conf',
    default => sub {
        require Package::Conf;
        my ($self) = @_;
        Package::Conf->new(
            src_dir => $self->from,
            cli_args => $self->_args,
            pkg_name => $self->_pkg_name,
        );
    },
);

has _pkg_name => (
    is => 'rw',
);

has _args => (
    is => 'rw',
    default => sub {[]},
);

sub create_git_repo {
    my ($self, $pkg_name, $desc) = @_;
    my $stash = $self->conf->stash;
    # XXX Check Net::Ping->new->ping("github.com");
    if (my $github = $stash->{git}{github}) {
        my $login = $github->{login};
        my $token = $github->{token};
        system(qq{curl -F login=$login -F token=$token https://github.com/api/v2/yaml/repos/create -F name=$pkg_name -F "description=$desc"});
        system("git push origin master");
    }
}

1;

=head1 SYNOPSIS

From the command line:

    > pkg help

    > pkg new \
            --from=pkg/perl/dzil \
            --module=Foo::Bar \
            --desc='Foo to the Bar' \
            foo-bar-pm

=head1 DESCRIPTION

C<pkg> is your tool for creating distributable, modular packages, in a variety
of programming languages. Perl 5 modules for CPAN, Python modules for PyPI,
Ruby modules for RubyGems, etc.

In truth, pkg is nothing more than a simple way to create a new directory of
starter files, by applying a set of configuration information to a set of file
templates. The information is all completely in your control. You can use
other people's templates or create your own.

=head1 QUICK START

Here's the quick and simple way to get started, assuming you are familiar with
L<cpanm> and C<git>. From the command line:

    # Go to the directory where you keep your repos:
    cd $HOME/src/
    # Get the base pkg directory:
    git clone https://github.com/ingydotnet/pkg-pkg.git pkg
    # pkg/README will contain a list of known pkg template repos
    # Get a basic pkg template. In the case, for a Perl module:
    git clone https://github.com/rafl/perl-pkg.git pkg/perl
    # Maybe get a more specific Perl module template:
    git clone https://github.com/rafl/perl-dzil-pkg.git pkg/perl/dzil
    # Edit the conf files appropriately
    edit pkg/pkg.conf pkg/perl/pkg.conf pkg/perl/dzil/pkg.conf
    # Now create a new perl module in the foo-bar-pm directory
    pkg new --from=pkg/perl --module=Foo::Bar --desc='Foo Bar' foo-bar-pm
    # Make another new module!
    pkg new --from=pkg/perl/dzil --module=Bar::Bar --desc='Bar Bar' bar-bar-pm

That was easy.

=head1 TEMPLATES

The main template repository is: https://github.com/ingydotnet/pkg-pkg.git

The README in this repo contains repos for many other repos. You can easily
create your own too. Just make sure there is a C<pkg.conf> file in each
template dir (even if it is empty). Template directories inherit from their
parent directory.

More doc soon.