App-Easer
view release on metacpan or search on metacpan
lib/App/Easer/V1.pm view on Meta::CPAN
my $command = fetch_spec_for($self, $child);
my $help = $command->{help};
my @aliases = ($command->{supports} // [$child])->@*;
next unless @aliases;
printf {$fh} "%15s: %s\n", shift(@aliases), $help;
printf {$fh} "%15s (also as: %s)\n", '', join ', ', @aliases
if @aliases;
} ## end for my $child ($children...)
close $fh;
return $retval;
} ## end sub list_commands
sub load_application ($application) {
return $application if 'HASH' eq ref $application;
my $text;
if ('SCALAR' eq ref $application) {
$text = $$application;
}
else {
my $fh =
'GLOB' eq ref $application
? $application
: do {
open my $fh, '<:encoding(UTF-8)', $application
or die "cannot open '$application'\n";
$fh;
};
local $/; # slurp mode
$text = <$fh>;
close $fh;
} ## end else [ if ('SCALAR' eq ref $application)]
return eval {
require JSON::PP;
JSON::PP::decode_json($text);
} // eval { eval $text; } // die "cannot load application\n";
} ## end sub load_application ($application)
sub merger ($self, $spec = {}) {
my $merger = $spec->{merge}
// $self->{application}{configuration}{merge} // \&hash_merge;
return $self->{factory}->($merger, 'merge'); # "resolve"
}
sub env_namer ($self, $cspec) {
my $namenv = $cspec->{namenv}
// $self->{application}{configuration}{namenv} // \&stock_NamEnv;
$namenv = $self->{factory}->($namenv, 'namenv'); # "resolve"
return sub ($ospec) { $namenv->($self, $cspec, $ospec) };
} ## end sub name_for_option ($o)
sub name_for_option ($o) {
return $o->{name} if defined $o->{name};
return $1 if defined $o->{getopt} && $o->{getopt} =~ m{\A(\w+)}mxs;
return lc $o->{environment}
if defined $o->{environment} && $o->{environment} ne '1';
return '~~~';
} ## end sub name_for_option ($o)
sub params_validate ($self, $spec, $args) {
my $validator = $spec->{validate}
// $self->{application}{configuration}{validate} // return;
require Params::Validate;
Params::Validate::validate($self->{configs}[-1]->%*, $validator);
} ## end sub params_validate
sub print_commands ($self, $target) {
my $command = fetch_spec_for($self, $target);
my $fh =
$self->{application}{configuration}{'help-on-stderr'}
? \*STDERR
: \*STDOUT;
if (my @children = get_children($self, $command)) {
print {$fh} list_commands($self, \@children);
}
else {
print {$fh} "no sub-commands\n";
}
}
sub print_help ($self, $target) {
my $command = fetch_spec_for($self, $target);
my $enamr = env_namer($self, $command);
my $fh =
$self->{application}{configuration}{'help-on-stderr'}
? \*STDERR
: \*STDOUT;
print {$fh} $command->{help}, "\n\n";
if (defined(my $description = $command->{description})) {
$description =~ s{\A\s+|\s+\z}{}gmxs; # trim
$description =~ s{^}{ }gmxs; # add some indentation
print {$fh} "Description:\n$description\n\n";
}
printf {$fh} "Can be called as: %s\n\n", join ', ',
$command->{supports}->@*
if $command->{supports};
my $options = $command->{options} // [];
if ($options->@*) {
print {$fh} "Options:\n";
my $n = 0; # count the option
for my $option ($options->@*) {
print {$fh} "\n" if $n++;
printf {$fh} "%15s: %s\n", name_for_option($option),
$option->{help} // '';
if (exists $option->{getopt}) {
my @lines = commandline_help($option->{getopt});
printf {$fh} "%15s command-line: %s\n", '', shift(@lines);
printf {$fh} "%15s %s\n", '', $_ for @lines;
}
if (defined(my $env_name = $enamr->($option))) {
printf {$fh} "%15s environment : %s\n", '', $env_name;
}
printf {$fh} "%15s default : %s\n", '',
$option->{default} // '*undef*'
if exists $option->{default};
} ## end for my $option ($options...)
print {$fh} "\n";
} ## end if ($options->@*)
else {
print {$fh} "This command has no options.\n\n";
}
if (my @children = get_children($self, $command)) {
print {$fh} "Sub commands:\n", list_commands($self, \@children),
"\n";
}
else {
print {$fh} "no sub-commands\n\n";
}
}
sub stock_SpecFromHash ($s, $cmd) {
return $cmd if ref($cmd) eq 'HASH';
return $s->{application}{commands}{$cmd} // undef;
}
sub stock_SpecFromHashOrModule ($s, $cmd) {
return $cmd if ref($cmd) eq 'HASH';
return $s->{application}{commands}{$cmd}
//= $s->{factory}->($cmd, 'spec')->();
}
sub fetch_spec_for ($self, $command) {
my $fetcher = $self->{application}{configuration}{specfetch}
// \&stock_SpecFromHash;
return $self->{factory}->($fetcher, 'specfetch')->($self, $command);
}
sub run ($application, $args) {
$application = add_auto_commands(load_application($application));
my $self = {
application => $application,
configs => [],
factory => generate_factory($application->{factory} // {}),
helpers => {
'print-commands' => \&print_commands,
'print-help' => \&print_help,
},
trail => [['MAIN', $application->{commands}{MAIN}{name}]],
};
while ('necessary') {
my $command = $self->{trail}[-1][0];
my $spec = fetch_spec_for($self, $command)
or die "no definition for '$command'\n";
$args = collect_options($self, $spec, $args);
validate_configuration($self, $spec, $args);
commit_configuration($self, $spec, $args);
my ($subc, $alias) = fetch_subcommand($self, $spec, $args) or last;
push $self->{trail}->@*, [$subc, $alias];
} ## end while ('necessary')
return execute($self, $args) // 0;
} ## end sub run
sub slurp ($file, $mode = '<:encoding(UTF-8)') {
open my $fh, $mode, $file or die "open('$file'): $!\n";
local $/;
return <$fh>;
}
sub sources ($self, $spec, $args) {
my $s = $spec->{sources}
// $self->{application}{configuration}{sources}
// \&stock_DefaultSources;
$s = $self->{factory}->($s, 'sources')->() if 'ARRAY' ne ref $s;
return $s->@*;
} ## end sub sources
sub stock_CmdLine ($self, $spec, $args) {
my @args = $args->@*;
my $goc = $spec->{'getopt-config'}
// default_getopt_config($self, $spec);
require Getopt::Long;
Getopt::Long::Configure('default', $goc->@*);
my %option_for;
my @specs = map {
my $go = $_->{getopt};
ref($go) eq 'ARRAY'
? ($go->[0] => sub { $go->[1]->(\%option_for, @_) })
: $go;
}
grep { exists $_->{getopt} } ($spec->{options} // [])->@*;
Getopt::Long::GetOptionsFromArray(\@args, \%option_for, @specs)
or die "bailing out\n";
# Check if we want to forbid the residual @args to start with a '-'
my $strict = !$spec->{'allow-residual-options'};
if ($strict && @args && $args[0] =~ m{\A -}mxs) {
Getopt::Long::Configure('default', 'gnu_getopt');
Getopt::Long::GetOptionsFromArray(\@args, {});
die "bailing out\n";
}
return (\%option_for, \@args);
} ## end sub stock_CmdLine
sub stock_JsonFileFromConfig ($self, $spec, $args) {
my $key = $spec->{'config-option'} // 'config';
return {} if !exists($spec->{config}{$key});
require JSON::PP;
return JSON::PP::decode_json(slurp($spec->{config}{$key}));
} ## end sub stock_JsonFileFromConfig
sub stock_JsonFiles ($self, $spec, @ignore) {
lib/App/Easer/V1.pm view on Meta::CPAN
sub stock_factory ($executable, $default_subname = '', $opts = {}) {
state $factory = sub ($executable, $default_subname) {
my @prefixes =
!defined $opts->{prefixes} ? ()
: 'ARRAY' eq ref $opts->{prefixes} ? ($opts->{prefixes}->@*)
: ($opts->{prefixes});
push @prefixes, {'+' => 'App::Easer::V1#stock_'};
SEARCH:
for my $expansion_for (@prefixes) {
for my $p (keys $expansion_for->%*) {
next if $p ne substr $executable, 0, length $p;
substr $executable, 0, length $p, $expansion_for->{$p};
last SEARCH;
}
} ## end SEARCH: for my $expansion_for (...)
# if it *still* "starts" with '=', it's "inline" Perl code
return eval $executable if $executable =~ s{\A \s* = \s* }{}mxs;
my ($package, $sname) = split m{\#}mxs, $executable;
$sname = $default_subname unless defined $sname && length $sname;
# first try to see if the sub is already available in $package
if (my $s = $package->can($sname)) { return $s }
# otherwise force loading of $package and retry
(my $path = "$package.pm") =~ s{::}{/}gmxs;
require $path;
if (my $s = $package->can($sname)) { return $s }
die "no '$sname' in '$package'\n";
};
state $cache = {};
my $args;
($executable, $args) = ($executable->{executable}, $executable)
if 'HASH' eq ref $executable;
$executable = $cache->{$executable . ' ' . $default_subname} //=
$factory->($executable, $default_subname)
if 'CODE' ne ref $executable;
return $executable unless $args;
return sub { $executable->($args, @_) };
} ## end sub stock_factory
sub stock_help ($self, $config, $args) {
print_help($self, get_descendant($self, $self->{trail}[-2][0], $args));
return 0;
} ## end sub stock_help
sub stock_DefaultSources { [qw< +Default +CmdLine +Environment +Parent >] }
sub stock_SourcesWithFiles {
[
qw< +Default +CmdLine +Environment +Parent
+JsonFileFromConfig +JsonFiles
>
]
} ## end sub stock_SourcesWithFiles
sub validate_configuration ($self, $spec, $args) {
my $from_spec = $spec->{validate};
my $from_self = $self->{application}{configuration}{validate};
my $validator;
if (defined $from_spec && 'HASH' ne ref $from_spec) {
$validator = $self->{factory}->($from_spec, 'validate');
}
elsif (defined $from_self && 'HASH' ne ref $from_self) {
$validator = $self->{factory}->($from_self, 'validate');
}
else { # use stock one
$validator = \¶ms_validate;
}
$validator->($self, $spec, $args);
} ## end sub validate_configuration
exit run(
$ENV{APPEASER} // {
commands => {
MAIN => {
name => 'main app',
help => 'this is the main app',
description => 'Yes, this really is the main app',
options => [
{
name => 'foo',
description => 'option foo!',
getopt => 'foo|f=s',
environment => 'FOO',
default => 'bar',
},
],
execute => sub ($global, $conf, $args) {
my $foo = $conf->{foo};
say "Hello, $foo!";
return 0;
},
'default-child' => '', # run execute by default
},
},
},
[@ARGV]
) unless caller;
1;
( run in 0.586 second using v1.01-cache-2.11-cpan-39bf76dae61 )