Pinto-Remote-SelfContained

 view release on metacpan or  search on metacpan

lib/Pinto/Remote/SelfContained/App.pm  view on Meta::CPAN

package
    Pinto::Remote::SelfContained::App; # hide from PAUSE

use v5.10;
use Moo;

use Getopt::Long::Descriptive qw(describe_options);
use List::Util qw(max pairgrep);
use Path::Tiny qw(path);
use Pinto::Remote::SelfContained;
use Pinto::Remote::SelfContained::Chrome;
use Pinto::Remote::SelfContained::Types qw(Uri);
use Pinto::Remote::SelfContained::Util qw(current_username);
use Types::Standard qw(ArrayRef Bool HashRef Int Maybe Str);

use namespace::clean;

our $VERSION = '1.000';

has root => (is => 'ro', isa => Uri, coerce => 1, required => 1);

has username => (is => 'ro', isa => Str, default => sub { current_username() });
has password => (is => 'ro', isa => Maybe[Str]);

has quiet => (is => 'ro', isa => Bool);
has verbose => (is => 'ro', isa => Int, default => 1);

has action_name => (is => 'ro', isa => Str, required => 1);
has args => (is => 'ro', isa => HashRef, default => sub { +{} });

my @ATTRS_FROM_OPTIONS = qw(root username password quiet verbose);

sub command_info {
    # The "args" is a string listing argument names, each optionally suffixed
    # by one of ?+* to indicate optional, required-slurpy, optional-slurpy.
    # Optional items must follow all required ones, with the exception that
    # 'foo? bar' is a permitted spec; this isn't verified.
    my %ret = (
        add => {
            summary => 'add local archives to the repository',
            usage_desc => '%c %o ARCHIVE',
            args => sub {
                my (undef, $usage) = splice @_, 0, 2;
                $usage->die({ pre_text => "Need exactly one archive\n\n" })
                    if @_ != 1;
                my $filename = $_[0];
                die "Archive filename $filename must be a tarball\n"
                    if $filename !~ /\.tar\.gz/;
                return archives => [{
                    name => 'archives',
                    filename => $filename,
                    type => 'application/x-tar',
                    encoding => 'gzip',
                }];
            },
            opt_spec => [
                [ 'author=s'                          => 'The ID of the archive author' ],
                [ 'cascade'                           => 'Always pick latest upstream package' ],
                [ 'diff-style=s'                      => 'Set style of diff reports' ],
                [ 'dry-run'                           => 'Do not commit any changes' ],
                [ 'message|m=s'                       => 'Message to describe the change' ],
                [ 'no-fail'                           => 'Do not fail when there is an error' ],
                [ 'no-index|x=s@'                     => 'Do not index matching packages' ],
                [ 'recurse!'                          => 'Recursively pull prereqs (negatable)' ],
                [ 'pin'                               => 'Pin packages to the stack' ],
                [ 'skip-missing-prerequisite|k=s@'    => 'Skip missing prereq (repeatable)' ],
                [ 'skip-all-missing-prerequisites|K'  => 'Skip all missing prereqs' ],
                [ 'stack|s=s'                         => 'Put packages into this stack' ],
                [ 'use-default-message|M'             => 'Use the generated message' ],
                [ 'with-development-prerequisites|wd' => 'Also pull prereqs for development' ],
            ],
        },
        clean => {
            summary => 'remove orphaned distribution archives',
            usage_desc => '%c %o',
            opt_spec => [],
        },
        commands => {
            pos => 10,
            summary => q[list the application's commands],
            usage_desc => '%c %o',
            opt_spec => [],
        },
        copy => {
            summary => 'create a new stack by copying another',
            usage_desc => '%c %o FROM-STACK TO-STACK',
            args => 'stack to_stack',
            opt_spec => [
                [ 'default'         => 'Make the new stack the default stack' ],
                [ 'description|d=s' => 'Brief description of the stack' ],
                [ 'lock'            => 'Lock the new stack to prevent changes' ],
            ],
        },
        default => {
            summary => 'mark the default stack',
            usage_desc => '%c %o (--none | STACK)',
            args => sub {
                my ($opts, $usage) = splice @_, 0, 2;
                $usage->die({ pre_text => "You must specify either a stack or --none\n\n" })
                    if !@_ && !$opts->{none};
                $usage->die({ pre_text => "You cannot specify both a stack and --none\n\n" })
                    if @_ && $opts->{none};
                $usage->die({ pre_text => "Too many arguments\n\n" })
                    if @_ > 1;
                return @_ ? (stack => $_[0]) : ();
            },
            opt_spec => [
                [ 'none' => 'Unmark the default stack' ],
            ],
        },
        delete => {
            summary => 'permanently remove an archive',
            usage_desc => '%c %o TARGET...',
            args => 'targets*',
            opt_spec => [
                [ 'force' => 'Delete even if packages are pinned' ],
            ],
        },
        diff => {
            summary => 'show difference between two stacks',
            usage_desc => '%c %o [LEFT] RIGHT',
            args => 'left? right',
            opt_spec => [
                [ 'diff-style=s' => 'Diff style (concise|detailed)' ],
                [ 'format=s'     => 'Format specification' ],
            ],
        },
        help => {
            pos => 20,
            summary => q[display a command's help screen],
            usage_desc => '%c %o [SUBCOMMAND]...',
            opt_spec => [],
        },
        install => {
            summary => 'install stuff from the repository',
            usage_desc => '%c %o TARGET...',
            args => sub {
                my ($opts) = splice @_, 0, 2;
                if (my $cpanm_options = $opts->{cpanm_options}) {
                    my %new;
                    for my $item (@$cpanm_options) {
                        my ($name, $value) = $item =~ /\A--?(.+?)(?:=(.*?))?\z/ms;
                        $new{$name} = $value;
                    }
                    $opts->{cpanm_options} = \%new;
                }
                $opts->{cpanm_options}{'local-lib'}           = $_ for grep defined, delete $opts->{local_lib};
                $opts->{cpanm_options}{'local-lib-contained'} = $_ for grep defined, delete $opts->{local_lib_contained};
                return targets => [@_];
            },
            opt_spec => [
                [ 'cascade'                 => 'Always pick latest upstream package' ],
                [ 'cpanm-exe|cpanm=s'       => 'Path to the cpanm executable' ],
                [ 'cpanm-options|o=s@'      => 'name=value pairs of cpanm options' ],
                [ 'diff-style=s'            => 'Set style of diff reports' ],
                [ 'local-lib|l=s'           => 'install into a local lib directory' ],
                [ 'local-lib-contained|L=s' => 'install into a contained local lib directory' ],
                [ 'message|m=s'             => 'Message to describe the change' ],
                [ 'do-pull'                 => 'pull missing prereqs onto the stack first' ],
                [ 'stack|s=s'               => 'Install modules from this stack' ],
                [ 'use-default-message|M'   => 'Use the generated message' ],
            ],
        },
        kill => {
            summary => 'permanently delete a stack',
            usage_desc => '%c %o STACK',
            args => 'stack',
            opt_spec => [
                [ 'force' => 'Kill even if stack is locked' ],
            ],
        },
        list => {
            summary => 'show the packages in a stack',
            usage_desc => '%c %o [STACK]',
            args => 'stack?',
            opt_spec => [
                [ 'all|a'             => 'List everything in the repository'],
                [ 'authors|A=s'       => 'Limit to matching author identities' ],
                [ 'distributions|D=s' => 'Limit to matching distribution names' ],
                [ 'packages|P=s'      => 'Limit to matching package names' ],
                [ 'pinned!'           => 'Limit to pinned packages (negatable)' ],
                [ 'format=s'          => 'Format specification' ],
                [ 'stack|s=s'         => 'List contents of this stack' ],
            ],
        },
        lock => {
            summary => 'mark a stack as read-only',
            usage_desc => '%c %o [STACK]',
            args => 'stack?',
            opt_spec => [
                [ 'stack|s=s' => 'Lock this stack' ],
            ],
        },
        log => {
            summary => 'show the revision logs of a stack',
            usage_desc => '%c %o [STACK]',
            args => 'stack?',
            opt_spec => [
                [ 'stack|s=s'    => 'Show history for this stack'  ],
                [ 'with-diffs|d' => 'Show a diff for each revision'],
                [ 'diff-style=s' => 'Diff style (concise|detailed)' ],
            ],
        },
        look => {
            summary => 'unpack and explore distributions with your shell',
            usage_desc => '%c %o TARGET...',
            args => 'targets+',
            opt_spec => [
                [ 'stack|s=s' => 'Resolve targets against this stack' ],
            ],
        },
        new => {
            summary => 'create a new empty stack',
            usage_desc => '%c %o STACK',
            args => 'stack',
            opt_spec => [
                [ 'default'                   => 'Make the new stack the default stack' ],
                [ 'description|d=s'           => 'Brief description of the stack' ],
                [ 'target-perl-version|tpv=s' => 'Target Perl version for this stack' ],
            ],
        },
        nop => {
            summary => 'do nothing',
            usage_desc => '%c %o',
            opt_spec => [
                [ 'sleep=i' => 'seconds to sleep before exiting' ],
            ],
        },
        pin => {
            summary => 'force a package to stay in a stack',
            usage_desc => '%c %o TARGET...',
            args => 'targets*',
            opt_spec => [
                [ 'diff-style=s'          => 'Set style of diff reports' ],
                [ 'dry-run'               => 'Do not commit any changes' ],
                [ 'message|m=s'           => 'Message to describe the change' ],
                [ 'stack|s=s'             => 'Pin targets to this stack' ],
                [ 'use-default-message|M' => 'Use the generated message' ],
            ],
        },
        props => {
            summary => 'show or set stack properties',
            usage_desc => '%c %o [STACK]',
            args => 'stack?',
            opt_spec => [
                [ 'format=s'             => 'Format specification' ],
                [ 'properties|prop|P=s%' => 'name=value pairs of properties' ],
            ],
        },
        pull => {
            summary => 'pull archives from upstream repositories',
            usage_desc => '%c %o TARGET...',
            args => 'targets*',
            opt_spec => [
                [ 'cascade'                           => 'Always pick latest upstream package' ],
                [ 'diff-style=s'                      => 'Set style of diff reports' ],
                [ 'dry-run'                           => 'Do not commit any changes' ],
                [ 'message|m=s'                       => 'Message to describe the change' ],
                [ 'no-fail'                           => 'Do not fail when there is an error' ],
                [ 'recurse!'                          => 'Recursively pull prereqs (negatable)' ],
                [ 'pin'                               => 'Pin the packages to the stack' ],
                [ 'skip-missing-prerequisite|k=s@'    => 'Skip missing prereq (repeatable)' ],
                [ 'skip-all-missing-prerequisites|K'  => 'Skip all missing prereqs' ],
                [ 'stack|s=s'                         => 'Put packages into this stack' ],
                [ 'use-default-message|M'             => 'Use the generated message' ],
                [ 'with-development-prerequisites|wd' => 'Also pull prereqs for development' ],
            ],
        },
        register => {
            summary => 'put existing packages on a stack',
            usage_desc => '%c %o TARGET...',
            args => 'targets*',
            opt_spec => [
                [ 'diff-style=s'          => 'Set style of diff reports' ],
                [ 'dry-run'               => 'Do not commit any changes' ],
                [ 'message|m=s'           => 'Message to describe the change' ],
                [ 'pin'                   => 'Pin packages to the stack' ],
                [ 'stack|s=s'             => 'Remove packages from this stack' ],
                [ 'use-default-message|M' => 'Use the generated message' ],
            ],
        },
        rename => {
            summary => 'change the name of a stack',
            usage_desc => '%c %o STACK TO-STACK',
            args => 'stack to_stack',
            opt_spec => [],
        },
        reset => {
            summary => 'reset stack to a prior revision',
            usage_desc => '%c %o [STACK] REVISION',
            args => 'stack? revision',
            opt_spec => [
                [ 'force'      => 'Reset even if revision is not ancestor' ],
                [ 'stack|s=s'  => 'Reset this stack' ],
            ],
        },
        revert => {
            summary => 'revert stack to a prior revision',
            usage_desc => '%c %o [STACK] REVISION',
            args => 'stack? revision',
            opt_spec => [
                [ 'dry-run'                   => 'Do not commit any changes' ],
                [ 'force'                     => 'Revert even if revision is not ancestor' ],
                [ 'message|m=s'               => 'Message to describe the change' ],
                [ 'stack|s=s'                 => 'Revert this stack' ],
                [ 'use-default-message|M'     => 'Use the generated message' ],
            ],
        },
        roots => {
            summary => 'show the roots of a stack',
            usage_desc => '%c %o [STACK]',
            args => 'stack?',
            opt_spec => [
                [ 'format=s'          => 'Format specification' ],
                [ 'stack|s=s'         => 'Show roots of this stack' ],
            ],
        },
        stacks => {
            summary => 'show available stacks',
            usage_desc => '%c %o',
            opt_spec => [
                [ 'format=s' => 'Format of the listing' ],
            ],
        },
        statistics => {
            summary => 'report statistics about the repository',
            usage_desc => '%c %o [STACK]',
            args => 'stack?',
            opt_spec => [],
        },
        unlock => {
            summary => 'mark a stack as writable',
            usage_desc => '%c %o [STACK]',
            args => 'stack?',
            opt_spec => [
                [ 'stack|s=s' => 'Unlock this stack' ],
            ],
        },
        unpin => {
            summary => 'free packages that have been pinned',
            usage_desc => '%c %o TARGET...',
            args => 'targets*',
            opt_spec => [
                [ 'diff-style=s'          => 'Set style of diff reports' ],
                [ 'dry-run'               => 'Do not commit any changes' ],
                [ 'message|m=s'           => 'Message to describe the change' ],
                [ 'stack|s=s'             => 'Unpin targets from this stack' ],
                [ 'use-default-message|M' => 'Use the generated message' ],
            ],
        },
        unregister => {
            summary => 'remove packages from a stack',
            usage_desc => '%c %o TARGET...',
            args => 'targets*',
            opt_spec => [
                [ 'diff-style=s'          => 'Set style of diff reports' ],
                [ 'dry-run'               => 'Do not commit any changes' ],
                [ 'force'                 => 'Remove packages even if pinned' ],
                [ 'message|m=s'           => 'Message to describe the change' ],
                [ 'stack|s=s'             => 'Remove packages from this stack' ],
                [ 'use-default-message|M' => 'Use the generated message' ],
            ],
        },
        update => {
            summary => 'update packages to latest versions',
            usage_desc => '%c %o TARGET...',
            args => 'targets*',
            opt_spec => [
                [ 'all'                               => 'Update all packages in the stack' ],
                [ 'cascade'                           => 'Always pick latest upstream package' ],
                [ 'diff-style=s'                      => 'Set style of diff reports' ],
                [ 'dry-run'                           => 'Do not commit any changes' ],
                [ 'force'                             => 'Force update, even if pinned' ],
                [ 'message|m=s'                       => 'Message to describe the change' ],
                [ 'no-fail'                           => 'Do not fail when there is an error' ],
                [ 'recurse!'                          => 'Recursively pull prereqs (negatable)' ],
                [ 'pin'                               => 'Pin the packages to the stack' ],
                [ 'roots'                             => 'Update all root packages in the stack' ],
                [ 'skip-missing-prerequisite|k=s@'    => 'Skip missing prereq (repeatable)' ],
                [ 'skip-all-missing-prerequisites|K'  => 'Skip all missing prereqs' ],
                [ 'stack|s=s'                         => 'Update packages in this stack' ],
                [ 'use-default-message|M'             => 'Use the generated message' ],
                [ 'with-development-prerequisites|wd' => 'Also pull prereqs for development' ],
            ],
        },
        verify => {
            summary => 'report archives that are missing',
            usage_desc => '%c %o',
            opt_spec => [],
        },
    );
    for my $cmd (keys %ret) {
        $ret{$cmd}{usage_desc} =~ s/^%c\K/ $cmd/;
        $ret{$cmd}{usage_desc} .= " - $ret{$cmd}{summary}";
    }
    return \%ret;
}

sub command_alias {
    +{
        cp => 'copy',
        del => 'delete',
        history => 'log',
        ls => 'list',
        mv => 'rename',
        remove => 'delete',
        rm => 'delete',
        stats => 'statistics',
        up => 'update',
    };
}

sub global_opt_spec {
    return (
        [ 'root|r=s'           => 'Path to your repository root directory (required)' ],
        [ 'color|colour!'      => '(Currently ignored)' ],
        [ 'password|p=s'       => 'Password for server authentication' ],
        [ 'quiet|q'            => 'Only report fatal errors' ],
        [ 'username|u=s'       => 'Username for server authentication' ],
        [ 'verbose|v+'         => 'More diagnostic output (repeatable)' ],
        [],
        [ 'help|?'             => 'Print usage message and exit', { shortcircuit => 1 }],
    );
}

sub help_summary {
    my ($class) = @_;

    my %command_info = %{ $class->command_info };
    my $len = max(map length, keys %command_info);



( run in 1.575 second using v1.01-cache-2.11-cpan-9581c071862 )