Pinto-Remote-SelfContained

 view release on metacpan or  search on metacpan

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

        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);
    my $fmt = "  %${len}s: %s";
    return join '',
        "Available commands:\n\n",
        map defined() ? sprintf("    %*s: %s\n", $len, $_, $command_info{$_}{summary}) : '', (
            (sort { $command_info{$a}{pos} <=> $command_info{$b}{pos} }
             grep defined $command_info{$_}{pos}, keys %command_info),
            undef,
            (grep !defined $command_info{$_}{pos}, sort keys %command_info),
        );
}

sub parse_from_argv {
    my ($class, $argv) = @_;

    my $orig_cmd = do {
        local @ARGV = @$argv;
        () = describe_options('', $class->global_opt_spec, { getopt_conf => ['gnu_getopt', 'pass_through'] });
        print($class->help_summary), exit
            if !@ARGV; # "pintor", "pintor --help", "pintor -r URL", "pintor --username fred", etc
        $ARGV[0];
    };

    my $cmd = $class->command_alias->{$orig_cmd} // $orig_cmd;
    my $command_info = $class->command_info;
    my $info = $command_info->{$cmd} // do {
        print $class->help_summary;
        exit 2;
    };

    my $usage_desc = $info->{usage_desc};
    my @opt_spec = (@{ $info->{opt_spec} }, [], $class->global_opt_spec);

    local @ARGV = @$argv;
    my ($opt, $usage) = describe_options($usage_desc, @opt_spec, { getopt_conf => ['gnu_getopt'] });

    die "BUG; cmd=$cmd but not next in argv"
        if !@ARGV || shift(@ARGV) ne $orig_cmd;

    if ($opt->help || $cmd eq 'help' && !@ARGV) {
        say $usage->text;
        exit 0;
    }
    elsif ($cmd eq 'commands') {
        print $class->help_summary;
        exit 0;
    }
    elsif ($cmd eq 'help') {
        my $exit_status = $class->run_help_command($usage, @ARGV);
        exit $exit_status;
    }
    elsif (my @missing = grep !defined $opt->{$_}, qw(root)) {
        my $missing = join ', ', map "--$_", @missing;
        $usage->die({ pre_text => "Required options not found: $missing\n\n" });
    }

    my %parsed = $class->parse_arguments($info, $opt, $usage, @ARGV);
    my %args = (%$opt, %parsed);
    my %attrs = (action_name => $cmd);
    for my $attr (@ATTRS_FROM_OPTIONS) {
        next if !exists $args{$attr};
        $attrs{$attr} = delete $args{$attr};
    }

    return %attrs, args => \%args;
}

sub run_help_command {
    my ($class, $parent_usage, @argv) = @_;

    my $command_info = $class->command_info;
    (my $usage_text = $parent_usage->text) =~ s/\n\K/\n    Global options:\n/;
    say $usage_text;

    my $exit_status = 0;
    my $command_alias = $class->command_alias;
    for my $arg (@argv) {
        my $cmd = $command_alias->{$arg} // $arg;
        my $info = $command_info->{$cmd} // do {
            warn "No command '$cmd' found\n\n";
            $exit_status = 2;
            next;
        };
        my (undef, $usage) = do {
            local @ARGV;
            describe_options($info->{usage_desc}, [], @{ $info->{opt_spec} }, {
                getopt_conf => ['gnu_getopt', 'pass_through'],
            });
        };
        say $usage->text;
    }
    return $exit_status;
}

sub parse_arguments {
    my ($class, $info, $opt, $usage, @remaining_argv) = @_;
    if (!$info->{args}) {
        $usage->die({ pre_text => "Too many arguments\n\n" })
            if @remaining_argv;
        return;
    }
    elsif (ref $info->{args}) {
        return $info->{args}->($opt, $usage, @remaining_argv);
    }
    elsif ($info->{args} =~ /^([a-z]+)\? ([a-z]+)\z/) {
        my ($optional, $required) = ($1, $2);
        $usage->die({ pre_text => "You must specify at least one argument\n\n" })
            if !@remaining_argv;
        $usage->die({ pre_text => "You must specify at most two arguments\n\n" })
            if @remaining_argv > 2;
        my %ret = ($required => pop @remaining_argv);
        $ret{$optional} = $remaining_argv[0] if @remaining_argv;
        $usage->die({ pre_text => "\u$_ specified as both option and argument\n\n" })
            for grep $opt->{$_}, sort keys %ret;
        return %ret;
    }
    else {
        my @items = split / /, $info->{args};
        my $slurpy = $items[-1] =~ /[*+]\z/ ? pop @items : undef;
        $usage->die({ pre_text => "Not enough arguments\n\n" })
            if @remaining_argv < grep !/\?\z/, @items;
        $usage->die({ pre_text => "Too many arguments\n\n" })
            if !defined $slurpy && @remaining_argv > @items;
        my %ret;
        for my $arg_name (@items) {
            $arg_name =~ s/(?<!\?)\z/?/ if exists $opt->{$arg_name};
            last if $arg_name =~ s/\?\z// && !@remaining_argv;
            $usage->die({ pre_text => "No $arg_name argument supplied\n\n" })
                if !@remaining_argv;
            $usage->die({ pre_text => "\u$arg_name supplied as both option and argument\n\n" })
                if exists $opt->{$arg_name};
            $ret{$arg_name} = shift @remaining_argv;
        }
        if (defined $slurpy) {
            $slurpy =~ s/\*\z//;
            $usage->die({ pre_text => "Need at least one $slurpy argument\n\n" })
                if $slurpy =~ s/\+\z// && !@remaining_argv;
            $usage->die({ pre_text => "$slurpy supplied as both option and argument\n\n" })
                if exists $opt->{$slurpy};
            $ret{$slurpy} = [@remaining_argv] if @remaining_argv;
        }
        return %ret;
    }
}



( run in 1.170 second using v1.01-cache-2.11-cpan-d01c6094234 )