File Coverage

blib/lib/Perinci/CmdLine/Inline.pm
Criterion Covered Total %
statement 693 784 88.3
branch 223 342 65.2
condition 73 118 61.8
subroutine 23 23 100.0
pod 1 1 100.0
total 1013 1268 79.8


line stmt bran cond sub pod time code
1             # false positive? line 825
2             ## no critic: Modules::RequireFilenameMatchesPackage
3              
4             # line 820, don't know how to turn off this warning?
5             ## no critic: ValuesAndExpressions::ProhibitCommaSeparatedStatements
6              
7             # false positive? perlcritic gives line 2333 which is way more than the number of lines of this script
8             ## no critic: InputOutput::RequireBriefOpen
9              
10             package Perinci::CmdLine::Inline;
11              
12             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
13             our $DATE = '2020-05-18'; # DATE
14             our $DIST = 'Perinci-CmdLine-Inline'; # DIST
15             our $VERSION = '0.551'; # VERSION
16              
17 2     2   88582 use 5.010001;
  2         8  
18 2     2   10 use strict 'subs', 'vars';
  2         5  
  2         51  
19 2     2   10 use warnings;
  2         3  
  2         45  
20 2     2   10 use Log::ger;
  2         3  
  2         13  
21              
22 2     2   1454 use Data::Dmp;
  2         3557  
  2         106  
23 2     2   15 use JSON::MaybeXS ();
  2         3  
  2         33  
24 2     2   1201 use Module::CoreList::More;
  2         365446  
  2         253  
25 2     2   29 use Module::Path::More qw(module_path);
  2         4  
  2         170  
26 2     2   14 use Perinci::Sub::Util qw(err);
  2         4  
  2         91  
27              
28 2     2   11 use Exporter qw(import);
  2         6  
  2         624  
29             our @EXPORT_OK = qw(gen_inline_pericmd_script);
30              
31             our %SPEC;
32              
33             sub _dsah_plc {
34 125     125   214 state $plc = do {
35 2         2080 require Data::Sah;
36 2         3279 Data::Sah->new->get_compiler('perl');
37             };
38 125         54680 $plc;
39             }
40              
41             sub _pack_module {
42 464     464   2138 my ($cd, $mod) = @_;
43 464 50       1788 return unless $cd->{gen_args}{pack_deps};
44 464 50       1188 return if $cd->{module_srcs}{$mod};
45 464         2503 log_info("Adding source code of module %s ...", $mod);
46 464 50       4666 log_warn("%s is a core module", $mod) if Module::CoreList::More->is_still_core($mod);
47 464 50       430953 my $path = module_path(module => $mod) or die "Can't load module '$mod'";
48 464         185626 local $/;
49 464 50       17505 open my($fh), "<", $path or die "Can't read file '$path': $!";
50 464         28618 $cd->{module_srcs}{$mod} = <$fh>;
51             }
52              
53             sub _get_meta_from_url {
54 2     2   15 no strict 'refs';
  2         5  
  2         6094  
55              
56 64     64   394 my $url = shift;
57              
58 64 50       1067 $url =~ m!\A(?:pl:)?((?:/[^/]+)+)/([^/]*)\z!
59             or return [412, "URL scheme not supported, only local Perl ".
60             "URL currently supported"];
61 64         725 my ($mod_pm, $short_func_name) = ($1, $2);
62 64         574 $mod_pm =~ s!\A/!!;
63 64         706 (my $mod = $mod_pm) =~ s!/!::!g;
64 64         282 $mod_pm .= ".pm";
65 64         442 require $mod_pm;
66 64 100       137 my $meta = ${"$mod\::SPEC"}{length $short_func_name ? $short_func_name : ':package'};
  64         1149  
67 64 100 50     752 $meta //= {v=>1.1} if !length $short_func_name; # provide a default empty package metadata
68 64 50       238 return [412, "Can't find meta for URL '$url'"] unless $meta;
69 64 100       187 if (length $short_func_name) {
70 55 50       88 defined &{"$mod\::$short_func_name"}
  55         442  
71             or return [412, "Can't find function '$short_func_name' for URL '$url'"];
72             }
73             return [200, "OK", $meta, {
74             'func.module' => $mod,
75 64         176 'func.module_version' => ${"$mod\::VERSION"},
  64         1557  
76             'func.short_func_name' => $short_func_name,
77             'func.func_name' => "$mod\::$short_func_name",
78             }];
79             }
80              
81             sub _gen_read_env {
82 48     48   175 my ($cd) = @_;
83 48         96 my @l2;
84              
85 48 100       436 return "" unless $cd->{gen_args}{read_env};
86              
87 4         15 _pack_module($cd, "Complete::Bash");
88 4         18 _pack_module($cd, "Log::ger"); # required by Complete::Bash
89 4         15 push @l2, "{\n";
90 4         14 push @l2, ' last unless $_pci_r->{read_env};', "\n";
91 4         18 push @l2, ' my $env = $ENV{', dmp($cd->{gen_args}{env_name}), '};', "\n";
92 4         166 push @l2, ' last unless defined $env;', "\n";
93 4         10 push @l2, ' require Complete::Bash;', "\n";
94 4         6 push @l2, ' my ($words, undef) = @{ Complete::Bash::parse_cmdline($env, 0) };', "\n";
95 4         8 push @l2, ' unshift @ARGV, @$words;', "\n";
96 4         9 push @l2, "}\n";
97              
98 4         22 join("", @l2);
99             }
100              
101             sub _gen_enable_log {
102 1     1   2 my ($cd) = @_;
103              
104 1         6 _pack_module($cd, 'Log::ger');
105 1         10 _pack_module($cd, 'Log::ger::Output');
106 1         4 _pack_module($cd, 'Log::ger::Output::Composite');
107 1         6 _pack_module($cd, 'Log::ger::Output::Screen');
108 1         5 _pack_module($cd, 'Log::ger::Output::SimpleFile');
109 1         4 _pack_module($cd, "Data::Dmp"); # required by Log::ger::Output::Composite
110 1         5 _pack_module($cd, 'Log::ger::Util');
111              
112 1         3 my @l;
113              
114 1         3 push @l, "### begin code_before_enable_logging\n";
115 1 50       5 push @l, $cd->{gen_args}{code_before_enable_logging}, "\n" if defined $cd->{gen_args}{code_before_enable_logging};
116 1         3 push @l, "### end code_before_enable_logging\n";
117              
118 1         9 push @l, "### enable logging\n";
119 1         7 push @l, '$_pci_log_outputs->{Screen} = { conf => { formatter => sub { '.dmp("$cd->{script_name}: ").' . $_[0] } } };', "\n";
120              
121 1         37 push @l, "#### begin code_add_extra_log_outputs\n";
122 1 50       6 push @l, $cd->{gen_args}{code_add_extra_log_outputs}, "\n" if defined $cd->{gen_args}{code_add_extra_log_outputs};
123 1         3 push @l, "#### end code_add_extra_log_outputs\n";
124              
125 1         4 push @l, 'require Log::ger::Output; Log::ger::Output->set("Composite", outputs => $_pci_log_outputs);', "\n";
126 1         4 push @l, 'require Log::ger; Log::ger->import;', "\n";
127 1         2 push @l, "\n";
128              
129 1         2 push @l, "### begin code_after_enable_logging\n";
130 1 50       43 push @l, $cd->{gen_args}{code_after_enable_logging}, "\n" if defined $cd->{gen_args}{code_after_enable_logging};
131 1         4 push @l, "### end code_after_enable_logging\n";
132              
133 1         8 join("", @l);
134             }
135              
136             sub _gen_read_config {
137 48     48   137 my ($cd) = @_;
138 48         86 my @l2;
139              
140 48 100       448 return "" unless $cd->{gen_args}{read_config};
141              
142 12         43 push @l2, 'if ($_pci_r->{read_config}) {', "\n";
143 12         43 _pack_module($cd, "Perinci::CmdLine::Util::Config");
144 12         66 _pack_module($cd, "Log::ger"); # required by Perinci::CmdLine::Util::Config
145 12         64 _pack_module($cd, "Config::IOD::Reader"); # required by Perinci::CmdLine::Util::Config
146 12         61 _pack_module($cd, "Config::IOD::Base"); # required by Config::IOD::Reader
147 12         77 _pack_module($cd, "Data::Sah::Normalize"); # required by Perinci::CmdLine::Util::Config
148 12         61 _pack_module($cd, "Perinci::Sub::Normalize"); # required by Perinci::CmdLine::Util::Config
149 12         61 _pack_module($cd, "Sah::Schema::rinci::function_meta"); # required by Perinci::Sub::Normalize
150 12 50       98 push @l2, 'log_trace("Reading config file(s) ...");', "\n" if $cd->{gen_args}{log};
151 12         42 push @l2, ' require Perinci::CmdLine::Util::Config;', "\n";
152 12         31 push @l2, "\n";
153 12         53 push @l2, ' my $res = Perinci::CmdLine::Util::Config::read_config(', "\n";
154 12         31 push @l2, ' config_paths => $_pci_r->{config_paths},', "\n";
155 12         62 push @l2, ' config_filename => ', dmp($cd->{gen_args}{config_filename}), ",\n";
156 12         928 push @l2, ' config_dirs => ', dmp($cd->{gen_args}{config_dirs}), ' // ["$ENV{HOME}/.config", $ENV{HOME}, "/etc"],', "\n";
157 12         1087 push @l2, ' program_name => ', dmp($cd->{script_name}), ",\n";
158 12         340 push @l2, ' );', "\n";
159 12         39 push @l2, ' _pci_err($res) unless $res->[0] == 200;', "\n";
160 12         32 push @l2, ' $_pci_r->{config} = $res->[2];', "\n";
161 12         27 push @l2, ' $_pci_r->{read_config_files} = $res->[3]{"func.read_files"};', "\n";
162 12         32 push @l2, ' $_pci_r->{_config_section_read_order} = $res->[3]{"func.section_read_order"}; # we currently dont want to publish this request key', "\n";
163 12         29 push @l2, "\n";
164 12         28 push @l2, ' $res = Perinci::CmdLine::Util::Config::get_args_from_config(', "\n";
165 12         32 push @l2, ' r => $_pci_r,', "\n";
166 12         35 push @l2, ' config => $_pci_r->{config},', "\n";
167 12         24 push @l2, ' args => \%_pci_args,', "\n";
168 12         37 push @l2, ' program_name => ', dmp($cd->{script_name}), ",\n";
169 12         319 push @l2, ' subcommand_name => $_pci_r->{subcommand_name},', "\n";
170 12         44 push @l2, ' config_profile => $_pci_r->{config_profile},', "\n";
171 12         27 push @l2, ' common_opts => {},', "\n"; # XXX so currently we can't set e.g. format or
172 12         34 push @l2, ' meta => $_pci_metas->{ $_pci_r->{subcommand_name} },', "\n";
173 12         29 push @l2, ' meta_is_normalized => 1,', "\n";
174 12         33 push @l2, ' );', "\n";
175 12         84 push @l2, ' die $res unless $res->[0] == 200;', "\n";
176 12         33 push @l2, ' my $found = $res->[3]{"func.found"};', "\n";
177 12         93 push @l2, ' if (defined($_pci_r->{config_profile}) && !$found && defined($_pci_r->{read_config_files}) && @{$_pci_r->{read_config_files}} && !$_pci_r->{ignore_missing_config_profile_section}) {', "\n";
178 12         78 push @l2, ' _pci_err([412, "Profile \'$_pci_r->{config_profile}\' not found in configuration file"]);', "\n";
179 12         37 push @l2, ' }', "\n";
180 12         28 push @l2, '}', "\n"; # if read_config
181              
182 12         159 join ("", @l2);
183             }
184              
185             sub _gen_pci_check_args {
186 48     48   226 my ($cd) = @_;
187              
188 48         119 my @l2;
189 48         163 push @l2, ' my ($args) = @_;', "\n";
190 48         338 push @l2, ' my $sc_name = $_pci_r->{subcommand_name};', "\n";
191 48         233 my $i = -1;
192 48         115 for my $sc_name (sort keys %{$cd->{metas}}) {
  48         402  
193 53         236 $i++;
194 53         164 my $meta = $cd->{metas}{$sc_name};
195 53   100     337 my $args_prop = $meta->{args} // {};
196 53 100       702 push @l2, ' '.($i ? "elsif":"if").' ($sc_name eq '.dmp($sc_name).") {\n";
197 53         3798 push @l2, " FILL_FROM_POS: {\n";
198 53         148 push @l2, " 1;\n"; # to avoid syntax error when there is 0 args
199 53         439 for my $arg (sort {
200             ($args_prop->{$b}{pos} // 9999) <=>
201 97   100     400 ($args_prop->{$a}{pos} // 9999)
      100        
202             } keys %$args_prop) {
203 83         210 my $arg_spec = $args_prop->{$arg};
204 83         236 my $arg_opts = $cd->{ggl_res}{$sc_name}[3]{'func.opts_by_arg'}{$arg};
205 83 100       261 next unless defined $arg_spec->{pos};
206 67         313 push @l2, ' if (@ARGV > '.$arg_spec->{pos}.') {';
207 67         244 push @l2, ' if (exists $args->{"'.$arg.'"}) {';
208 67         238 push @l2, ' return [400, "You specified '.$arg_opts->[0].' but also argument #'.$arg_spec->{pos}.'"];';
209 67         231 push @l2, " } else {";
210 67 100 66     413 if ($arg_spec->{slurpy} // $arg_spec->{greedy}) {
211 5         36 push @l2, ' $args->{"'.$arg.'"} = [splice(@ARGV, '.$arg_spec->{pos}.')];';
212             } else {
213 62         223 push @l2, ' $args->{"'.$arg.'"} = delete($ARGV['.$arg_spec->{pos}.']);';
214             }
215 67         218 push @l2, " }";
216 67         252 push @l2, " }\n";
217             }
218 53         375 push @l2, " }\n";
219 53         190 push @l2, ' my @check_argv = @ARGV;', "\n";
220              
221 53         315 push @l2, ' # fill from cmdline_src', "\n";
222             {
223 53         122 my $stdin_seen;
  53         153  
224             my $req_gen_iter;
225 53         183 for my $arg (sort {
226 86         147 my $asa = $args_prop->{$a};
227 86         123 my $asb = $args_prop->{$b};
228 86   50     338 my $csa = $asa->{cmdline_src} // '';
229 86   50     438 my $csb = $asb->{cmdline_src} // '';
230             # stdin_line is processed before stdin
231             ($csa eq 'stdin_line' ? 1:2) <=>
232             ($csa eq 'stdin_line' ? 1:2)
233             ||
234 86 50 100     469 ($asa->{pos} // 9999) <=> ($asb->{pos} // 9999)
    50 100        
    0          
235             } keys %$args_prop) {
236 83         232 my $arg_spec = $args_prop->{$arg};
237 83         171 my $cs = $arg_spec->{cmdline_src};
238 83   50     312 my $sch = $arg_spec->{schema} // '';
239 83 100 66     339 $sch = $sch->[1]{of} if $arg_spec->{stream} && $sch->[0] eq 'array';
240 83         206 my $type = Data::Sah::Util::Type::get_type($sch);
241 83 100       921 next unless $cs;
242 4 50       50 if ($cs eq 'stdin_line') {
    50          
    50          
    50          
    50          
    0          
243             # XXX support stdin_line, cmdline_prompt, is_password (for disabling echo)
244 0         0 return [501, "cmdline_src=stdin_line is not yet supported"];
245             } elsif ($cs eq 'stdin_or_file') {
246 0 0       0 return [400, "arg $arg: More than one cmdline_src=/stdin/ is found (arg=$stdin_seen)"]
247             if defined $stdin_seen;
248 0         0 $stdin_seen = $arg;
249             # XXX support - to mean stdin
250 0         0 push @l2, ' { my $fh;';
251 0         0 push @l2, ' if (exists $args->{"'.$arg.'"}) {';
252 0         0 push @l2, ' open $fh, "<", $args->{"'.$arg.'"} or _pci_err([500,"Cannot open file \'".$args->{"'.$arg.'"}."\': $!"]);';
253 0         0 push @l2, ' } else { $fh = \*STDIN }';
254 0 0       0 if ($arg_spec->{stream}) {
    0          
255 0         0 $req_gen_iter++;
256 0         0 push @l2, ' $args->{"'.$arg.'"} = _pci_gen_iter($fh, "'.$type.'", "'.$arg.'")';
257             } elsif ($type eq 'array') {
258 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; [<$fh>] }';
259             } else {
260 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; ~~<$fh> }';
261             }
262 0         0 push @l2, " }\n";
263             } elsif ($cs eq 'file') {
264             # XXX support - to mean stdin
265 0 0       0 push @l2, ' if (!(exists $args->{"'.$arg.'"}) && '.($arg_spec->{req} ? 1:0).') { _pci_err([500,"Please specify filename for argument \''.$arg.'\'"]) }';
266 0         0 push @l2, ' if (exists $args->{"'.$arg.'"}) {';
267 0         0 push @l2, ' open my($fh), "<", $args->{"'.$arg.'"} or _pci_err([500,"Cannot open file \'".$_pci_args{"'.$arg.'"}."\': $!"]);';
268 0 0       0 if ($arg_spec->{stream}) {
    0          
269 0         0 $req_gen_iter++;
270 0         0 push @l2, ' $args->{"'.$arg.'"} = _pci_gen_iter($fh, "'.$type.'", "'.$arg.'")';
271             } elsif ($type eq 'array') {
272 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; [<$fh>] }';
273             } else {
274 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; ~~<$fh> }';
275             }
276 0         0 push @l2, " }\n";
277             } elsif ($cs eq 'stdin') {
278 0 0       0 return [400, "arg $arg: More than one cmdline_src=/stdin/ is found (arg=$stdin_seen)"]
279             if defined $stdin_seen;
280 0         0 $stdin_seen = $arg;
281 0         0 push @l2, ' unless (exists $args->{"'.$arg.'"}) {';
282 0 0       0 if ($arg_spec->{stream}) {
    0          
283 0         0 $req_gen_iter++;
284 0         0 push @l2, ' $args->{"'.$arg.'"} = _pci_gen_iter(\*STDIN, "'.$type.'", "'.$arg.'")';
285             } elsif ($type eq 'array') {
286 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; [<STDIN>] }';
287             } else {
288 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; ~~<STDIN> }';
289             }
290 0         0 push @l2, " }\n";
291             } elsif ($cs eq 'stdin_or_files') {
292 4 50       22 return [400, "arg $arg: More than one cmdline_src=/stdin/ is found (arg=$stdin_seen)"]
293             if defined $stdin_seen;
294 4         14 $stdin_seen = $arg;
295 4         36 push @l2, ' unless (exists $args->{"'.$arg.'"}) {';
296 4         29 push @l2, ' @check_argv = ();';
297 4 50       17 if ($arg_spec->{stream}) {
    0          
298 4         14 $req_gen_iter++;
299 4         33 push @l2, ' $args->{"'.$arg.'"} = _pci_gen_iter(\*ARGV, "'.$type.'", "'.$arg.'")';
300             } elsif ($type eq 'array') {
301 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; [<>] }';
302             } else {
303 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; ~~<> }';
304             }
305 4         33 push @l2, " }\n";
306             } elsif ($cs eq 'stdin_or_args') {
307 0 0       0 return [400, "arg $arg: More than one cmdline_src=/stdin/ is found (arg=$stdin_seen)"]
308             if defined $stdin_seen;
309 0         0 $stdin_seen = $arg;
310 0         0 push @l2, ' unless (exists $args->{"'.$arg.'"}) {';
311 0         0 push @l2, ' @check_argv = ();';
312 0 0       0 if ($arg_spec->{stream}) {
    0          
313 0         0 $req_gen_iter++;
314 0         0 push @l2, ' $args->{"'.$arg.'"} = _pci_gen_iter(\*STDIN, "'.$type.'", "'.$arg.'")';
315             } elsif ($type eq 'array') {
316 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; [map {chomp;$_} <>] }';
317             } else {
318 0         0 push @l2, ' $args->{"'.$arg.'"} = do { local $/; ~~<> }';
319             }
320 0         0 push @l2, " }\n";
321             } else {
322 0         0 return [400, "arg $arg: unknown cmdline_src value '$cs'"];
323             }
324             }
325              
326 53 100       146 unless ($req_gen_iter) {
327 49         153 delete $cd->{sub_srcs}{_pci_gen_iter};
328 49         148 delete $cd->{module_srcs}{'Data::Sah::Util::Type'};
329             }
330             } # fill from cmdline_src
331 53         133 push @l2, "\n";
332              
333 53         365 push @l2, ' # fill defaults from "default" property and check against schema', "\n";
334             GEN_VALIDATION:
335             {
336 53         164 my $has_validation;
  53         202  
337             my @l3;
338 53         0 my @modules_for_all_args;
339 53         0 my @req_stmts;
340 53         384 for my $arg (sort keys %$args_prop) {
341 83         239 my $arg_spec = $args_prop->{$arg};
342              
343             # we don't validate streaming input for now
344 83 100       240 next if $arg_spec->{stream};
345              
346 79         147 my $arg_schema = $arg_spec->{schema};
347 79         327 my $arg_term = '$args->{"'.$arg.'"}';
348 79 50       288 if (defined $arg_spec->{default}) {
349 0         0 push @l3, " $arg_term //= ".dmp($arg_spec->{default}).";\n";
350             }
351              
352 79 50 33     706 if ($arg_schema && $cd->{gen_args}{validate_args}) {
353 79         188 $has_validation++;
354             my $dsah_cd = _dsah_plc->compile(
355             schema => $arg_schema,
356             schema_is_normalized => 1,
357             indent_level => 3,
358              
359             data_term => $arg_term,
360             err_term => '$_sahv_err',
361             return_type => 'str',
362              
363             core_or_pp => 1,
364             ( whitelist_modules => $cd->{gen_args}{allow_prereq} ) x !!$cd->{gen_args}{allow_prereq},
365 79         393 );
366 79 50       367230 die "Incompatible Data::Sah version (cd v=$dsah_cd->{v}, expected 2)" unless $dsah_cd->{v} == 2;
367             # add require statements for modules needed during
368             # validation
369 79         162 for my $mod_rec (@{$dsah_cd->{modules}}) {
  79         245  
370 185 100       1109 next unless $mod_rec->{phase} eq 'runtime';
371 96 100       253 next if grep { ($mod_rec->{use_statement} && $_->{use_statement} && $_->{use_statement} eq $mod_rec->{use_statement}) ||
372 82 100 100     981 $_->{name} eq $mod_rec->{name} } @modules_for_all_args;
      66        
373 46         123 push @modules_for_all_args, $mod_rec;
374 46 100       250 if ($mod_rec->{name} =~ /\A(Scalar::Util::Numeric::PP)\z/) {
375 12         69 _pack_module($cd, $mod_rec->{name});
376             }
377 46         356 my $mod_is_core = Module::CoreList::More->is_still_core($mod_rec->{name});
378             log_warn("Validation code requires non-core module '%s'", $mod_rec->{name})
379             unless $mod_is_core && !$cd->{module_srcs}{$mod_rec->{name}} &&
380 46 50 66     47507 !($cd->{gen_args}{allow_prereq} && grep { $_ eq $mod_rec->{name} } @{$cd->{gen_args}{allow_prereq}});
      33        
      66        
381             # skip modules that we already require at the
382             # beginning of script
383 46 50       217 next if exists $cd->{req_modules}{$mod_rec->{name}};
384 46         155 push @req_stmts, _dsah_plc->stmt_require_module($mod_rec);
385             }
386 79         431 push @l3, " if (exists $arg_term) {\n";
387 79         290 push @l3, " \$_sahv_dpath = [];\n";
388 79         355 push @l3, $dsah_cd->{result}, "\n";
389 79         267 push @l3, " ; if (\$_sahv_err) { return [400, \"Argument validation failed: \$_sahv_err\"] }\n";
390 79         2037 push @l3, " } # if date arg exists\n";
391             }
392             }
393 53         238 push @l3, "\n";
394              
395 53 100       230 if ($has_validation) {
396 29         73 push @l2, map {" $_\n"} @req_stmts;
  46         328  
397 29         233 push @l2, " my \$_sahv_dpath;\n";
398 29         103 push @l2, " my \$_sahv_err;\n";
399             }
400              
401 53         270 push @l2, @l3;
402             } # GEN_VALIDATION
403              
404 53         410 push @l2, ' # check required args', "\n";
405 53         257 for my $arg (sort keys %$args_prop) {
406 83         173 my $arg_spec = $args_prop->{$arg};
407 83 100       206 if ($arg_spec->{req}) {
408 14         154 push @l2, ' return [400, "Missing required argument: '.$arg.'"] unless exists $args->{"'.$arg.'"};', "\n";
409             }
410 83 100       294 if ($arg_spec->{schema}[1]{req}) {
411 18         191 push @l2, ' return [400, "Missing required value for argument: '.$arg.'"] if exists($args->{"'.$arg.'"}) && !defined($args->{"'.$arg.'"});', "\n";
412             }
413             }
414              
415 53         198 push @l2, ' _pci_err([500, "Extraneous command-line argument(s): ".join(", ", @check_argv)]) if @check_argv;', "\n";
416 53         264 push @l2, ' [200];', "\n";
417 53         216 push @l2, ' }';
418             } # for subcommand
419 48         327 push @l2, ' else { _pci_err([500, "Unknown subcommand1: $sc_name"]); }', "\n";
420 48         1339 $cd->{module_srcs}{"Local::_pci_check_args"} = "sub _pci_check_args {\n".join('', @l2)."}\n1;\n";
421             }
422              
423             sub _gen_common_opt_handler {
424 408     408   754 my ($cd, $co) = @_;
425              
426 408         511 my @l;
427              
428 408         650 my $has_subcommands = $cd->{gen_args}{subcommands};
429              
430 408 100       2917 if ($co eq 'help') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
431 48 100       153 if ($has_subcommands) {
432 9         26 push @l, 'my $sc_name = $_pci_r->{subcommand_name}; ';
433 9         21 push @l, 'my $first_non_opt_arg; for (@ARGV) { next if /^-/; $first_non_opt_arg = $_; last } if (!length $sc_name && defined $first_non_opt_arg) { $sc_name = $first_non_opt_arg } ';
434 9         30 push @l, 'if (!length $sc_name) { print $help_msg } ';
435 9         29 for (sort keys %{ $cd->{helps} }) {
  9         60  
436 23         1414 push @l, 'elsif ($sc_name eq '.dmp($_).') { print '.dmp($cd->{helps}{$_}).' } ';
437             }
438 9         897 push @l, 'else { _pci_err([500, "Unknown subcommand2: $sc_name"]) } ';
439 9         37 push @l, 'exit 0';
440             } else {
441 39         1641 require Perinci::CmdLine::Help;
442             my $res = Perinci::CmdLine::Help::gen_help(
443             meta => $cd->{metas}{''},
444             meta_is_normalized => 1,
445             common_opts => $cd->{copts},
446             program_name => $cd->{script_name},
447 39         3923 );
448 39 50       269975 return [500, "Can't generate help: $res->[0] - $res->[1]"]
449             unless $res->[0] == 200;
450 39         205 push @l, 'print ', dmp($res->[2]), '; exit 0;';
451             }
452             } elsif ($co eq 'version') {
453 2     2   19 no strict 'refs';
  2         5  
  2         6224  
454 48         173 my $mod = $cd->{sc_mods}{''};
455 48         104 push @l, "no warnings 'once'; ";
456 48 50       295 push @l, "require $mod; " if $mod;
457 48         215 push @l, 'print "', $cd->{script_name} , ' version ", ';
458 48 50       197 if ($cd->{gen_args}{script_version_from_main_version}) {
459 0         0 push @l, "\$main::VERSION // '?'", ", (\$main::DATE ? \" (\$main\::DATE)\" : '')";
460             } else {
461 48 50       403 push @l, defined($cd->{gen_args}{script_version}) ? "\"$cd->{gen_args}{script_version}\"" :
462             "(\$$mod\::VERSION // '?')",
463             ", (\$$mod\::DATE ? \" (\$$mod\::DATE)\" : '')";
464             }
465 48         180 push @l, ', "\\n"; ';
466             push @l, 'print " Generated by ', __PACKAGE__ , ' version ',
467 48         670 (${__PACKAGE__."::VERSION"} // 'dev'),
468 48 50 50     105 (${__PACKAGE__."::DATE"} ? " (".${__PACKAGE__."::DATE"}.")" : ""),
  48         490  
  48         527  
469             '\n"; ';
470 48         408 push @l, 'exit 0';
471             } elsif ($co eq 'log_level') {
472 1         3 push @l, 'if ($_[1] eq "trace") { require Log::ger::Util; Log::ger::Util::set_level("trace"); Log::ger::Output::Composite::set_level("trace") } ';
473 1         2 push @l, 'if ($_[1] eq "debug") { require Log::ger::Util; Log::ger::Util::set_level("debug"); Log::ger::Output::Composite::set_level("debug") } ';
474 1         2 push @l, 'if ($_[1] eq "info" ) { require Log::ger::Util; Log::ger::Util::set_level("info" ); Log::ger::Output::Composite::set_level("info") } ';
475 1         4 push @l, 'if ($_[1] eq "error") { require Log::ger::Util; Log::ger::Util::set_level("warn" ); Log::ger::Output::Composite::set_level("warn") } ';
476 1         9 push @l, 'if ($_[1] eq "fatal") { require Log::ger::Util; Log::ger::Util::set_level("debug"); Log::ger::Output::Composite::set_level("debug") } ';
477 1         8 push @l, 'if ($_[1] eq "none") { require Log::ger::Util; Log::ger::Util::set_level("off" ); Log::ger::Output::Composite::set_level("off") } ';
478 1         8 push @l, 'if ($_[1] eq "off") { require Log::ger::Util; Log::ger::Util::set_level("off" ); Log::ger::Output::Composite::set_level("off") } ';
479 1         3 push @l, '$_pci_r->{log_level} = $_[1];';
480             } elsif ($co eq 'trace') {
481 1         7 push @l, 'require Log::ger::Util; Log::ger::Util::set_level("trace"); $_pci_r->{log_level} = "trace";';
482             } elsif ($co eq 'debug') {
483 1         7 push @l, 'require Log::ger::Util; Log::ger::Util::set_level("debug"); $_pci_r->{log_level} = "debug";';
484             } elsif ($co eq 'verbose') {
485 1         7 push @l, 'require Log::ger::Util; Log::ger::Util::set_level("info" ); $_pci_r->{log_level} = "info" ;';
486             } elsif ($co eq 'quiet') {
487 1         7 push @l, 'require Log::ger::Util; Log::ger::Util::set_level("error"); $_pci_r->{log_level} = "error";';
488             } elsif ($co eq 'subcommands') {
489 9         31 my $scs_text = "Available subcommands:\n";
490 9         22 for (sort keys %{ $cd->{metas} }) {
  9         42  
491 14         52 $scs_text .= " $_\n";
492             }
493 9         37 push @l, 'print ', dmp($scs_text), '; exit 0';
494             } elsif ($co eq 'cmd') {
495 18         41 push @l, '$_[2]{subcommand} = [$_[1]]; '; # for Getopt::Long::Subcommand
496 18         36 push @l, '$_pci_r->{subcommand_name} = $_[1];';
497             } elsif ($co eq 'format') {
498 48         154 push @l, '$_pci_r->{format} = $_[1];';
499             } elsif ($co eq 'json') {
500 48         99 push @l, '$_pci_r->{format} = (-t STDOUT) ? "json-pretty" : "json";';
501             } elsif ($co eq 'naked_res') {
502 48         91 push @l, '$_pci_r->{naked_res} = 1;';
503             } elsif ($co eq 'no_naked_res') {
504 48         97 push @l, '$_pci_r->{naked_res} = 0;';
505             } elsif ($co eq 'no_config') {
506 12         34 push @l, '$_pci_r->{read_config} = 0;';
507             } elsif ($co eq 'config_path') {
508 12         40 push @l, '$_pci_r->{config_paths} //= []; ';
509 12         85 push @l, 'push @{ $_pci_r->{config_paths} }, $_[1];';
510             } elsif ($co eq 'config_profile') {
511 12         72 push @l, '$_pci_r->{config_profile} = $_[1];';
512             } elsif ($co eq 'no_env') {
513 4         18 push @l, '$_pci_r->{read_env} = 0;';
514             } elsif ($co eq 'page_result') {
515 48         167 push @l, '$_pci_r->{page_result} = 1;';
516             } else {
517 0         0 die "BUG: Unrecognized common_opt '$co'";
518             }
519 408         6304 join "", @l;
520             }
521              
522             sub _gen_get_args {
523 48     48   228 my ($cd) = @_;
524              
525 48         244 my @l;
526              
527 48         206 push @l, 'my %mentioned_args;', "\n";
528              
529 48         181 _pack_module($cd, "Getopt::Long::EvenLess");
530 48         239 push @l, "require Getopt::Long::EvenLess;\n";
531 48 100       508 push @l, 'log_trace("Parsing command-line arguments ...");', "\n" if $cd->{gen_args}{log};
532              
533 48 100       305 if ($cd->{gen_args}{subcommands}) {
534              
535 9         55 _pack_module($cd, "Getopt::Long::Subcommand");
536 9         101 push @l, "require Getopt::Long::Subcommand;\n";
537             # we haven't added the Complete::* that Getopt::Long::Subcommand depends on
538              
539             # generate help message for all subcommands
540             {
541 9         31 require Perinci::CmdLine::Help;
  9         79  
542 9         139 my %helps; # key = subcommand name
543 9         25 for my $sc_name (sort keys %{ $cd->{metas} }) {
  9         103  
544 14 50       65 next if $sc_name eq '';
545 14         44 my $meta = $cd->{metas}{$sc_name};
546             my $res = Perinci::CmdLine::Help::gen_help(
547             meta => $meta,
548 14         35 common_opts => { map {$_ => $cd->{copts}{$_}} grep { $_ !~ /\A(subcommands|cmd)\z/ } keys %{$cd->{copts}} },
  104         383  
  132         418  
  14         72  
549             program_name => "$cd->{script_name} $sc_name",
550             );
551 14 50       85860 return [500, "Can't generate help (subcommand='$sc_name'): $res->[0] - $res->[1]"]
552             unless $res->[0] == 200;
553 14         73 $helps{$sc_name} = $res->[2];
554             }
555             # generate help when there is no subcommand specified
556             my $res = Perinci::CmdLine::Help::gen_help(
557             meta => {v=>1.1},
558             common_opts => $cd->{copts},
559             program_name => $cd->{script_name},
560             program_summary => $cd->{gen_args}{script_summary},
561             subcommands => $cd->{gen_args}{subcommands},
562 9         140 );
563 9 50       51454 return [500, "Can't generate help (subcommand=''): $res->[0] - $res->[1]"]
564             unless $res->[0] == 200;
565 9         34 $helps{''} = $res->[2];
566              
567 9         40 $cd->{helps} = \%helps;
568             }
569              
570 9         62 push @l, 'my $help_msg = ', dmp($cd->{helps}{''}), ";\n";
571              
572 9         1037 my @sc_names = sort keys %{ $cd->{metas} };
  9         86  
573              
574 9         43 for my $stage (1, 2) {
575 18 100       57 if ($stage == 1) {
576 9         28 push @l, 'my $go_spec1 = {', "\n";
577             } else {
578 9         19 push @l, 'my $go_spec2 = {', "\n";
579 9         20 push @l, " options => {\n";
580             }
581              
582             # common options
583 18         68 my $ggl_res = $cd->{ggl_res}{$sc_names[0]};
584 18         39 my $specmetas = $ggl_res->[3]{'func.specmeta'};
585 18         176 for my $o (sort keys %$specmetas) {
586 214         344 my $specmeta = $specmetas->{$o};
587 214         347 my $co = $specmeta->{common_opt};
588 214 100       368 next unless $co;
589 174 100       279 if ($stage == 1) {
590 87         252 push @l, " '$o' => sub { ", _gen_common_opt_handler($cd, $co), " },\n";
591             } else {
592 87         185 push @l, " '$o' => {\n";
593 87 100       149 if ($co eq 'cmd') {
594 9         34 push @l, " handler => sub { ", _gen_common_opt_handler($cd, $co), " },\n";
595             } else {
596 78         107 push @l, " handler => sub {},\n";
597             }
598 87         171 push @l, " },\n";
599             }
600             }
601 18 100       65 if ($stage == 1) {
602 9         33 push @l, "};\n"; # end of %go_spec1
603             } else {
604 9         61 push @l, " },\n"; # end of options
605             }
606              
607 18 100       58 if ($stage == 2) {
608             # subcommand options
609 9         28 push @l, " subcommands => {\n";
610 9         13 for my $sc_name (sort keys %{ $cd->{metas} }) {
  9         44  
611 14         35 my $meta = $cd->{metas}{$sc_name};
612 14         46 push @l, " '$sc_name' => {\n";
613 14         26 push @l, " options => {\n";
614 14         28 my $ggl_res = $cd->{ggl_res}{$sc_name};
615 14         32 my $specmetas = $ggl_res->[3]{'func.specmeta'};
616 14         84 for my $o (sort keys %$specmetas) {
617 157         226 my $specmeta = $specmetas->{$o};
618 157         225 my $argname = $specmeta->{arg}; # XXX can't handle submetadata yet
619 157 100       286 next unless defined $argname;
620 25         52 my $arg_spec = $meta->{args}{$argname};
621 25         78 push @l, " '$o' => {\n";
622 25         68 push @l, " handler => sub { ";
623 25 50 33     85 if ($specmeta->{is_alias} && $specmeta->{is_code}) {
624 0         0 my $alias_spec = $arg_spec->{cmdline_aliases}{$specmeta->{alias}};
625 0 0       0 if ($specmeta->{is_code}) {
626 0         0 push @l, 'my $code = ', dmp($alias_spec->{code}), '; ';
627 0         0 push @l, '$code->(\%_pci_args);';
628             } else {
629 0         0 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = $_[1];';
630             }
631             } else {
632 25 50 50     220 if (($specmeta->{parsed}{type} // '') =~ /\@/) {
    100          
    50          
633 0         0 push @l, 'if ($mentioned_args{\'', $specmeta->{arg}, '\'}++) { push @{ $_pci_args{\'', $specmeta->{arg}, '\'} }, $_[1] } else { $_pci_args{\'', $specmeta->{arg}, '\'} = [$_[1]] }';
634             } elsif ($specmeta->{is_json}) {
635 10         54 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = _pci_json()->decode($_[1]);';
636             } elsif ($specmeta->{is_neg}) {
637 0         0 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = 0;';
638             } else {
639 15         74 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = $_[1];';
640             }
641             }
642 25         68 push @l, " },\n"; # end of handler
643 25         55 push @l, " },\n"; # end of option
644             }
645 14         80 push @l, " },\n"; # end of options
646 14         49 push @l, " },\n"; # end of subcommand
647             }
648 9         23 push @l, " },\n"; # end of subcommands
649 9         52 push @l, " default_subcommand => ".dmp($cd->{gen_args}{default_subcommand}).",\n";
650              
651 9         279 push @l, "};\n"; # end of %go_spec2
652             } # subcommand options
653             } # stage
654              
655 9         18 push @l, "{\n";
656 9         35 push @l, ' local @ARGV = @ARGV;', "\n";
657 9         29 push @l, ' my $old_conf = Getopt::Long::EvenLess::Configure("pass_through");', "\n";
658 9         67 push @l, ' Getopt::Long::EvenLess::GetOptions(%$go_spec1);', "\n";
659 9         65 push @l, ' Getopt::Long::EvenLess::Configure($old_conf);', "\n";
660 9         71 push @l, ' { my $first_non_opt_arg; for (@ARGV) { next if /^-/; $first_non_opt_arg = $_; last } if (!length $_pci_r->{subcommand_name} && defined $first_non_opt_arg) { $_pci_r->{subcommand_name} = $first_non_opt_arg } }', "\n";
661 9 100       61 push @l, ' if (!length $_pci_r->{subcommand_name}) { $_pci_r->{subcommand_name} = '.dmp($cd->{gen_args}{default_subcommand}).' } ' if defined $cd->{gen_args}{default_subcommand};
662 9         152 push @l, "}\n";
663 9         79 push @l, _gen_read_env($cd);
664 9         88 push @l, _gen_read_config($cd);
665 9         32 push @l, 'my $res = Getopt::Long::Subcommand::GetOptions(%$go_spec2);', "\n";
666 9 50       41 push @l, '_pci_debug("args after GetOptions: ", \%_pci_args);', "\n" if $cd->{gen_args}{with_debug};
667 9         29 push @l, '_pci_err([500, "GetOptions failed"]) unless $res->{success};', "\n";
668 9         69 push @l, 'if (!length $_pci_r->{subcommand_name}) { print $help_msg; exit 0 }', "\n";
669              
670             } else {
671              
672 39         215 my $meta = $cd->{metas}{''};
673             # stage 1 is catching common options only (--help, etc)
674 39         270 for my $stage (1, 2) {
675 78         272 push @l, "my \$go_spec$stage = {\n";
676 78         137 for my $go_spec (sort keys %{ $cd->{ggl_res}{''}[2] }) {
  78         708  
677 900         1647 my $specmeta = $cd->{ggl_res}{''}[3]{'func.specmeta'}{$go_spec};
678 900         1435 my $co = $specmeta->{common_opt};
679 900 100 100     2217 next if $stage == 1 && !$co;
680 762         1618 push @l, " '$go_spec' => sub { "; # begin option handler
681 762 100       1150 if ($co) {
682 624 100       1007 if ($stage == 1) {
683 312         650 push @l, _gen_common_opt_handler($cd, $co);
684             } else {
685             # empty, we've done handling common options in stage 1
686             }
687             } else {
688 138         388 my $arg_spec = $meta->{args}{$specmeta->{arg}};
689 138         294 push @l, ' ';
690 138 50 66     463 if ($stage == 1) {
    50          
691             # in stage 1, we do not yet deal with argument options
692             } elsif ($specmeta->{is_alias} && $specmeta->{is_code}) {
693 0         0 my $alias_spec = $arg_spec->{cmdline_aliases}{$specmeta->{alias}};
694 0 0       0 if ($specmeta->{is_code}) {
695 0         0 push @l, 'my $code = ', dmp($alias_spec->{code}), '; ';
696 0         0 push @l, '$code->(\%_pci_args);';
697             } else {
698 0         0 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = $_[1];';
699             }
700             } else {
701 138 100 100     674 if (($specmeta->{parsed}{type} // '') =~ /\@/) {
    100          
    100          
702 8         50 push @l, 'if ($mentioned_args{\'', $specmeta->{arg}, '\'}++) { push @{ $_pci_args{\'', $specmeta->{arg}, '\'} }, $_[1] } else { $_pci_args{\'', $specmeta->{arg}, '\'} = [$_[1]] }';
703             } elsif ($specmeta->{is_json}) {
704 59         181 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = _pci_json()->decode($_[1]);';
705             } elsif ($specmeta->{is_neg}) {
706 10         31 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = 0;';
707             } else {
708 61         140 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = $_[1];';
709             }
710             }
711 138         338 push @l, "\n";
712             }
713 762         1366 push @l, " },\n"; # end option handler
714             } # options
715 78         219 push @l, "};\n";
716             } # stage
717 39         126 push @l, 'my $old_conf = Getopt::Long::EvenLess::Configure("pass_through");', "\n";
718 39         273 push @l, 'Getopt::Long::EvenLess::GetOptions(%$go_spec1);', "\n";
719 39         138 push @l, 'Getopt::Long::EvenLess::Configure($old_conf);', "\n";
720 39         298 push @l, _gen_read_env($cd);
721 39         331 push @l, _gen_read_config($cd);
722 39         173 push @l, 'my $res = Getopt::Long::EvenLess::GetOptions(%$go_spec2);', "\n";
723 39         248 push @l, '_pci_err([500, "GetOptions failed"]) unless $res;', "\n";
724 39 50       205 push @l, '_pci_debug("args after GetOptions (stage 2): ", \%_pci_args);', "\n" if $cd->{gen_args}{with_debug};
725              
726             }
727              
728 48         1279 join "", @l;
729             }
730              
731             # keep synchronize with Perinci::CmdLine::Base
732             my %pericmd_attrs = (
733              
734             # the currently unsupported/unused/irrelevant
735             (map {(
736             $_ => {
737             schema => 'any*',
738             },
739             )} qw/actions common_opts completion
740             default_format
741             description exit formats
742             riap_client riap_version riap_client_args
743             tags
744             get_subcommand_from_arg
745             /),
746              
747             pass_cmdline_object => {
748             summary => 'Whether to pass Perinci::CmdLine::Inline object',
749             schema => 'bool*',
750             default => 0,
751             },
752             script_name => {
753             schema => 'str*',
754             },
755             script_summary => {
756             schema => 'str*',
757             },
758             script_version => {
759             summary => 'Script version (otherwise will use version from url metadata)',
760             schema => 'str',
761             },
762             script_version_from_main_version => {
763             summary => "Use script's \$main::VERSION for the version",
764             schema => 'bool*',
765             },
766             url => {
767             summary => 'Program URL',
768             schema => 'riap::url*',
769             pos => 0,
770             },
771             extra_urls_for_version => {
772             summary => 'More URLs to show version for --version',
773             description => <<'_',
774              
775             Currently not implemented in Perinci::CmdLine::Inline.
776              
777             _
778             schema => ['array*', of=>'riap::url*'],
779             },
780             skip_format => {
781             summary => 'Assume that function returns raw text that need '.
782             'no formatting, do not offer --format, --json, --naked-res',
783             schema => 'bool*',
784             default => 0,
785             },
786             use_utf8 => {
787             summary => 'Whether to set utf8 flag on output',
788             schema => 'bool*',
789             default => 0,
790             },
791             use_cleanser => {
792             summary => 'Whether to use data cleanser routine first before producing JSON',
793             schema => 'bool*',
794             default => 1,
795             description => <<'_',
796              
797             When a function returns result, and the user wants to display the result as
798             JSON, the result might need to be cleansed first (e.g. using <pm:Data::Clean>)
799             before it can be encoded to JSON, for example it might contain Perl objects or
800             scalar references or other stuffs. If you are sure that your function does not
801             produce those kinds of data, you can set this to false to produce a more
802             lightweight script.
803              
804             _
805             },
806             );
807              
808             $SPEC{gen_inline_pericmd_script} = {
809             v => 1.1,
810             summary => 'Generate inline Perinci::CmdLine CLI script',
811             description => <<'_',
812              
813             The goal of this module is to let you create a CLI script from a Riap
814             function/metadata. This is like what <pm:Perinci::CmdLine::Lite> or
815             <pm:Perinci::CmdLine::Classic> does, except that the generated CLI script will have
816             the functionalities inlined so it only need core Perl modules and not any of the
817             `Perinci::CmdLine::*` or other modules to run (excluding what modules the Riap
818             function itself requires).
819              
820             It's useful if you want a CLI script that is even more lightweight (in terms of
821             startup overhead or dependencies) than the one using <pm:Perinci::CmdLine::Lite>.
822              
823             So to reiterate, the goal of this module is to create a Perinci::CmdLine-based
824             script which only requires core modules, and has as little startup overhead as
825             possible.
826              
827             Currently it only supports a subset of features compared to other
828             `Perinci::CmdLine::*` implementations:
829              
830             * Only support local Riap URL (e.g. `/Foo/bar`, not
831             `http://example.org/Foo/bar`);
832              
833             As an alternative to this module, if you are looking to reduce dependencies, you
834             might also want to try using `depak` to fatpack/datapack your
835             <pm:Perinci::CmdLine::Lite>-based script.
836              
837             _
838             args_rels => {
839             'dep_any&' => [
840             [meta_is_normalized => ['meta']],
841             [default_subcommand => ['subcommands']],
842             ],
843             'req_one&' => [
844             [qw/url meta/],
845             [qw/url subcommands/],
846             ],
847             'choose_all&' => [
848             [qw/meta sub_name/],
849             ],
850             },
851             args => {
852             (map {
853             $_ => {
854             %{ $pericmd_attrs{$_} },
855             summary => $pericmd_attrs{$_}{summary} // 'Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base',
856             tags => ['category:pericmd-attribute'],
857             },
858             } keys %pericmd_attrs),
859              
860             meta => {
861             summary => 'An alternative to specifying `url`',
862             schema => 'hash',
863             tags => ['category:input'],
864             },
865             meta_is_normalized => {
866             schema => 'bool',
867             tags => ['category:input'],
868             },
869             sub_name => {
870             schema => 'str*',
871             tags => ['category:input'],
872             },
873              
874             subcommands => {
875             schema => ['hash*', of=>'hash*'],
876             tags => ['category:input'],
877             },
878             default_subcommand => {
879             schema => 'str*',
880             tags => ['category:input'],
881             },
882              
883             shebang => {
884             summary => 'Set shebang line',
885             schema => 'str*',
886             },
887             validate_args => {
888             summary => 'Whether the CLI script should validate arguments using schemas',
889             schema => 'bool',
890             default => 1,
891             },
892             #validate_result => {
893             # summary => 'Whether the CLI script should validate result using schemas',
894             # schema => 'bool',
895             # default => 1,
896             #},
897             read_config => {
898             summary => 'Whether the CLI script should read configuration files',
899             schema => 'bool*',
900             default => 1,
901             },
902             config_filename => {
903             summary => 'Configuration file name(s)',
904             schema => ['any*', of=>[
905             'str*',
906             'hash*',
907             ['array*', of=>['any*', of=>['str*','hash*']]],
908             ]],
909             },
910             config_dirs => {
911             'x.name.is_plural' => 1,
912             'x.name.singular' => 'config_dir',
913             summary => 'Where to search for configuration files',
914             schema => ['array*', of=>'str*'],
915             },
916             read_env => {
917             summary => 'Whether CLI script should read environment variable that sets default options',
918             schema => 'bool*',
919             },
920             env_name => {
921             summary => 'Name of environment variable name that sets default options',
922             schema => 'str*',
923             },
924             log => {
925             summary => 'Whether to enable logging',
926             schema => 'bool*',
927             default => 0,
928             },
929              
930             with_debug => {
931             summary => 'Generate script with debugging outputs',
932             schema => 'bool',
933             tags => ['category:debugging'],
934             },
935             include => {
936             summary => 'Include extra modules',
937             'summary.alt.plurality.singular' => 'Include an extra module',
938             schema => ['array*', of=>'perl::modname*'],
939             cmdline_aliases => {I=>{}},
940             },
941              
942             code_after_shebang => {
943             summary => 'Put at the very beginning of generated script, after the shebang line',
944             schema => 'str*',
945             tags => ['category:extra-code'],
946             },
947             code_before_parse_cmdline_options => {
948             schema => 'str*',
949             tags => ['category:extra-code'],
950             },
951             code_before_enable_logging => {
952             schema => 'str*',
953             tags => ['category:extra-code'],
954             },
955             code_add_extra_log_outputs => {
956             schema => 'str*',
957             tags => ['category:extra-code'],
958             },
959             code_after_enable_logging => {
960             schema => 'str*',
961             tags => ['category:extra-code'],
962             },
963             code_after_end => {
964             summary => 'Put at the very end of generated script',
965             schema => 'str*',
966             tags => ['category:extra-code'],
967             },
968              
969             allow_prereq => {
970             summary => 'A list of modules that can be depended upon',
971             schema => ['array*', of=>'str*'], # XXX perl::modname
972             description => <<'_',
973              
974             By default, Perinci::CmdLine::Inline will strive to make the script freestanding
975             and require core modules. A dependency to a non-core module will cause failure
976             (unless `pack_deps` option is set to false). However, you can pass a list of
977             modules that is allowed here.
978              
979             _
980             },
981              
982             pack_deps => {
983             summary => 'Whether to pack dependencies into the script',
984             schema => ['bool*'],
985             default => 1,
986             description => <<'_',
987              
988             By default, Perinci::CmdLine::Inline will use datapacking technique (i.e. embed
989             dependencies into DATA section and load it on-demand using require() hook) to
990             make the script freestanding. However, in some situation this is unwanted, e.g.
991             when we want to produce a script that can be packaged as a Debian package
992             (Debian policy forbids embedding convenience copy of code,
993             https://www.debian.org/doc/debian-policy/ch-source.html#s-embeddedfiles ).
994              
995             _
996             },
997             pod => {
998             summary => 'Whether to generate POD for the script',
999             schema => ['bool*'],
1000             default => 1,
1001             },
1002              
1003             output_file => {
1004             summary => 'Set output file, defaults to stdout',
1005             schema => 'filename*',
1006             cmdline_aliases => {o=>{}},
1007             tags => ['category:output'],
1008             },
1009             overwrite => {
1010             schema => 'bool',
1011             tags => ['category:output'],
1012             },
1013             stripper => {
1014             summary => 'Whether to strip code using Perl::Stripper',
1015             schema => 'bool*',
1016             default => 0,
1017             },
1018             },
1019             };
1020             sub gen_inline_pericmd_script {
1021 50     50 1 3202672 require Data::Sah::Util::Type;
1022              
1023 50         3990 my %args = @_;
1024 50         668 $args{url} = "$args{url}"; # stringify URI object to avoid JSON encoder croaking
1025              
1026             # XXX schema
1027 50   50     1574 $args{validate_args} //= 1;
1028             #$args{validate_result} //= 1;
1029 50   50     596 $args{pack_deps} //= 1;
1030 50   50     238 $args{read_config} //= 1;
1031 50   50     163 $args{read_env} //= 1;
1032 50   50     757 $args{use_cleanser} //= 1;
1033              
1034             my $cd = {
1035             gen_args => \%args,
1036             script_name => $args{script_name},
1037 50         989 req_modules => {}, # modules which we will 'require' at the beginning of script. currently unused.
1038             vars => {},
1039             subs => {},
1040             module_srcs => {},
1041             core_deps => {}, # core modules required by the generated script. so we can specify dependencies to it, in environments where not all core modules are available.
1042             };
1043              
1044             GET_META:
1045             {
1046 50         226 my %metas; # key=subcommand name, '' if no subcommands
  50         291  
1047             my %mods; # key=module name, value={version=>..., ...}
1048 50         0 my %sc_mods; # key=subcommand name, value=module name
1049 50         0 my %func_names; # key=subcommand name, value=qualified function name
1050 50         236 my $script_name = $args{script_name};
1051              
1052 50         119 my $scs = $args{subcommands};
1053 50 100       181 if ($scs) {
1054 9         99 for my $sc_name (keys %$scs) {
1055 14         55 my $sc_spec = $scs->{$sc_name};
1056 14         244 my $res = _get_meta_from_url($sc_spec->{url});
1057 14 50       82 return $res if $res->[0] != 200;
1058             $mods{ $res->[3]{'func.module'} } = {
1059 14         132 version => $res->[3]{'func.module_version'},
1060             };
1061 14         85 $metas{$sc_name} = $res->[2];
1062 14         54 $sc_mods{$sc_name} = $res->[3]{'func.module'};
1063 14         76 $func_names{$sc_name} = $res->[3]{'func.func_name'};
1064             }
1065             }
1066              
1067 50         258 my $url = $args{url};
1068 50 50       178 if ($url) {
1069 50         642 my $res = _get_meta_from_url($url);
1070 50 50       252 return $res if $res->[0] != 200;
1071             $mods{ $res->[3]{'func.module'} } = {
1072 50         405 version => $res->[3]{'func.module_version'},
1073             };
1074 50         264 $sc_mods{''} = $res->[3]{'func.module'};
1075 50 100       221 unless ($scs) {
1076 41         340 $metas{''} = $res->[2];
1077 41         173 $func_names{''} = $res->[3]{'func.func_name'};
1078             }
1079 50 100       400 if (length (my $sfn = $res->[3]{'func.short_func_name'})) {
1080 41   66     314 $script_name //= do {
1081 27         81 local $_ = $sfn;
1082 27         202 s/_/-/g;
1083 27         190 $_;
1084             };
1085             }
1086             }
1087              
1088 50 0 33     166 if (!$url && !$scs) {
1089 0         0 $metas{''} = $args{meta};
1090 0         0 $func_names{''} = $args{sub_name};
1091 0   0     0 $script_name //= do {
1092 0         0 local $_ = $args{sub_name};
1093 0         0 s/_/-/g;
1094 0         0 $_;
1095             };
1096             }
1097              
1098 50   66     265 $script_name //= do {
1099 7         53 local $_ = $0;
1100 7         83 s!.+[/\\]!!;
1101 7         51 $_;
1102             };
1103              
1104 50 50       257 last if $args{meta_is_normalized};
1105 50         285 require Perinci::Sub::Normalize;
1106 50         224 for (keys %metas) {
1107 55         2954 $metas{$_} = Perinci::Sub::Normalize::normalize_function_metadata($metas{$_});
1108             }
1109              
1110 50         23275 $cd->{script_name} = $script_name;
1111 50         468 $cd->{metas} = \%metas;
1112 50         378 $cd->{mods} = \%mods;
1113 50         395 $cd->{sc_mods} = \%sc_mods;
1114 50         414 $cd->{func_names} = \%func_names;
1115             } # GET_META
1116              
1117 50   66     921 $args{config_filename} //= "$cd->{script_name}.conf";
1118 50   66     428 $args{env_name} //= do {
1119 49         300 my $env = uc "$cd->{script_name}_OPT";
1120 49         536 $env =~ s/[^A-Z0-9]+/_/g;
1121 49 100       294 $env = "_$env" if $env =~ /\A\d/;
1122 49         189 $env;
1123             };
1124              
1125 50         125 for (
1126             # required by Perinci::Result::Format::Lite. this will be removed if we
1127             # don't need formatting.
1128             "Data::Check::Structure",
1129              
1130             # required by _pci_gen_iter. this will be removed if we don't need
1131             # _pci_gen_iter
1132             "Data::Sah::Util::Type",
1133              
1134             # this will be removed if we don't need formatting
1135             "Perinci::Result::Format::Lite",
1136              
1137             # this will be removed if we don't need formatting
1138             "Text::Table::Tiny",
1139              
1140 50   100     245 @{ $args{include} // [] },
1141             ) {
1142 246         1251 _pack_module($cd, $_);
1143             }
1144              
1145             GEN_SCRIPT:
1146             {
1147 50         167 my @l;
  50         124  
1148              
1149             {
1150 50         94 require Perinci::CmdLine::Base;
  50         2090  
1151 2     2   48 no warnings 'once';
  2         17  
  2         5904  
1152 50         28373 my %copts;
1153 50         538 $copts{help} = $Perinci::CmdLine::Base::copts{help};
1154 50         216 $copts{version} = $Perinci::CmdLine::Base::copts{version};
1155 50 100       204 if ($args{log}) {
1156             $copts{log_level} = {
1157 1         40 getopt => "log-level=s",
1158             summary => "Set logging level (trace|debug|info|warn|error|fatal|none)",
1159             };
1160             $copts{trace} = {
1161 1         11 getopt => "trace",
1162             summary => "Set logging level to trace",
1163             };
1164             $copts{debug} = {
1165 1         10 getopt => "debug",
1166             summary => "Set logging level to debug",
1167             };
1168             $copts{verbose} = {
1169 1         15 getopt => "verbose",
1170             summary => "Set logging level to info",
1171             };
1172             $copts{quiet} = {
1173 1         10 getopt => "quiet",
1174             summary => "Set logging level to error",
1175             };
1176              
1177 1         12 $cd->{vars}{'$_pci_log_outputs'} = {};
1178             }
1179 50 50       453 unless ($args{skip_format}) {
1180 50         375 $copts{json} = $Perinci::CmdLine::Base::copts{json};
1181 50         356 $copts{format} = $Perinci::CmdLine::Base::copts{format};
1182             # "naked_res!" currently not supported by
1183             # Getopt::Long::EvenLess, so we split it. the downside is that
1184             # we don't hide the default, by default.
1185             $copts{naked_res} = {
1186 50         809 getopt => "naked-res",
1187             summary => "When outputing as JSON, strip result envelope",
1188             };
1189             $copts{no_naked_res} = {
1190 50         792 getopt => "no-naked-res|nonaked-res",
1191             summary => "When outputing as JSON, don't strip result envelope",
1192             };
1193             }
1194 50 100       223 if ($args{subcommands}) {
1195 9         56 $copts{subcommands} = $Perinci::CmdLine::Base::copts{subcommands};
1196 9         92 $copts{cmd} = $Perinci::CmdLine::Base::copts{cmd};
1197             }
1198 50 100       226 if ($args{read_config}) {
1199 12         61 for (qw/config_path no_config config_profile/) {
1200 36         377 $copts{$_} = $Perinci::CmdLine::Base::copts{$_};
1201             }
1202             }
1203 50 100       187 if ($args{read_env}) {
1204 4         29 for (qw/no_env/) {
1205 4         44 $copts{$_} = $Perinci::CmdLine::Base::copts{$_};
1206             }
1207             }
1208 50         129 for (qw/page_result/) {
1209 50         438 $copts{$_} = $Perinci::CmdLine::Base::copts{$_};
1210             }
1211 50         219 $cd->{copts} = \%copts;
1212             }
1213              
1214 50         120 my $shebang_line;
1215             {
1216 50   33     76 $shebang_line = $args{shebang} // $^X;
  50         791  
1217 50 50       317 $shebang_line = "#!$shebang_line" unless $shebang_line =~ /\A#!/;
1218 50 50       305 $shebang_line .= "\n" unless $shebang_line =~ /\R\z/;
1219             }
1220              
1221             # this will be removed if we don't use streaming input or read from
1222             # stdin
1223 50         596 $cd->{sub_srcs}{_pci_gen_iter} = <<'_';
1224             require Data::Sah::Util::Type;
1225             my ($fh, $type, $argname) = @_;
1226             if (Data::Sah::Util::Type::is_simple($type)) {
1227             return sub {
1228             # XXX this will be configurable later. currently by default reading
1229             # binary is per-64k while reading string is line-by-line.
1230             local $/ = \(64*1024) if $type eq 'buf';
1231              
1232             state $eof;
1233             return undef if $eof;
1234             my $l = <$fh>;
1235             unless (defined $l) {
1236             $eof++; return undef;
1237             }
1238             $l;
1239             };
1240             } else {
1241             my $i = -1;
1242             return sub {
1243             state $eof;
1244             return undef if $eof;
1245             $i++;
1246             my $l = <$fh>;
1247             unless (defined $l) {
1248             $eof++; return undef;
1249             }
1250             eval { $l = _pci_json()->decode($l) };
1251             if ($@) {
1252             die "Invalid JSON in stream argument '$argname' record #$i: $@";
1253             }
1254             $l;
1255             };
1256             }
1257             _
1258              
1259 50         475 $cd->{sub_srcs}{_pci_err} = <<'_';
1260             my $res = shift;
1261             print STDERR "ERROR $res->[0]: $res->[1]\n";
1262             exit $res->[0]-300;
1263             _
1264              
1265 50 50       250 if ($args{with_debug}) {
1266 0         0 _pack_module($cd, "Data::Dmp");
1267 0         0 _pack_module($cd, "Regexp::Stringify"); # needed by Data::Dmp
1268 0         0 $cd->{sub_srcs}{_pci_debug} = <<'_';
1269             require Data::Dmp;
1270             print "DEBUG: ", Data::Dmp::dmp(@_), "\n";
1271             _
1272             }
1273              
1274 50         310 $cd->{sub_srcs}{_pci_json} = <<'_';
1275             state $json = do {
1276             if (eval { require JSON::XS; 1 }) { JSON::XS->new->canonical(1)->allow_nonref }
1277             else { require JSON::PP; JSON::PP->new->canonical(1)->allow_nonref }
1278             };
1279             $json;
1280             _
1281 50         542 $cd->{sub_src_core_deps}{_pci_json}{'JSON::PP'} = 0;
1282              
1283             {
1284 50 50       219 last unless $args{use_cleanser};
1285 50         292 require Module::CoreList;
1286 50         1626 require Data::Clean::JSON;
1287 50         9586 my $cleanser = Data::Clean::JSON->new(
1288             # TODO: probably change back to using Storable since 3.08+
1289             # now support Regexp objects.
1290             '!clone_func' => 'Clone::PP::clone',
1291             );
1292 50         150827 my $src = $cleanser->{_cd}{src};
1293 50         399 my $src1 = 'sub _pci_clean_json { ';
1294 50         143 for my $mod (keys %{ $cleanser->{_cd}{modules} }) {
  50         262  
1295 100         91366 $src1 .= "require $mod; ";
1296 100 100       1206 next if Module::CoreList->is_core($mod);
1297 50         18802 _pack_module($cd, $mod);
1298             }
1299 50         91006 $cd->{module_srcs}{'Local::_pci_clean_json'} = "$src1 use feature 'state'; state \$cleanser = $src; \$cleanser->(shift) }\n1;\n";
1300             }
1301              
1302             {
1303 50         123 require Perinci::Sub::GetArgs::Argv;
  50         545  
  50         2605  
1304 50         20470 my %ggl_res; # key = subcommand name
1305             my %args_as; # key = subcommand name
1306 50         122 for my $sc_name (keys %{ $cd->{metas} }) {
  50         278  
1307 55         169 my $meta = $cd->{metas}{$sc_name};
1308 55   100     840 my $args_as = $meta->{args_as} // 'hash';
1309 55 100       665 if ($args_as !~ /\A(hashref|hash)\z/) {
1310 2         69 return [501, "args_as=$args_as currently unsupported at subcommand='$sc_name'"];
1311             }
1312 53         227 $args_as{$sc_name} = $args_as;
1313              
1314             my $ggl_res = Perinci::Sub::GetArgs::Argv::gen_getopt_long_spec_from_meta(
1315             meta => $meta,
1316             meta_is_normalized => 1,
1317             per_arg_json => 1,
1318             common_opts => $cd->{copts},
1319 53         832 );
1320 53 50       90747 return [500, "Can't generate Getopt::Long spec from meta (subcommand='$sc_name'): ".
1321             "$ggl_res->[0] - $ggl_res->[1]"]
1322             unless $ggl_res->[0] == 200;
1323 53         215 $ggl_res{$sc_name} = $ggl_res;
1324             }
1325 48         279 $cd->{ggl_res} = \%ggl_res;
1326 48         345 $cd->{args_as} = \%args_as;
1327 48         568 _gen_pci_check_args($cd);
1328             }
1329              
1330             $cd->{vars}{'$_pci_r'} = {
1331             naked_res => 0,
1332             subcommand_name => '',
1333             read_config => $args{read_config},
1334             read_env => $args{read_env},
1335 48         920 };
1336              
1337 48         201 $cd->{vars}{'%_pci_args'} = undef;
1338 48         368 push @l, "### get arguments (from config file, env, command-line args\n\n";
1339 48         624 push @l, "{\n", _gen_get_args($cd), "}\n\n";
1340              
1341             # gen code to check arguments
1342 48         159 push @l, "### check arguments\n\n";
1343 48         160 push @l, "{\n";
1344 48 50       220 push @l, 'require Local::_pci_check_args; ' if $cd->{gen_args}{pack_deps};
1345 48         128 push @l, 'my $res = _pci_check_args(\\%_pci_args);', "\n";
1346 48 50       182 push @l, '_pci_debug("args after _pci_check_args: ", \%_pci_args);', "\n" if $cd->{gen_args}{with_debug};
1347 48         172 push @l, '_pci_err($res) if $res->[0] != 200;', "\n";
1348 48         122 push @l, '$_pci_r->{args} = \\%_pci_args;', "\n";
1349 48         97 push @l, "}\n\n";
1350              
1351             # generate code to call function
1352 48         90 push @l, "### call function\n\n";
1353 48         156 $cd->{vars}{'$_pci_meta_result_stream'} = 0;
1354 48         131 $cd->{vars}{'$_pci_meta_skip_format'} = 0;
1355 48         143 $cd->{vars}{'$_pci_meta_result_type'} = undef;
1356 48         107 $cd->{vars}{'$_pci_meta_result_type_is_simple'} = undef;
1357 48         100 push @l, "{\n";
1358 48 100       170 push @l, 'log_trace("Calling function ...");', "\n" if $cd->{gen_args}{log};
1359 48         109 push @l, 'my $sc_name = $_pci_r->{subcommand_name};' . "\n";
1360             push @l, '$_pci_args{-cmdline} = Perinci::CmdLine::Inline::Object->new(@{', dmp([%args]), '});', "\n"
1361 48 50       164 if $args{pass_cmdline_object};
1362             {
1363 48         85 my $i = -1;
  48         98  
1364 48         105 for my $sc_name (sort keys %{ $cd->{metas} }) {
  48         234  
1365 53         92 $i++;
1366 53         129 my $meta = $cd->{metas}{$sc_name};
1367 53 100       279 push @l, ($i ? 'elsif' : 'if').' ($sc_name eq '.dmp($sc_name).") {\n";
1368 53 100       1834 push @l, ' $_pci_meta_result_stream = 1;'."\n" if $meta->{result}{stream};
1369 53 50       188 push @l, ' $_pci_meta_skip_format = 1;'."\n" if $meta->{'cmdline.skip_format'};
1370 53   100     736 push @l, ' $_pci_meta_result_type = '.dmp(Data::Sah::Util::Type::get_type($meta->{result}{schema} // '') // '').";\n";
      50        
1371 53 100 100     2598 push @l, ' $_pci_meta_result_type_is_simple = 1;'."\n" if Data::Sah::Util::Type::is_simple($meta->{result}{schema} // '');
1372 53 50       1375 push @l, " require $cd->{sc_mods}{$sc_name};\n" if $cd->{sc_mods}{$sc_name};
1373 53 100       357 push @l, ' eval { $_pci_r->{res} = ', $cd->{func_names}{$sc_name}, ($cd->{args_as}{$sc_name} eq 'hashref' ? '(\\%_pci_args)' : '(%_pci_args)'), ' };', "\n";
1374 53         369 push @l, ' if ($@) { die if $ENV{PERINCI_CMDLINE_INLINE_DEBUG_DIE}; $_pci_r->{res} = [500, "Function died: $@"] }', "\n";
1375 53 100       226 if ($meta->{result_naked}) {
1376 1         19 push @l, ' $_pci_r->{res} = [200, "OK (envelope added by Perinci::CmdLine::Inline)", $_pci_r->{res}];', "\n";
1377             }
1378 53         170 push @l, "}\n";
1379             }
1380             }
1381 48         123 push @l, "}\n\n";
1382              
1383             # generate code to format & display result
1384 48         95 push @l, "### format & display result\n\n";
1385 48         96 push @l, "{\n";
1386 48 100       406 push @l, 'log_trace("Displaying result ...");', "\n" if $cd->{gen_args}{log};
1387              
1388 48         209 push @l, 'my $fh;', "\n";
1389 48         305 push @l, 'if ($_pci_r->{page_result} // $ENV{PAGE_RESULT} // $_pci_r->{res}[3]{"cmdline.page_result"}) {', "\n";
1390 48         334 push @l, 'my $pager = $_pci_r->{pager} // $_pci_r->{res}[3]{"cmdline.pager"} // $ENV{PAGER} // "less -FRSX";', "\n";
1391 48         148 push @l, 'open $fh, "| $pager";', "\n";
1392 48         96 push @l, '} else {', "\n";
1393 48         94 push @l, '$fh = \*STDOUT;', "\n";
1394 48         233 push @l, '}', "\n";
1395              
1396 48         141 push @l, 'my $fres;', "\n";
1397 48         306 push @l, 'my $save_res; if (exists $_pci_r->{res}[3]{"cmdline.result"}) { $save_res = $_pci_r->{res}[2]; $_pci_r->{res}[2] = $_pci_r->{res}[3]{"cmdline.result"} }', "\n";
1398 48         134 push @l, 'my $is_success = $_pci_r->{res}[0] =~ /\A2/ || $_pci_r->{res}[0] == 304;', "\n";
1399 48         139 push @l, 'my $is_stream = $_pci_r->{res}[3]{stream} // $_pci_meta_result_stream // 0;'."\n";
1400 48 50       302 push @l, 'if ($is_success && (', ($args{skip_format} ? 1:0), ' || $_pci_meta_skip_format || $_pci_r->{res}[3]{"cmdline.skip_format"})) { $fres = $_pci_r->{res}[2] }', "\n";
1401 48         130 push @l, 'elsif ($is_success && $is_stream) {}', "\n";
1402 48         115 push @l, 'else { ';
1403 48 50 33     555 push @l, 'require Local::_pci_clean_json; ' if $args{pack_deps} && $args{use_cleanser};
1404 48         138 push @l, 'require Perinci::Result::Format::Lite; $is_stream=0; ';
1405 48 50       180 push @l, '_pci_clean_json($_pci_r->{res}); ' if $args{use_cleanser};
1406 48         339 push @l, '$fres = Perinci::Result::Format::Lite::format($_pci_r->{res}, ($_pci_r->{format} // $_pci_r->{res}[3]{"cmdline.default_format"} // "text"), $_pci_r->{naked_res}, 0) }', "\n";
1407 48         135 push @l, "\n";
1408              
1409 48 50       372 push @l, 'my $use_utf8 = $_pci_r->{res}[3]{"x.hint.result_binary"} ? 0 : '.($args{use_utf8} ? 1:0).";\n";
1410 48         295 push @l, 'if ($use_utf8) { binmode STDOUT, ":encoding(utf8)" }', "\n";
1411              
1412 48         443 push @l, 'if ($is_stream) {', "\n";
1413 48         405 push @l, ' my $code = $_pci_r->{res}[2]; if (ref($code) ne "CODE") { die "Result is a stream but no coderef provided" } if ($_pci_meta_result_type_is_simple) { while(defined(my $l=$code->())) { print $fh $l; print $fh "\n" unless $_pci_meta_result_type eq "buf"; } } else { while (defined(my $rec=$code->())) { if (!defined($rec) || ref $rec) { print $fh _pci_json()->encode($rec),"\n" } else { print $fh $rec,"\n" } } }', "\n";
1414 48         229 push @l, '} else {', "\n";
1415 48         279 push @l, ' print $fh $fres;', "\n";
1416 48         176 push @l, '}', "\n";
1417 48         375 push @l, 'if (defined $save_res) { $_pci_r->{res}[2] = $save_res }', "\n";
1418 48         115 push @l, "}\n\n";
1419              
1420             # generate code to exit with code
1421 48         81 push @l, "### exit\n\n";
1422 48         88 push @l, "{\n";
1423 48         307 push @l, 'my $status = $_pci_r->{res}[0];', "\n";
1424 48         332 push @l, 'my $exit_code = $_pci_r->{res}[3]{"cmdline.exit_code"} // ($status =~ /200|304/ ? 0 : ($status-300));', "\n";
1425 48         286 push @l, 'exit($exit_code);', "\n";
1426 48         106 push @l, "}\n\n";
1427              
1428             # remove unneeded modules
1429 48 50       158 if ($args{skip_format}) {
1430 0         0 delete $cd->{module_srcs}{'Data::Check::Structure'};
1431 0         0 delete $cd->{module_srcs}{'Perinci::Result::Format::Lite'};
1432 0         0 delete $cd->{module_srcs}{'Text::Table::Tiny'};
1433             }
1434              
1435 48 50       355 if ($args{pass_cmdline_object}) {
1436 0         0 require Class::GenSource;
1437 0         0 my $cl = 'Perinci::CmdLine::Inline::Object';
1438             $cd->{module_srcs}{$cl} =
1439             Class::GenSource::gen_class_source_code(
1440             name => $cl,
1441             attributes => {
1442 0         0 map { $_ => {} } keys %pericmd_attrs,
  0         0  
1443             },
1444             );
1445             }
1446              
1447 48         145 my ($dp_code1, $dp_code2, $dp_code3);
1448 48 50       140 if ($args{pack_deps}) {
1449 48         1639 require Module::DataPack;
1450             my $dp_res = Module::DataPack::datapack_modules(
1451             module_srcs => $cd->{module_srcs},
1452             stripper => $args{stripper},
1453 48         5554 );
1454 48 50       208217 return [500, "Can't datapack: $dp_res->[0] - $dp_res->[1]"]
1455             unless $dp_res->[0] == 200;
1456 48         147 $dp_code2 = "";
1457 48         13669 ($dp_code1, $dp_code3) = $dp_res->[2] =~ /(.+?)^(__DATA__\n.+)/sm;
1458             } else {
1459 0         0 $dp_code1 = "";
1460 0         0 $dp_code2 = "";
1461 0         0 $dp_code3 = "";
1462 0         0 for my $pkg (sort keys %{ $cd->{module_srcs} }) {
  0         0  
1463 0         0 my $src = $cd->{module_srcs}{$pkg};
1464 0         0 $dp_code2 .= "# BEGIN $pkg\n$src\n# END $pkg\n\n";
1465             }
1466             }
1467              
1468 48         185 my $pod;
1469 48 50 50     280 if ($args{pod} // 1) {
1470 48         2121 require Perinci::CmdLine::POD;
1471             my $res = Perinci::CmdLine::POD::gen_pod_for_pericmd_script(
1472             url => $args{url},
1473             program_name => $cd->{script_name},
1474             summary => $args{script_summary},
1475             common_opts => $cd->{copts},
1476             subcommands => $args{subcommands},
1477             default_subcommand => $args{default_subcommand},
1478             per_arg_json => 1,
1479             per_arg_yaml => 0,
1480             read_env => $args{read_env},
1481             env_name => $args{env_name},
1482             read_config => $args{read_config},
1483             config_filename => $args{config_filenames},
1484             config_dirs => $args{config_dirs},
1485 48         21833 completer_script => "_$cd->{script_name}",
1486             );
1487 48 50       490002 return err($res, 500, "Can't generate POD") unless $res->[0] == 200;
1488 48         437 $pod = $res->[2];
1489             }
1490              
1491             # generate final result
1492             $cd->{result} = join(
1493             "",
1494             $shebang_line, "\n",
1495              
1496             "### begin code_after_shebang\n",
1497             ($args{code_after_shebang}, "\n") x !!$args{code_after_shebang},
1498             "### end code_after_shebang\n",
1499              
1500             "# PERICMD_INLINE_SCRIPT: ", do {
1501 48         729 my %tmp = %args;
1502             # don't show the potentially long/undumpable argument values
1503 48         262 for (grep {/^code_/} keys %tmp) {
  915         2250  
1504 48         381 $tmp{$_} = "...";
1505             }
1506 48         1630 JSON::MaybeXS->new->canonical(1)->encode(\%tmp);
1507             }, "\n\n",
1508              
1509             'my $_pci_metas = ', do {
1510 48         3191 local $Data::Dmp::OPT_DEPARSE=0;
1511 48         326 dmp($cd->{metas});
1512             }, ";\n\n",
1513              
1514             "# This script is generated by ", __PACKAGE__,
1515 48         33195 " version ", (${__PACKAGE__."::VERSION"} // 'dev'), " on ",
1516             scalar(localtime), ".\n\n",
1517              
1518 48   50     446 (keys %{$cd->{mods}} ? "# Rinci metadata taken from these modules: ".join(", ", map {"$_ ".($cd->{mods}{$_}{version} // "(no version)")} sort keys %{$cd->{mods}})."\n\n" : ""),
  48         1067  
  48         216  
1519              
1520             "# You probably should not manually edit this file.\n\n",
1521              
1522             # for dzil
1523             "# DATE\n",
1524             "# VERSION\n",
1525             "# PODNAME: ", ($args{script_name} // ''), "\n",
1526             do {
1527 48   66     471 my $abstract = $args{script_summary} // $cd->{metas}{''}{summary};
1528 48 100       203 if ($abstract) {
1529 38         487 ("# ABSTRACT: ", $abstract, "\n");
1530             } else {
1531 10         33 ();
1532             }
1533             },
1534             "\n",
1535              
1536             $dp_code1,
1537              
1538             "package main;\n",
1539             "use 5.010001;\n",
1540             "use strict;\n",
1541             "#use warnings;\n\n",
1542              
1543             "# load modules\n",
1544 0         0 (map {"require $_;\n"} sort keys %{$cd->{req_modules}}),
  48         264  
1545             "\n",
1546              
1547             "\n",
1548              
1549             "### declare global variables\n\n",
1550 289 100       8853 (map { "our $_" . (defined($cd->{vars}{$_}) ? " = ".dmp($cd->{vars}{$_}) : "").";\n" } sort keys %{$cd->{vars}}),
  48         344  
1551 48         297 (keys(%{$cd->{vars}}) ? "\n" : ""),
1552              
1553             $args{log} ? _gen_enable_log($cd) : '',
1554              
1555             "### declare subroutines\n\n",
1556             (map {
1557 100         206 my $sub = $_;
1558 100 100       304 if ($cd->{sub_src_core_deps}{$sub}) {
1559 48         101 for my $mod (keys %{ $cd->{sub_src_core_deps}{$sub} }) {
  48         220  
1560             $cd->{core_deps}{$mod} //=
1561 48   33     736 $cd->{sub_src_core_deps}{$sub}{$mod};
1562             }
1563             }
1564 100 50       15771 "sub $sub" . (ref($cd->{sub_srcs}{$sub}) eq 'ARRAY' ?
1565             "($cd->{sub_srcs}{$sub}[0]) {\n$cd->{sub_srcs}{$sub}[1]}\n\n" : " {\n$cd->{sub_srcs}{$sub}}\n\n")}
1566 48         255 sort keys %{$cd->{sub_srcs}}),
1567              
1568             "### begin code_before_parse_cmdline_options\n",
1569             ($args{code_before_parse_cmdline_options}, "\n") x !!$args{code_before_parse_cmdline_options},
1570             "### end code_before_parse_cmdline_options\n",
1571              
1572             @l,
1573              
1574             $dp_code2,
1575              
1576             defined $pod ? ("=pod\n\n", "=encoding UTF-8\n\n", $pod, "\n\n=cut\n\n") : (),
1577              
1578             $dp_code3,
1579              
1580             "### begin code_after_end\n",
1581             ($args{code_after_end}, "\n") x !!$args{code_after_end},
1582 48 50 50     310 "### end code_after_end\n",
    50 100        
    100          
    50          
1583             );
1584             }
1585              
1586             WRITE_OUTPUT:
1587             {
1588 48         302 my ($fh, $output_is_stdout);
  48         348  
1589 48 50 33     343 if (!defined($args{output_file}) || $args{output_file} eq '-') {
1590 48         110 $output_is_stdout++;
1591             } else {
1592 0 0       0 if (-f $args{output_file}) {
1593             return [412, "Output file '$args{output_file}' exists, ".
1594             "won't overwrite (see --overwrite)"]
1595 0 0       0 unless $args{overwrite};
1596             }
1597             open $fh, ">", $args{output_file}
1598 0 0       0 or return [500, "Can't open $args{output_file}: $!"];
1599             }
1600              
1601 48 50       134 if ($output_is_stdout) {
1602             return [200, "OK", $cd->{result}, {
1603 48         509 'func.raw_result' => $cd,
1604             }];
1605             } else {
1606 0           print $fh $cd->{result};
1607 0 0         close $fh or return [500, "Can't write $args{output_file}: $!"];
1608 0 0         chmod 0755, $args{output_file} or do {
1609 0           warn "Can't chmod 755 $args{output_file}: $!";
1610             };
1611 0           return [200, "OK", undef, {
1612             'func.raw_result'=>$cd,
1613             }];
1614             }
1615             }
1616             }
1617              
1618             1;
1619             # ABSTRACT: Generate inline Perinci::CmdLine CLI script
1620              
1621             __END__
1622              
1623             =pod
1624              
1625             =encoding UTF-8
1626              
1627             =head1 NAME
1628              
1629             Perinci::CmdLine::Inline - Generate inline Perinci::CmdLine CLI script
1630              
1631             =head1 VERSION
1632              
1633             This document describes version 0.551 of Perinci::CmdLine::Inline (from Perl distribution Perinci-CmdLine-Inline), released on 2020-05-18.
1634              
1635             =head1 SYNOPSIS
1636              
1637             % gen-inline-pericmd-script /Perinci/Examples/gen_array -o gen-array
1638              
1639             % ./gen-array
1640             ERROR 400: Missing required argument(s): len
1641              
1642             % ./gen-array --help
1643             ... help message printed ...
1644              
1645             % ./gen-array 3
1646             2
1647             3
1648             1
1649              
1650             % ./gen-array 3 --json
1651             [200,"OK",[3,1,2],{}]
1652              
1653             =head1 DESCRIPTION
1654              
1655             =head1 COMPILATION DATA KEYS
1656              
1657             A hash structure, C<$cd>, is constructed and passed around between routines
1658             during the generation process. It contains the following keys:
1659              
1660             =over
1661              
1662             =item * module_srcs => hash
1663              
1664             Generated script's module source codes. To reduce startup overhead and
1665             dependency, these modules' source codes are included in the generated script
1666             using the datapack technique (see L<Module::DataPack>).
1667              
1668             Among the modules are L<Getopt::Long::EvenLess> to parse command-line options,
1669             L<Text::Table::Tiny> to produce text table output, and also a few generated
1670             modules to modularize the generated script's structure.
1671              
1672             =item * vars => hash
1673              
1674             Generated script's global variables. Keys are variable names (including the
1675             sigils) and values are initial variable values (undef means unitialized).
1676              
1677             =item * sub_srcs => hash
1678              
1679             Generated script's subroutine source codes. Keys are subroutines' names and
1680             values are subroutines' source codes.
1681              
1682             =back
1683              
1684             =head1 ENVIRONMENT (GENERATED SCRIPTS)
1685              
1686             These are environment variables observed by the generated scripts.
1687              
1688             =head2 PERINCI_CMDLINE_INLINE_DEBUG_DIE
1689              
1690             Bool. If set to true, then will rethrow exception instead of converting it into
1691             enveloped result. This makes debugging easier.
1692              
1693             =head1 FUNCTIONS
1694              
1695              
1696             =head2 gen_inline_pericmd_script
1697              
1698             Usage:
1699              
1700             gen_inline_pericmd_script(%args) -> [status, msg, payload, meta]
1701              
1702             Generate inline Perinci::CmdLine CLI script.
1703              
1704             The goal of this module is to let you create a CLI script from a Riap
1705             function/metadata. This is like what L<Perinci::CmdLine::Lite> or
1706             L<Perinci::CmdLine::Classic> does, except that the generated CLI script will have
1707             the functionalities inlined so it only need core Perl modules and not any of the
1708             C<Perinci::CmdLine::*> or other modules to run (excluding what modules the Riap
1709             function itself requires).
1710              
1711             It's useful if you want a CLI script that is even more lightweight (in terms of
1712             startup overhead or dependencies) than the one using L<Perinci::CmdLine::Lite>.
1713              
1714             So to reiterate, the goal of this module is to create a Perinci::CmdLine-based
1715             script which only requires core modules, and has as little startup overhead as
1716             possible.
1717              
1718             Currently it only supports a subset of features compared to other
1719             C<Perinci::CmdLine::*> implementations:
1720              
1721             =over
1722              
1723             =item * Only support local Riap URL (e.g. C</Foo/bar>, not
1724             CLL<http://example.org/Foo/bar>);
1725              
1726             =back
1727              
1728             As an alternative to this module, if you are looking to reduce dependencies, you
1729             might also want to try using C<depak> to fatpack/datapack your
1730             L<Perinci::CmdLine::Lite>-based script.
1731              
1732             This function is not exported by default, but exportable.
1733              
1734             Arguments ('*' denotes required arguments):
1735              
1736             =over 4
1737              
1738             =item * B<actions> => I<any>
1739              
1740             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1741              
1742             =item * B<allow_prereq> => I<array[str]>
1743              
1744             A list of modules that can be depended upon.
1745              
1746             By default, Perinci::CmdLine::Inline will strive to make the script freestanding
1747             and require core modules. A dependency to a non-core module will cause failure
1748             (unless C<pack_deps> option is set to false). However, you can pass a list of
1749             modules that is allowed here.
1750              
1751             =item * B<code_add_extra_log_outputs> => I<str>
1752              
1753             =item * B<code_after_enable_logging> => I<str>
1754              
1755             =item * B<code_after_end> => I<str>
1756              
1757             Put at the very end of generated script.
1758              
1759             =item * B<code_after_shebang> => I<str>
1760              
1761             Put at the very beginning of generated script, after the shebang line.
1762              
1763             =item * B<code_before_enable_logging> => I<str>
1764              
1765             =item * B<code_before_parse_cmdline_options> => I<str>
1766              
1767             =item * B<common_opts> => I<any>
1768              
1769             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1770              
1771             =item * B<completion> => I<any>
1772              
1773             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1774              
1775             =item * B<config_dirs> => I<array[str]>
1776              
1777             Where to search for configuration files.
1778              
1779             =item * B<config_filename> => I<str|hash|array[str|hash]>
1780              
1781             Configuration file name(s).
1782              
1783             =item * B<default_format> => I<any>
1784              
1785             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1786              
1787             =item * B<default_subcommand> => I<str>
1788              
1789             =item * B<description> => I<any>
1790              
1791             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1792              
1793             =item * B<env_name> => I<str>
1794              
1795             Name of environment variable name that sets default options.
1796              
1797             =item * B<exit> => I<any>
1798              
1799             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1800              
1801             =item * B<extra_urls_for_version> => I<array[riap::url]>
1802              
1803             More URLs to show version for --version.
1804              
1805             Currently not implemented in Perinci::CmdLine::Inline.
1806              
1807             =item * B<formats> => I<any>
1808              
1809             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1810              
1811             =item * B<get_subcommand_from_arg> => I<any>
1812              
1813             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1814              
1815             =item * B<include> => I<array[perl::modname]>
1816              
1817             Include extra modules.
1818              
1819             =item * B<log> => I<bool> (default: 0)
1820              
1821             Whether to enable logging.
1822              
1823             =item * B<meta> => I<hash>
1824              
1825             An alternative to specifying `url`.
1826              
1827             =item * B<meta_is_normalized> => I<bool>
1828              
1829             =item * B<output_file> => I<filename>
1830              
1831             Set output file, defaults to stdout.
1832              
1833             =item * B<overwrite> => I<bool>
1834              
1835             =item * B<pack_deps> => I<bool> (default: 1)
1836              
1837             Whether to pack dependencies into the script.
1838              
1839             By default, Perinci::CmdLine::Inline will use datapacking technique (i.e. embed
1840             dependencies into DATA section and load it on-demand using require() hook) to
1841             make the script freestanding. However, in some situation this is unwanted, e.g.
1842             when we want to produce a script that can be packaged as a Debian package
1843             (Debian policy forbids embedding convenience copy of code,
1844             https://www.debian.org/doc/debian-policy/ch-source.html#s-embeddedfiles ).
1845              
1846             =item * B<pass_cmdline_object> => I<bool> (default: 0)
1847              
1848             Whether to pass Perinci::CmdLine::Inline object.
1849              
1850             =item * B<pod> => I<bool> (default: 1)
1851              
1852             Whether to generate POD for the script.
1853              
1854             =item * B<read_config> => I<bool> (default: 1)
1855              
1856             Whether the CLI script should read configuration files.
1857              
1858             =item * B<read_env> => I<bool>
1859              
1860             Whether CLI script should read environment variable that sets default options.
1861              
1862             =item * B<riap_client> => I<any>
1863              
1864             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1865              
1866             =item * B<riap_client_args> => I<any>
1867              
1868             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1869              
1870             =item * B<riap_version> => I<any>
1871              
1872             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1873              
1874             =item * B<script_name> => I<str>
1875              
1876             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1877              
1878             =item * B<script_summary> => I<str>
1879              
1880             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1881              
1882             =item * B<script_version> => I<str>
1883              
1884             Script version (otherwise will use version from url metadata).
1885              
1886             =item * B<script_version_from_main_version> => I<bool>
1887              
1888             Use script's $main::VERSION for the version.
1889              
1890             =item * B<shebang> => I<str>
1891              
1892             Set shebang line.
1893              
1894             =item * B<skip_format> => I<bool> (default: 0)
1895              
1896             Assume that function returns raw text that need no formatting, do not offer --format, --json, --naked-res.
1897              
1898             =item * B<stripper> => I<bool> (default: 0)
1899              
1900             Whether to strip code using Perl::Stripper.
1901              
1902             =item * B<sub_name> => I<str>
1903              
1904             =item * B<subcommands> => I<hash>
1905              
1906             =item * B<tags> => I<any>
1907              
1908             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1909              
1910             =item * B<url> => I<riap::url>
1911              
1912             Program URL.
1913              
1914             =item * B<use_cleanser> => I<bool> (default: 1)
1915              
1916             Whether to use data cleanser routine first before producing JSON.
1917              
1918             When a function returns result, and the user wants to display the result as
1919             JSON, the result might need to be cleansed first (e.g. using L<Data::Clean>)
1920             before it can be encoded to JSON, for example it might contain Perl objects or
1921             scalar references or other stuffs. If you are sure that your function does not
1922             produce those kinds of data, you can set this to false to produce a more
1923             lightweight script.
1924              
1925             =item * B<use_utf8> => I<bool> (default: 0)
1926              
1927             Whether to set utf8 flag on output.
1928              
1929             =item * B<validate_args> => I<bool> (default: 1)
1930              
1931             Whether the CLI script should validate arguments using schemas.
1932              
1933             =item * B<with_debug> => I<bool>
1934              
1935             Generate script with debugging outputs.
1936              
1937              
1938             =back
1939              
1940             Returns an enveloped result (an array).
1941              
1942             First element (status) is an integer containing HTTP status code
1943             (200 means OK, 4xx caller error, 5xx function error). Second element
1944             (msg) is a string containing error message, or 'OK' if status is
1945             200. Third element (payload) is optional, the actual result. Fourth
1946             element (meta) is called result metadata and is optional, a hash
1947             that contains extra information.
1948              
1949             Return value: (any)
1950              
1951             =head1 FAQ
1952              
1953             =head2 What about tab completion?
1954              
1955             Use L<App::GenPericmdCompleterScript> to generate a separate completion script.
1956             If you use L<Dist::Zilla>, see also L<Dist::Zilla::Plugin::GenPericmdScript>
1957             which lets you generate script (and its completion script) during build.
1958              
1959             =head1 HOMEPAGE
1960              
1961             Please visit the project's homepage at L<https://metacpan.org/release/Perinci-CmdLine-Inline>.
1962              
1963             =head1 SOURCE
1964              
1965             Source repository is at L<https://github.com/perlancar/perl-Perinci-CmdLine-Inline>.
1966              
1967             =head1 BUGS
1968              
1969             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-CmdLine-Inline>
1970              
1971             When submitting a bug or request, please include a test-file or a
1972             patch to an existing test-file that illustrates the bug or desired
1973             feature.
1974              
1975             =head1 SEE ALSO
1976              
1977             L<Perinci::CmdLine>, L<Perinci::CmdLine::Any>, L<Perinci::CmdLine::Lite>,
1978             L<Perinci::CmdLine::Classic>
1979              
1980             L<App::GenPericmdScript>
1981              
1982             =head1 AUTHOR
1983              
1984             perlancar <perlancar@cpan.org>
1985              
1986             =head1 COPYRIGHT AND LICENSE
1987              
1988             This software is copyright (c) 2020, 2018, 2017, 2016, 2015 by perlancar@cpan.org.
1989              
1990             This is free software; you can redistribute it and/or modify it under
1991             the same terms as the Perl 5 programming language system itself.
1992              
1993             =cut