Perinci-CmdLine-Lite
view release on metacpan or search on metacpan
lib/Perinci/CmdLine/Base.pm view on Meta::CPAN
unless (defined $l) {
$eof++; return undef;
}
eval { $l = $json->decode($l) };
if ($@) {
die "Invalid JSON in stream argument '$argname' record #$i: $@";
}
$l;
};
}
}
# parse cmdline_src argument spec properties for filling argument value from
# file and/or stdin. currently does not support argument submetadata.
sub parse_cmdline_src {
my ($self, $r) = @_;
my $action = $r->{action};
my $meta = $r->{meta};
#if ($self->use_utf8) {
# require open; open->import(":utf8");
#} elsif ($self->use_locale) {
# require open; open->import(":locale");
#}
my $url = $r->{subcommand_data}{url} // $self->{url} // '';
my $is_network = $url =~ m!^(https?|riap[^:]+):!;
# handle cmdline_src
if ($action eq 'call') {
my $args_p = $meta->{args} // {};
my $stdin_seen;
for my $an (sort {
my $csa = $args_p->{$a}{cmdline_src};
my $csb = $args_p->{$b}{cmdline_src};
my $posa = $args_p->{$a}{pos} // 9999;
my $posb = $args_p->{$b}{pos} // 9999;
# first, always put stdin_line before stdin / stdin_or_files
(
!$csa || !$csb ? 0 :
$csa eq 'stdin_line' && $csb eq 'stdin_line' ? 0 :
$csa eq 'stdin_line' && $csb =~ /^(stdin|stdin_or_files?|stdin_or_args)/ ? -1 :
$csb eq 'stdin_line' && $csa =~ /^(stdin|stdin_or_files?|stdin_or_args)/ ? 1 : 0
)
||
# then order by pos
($posa <=> $posb)
||
# then by name
($a cmp $b)
} keys %$args_p) {
#log_trace("TMP: handle cmdline_src for arg=%s", $an);
my $as = $args_p->{$an};
my $src = $as->{cmdline_src};
my $type = $as->{schema}[0]
or die "BUG: No schema is defined for arg '$an'";
# Riap::HTTP currently does not support streaming input
my $do_stream = $as->{stream} && $url !~ /^https?:/;
if ($src) {
die [531,
"Invalid 'cmdline_src' value for argument '$an': $src"]
unless $src =~ /\A(stdin|file|stdin_or_files?|stdin_or_args|stdin_line)\z/;
die [531,
"Sorry, argument '$an' is set cmdline_src=$src, but type ".
"is not str/buf/array, only those are supported now"]
unless $do_stream || $type =~ /\A(str|buf|array)\z/; # XXX stdin_or_args needs array only, not str/buf
if ($src =~ /\A(stdin|stdin_or_files?|stdin_or_args)\z/) {
die [531, "Only one argument can be specified ".
"cmdline_src stdin/stdin_or_file/stdin_or_files/stdin_or_args"]
if $stdin_seen++;
}
my $is_ary = $type eq 'array';
if ($src eq 'stdin_line' && !exists($r->{args}{$an})) {
require Perinci::Object;
my $term_readkey_available = eval { require Term::ReadKey; 1 };
my $prompt = Perinci::Object::rimeta($as)->langprop('cmdline_prompt') //
sprintf($self->default_prompt_template, $an);
print $prompt;
my $iactive = is_interactive(*STDOUT);
Term::ReadKey::ReadMode('noecho')
if $term_readkey_available && $iactive && $as->{is_password};
chomp($r->{args}{$an} = <STDIN>);
do { print "\n"; Term::ReadKey::ReadMode(0) if $term_readkey_available }
if $iactive && $as->{is_password};
$r->{args}{"-cmdline_src_$an"} = 'stdin_line';
} elsif ($src eq 'stdin' || $src eq 'file' &&
($r->{args}{$an}//"") eq '-') {
die [400, "Argument $an must be set to '-' which means ".
"from stdin"]
if defined($r->{args}{$an}) &&
$r->{args}{$an} ne '-';
#log_trace("Getting argument '$an' value from stdin ...");
$r->{args}{$an} = $do_stream ?
__gen_iter(\*STDIN, $as, $an) :
$is_ary ? [<STDIN>] :
do {local $/; ~~<STDIN>};
$r->{args}{"-cmdline_src_$an"} = 'stdin';
} elsif ($src eq 'stdin_or_file' || $src eq 'stdin_or_files') {
# push back argument value to @ARGV so <> can work to slurp
# all the specified files
local @ARGV = @ARGV;
unshift @ARGV, $r->{args}{$an}
if defined $r->{args}{$an};
# with stdin_or_file, we only accept one file
splice @ARGV, 1
if @ARGV > 1 && $src eq 'stdin_or_file';
#log_trace("Getting argument '$an' value from ".
# "$src, \@ARGV=%s ...", \@ARGV);
# perl doesn't seem to check files, so we check it here
for (@ARGV) {
next if $_ eq '-';
die [500, "Can't read file '$_': $!"] if !(-r $_);
}
lib/Perinci/CmdLine/Base.pm view on Meta::CPAN
Called at the start of C<run()>. Can be used to set some initial values of other
C<$r> keys. Or setup the logger.
=head2 $cmd->hook_before_read_config_file($r)
Only called when C<read_config> attribute is true.
=head2 $cmd->hook_after_read_config_file($r)
Only called when C<read_config> attribute is true.
=head2 $cmd->hook_after_get_meta($r)
Called after the C<get_meta> method gets function metadata, which normally
happens during parsing argument, because parsing function arguments require the
metadata (list of arguments, etc).
PC:Lite as well as PC:Classic use this hook to insert a common option
C<--dry-run> if function metadata expresses that function supports dry-run mode.
PC:Lite also checks the C<deps> property here. PC:Classic doesn't do this
because it uses function wrapper (L<Perinci::Sub::Wrapper>) which does this.
=head2 $cmd->hook_after_parse_argv($r)
Called after C<run()> calls C<parse_argv()> and before it checks the result.
C<$r->{parse_argv_res}> will contain the result of C<parse_argv()>. The hook
gets a chance to, e.g. fill missing arguments from other source.
Note that for sources specified in the C<cmdline_src> property, this base class
will do the filling in after running this hook, so no need to do that here.
PC:Lite uses this hook to give default values to function arguments C<<
$r->{args} >> from the Rinci metadata. PC:Classic doesn't do this because it
uses function wrapper (L<Perinci::Sub::Wrapper>) which will do this as well as
some other stuffs (validate function arguments, etc).
=head2 $cmd->hook_before_action($r)
Called before calling the C<action_ACTION> method. Some ideas to do in this
hook: modifying action to run (C<< $r->{action} >>), last check of arguments
(C<< $r->{args} >>) before passing them to function.
PC:Lite uses this hook to validate function arguments. PC:Classic does not do
this because it uses function wrapper which already does this.
=head2 $cmd->hook_after_action($r)
Called after calling C<action_ACTION> method. Some ideas to do in this hook:
preformatting result (C<< $r->{res} >>).
=head2 $cmd->hook_format_result($r)
The hook is supposed to format result in C<$res->{res}> (an array).
All direct subclasses of PC:Base do the formatting here.
=head2 $cmd->hook_display_result($r)
The hook is supposed to display the formatted result (stored in C<$r->{fres}>)
to STDOUT. But in the case of streaming output, this hook can also set it up.
All direct subclasses of PC:Base do the formatting here.
=head2 $cmd->hook_after_run($r)
Called at the end of C<run()>, right before it exits (if C<exit> attribute is
true) or returns C<$r->{res}>. The hook has a chance to modify exit code or
result.
=head1 SPECIAL ARGUMENTS
Below is list of special arguments that may be passed to your function by the
framework. Per L<Rinci> specification, these are prefixed by C<-> (dash).
=head2 -dry_run => bool
Only when in dry run mode, to notify function that we are in dry run mode.
=head2 -cmdline => obj
Only when C<pass_cmdline_object> attribute is set to true. This can be useful
for the function to know about various stuffs, by probing the framework object.
=head2 -cmdline_r => hash
Only when C<pass_cmdline_object> attribute is set to true. Contains the C<$r>
per-request hash/stash. This can be useful for the function to know about
various stuffs, e.g. parsed configuration data, etc.
=head2 -cmdline_src_ARGNAME => str
This will be set if argument is retrieved from C<file>, C<stdin>,
C<stdin_or_file>, C<stdin_or_files>, or C<stdin_line>.
=head2 -cmdline_srcfilenames_ARGNAME => array
An extra information if argument value is retrieved from file(s), so the
function can know the filename(s).
=head1 METADATA PROPERTY ATTRIBUTE
This module observes the following Rinci metadata property attributes:
=head2 cmdline.default_format => STR
Set default output format (if user does not specify via --format command-line
option).
=head2 cmdline.skip_format => bool
If you set it to 1, you specify that function's result never needs formatting
(i.e. the function outputs raw text to be outputted directly), so no formatting
will be done. See also: C<skip_format> attribute, C<cmdline.skip_format> result
metadata attribute.
=head2 METADATA'S ARGUMENT SPECIFICATION ATTRIBUTE
=head1 RESULT METADATA
This module interprets the following result metadata property/attribute:
lib/Perinci/CmdLine/Base.pm view on Meta::CPAN
The main method to run your application. See L</"PROGRAM FLOW (NORMAL)"> for
more details on what this method does.
=head2 $cmd->do_completion() => ENVRES
Called by run().
=head2 $cmd->parse_argv() => ENVRES
Called by run().
=head2 $cmd->get_meta($r, $url) => ENVRES
Called by parse_argv() or do_completion(). Subclass has to implement this.
=head1 ENVIRONMENT
=head2 BROWSER
String. When L</"VIEWER"> is not set, then this environment variable will be
used to select external viewer program.
=head2 LOG_DUMP_CONFIG
Boolean. If set to true, will dump parsed configuration at the trace level.
=head2 PAGE_RESULT
Boolean. Can be set to 1 to force paging of result. Can be set to 0 to
explicitly disable paging even though C<cmd.page_result> result metadata
attribute is active.
See also: L</"PAGER">.
=head2 PAGER
String. Like in other programs, can be set to select the pager program (when
C<cmdline.page_result> result metadata is active). Can also be set to C<''> or
C<0> to explicitly disable paging even though C<cmd.page_result> result metadata
is active.
=head2 PERINCI_CMDLINE_OUTPUT_DIR
String. If set, then aside from displaying output as usual, the unformatted
result (enveloped result) will also be saved as JSON to an output directory. The
filename will be I<UTC timestamp in ISO8601 format>C<.out>, e.g.:
2017-12-11T123456.000000000Z.out
2017-12-11T123456.000000000Z.out.1 (if the same filename already exists)
or each output (C<.out>) file there will also be a corresponding C<.meta> file
that contains information like: command-line arguments, PID, etc. Some notes:
Output directory must already exist, or Perinci::CmdLine will display a warning
and then skip saving output.
Data that is not representable as JSON will be cleansed using
L<Data::Clean::ForJSON>.
Streaming output will not be saved appropriately, because streaming output
contains coderef that will be called repeatedly during the normal displaying of
result.
=head2 PERINCI_CMDLINE_PLUGINS
String. A list of plugins to load at the start of program. If it begins with a
C>[> (opening square bracket), it will be assumed to be in JSON encoding:
["PluginName1",{"arg1name":"arg1val","arg2name":"arg2val",...},"PluginName2", ...]
otherwise it is assumed to be a comma-separated string in this syntax:
-PluginName1,arg1name,arg1val,arg2name,arg2val,...,-PluginName2,...
Plugin name is module name without the C<Perinci::CmdLine::Plugin::> prefix. The
argument list can be skipped if you don't want to pass arguments to a plugin.
=head2 PERINCI_CMDLINE_PROGRAM_NAME
String. Can be used to set CLI program name.
=head2 UTF8
Boolean. To set default for C<use_utf8> attribute.
=head2 VIEW_RESULT
Boolean. Can be set to 1 to force using viewer to view result. Can be set to 0
to explicitly disable using viewer to view result even though
C<cmdline.view_result> result metadata attribute is active.
=head2 VIEWER
String. Can be set to select the viewer program to override C<cmdline.viewer>.
Can also be set to C<''> or C<0> to explicitly disable using viewer to view
result even though C<cmdline.view_result> result metadata attribute is active.
See also L</"BROWSER">.
=head1 HOMEPAGE
Please visit the project's homepage at L<https://metacpan.org/release/Perinci-CmdLine-Lite>.
=head1 SOURCE
Source repository is at L<https://github.com/perlancar/perl-Perinci-CmdLine-Lite>.
=head1 AUTHOR
perlancar <perlancar@cpan.org>
=head1 CONTRIBUTING
To contribute, you can send patches by email/via RT, or send pull requests on
GitHub.
Most of the time, you don't need to build the distribution yourself. You can
simply modify the code, then test via:
( run in 1.524 second using v1.01-cache-2.11-cpan-140bd7fdf52 )