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-02'; # DATE
14             our $DIST = 'Perinci-CmdLine-Inline'; # DIST
15             our $VERSION = '0.550'; # VERSION
16              
17 2     2   104269 use 5.010001;
  2         8  
18 2     2   11 use strict 'subs', 'vars';
  2         5  
  2         55  
19 2     2   11 use warnings;
  2         5  
  2         50  
20 2     2   11 use Log::ger;
  2         4  
  2         12  
21              
22 2     2   1530 use Data::Dmp;
  2         4000  
  2         116  
23 2     2   16 use JSON::MaybeXS ();
  2         5  
  2         35  
24 2     2   1018 use Module::CoreList::More;
  2         395160  
  2         150  
25 2     2   27 use Module::Path::More qw(module_path);
  2         5  
  2         179  
26 2     2   17 use Perinci::Sub::Util qw(err);
  2         8  
  2         109  
27              
28 2     2   17 use Exporter qw(import);
  2         5  
  2         721  
29             our @EXPORT_OK = qw(gen_inline_pericmd_script);
30              
31             our %SPEC;
32              
33             sub _dsah_plc {
34 125     125   264 state $plc = do {
35 2         1978 require Data::Sah;
36 2         3272 Data::Sah->new->get_compiler('perl');
37             };
38 125         59254 $plc;
39             }
40              
41             sub _pack_module {
42 464     464   2060 my ($cd, $mod) = @_;
43 464 50       1682 return unless $cd->{gen_args}{pack_deps};
44 464 50       1223 return if $cd->{module_srcs}{$mod};
45 464         2607 log_info("Adding source code of module %s ...", $mod);
46 464 50       4776 log_warn("%s is a core module", $mod) if Module::CoreList::More->is_still_core($mod);
47 464 50       492145 my $path = module_path(module => $mod) or die "Can't load module '$mod'";
48 464         201809 local $/;
49 464 50       18576 open my($fh), "<", $path or die "Can't read file '$path': $!";
50 464         31651 $cd->{module_srcs}{$mod} = <$fh>;
51             }
52              
53             sub _get_meta_from_url {
54 2     2   17 no strict 'refs';
  2         4  
  2         7050  
55              
56 64     64   335 my $url = shift;
57              
58 64 50       1206 $url =~ m!\A(?:pl:)?((?:/[^/]+)+)/([^/]*)\z!
59             or return [412, "URL scheme not supported, only local Perl ".
60             "URL currently supported"];
61 64         800 my ($mod_pm, $short_func_name) = ($1, $2);
62 64         655 $mod_pm =~ s!\A/!!;
63 64         663 (my $mod = $mod_pm) =~ s!/!::!g;
64 64         279 $mod_pm .= ".pm";
65 64         447 require $mod_pm;
66 64 100       170 my $meta = ${"$mod\::SPEC"}{length $short_func_name ? $short_func_name : ':package'};
  64         1348  
67 64 100 50     976 $meta //= {v=>1.1} if !length $short_func_name; # provide a default empty package metadata
68 64 50       297 return [412, "Can't find meta for URL '$url'"] unless $meta;
69 64 100       265 if (length $short_func_name) {
70 55 50       98 defined &{"$mod\::$short_func_name"}
  55         510  
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         204 'func.module_version' => ${"$mod\::VERSION"},
  64         1693  
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   178 my ($cd) = @_;
83 48         103 my @l2;
84              
85 48 100       479 return "" unless $cd->{gen_args}{read_env};
86              
87 4         16 _pack_module($cd, "Complete::Bash");
88 4         24 _pack_module($cd, "Log::ger"); # required by Complete::Bash
89 4         19 push @l2, "{\n";
90 4         15 push @l2, ' last unless $_pci_r->{read_env};', "\n";
91 4         22 push @l2, ' my $env = $ENV{', dmp($cd->{gen_args}{env_name}), '};', "\n";
92 4         167 push @l2, ' last unless defined $env;', "\n";
93 4         11 push @l2, ' require Complete::Bash;', "\n";
94 4         12 push @l2, ' my ($words, undef) = @{ Complete::Bash::parse_cmdline($env, 0) };', "\n";
95 4         11 push @l2, ' unshift @ARGV, @$words;', "\n";
96 4         10 push @l2, "}\n";
97              
98 4         22 join("", @l2);
99             }
100              
101             sub _gen_enable_log {
102 1     1   4 my ($cd) = @_;
103              
104 1         13 _pack_module($cd, 'Log::ger');
105 1         7 _pack_module($cd, 'Log::ger::Output');
106 1         11 _pack_module($cd, 'Log::ger::Output::Composite');
107 1         8 _pack_module($cd, 'Log::ger::Output::Screen');
108 1         7 _pack_module($cd, 'Log::ger::Output::SimpleFile');
109 1         14 _pack_module($cd, "Data::Dmp"); # required by Log::ger::Output::Composite
110 1         6 _pack_module($cd, 'Log::ger::Util');
111              
112 1         5 my @l;
113              
114 1         10 push @l, "### begin code_before_enable_logging\n";
115 1 50       14 push @l, $cd->{gen_args}{code_before_enable_logging}, "\n" if defined $cd->{gen_args}{code_before_enable_logging};
116 1         5 push @l, "### end code_before_enable_logging\n";
117              
118 1         4 push @l, "### enable logging\n";
119 1         13 push @l, '$_pci_log_outputs->{Screen} = { conf => { formatter => sub { '.dmp("$cd->{script_name}: ").' . $_[0] } } };', "\n";
120              
121 1         43 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         4 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         3 push @l, "\n";
128              
129 1         2 push @l, "### begin code_after_enable_logging\n";
130 1 50       36 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         9 join("", @l);
134             }
135              
136             sub _gen_read_config {
137 48     48   182 my ($cd) = @_;
138 48         100 my @l2;
139              
140 48 100       229 return "" unless $cd->{gen_args}{read_config};
141              
142 12         33 push @l2, 'if ($_pci_r->{read_config}) {', "\n";
143 12         52 _pack_module($cd, "Perinci::CmdLine::Util::Config");
144 12         82 _pack_module($cd, "Log::ger"); # required by Perinci::CmdLine::Util::Config
145 12         88 _pack_module($cd, "Config::IOD::Reader"); # required by Perinci::CmdLine::Util::Config
146 12         97 _pack_module($cd, "Config::IOD::Base"); # required by Config::IOD::Reader
147 12         111 _pack_module($cd, "Data::Sah::Normalize"); # required by Perinci::CmdLine::Util::Config
148 12         88 _pack_module($cd, "Perinci::Sub::Normalize"); # required by Perinci::CmdLine::Util::Config
149 12         86 _pack_module($cd, "Sah::Schema::rinci::function_meta"); # required by Perinci::Sub::Normalize
150 12 50       152 push @l2, 'log_trace("Reading config file(s) ...");', "\n" if $cd->{gen_args}{log};
151 12         70 push @l2, ' require Perinci::CmdLine::Util::Config;', "\n";
152 12         35 push @l2, "\n";
153 12         42 push @l2, ' my $res = Perinci::CmdLine::Util::Config::read_config(', "\n";
154 12         29 push @l2, ' config_paths => $_pci_r->{config_paths},', "\n";
155 12         82 push @l2, ' config_filename => ', dmp($cd->{gen_args}{config_filename}), ",\n";
156 12         1002 push @l2, ' config_dirs => ', dmp($cd->{gen_args}{config_dirs}), ' // ["$ENV{HOME}/.config", $ENV{HOME}, "/etc"],', "\n";
157 12         1218 push @l2, ' program_name => ', dmp($cd->{script_name}), ",\n";
158 12         378 push @l2, ' );', "\n";
159 12         37 push @l2, ' _pci_err($res) unless $res->[0] == 200;', "\n";
160 12         34 push @l2, ' $_pci_r->{config} = $res->[2];', "\n";
161 12         28 push @l2, ' $_pci_r->{read_config_files} = $res->[3]{"func.read_files"};', "\n";
162 12         36 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         28 push @l2, "\n";
164 12         34 push @l2, ' $res = Perinci::CmdLine::Util::Config::get_args_from_config(', "\n";
165 12         35 push @l2, ' r => $_pci_r,', "\n";
166 12         35 push @l2, ' config => $_pci_r->{config},', "\n";
167 12         34 push @l2, ' args => \%_pci_args,', "\n";
168 12         38 push @l2, ' program_name => ', dmp($cd->{script_name}), ",\n";
169 12         342 push @l2, ' subcommand_name => $_pci_r->{subcommand_name},', "\n";
170 12         38 push @l2, ' config_profile => $_pci_r->{config_profile},', "\n";
171 12         28 push @l2, ' common_opts => {},', "\n"; # XXX so currently we can't set e.g. format or
172 12         48 push @l2, ' meta => $_pci_metas->{ $_pci_r->{subcommand_name} },', "\n";
173 12         35 push @l2, ' meta_is_normalized => 1,', "\n";
174 12         39 push @l2, ' );', "\n";
175 12         30 push @l2, ' die $res unless $res->[0] == 200;', "\n";
176 12         36 push @l2, ' my $found = $res->[3]{"func.found"};', "\n";
177 12         122 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         49 push @l2, ' _pci_err([412, "Profile \'$_pci_r->{config_profile}\' not found in configuration file"]);', "\n";
179 12         33 push @l2, ' }', "\n";
180 12         32 push @l2, '}', "\n"; # if read_config
181              
182 12         166 join ("", @l2);
183             }
184              
185             sub _gen_pci_check_args {
186 48     48   243 my ($cd) = @_;
187              
188 48         94 my @l2;
189 48         422 push @l2, ' my ($args) = @_;', "\n";
190 48         200 push @l2, ' my $sc_name = $_pci_r->{subcommand_name};', "\n";
191 48         275 my $i = -1;
192 48         183 for my $sc_name (sort keys %{$cd->{metas}}) {
  48         445  
193 53         186 $i++;
194 53         158 my $meta = $cd->{metas}{$sc_name};
195 53   100     420 my $args_prop = $meta->{args} // {};
196 53 100       690 push @l2, ' '.($i ? "elsif":"if").' ($sc_name eq '.dmp($sc_name).") {\n";
197 53         4728 push @l2, " FILL_FROM_POS: {\n";
198 53         330 push @l2, " 1;\n"; # to avoid syntax error when there is 0 args
199 53         347 for my $arg (sort {
200             ($args_prop->{$b}{pos} // 9999) <=>
201 95   100     475 ($args_prop->{$a}{pos} // 9999)
      100        
202             } keys %$args_prop) {
203 83         224 my $arg_spec = $args_prop->{$arg};
204 83         279 my $arg_opts = $cd->{ggl_res}{$sc_name}[3]{'func.opts_by_arg'}{$arg};
205 83 100       302 next unless defined $arg_spec->{pos};
206 67         299 push @l2, ' if (@ARGV > '.$arg_spec->{pos}.') {';
207 67         242 push @l2, ' if (exists $args->{"'.$arg.'"}) {';
208 67         261 push @l2, ' return [400, "You specified '.$arg_opts->[0].' but also argument #'.$arg_spec->{pos}.'"];';
209 67         133 push @l2, " } else {";
210 67 100 66     494 if ($arg_spec->{slurpy} // $arg_spec->{greedy}) {
211 5         49 push @l2, ' $args->{"'.$arg.'"} = [splice(@ARGV, '.$arg_spec->{pos}.')];';
212             } else {
213 62         266 push @l2, ' $args->{"'.$arg.'"} = delete($ARGV['.$arg_spec->{pos}.']);';
214             }
215 67         182 push @l2, " }";
216 67         172 push @l2, " }\n";
217             }
218 53         348 push @l2, " }\n";
219 53         404 push @l2, ' my @check_argv = @ARGV;', "\n";
220              
221 53         371 push @l2, ' # fill from cmdline_src', "\n";
222             {
223 53         139 my $stdin_seen;
  53         485  
224             my $req_gen_iter;
225 53         435 for my $arg (sort {
226 78         173 my $asa = $args_prop->{$a};
227 78         135 my $asb = $args_prop->{$b};
228 78   50     412 my $csa = $asa->{cmdline_src} // '';
229 78   50     364 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 78 50 100     646 ($asa->{pos} // 9999) <=> ($asb->{pos} // 9999)
    50 100        
    0          
235             } keys %$args_prop) {
236 83         240 my $arg_spec = $args_prop->{$arg};
237 83         173 my $cs = $arg_spec->{cmdline_src};
238 83   50     303 my $sch = $arg_spec->{schema} // '';
239 83 100 66     351 $sch = $sch->[1]{of} if $arg_spec->{stream} && $sch->[0] eq 'array';
240 83         242 my $type = Data::Sah::Util::Type::get_type($sch);
241 83 100       1056 next unless $cs;
242 4 50       97 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       21 return [400, "arg $arg: More than one cmdline_src=/stdin/ is found (arg=$stdin_seen)"]
293             if defined $stdin_seen;
294 4         17 $stdin_seen = $arg;
295 4         56 push @l2, ' unless (exists $args->{"'.$arg.'"}) {';
296 4         37 push @l2, ' @check_argv = ();';
297 4 50       23 if ($arg_spec->{stream}) {
    0          
298 4         9 $req_gen_iter++;
299 4         22 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         38 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       247 unless ($req_gen_iter) {
327 49         179 delete $cd->{sub_srcs}{_pci_gen_iter};
328 49         160 delete $cd->{module_srcs}{'Data::Sah::Util::Type'};
329             }
330             } # fill from cmdline_src
331 53         375 push @l2, "\n";
332              
333 53         483 push @l2, ' # fill defaults from "default" property and check against schema', "\n";
334             GEN_VALIDATION:
335             {
336 53         131 my $has_validation;
  53         405  
337             my @l3;
338 53         0 my @modules_for_all_args;
339 53         0 my @req_stmts;
340 53         285 for my $arg (sort keys %$args_prop) {
341 83         229 my $arg_spec = $args_prop->{$arg};
342              
343             # we don't validate streaming input for now
344 83 100       361 next if $arg_spec->{stream};
345              
346 79         192 my $arg_schema = $arg_spec->{schema};
347 79         336 my $arg_term = '$args->{"'.$arg.'"}';
348 79 50       259 if (defined $arg_spec->{default}) {
349 0         0 push @l3, " $arg_term //= ".dmp($arg_spec->{default}).";\n";
350             }
351              
352 79 50 33     700 if ($arg_schema && $cd->{gen_args}{validate_args}) {
353 79         234 $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         327 );
366 79 50       422100 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         217 for my $mod_rec (@{$dsah_cd->{modules}}) {
  79         306  
370 180 100       1088 next unless $mod_rec->{phase} eq 'runtime';
371 96 100       268 next if grep { ($mod_rec->{use_statement} && $_->{use_statement} && $_->{use_statement} eq $mod_rec->{use_statement}) ||
372 82 100 100     1087 $_->{name} eq $mod_rec->{name} } @modules_for_all_args;
      66        
373 46         252 push @modules_for_all_args, $mod_rec;
374 46 100       321 if ($mod_rec->{name} =~ /\A(Scalar::Util::Numeric::PP)\z/) {
375 12         94 _pack_module($cd, $mod_rec->{name});
376             }
377 46         435 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     55282 !($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       265 next if exists $cd->{req_modules}{$mod_rec->{name}};
384 46         169 push @req_stmts, _dsah_plc->stmt_require_module($mod_rec);
385             }
386 79         537 push @l3, " if (exists $arg_term) {\n";
387 79         337 push @l3, " \$_sahv_dpath = [];\n";
388 79         388 push @l3, $dsah_cd->{result}, "\n";
389 79         188 push @l3, " ; if (\$_sahv_err) { return [400, \"Argument validation failed: \$_sahv_err\"] }\n";
390 79         1742 push @l3, " } # if date arg exists\n";
391             }
392             }
393 53         298 push @l3, "\n";
394              
395 53 100       229 if ($has_validation) {
396 29         176 push @l2, map {" $_\n"} @req_stmts;
  46         266  
397 29         92 push @l2, " my \$_sahv_dpath;\n";
398 29         220 push @l2, " my \$_sahv_err;\n";
399             }
400              
401 53         338 push @l2, @l3;
402             } # GEN_VALIDATION
403              
404 53         394 push @l2, ' # check required args', "\n";
405 53         293 for my $arg (sort keys %$args_prop) {
406 83         177 my $arg_spec = $args_prop->{$arg};
407 83 100       391 if ($arg_spec->{req}) {
408 14         109 push @l2, ' return [400, "Missing required argument: '.$arg.'"] unless exists $args->{"'.$arg.'"};', "\n";
409             }
410 83 100       312 if ($arg_spec->{schema}[1]{req}) {
411 18         113 push @l2, ' return [400, "Missing required value for argument: '.$arg.'"] if exists($args->{"'.$arg.'"}) && !defined($args->{"'.$arg.'"});', "\n";
412             }
413             }
414              
415 53         205 push @l2, ' _pci_err([500, "Extraneous command-line argument(s): ".join(", ", @check_argv)]) if @check_argv;', "\n";
416 53         332 push @l2, ' [200];', "\n";
417 53         446 push @l2, ' }';
418             } # for subcommand
419 48         376 push @l2, ' else { _pci_err([500, "Unknown subcommand1: $sc_name"]); }', "\n";
420 48         1430 $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   892 my ($cd, $co) = @_;
425              
426 408         623 my @l;
427              
428 408         800 my $has_subcommands = $cd->{gen_args}{subcommands};
429              
430 408 100       3264 if ($co eq 'help') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
431 48 100       193 if ($has_subcommands) {
432 9         25 push @l, 'my $sc_name = $_pci_r->{subcommand_name}; ';
433 9         25 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         63 push @l, 'if (!length $sc_name) { print $help_msg } ';
435 9         29 for (sort keys %{ $cd->{helps} }) {
  9         59  
436 23         1571 push @l, 'elsif ($sc_name eq '.dmp($_).') { print '.dmp($cd->{helps}{$_}).' } ';
437             }
438 9         920 push @l, 'else { _pci_err([500, "Unknown subcommand2: $sc_name"]) } ';
439 9         79 push @l, 'exit 0';
440             } else {
441 39         1874 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         4780 );
448 39 50       314023 return [500, "Can't generate help: $res->[0] - $res->[1]"]
449             unless $res->[0] == 200;
450 39         228 push @l, 'print ', dmp($res->[2]), '; exit 0;';
451             }
452             } elsif ($co eq 'version') {
453 2     2   20 no strict 'refs';
  2         5  
  2         6685  
454 48         223 my $mod = $cd->{sc_mods}{''};
455 48         393 push @l, "no warnings 'once'; ";
456 48 50       531 push @l, "require $mod; " if $mod;
457 48         380 push @l, 'print "', $cd->{script_name} , ' version ", ';
458 48 50       274 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       674 push @l, defined($cd->{gen_args}{script_version}) ? "\"$cd->{gen_args}{script_version}\"" :
462             "(\$$mod\::VERSION // '?')",
463             ", (\$$mod\::DATE ? \" (\$$mod\::DATE)\" : '')";
464             }
465 48         163 push @l, ', "\\n"; ';
466             push @l, 'print " Generated by ', __PACKAGE__ , ' version ',
467 48         687 (${__PACKAGE__."::VERSION"} // 'dev'),
468 48 50 50     134 (${__PACKAGE__."::DATE"} ? " (".${__PACKAGE__."::DATE"}.")" : ""),
  48         640  
  48         744  
469             '\n"; ';
470 48         537 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         3 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         3 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         3 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         2 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         3 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         3 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         8 push @l, '$_pci_r->{log_level} = $_[1];';
480             } elsif ($co eq 'trace') {
481 1         9 push @l, 'require Log::ger::Util; Log::ger::Util::set_level("trace"); $_pci_r->{log_level} = "trace";';
482             } elsif ($co eq 'debug') {
483 1         13 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         12 push @l, 'require Log::ger::Util; Log::ger::Util::set_level("error"); $_pci_r->{log_level} = "error";';
488             } elsif ($co eq 'subcommands') {
489 9         81 my $scs_text = "Available subcommands:\n";
490 9         33 for (sort keys %{ $cd->{metas} }) {
  9         56  
491 14         54 $scs_text .= " $_\n";
492             }
493 9         43 push @l, 'print ', dmp($scs_text), '; exit 0';
494             } elsif ($co eq 'cmd') {
495 18         38 push @l, '$_[2]{subcommand} = [$_[1]]; '; # for Getopt::Long::Subcommand
496 18         39 push @l, '$_pci_r->{subcommand_name} = $_[1];';
497             } elsif ($co eq 'format') {
498 48         397 push @l, '$_pci_r->{format} = $_[1];';
499             } elsif ($co eq 'json') {
500 48         411 push @l, '$_pci_r->{format} = (-t STDOUT) ? "json-pretty" : "json";';
501             } elsif ($co eq 'naked_res') {
502 48         116 push @l, '$_pci_r->{naked_res} = 1;';
503             } elsif ($co eq 'no_naked_res') {
504 48         148 push @l, '$_pci_r->{naked_res} = 0;';
505             } elsif ($co eq 'no_config') {
506 12         35 push @l, '$_pci_r->{read_config} = 0;';
507             } elsif ($co eq 'config_path') {
508 12         46 push @l, '$_pci_r->{config_paths} //= []; ';
509 12         36 push @l, 'push @{ $_pci_r->{config_paths} }, $_[1];';
510             } elsif ($co eq 'config_profile') {
511 12         44 push @l, '$_pci_r->{config_profile} = $_[1];';
512             } elsif ($co eq 'no_env') {
513 4         9 push @l, '$_pci_r->{read_env} = 0;';
514             } elsif ($co eq 'page_result') {
515 48         111 push @l, '$_pci_r->{page_result} = 1;';
516             } else {
517 0         0 die "BUG: Unrecognized common_opt '$co'";
518             }
519 408         7998 join "", @l;
520             }
521              
522             sub _gen_get_args {
523 48     48   187 my ($cd) = @_;
524              
525 48         248 my @l;
526              
527 48         250 push @l, 'my %mentioned_args;', "\n";
528              
529 48         193 _pack_module($cd, "Getopt::Long::EvenLess");
530 48         498 push @l, "require Getopt::Long::EvenLess;\n";
531 48 100       551 push @l, 'log_trace("Parsing command-line arguments ...");', "\n" if $cd->{gen_args}{log};
532              
533 48 100       281 if ($cd->{gen_args}{subcommands}) {
534              
535 9         47 _pack_module($cd, "Getopt::Long::Subcommand");
536 9         94 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         38 require Perinci::CmdLine::Help;
  9         81  
542 9         165 my %helps; # key = subcommand name
543 9         35 for my $sc_name (sort keys %{ $cd->{metas} }) {
  9         107  
544 14 50       65 next if $sc_name eq '';
545 14         39 my $meta = $cd->{metas}{$sc_name};
546             my $res = Perinci::CmdLine::Help::gen_help(
547             meta => $meta,
548 14         41 common_opts => { map {$_ => $cd->{copts}{$_}} grep { $_ !~ /\A(subcommands|cmd)\z/ } keys %{$cd->{copts}} },
  104         408  
  132         456  
  14         73  
549             program_name => "$cd->{script_name} $sc_name",
550             );
551 14 50       92734 return [500, "Can't generate help (subcommand='$sc_name'): $res->[0] - $res->[1]"]
552             unless $res->[0] == 200;
553 14         81 $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         83 );
563 9 50       54203 return [500, "Can't generate help (subcommand=''): $res->[0] - $res->[1]"]
564             unless $res->[0] == 200;
565 9         33 $helps{''} = $res->[2];
566              
567 9         42 $cd->{helps} = \%helps;
568             }
569              
570 9         52 push @l, 'my $help_msg = ', dmp($cd->{helps}{''}), ";\n";
571              
572 9         1152 my @sc_names = sort keys %{ $cd->{metas} };
  9         164  
573              
574 9         49 for my $stage (1, 2) {
575 18 100       74 if ($stage == 1) {
576 9         108 push @l, 'my $go_spec1 = {', "\n";
577             } else {
578 9         23 push @l, 'my $go_spec2 = {', "\n";
579 9         27 push @l, " options => {\n";
580             }
581              
582             # common options
583 18         75 my $ggl_res = $cd->{ggl_res}{$sc_names[0]};
584 18         55 my $specmetas = $ggl_res->[3]{'func.specmeta'};
585 18         156 for my $o (sort keys %$specmetas) {
586 214         375 my $specmeta = $specmetas->{$o};
587 214         354 my $co = $specmeta->{common_opt};
588 214 100       407 next unless $co;
589 174 100       334 if ($stage == 1) {
590 87         311 push @l, " '$o' => sub { ", _gen_common_opt_handler($cd, $co), " },\n";
591             } else {
592 87         207 push @l, " '$o' => {\n";
593 87 100       166 if ($co eq 'cmd') {
594 9         32 push @l, " handler => sub { ", _gen_common_opt_handler($cd, $co), " },\n";
595             } else {
596 78         137 push @l, " handler => sub {},\n";
597             }
598 87         160 push @l, " },\n";
599             }
600             }
601 18 100       72 if ($stage == 1) {
602 9         23 push @l, "};\n"; # end of %go_spec1
603             } else {
604 9         96 push @l, " },\n"; # end of options
605             }
606              
607 18 100       71 if ($stage == 2) {
608             # subcommand options
609 9         68 push @l, " subcommands => {\n";
610 9         30 for my $sc_name (sort keys %{ $cd->{metas} }) {
  9         86  
611 14         45 my $meta = $cd->{metas}{$sc_name};
612 14         50 push @l, " '$sc_name' => {\n";
613 14         31 push @l, " options => {\n";
614 14         38 my $ggl_res = $cd->{ggl_res}{$sc_name};
615 14         33 my $specmetas = $ggl_res->[3]{'func.specmeta'};
616 14         94 for my $o (sort keys %$specmetas) {
617 157         287 my $specmeta = $specmetas->{$o};
618 157         252 my $argname = $specmeta->{arg}; # XXX can't handle submetadata yet
619 157 100       292 next unless defined $argname;
620 25         52 my $arg_spec = $meta->{args}{$argname};
621 25         69 push @l, " '$o' => {\n";
622 25         86 push @l, " handler => sub { ";
623 25 50 33     98 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     172 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         47 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         88 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = $_[1];';
640             }
641             }
642 25         54 push @l, " },\n"; # end of handler
643 25         60 push @l, " },\n"; # end of option
644             }
645 14         54 push @l, " },\n"; # end of options
646 14         31 push @l, " },\n"; # end of subcommand
647             }
648 9         29 push @l, " },\n"; # end of subcommands
649 9         69 push @l, " default_subcommand => ".dmp($cd->{gen_args}{default_subcommand}).",\n";
650              
651 9         331 push @l, "};\n"; # end of %go_spec2
652             } # subcommand options
653             } # stage
654              
655 9         24 push @l, "{\n";
656 9         28 push @l, ' local @ARGV = @ARGV;', "\n";
657 9         26 push @l, ' my $old_conf = Getopt::Long::EvenLess::Configure("pass_through");', "\n";
658 9         69 push @l, ' Getopt::Long::EvenLess::GetOptions(%$go_spec1);', "\n";
659 9         85 push @l, ' Getopt::Long::EvenLess::Configure($old_conf);', "\n";
660 9         97 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       66 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         139 push @l, "}\n";
663 9         50 push @l, _gen_read_env($cd);
664 9         63 push @l, _gen_read_config($cd);
665 9         65 push @l, 'my $res = Getopt::Long::Subcommand::GetOptions(%$go_spec2);', "\n";
666 9 50       54 push @l, '_pci_debug("args after GetOptions: ", \%_pci_args);', "\n" if $cd->{gen_args}{with_debug};
667 9         34 push @l, '_pci_err([500, "GetOptions failed"]) unless $res->{success};', "\n";
668 9         66 push @l, 'if (!length $_pci_r->{subcommand_name}) { print $help_msg; exit 0 }', "\n";
669              
670             } else {
671              
672 39         148 my $meta = $cd->{metas}{''};
673             # stage 1 is catching common options only (--help, etc)
674 39         283 for my $stage (1, 2) {
675 78         527 push @l, "my \$go_spec$stage = {\n";
676 78         158 for my $go_spec (sort keys %{ $cd->{ggl_res}{''}[2] }) {
  78         845  
677 900         1866 my $specmeta = $cd->{ggl_res}{''}[3]{'func.specmeta'}{$go_spec};
678 900         1512 my $co = $specmeta->{common_opt};
679 900 100 100     2736 next if $stage == 1 && !$co;
680 762         1870 push @l, " '$go_spec' => sub { "; # begin option handler
681 762 100       1337 if ($co) {
682 624 100       1023 if ($stage == 1) {
683 312         821 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         331 my $arg_spec = $meta->{args}{$specmeta->{arg}};
689 138         251 push @l, ' ';
690 138 50 66     484 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     818 if (($specmeta->{parsed}{type} // '') =~ /\@/) {
    100          
    100          
702 8         107 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         328 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = _pci_json()->decode($_[1]);';
705             } elsif ($specmeta->{is_neg}) {
706 10         38 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = 0;';
707             } else {
708 61         176 push @l, '$_pci_args{\'', $specmeta->{arg}, '\'} = $_[1];';
709             }
710             }
711 138         292 push @l, "\n";
712             }
713 762         1540 push @l, " },\n"; # end option handler
714             } # options
715 78         287 push @l, "};\n";
716             } # stage
717 39         482 push @l, 'my $old_conf = Getopt::Long::EvenLess::Configure("pass_through");', "\n";
718 39         154 push @l, 'Getopt::Long::EvenLess::GetOptions(%$go_spec1);', "\n";
719 39         270 push @l, 'Getopt::Long::EvenLess::Configure($old_conf);', "\n";
720 39         224 push @l, _gen_read_env($cd);
721 39         273 push @l, _gen_read_config($cd);
722 39         123 push @l, 'my $res = Getopt::Long::EvenLess::GetOptions(%$go_spec2);', "\n";
723 39         125 push @l, '_pci_err([500, "GetOptions failed"]) unless $res;', "\n";
724 39 50       181 push @l, '_pci_debug("args after GetOptions (stage 2): ", \%_pci_args);', "\n" if $cd->{gen_args}{with_debug};
725              
726             }
727              
728 48         1440 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 3520774 require Data::Sah::Util::Type;
1022              
1023 50         3961 my %args = @_;
1024 50         939 $args{url} = "$args{url}"; # stringify URI object to avoid JSON encoder croaking
1025              
1026             # XXX schema
1027 50   50     1021 $args{validate_args} //= 1;
1028             #$args{validate_result} //= 1;
1029 50   50     686 $args{pack_deps} //= 1;
1030 50   50     330 $args{read_config} //= 1;
1031 50   50     247 $args{read_env} //= 1;
1032 50   50     865 $args{use_cleanser} //= 1;
1033              
1034             my $cd = {
1035             gen_args => \%args,
1036             script_name => $args{script_name},
1037 50         1049 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         243 my %metas; # key=subcommand name, '' if no subcommands
  50         268  
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         202 my $script_name = $args{script_name};
1051              
1052 50         322 my $scs = $args{subcommands};
1053 50 100       249 if ($scs) {
1054 9         106 for my $sc_name (keys %$scs) {
1055 14         71 my $sc_spec = $scs->{$sc_name};
1056 14         307 my $res = _get_meta_from_url($sc_spec->{url});
1057 14 50       80 return $res if $res->[0] != 200;
1058             $mods{ $res->[3]{'func.module'} } = {
1059 14         122 version => $res->[3]{'func.module_version'},
1060             };
1061 14         66 $metas{$sc_name} = $res->[2];
1062 14         59 $sc_mods{$sc_name} = $res->[3]{'func.module'};
1063 14         73 $func_names{$sc_name} = $res->[3]{'func.func_name'};
1064             }
1065             }
1066              
1067 50         261 my $url = $args{url};
1068 50 50       211 if ($url) {
1069 50         798 my $res = _get_meta_from_url($url);
1070 50 50       330 return $res if $res->[0] != 200;
1071             $mods{ $res->[3]{'func.module'} } = {
1072 50         433 version => $res->[3]{'func.module_version'},
1073             };
1074 50         234 $sc_mods{''} = $res->[3]{'func.module'};
1075 50 100       205 unless ($scs) {
1076 41         127 $metas{''} = $res->[2];
1077 41         190 $func_names{''} = $res->[3]{'func.func_name'};
1078             }
1079 50 100       385 if (length (my $sfn = $res->[3]{'func.short_func_name'})) {
1080 41   66     346 $script_name //= do {
1081 27         105 local $_ = $sfn;
1082 27         243 s/_/-/g;
1083 27         227 $_;
1084             };
1085             }
1086             }
1087              
1088 50 0 33     229 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     256 $script_name //= do {
1099 7         44 local $_ = $0;
1100 7         48 s!.+[/\\]!!;
1101 7         51 $_;
1102             };
1103              
1104 50 50       204 last if $args{meta_is_normalized};
1105 50         375 require Perinci::Sub::Normalize;
1106 50         230 for (keys %metas) {
1107 55         2539 $metas{$_} = Perinci::Sub::Normalize::normalize_function_metadata($metas{$_});
1108             }
1109              
1110 50         26363 $cd->{script_name} = $script_name;
1111 50         545 $cd->{metas} = \%metas;
1112 50         535 $cd->{mods} = \%mods;
1113 50         418 $cd->{sc_mods} = \%sc_mods;
1114 50         606 $cd->{func_names} = \%func_names;
1115             } # GET_META
1116              
1117 50   66     1046 $args{config_filename} //= "$cd->{script_name}.conf";
1118 50   66     627 $args{env_name} //= do {
1119 49         416 my $env = uc "$cd->{script_name}_OPT";
1120 49         617 $env =~ s/[^A-Z0-9]+/_/g;
1121 49 100       508 $env = "_$env" if $env =~ /\A\d/;
1122 49         272 $env;
1123             };
1124              
1125 50         173 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     298 @{ $args{include} // [] },
1141             ) {
1142 246         1257 _pack_module($cd, $_);
1143             }
1144              
1145             GEN_SCRIPT:
1146             {
1147 50         208 my @l;
  50         134  
1148              
1149             {
1150 50         101 require Perinci::CmdLine::Base;
  50         2120  
1151 2     2   50 no warnings 'once';
  2         18  
  2         6443  
1152 50         30859 my %copts;
1153 50         447 $copts{help} = $Perinci::CmdLine::Base::copts{help};
1154 50         230 $copts{version} = $Perinci::CmdLine::Base::copts{version};
1155 50 100       207 if ($args{log}) {
1156             $copts{log_level} = {
1157 1         27 getopt => "log-level=s",
1158             summary => "Set logging level (trace|debug|info|warn|error|fatal|none)",
1159             };
1160             $copts{trace} = {
1161 1         20 getopt => "trace",
1162             summary => "Set logging level to trace",
1163             };
1164             $copts{debug} = {
1165 1         14 getopt => "debug",
1166             summary => "Set logging level to debug",
1167             };
1168             $copts{verbose} = {
1169 1         19 getopt => "verbose",
1170             summary => "Set logging level to info",
1171             };
1172             $copts{quiet} = {
1173 1         15 getopt => "quiet",
1174             summary => "Set logging level to error",
1175             };
1176              
1177 1         14 $cd->{vars}{'$_pci_log_outputs'} = {};
1178             }
1179 50 50       433 unless ($args{skip_format}) {
1180 50         258 $copts{json} = $Perinci::CmdLine::Base::copts{json};
1181 50         399 $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         718 getopt => "naked-res",
1187             summary => "When outputing as JSON, strip result envelope",
1188             };
1189             $copts{no_naked_res} = {
1190 50         519 getopt => "no-naked-res|nonaked-res",
1191             summary => "When outputing as JSON, don't strip result envelope",
1192             };
1193             }
1194 50 100       260 if ($args{subcommands}) {
1195 9         106 $copts{subcommands} = $Perinci::CmdLine::Base::copts{subcommands};
1196 9         71 $copts{cmd} = $Perinci::CmdLine::Base::copts{cmd};
1197             }
1198 50 100       299 if ($args{read_config}) {
1199 12         96 for (qw/config_path no_config config_profile/) {
1200 36         314 $copts{$_} = $Perinci::CmdLine::Base::copts{$_};
1201             }
1202             }
1203 50 100       224 if ($args{read_env}) {
1204 4         26 for (qw/no_env/) {
1205 4         53 $copts{$_} = $Perinci::CmdLine::Base::copts{$_};
1206             }
1207             }
1208 50         197 for (qw/page_result/) {
1209 50         221 $copts{$_} = $Perinci::CmdLine::Base::copts{$_};
1210             }
1211 50         231 $cd->{copts} = \%copts;
1212             }
1213              
1214 50         100 my $shebang_line;
1215             {
1216 50   33     103 $shebang_line = $args{shebang} // $^X;
  50         705  
1217 50 50       320 $shebang_line = "#!$shebang_line" unless $shebang_line =~ /\A#!/;
1218 50 50       309 $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         592 $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         602 $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       232 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         513 $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         450 $cd->{sub_src_core_deps}{_pci_json}{'JSON::PP'} = 0;
1282              
1283             {
1284 50 50       283 last unless $args{use_cleanser};
1285 50         366 require Module::CoreList;
1286 50         2036 require Data::Clean::JSON;
1287 50         10784 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         170572 my $src = $cleanser->{_cd}{src};
1293 50         384 my $src1 = 'sub _pci_clean_json { ';
1294 50         312 for my $mod (keys %{ $cleanser->{_cd}{modules} }) {
  50         327  
1295 100         101105 $src1 .= "require $mod; ";
1296 100 100       1366 next if Module::CoreList->is_core($mod);
1297 50         21369 _pack_module($cd, $mod);
1298             }
1299 50         113354 $cd->{module_srcs}{'Local::_pci_clean_json'} = "$src1 use feature 'state'; state \$cleanser = $src; \$cleanser->(shift) }\n1;\n";
1300             }
1301              
1302             {
1303 50         333 require Perinci::Sub::GetArgs::Argv;
  50         466  
  50         2863  
1304 50         23134 my %ggl_res; # key = subcommand name
1305             my %args_as; # key = subcommand name
1306 50         146 for my $sc_name (keys %{ $cd->{metas} }) {
  50         279  
1307 55         449 my $meta = $cd->{metas}{$sc_name};
1308 55   100     1105 my $args_as = $meta->{args_as} // 'hash';
1309 55 100       676 if ($args_as !~ /\A(hashref|hash)\z/) {
1310 2         86 return [501, "args_as=$args_as currently unsupported at subcommand='$sc_name'"];
1311             }
1312 53         271 $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         970 );
1320 53 50       105177 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         317 $ggl_res{$sc_name} = $ggl_res;
1324             }
1325 48         234 $cd->{ggl_res} = \%ggl_res;
1326 48         400 $cd->{args_as} = \%args_as;
1327 48         716 _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         937 };
1336              
1337 48         393 $cd->{vars}{'%_pci_args'} = undef;
1338 48         477 push @l, "### get arguments (from config file, env, command-line args\n\n";
1339 48         456 push @l, "{\n", _gen_get_args($cd), "}\n\n";
1340              
1341             # gen code to check arguments
1342 48         185 push @l, "### check arguments\n\n";
1343 48         144 push @l, "{\n";
1344 48 50       241 push @l, 'require Local::_pci_check_args; ' if $cd->{gen_args}{pack_deps};
1345 48         141 push @l, 'my $res = _pci_check_args(\\%_pci_args);', "\n";
1346 48 50       437 push @l, '_pci_debug("args after _pci_check_args: ", \%_pci_args);', "\n" if $cd->{gen_args}{with_debug};
1347 48         204 push @l, '_pci_err($res) if $res->[0] != 200;', "\n";
1348 48         168 push @l, '$_pci_r->{args} = \\%_pci_args;', "\n";
1349 48         154 push @l, "}\n\n";
1350              
1351             # generate code to call function
1352 48         118 push @l, "### call function\n\n";
1353 48         245 $cd->{vars}{'$_pci_meta_result_stream'} = 0;
1354 48         337 $cd->{vars}{'$_pci_meta_skip_format'} = 0;
1355 48         203 $cd->{vars}{'$_pci_meta_result_type'} = undef;
1356 48         159 $cd->{vars}{'$_pci_meta_result_type_is_simple'} = undef;
1357 48         144 push @l, "{\n";
1358 48 100       224 push @l, 'log_trace("Calling function ...");', "\n" if $cd->{gen_args}{log};
1359 48         160 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       197 if $args{pass_cmdline_object};
1362             {
1363 48         475 my $i = -1;
  48         229  
1364 48         132 for my $sc_name (sort keys %{ $cd->{metas} }) {
  48         266  
1365 53         144 $i++;
1366 53         186 my $meta = $cd->{metas}{$sc_name};
1367 53 100       379 push @l, ($i ? 'elsif' : 'if').' ($sc_name eq '.dmp($sc_name).") {\n";
1368 53 100       2211 push @l, ' $_pci_meta_result_stream = 1;'."\n" if $meta->{result}{stream};
1369 53 50       224 push @l, ' $_pci_meta_skip_format = 1;'."\n" if $meta->{'cmdline.skip_format'};
1370 53   100     811 push @l, ' $_pci_meta_result_type = '.dmp(Data::Sah::Util::Type::get_type($meta->{result}{schema} // '') // '').";\n";
      50        
1371 53 100 100     3001 push @l, ' $_pci_meta_result_type_is_simple = 1;'."\n" if Data::Sah::Util::Type::is_simple($meta->{result}{schema} // '');
1372 53 50       1628 push @l, " require $cd->{sc_mods}{$sc_name};\n" if $cd->{sc_mods}{$sc_name};
1373 53 100       562 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         192 push @l, ' if ($@) { $_pci_r->{res} = [500, "Function died: $@"] }', "\n";
1375 53 100       232 if ($meta->{result_naked}) {
1376 1         22 push @l, ' $_pci_r->{res} = [200, "OK (envelope added by Perinci::CmdLine::Inline)", $_pci_r->{res}];', "\n";
1377             }
1378 53         245 push @l, "}\n";
1379             }
1380             }
1381 48         147 push @l, "}\n\n";
1382              
1383             # generate code to format & display result
1384 48         294 push @l, "### format & display result\n\n";
1385 48         162 push @l, "{\n";
1386 48 100       204 push @l, 'log_trace("Displaying result ...");', "\n" if $cd->{gen_args}{log};
1387              
1388 48         167 push @l, 'my $fh;', "\n";
1389 48         311 push @l, 'if ($_pci_r->{page_result} // $ENV{PAGE_RESULT} // $_pci_r->{res}[3]{"cmdline.page_result"}) {', "\n";
1390 48         185 push @l, 'my $pager = $_pci_r->{pager} // $_pci_r->{res}[3]{"cmdline.pager"} // $ENV{PAGER} // "less -FRSX";', "\n";
1391 48         192 push @l, 'open $fh, "| $pager";', "\n";
1392 48         191 push @l, '} else {', "\n";
1393 48         119 push @l, '$fh = \*STDOUT;', "\n";
1394 48         290 push @l, '}', "\n";
1395              
1396 48         363 push @l, 'my $fres;', "\n";
1397 48         147 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         292 push @l, 'my $is_success = $_pci_r->{res}[0] =~ /\A2/ || $_pci_r->{res}[0] == 304;', "\n";
1399 48         185 push @l, 'my $is_stream = $_pci_r->{res}[3]{stream} // $_pci_meta_result_stream // 0;'."\n";
1400 48 50       265 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         126 push @l, 'elsif ($is_success && $is_stream) {}', "\n";
1402 48         108 push @l, 'else { ';
1403 48 50 33     735 push @l, 'require Local::_pci_clean_json; ' if $args{pack_deps} && $args{use_cleanser};
1404 48         160 push @l, 'require Perinci::Result::Format::Lite; $is_stream=0; ';
1405 48 50       208 push @l, '_pci_clean_json($_pci_r->{res}); ' if $args{use_cleanser};
1406 48         445 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         175 push @l, "\n";
1408              
1409 48 50       412 push @l, 'my $use_utf8 = $_pci_r->{res}[3]{"x.hint.result_binary"} ? 0 : '.($args{use_utf8} ? 1:0).";\n";
1410 48         199 push @l, 'if ($use_utf8) { binmode STDOUT, ":encoding(utf8)" }', "\n";
1411              
1412 48         171 push @l, 'if ($is_stream) {', "\n";
1413 48         381 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         347 push @l, '} else {', "\n";
1415 48         184 push @l, ' print $fh $fres;', "\n";
1416 48         187 push @l, '}', "\n";
1417 48         130 push @l, 'if (defined $save_res) { $_pci_r->{res}[2] = $save_res }', "\n";
1418 48         97 push @l, "}\n\n";
1419              
1420             # generate code to exit with code
1421 48         93 push @l, "### exit\n\n";
1422 48         138 push @l, "{\n";
1423 48         128 push @l, 'my $status = $_pci_r->{res}[0];', "\n";
1424 48         260 push @l, 'my $exit_code = $_pci_r->{res}[3]{"cmdline.exit_code"} // ($status =~ /200|304/ ? 0 : ($status-300));', "\n";
1425 48         170 push @l, 'exit($exit_code);', "\n";
1426 48         116 push @l, "}\n\n";
1427              
1428             # remove unneeded modules
1429 48 50       182 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       171 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         144 my ($dp_code1, $dp_code2, $dp_code3);
1448 48 50       172 if ($args{pack_deps}) {
1449 48         1878 require Module::DataPack;
1450             my $dp_res = Module::DataPack::datapack_modules(
1451             module_srcs => $cd->{module_srcs},
1452             stripper => $args{stripper},
1453 48         5834 );
1454 48 50       226968 return [500, "Can't datapack: $dp_res->[0] - $dp_res->[1]"]
1455             unless $dp_res->[0] == 200;
1456 48         162 $dp_code2 = "";
1457 48         15208 ($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         212 my $pod;
1469 48 50 50     323 if ($args{pod} // 1) {
1470 48         2543 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         24405 completer_script => "_$cd->{script_name}",
1486             );
1487 48 50       556970 return err($res, 500, "Can't generate POD") unless $res->[0] == 200;
1488 48         407 $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         1184 my %tmp = %args;
1502             # don't show the potentially long/undumpable argument values
1503 48         354 for (grep {/^code_/} keys %tmp) {
  915         1928  
1504 48         197 $tmp{$_} = "...";
1505             }
1506 48         2067 JSON::MaybeXS->new->canonical(1)->encode(\%tmp);
1507             }, "\n\n",
1508              
1509             'my $_pci_metas = ', do {
1510 48         3898 local $Data::Dmp::OPT_DEPARSE=0;
1511 48         372 dmp($cd->{metas});
1512             }, ";\n\n",
1513              
1514             "# This script is generated by ", __PACKAGE__,
1515 48         38753 " version ", (${__PACKAGE__."::VERSION"} // 'dev'), " on ",
1516             scalar(localtime), ".\n\n",
1517              
1518 48   50     481 (keys %{$cd->{mods}} ? "# Rinci metadata taken from these modules: ".join(", ", map {"$_ ".($cd->{mods}{$_}{version} // "(no version)")} sort keys %{$cd->{mods}})."\n\n" : ""),
  48         1027  
  48         227  
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     661 my $abstract = $args{script_summary} // $cd->{metas}{''}{summary};
1528 48 100       238 if ($abstract) {
1529 38         401 ("# ABSTRACT: ", $abstract, "\n");
1530             } else {
1531 10         38 ();
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         435  
1545             "\n",
1546              
1547             "\n",
1548              
1549             "### declare global variables\n\n",
1550 289 100       9707 (map { "our $_" . (defined($cd->{vars}{$_}) ? " = ".dmp($cd->{vars}{$_}) : "").";\n" } sort keys %{$cd->{vars}}),
  48         418  
1551 48         349 (keys(%{$cd->{vars}}) ? "\n" : ""),
1552              
1553             $args{log} ? _gen_enable_log($cd) : '',
1554              
1555             "### declare subroutines\n\n",
1556             (map {
1557 100         210 my $sub = $_;
1558 100 100       396 if ($cd->{sub_src_core_deps}{$sub}) {
1559 48         100 for my $mod (keys %{ $cd->{sub_src_core_deps}{$sub} }) {
  48         368  
1560             $cd->{core_deps}{$mod} //=
1561 48   33     729 $cd->{sub_src_core_deps}{$sub}{$mod};
1562             }
1563             }
1564 100 50       16259 "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         288 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     430 "### end code_after_end\n",
    50 100        
    100          
    50          
1583             );
1584             }
1585              
1586             WRITE_OUTPUT:
1587             {
1588 48         355 my ($fh, $output_is_stdout);
  48         169  
1589 48 50 33     349 if (!defined($args{output_file}) || $args{output_file} eq '-') {
1590 48         118 $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       136 if ($output_is_stdout) {
1602             return [200, "OK", $cd->{result}, {
1603 48         489 '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.550 of Perinci::CmdLine::Inline (from Perl distribution Perinci-CmdLine-Inline), released on 2020-05-02.
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 FUNCTIONS
1685              
1686              
1687             =head2 gen_inline_pericmd_script
1688              
1689             Usage:
1690              
1691             gen_inline_pericmd_script(%args) -> [status, msg, payload, meta]
1692              
1693             Generate inline Perinci::CmdLine CLI script.
1694              
1695             The goal of this module is to let you create a CLI script from a Riap
1696             function/metadata. This is like what L<Perinci::CmdLine::Lite> or
1697             L<Perinci::CmdLine::Classic> does, except that the generated CLI script will have
1698             the functionalities inlined so it only need core Perl modules and not any of the
1699             C<Perinci::CmdLine::*> or other modules to run (excluding what modules the Riap
1700             function itself requires).
1701              
1702             It's useful if you want a CLI script that is even more lightweight (in terms of
1703             startup overhead or dependencies) than the one using L<Perinci::CmdLine::Lite>.
1704              
1705             So to reiterate, the goal of this module is to create a Perinci::CmdLine-based
1706             script which only requires core modules, and has as little startup overhead as
1707             possible.
1708              
1709             Currently it only supports a subset of features compared to other
1710             C<Perinci::CmdLine::*> implementations:
1711              
1712             =over
1713              
1714             =item * Only support local Riap URL (e.g. C</Foo/bar>, not
1715             CLL<http://example.org/Foo/bar>);
1716              
1717             =back
1718              
1719             As an alternative to this module, if you are looking to reduce dependencies, you
1720             might also want to try using C<depak> to fatpack/datapack your
1721             L<Perinci::CmdLine::Lite>-based script.
1722              
1723             This function is not exported by default, but exportable.
1724              
1725             Arguments ('*' denotes required arguments):
1726              
1727             =over 4
1728              
1729             =item * B<actions> => I<any>
1730              
1731             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1732              
1733             =item * B<allow_prereq> => I<array[str]>
1734              
1735             A list of modules that can be depended upon.
1736              
1737             By default, Perinci::CmdLine::Inline will strive to make the script freestanding
1738             and require core modules. A dependency to a non-core module will cause failure
1739             (unless C<pack_deps> option is set to false). However, you can pass a list of
1740             modules that is allowed here.
1741              
1742             =item * B<code_add_extra_log_outputs> => I<str>
1743              
1744             =item * B<code_after_enable_logging> => I<str>
1745              
1746             =item * B<code_after_end> => I<str>
1747              
1748             Put at the very end of generated script.
1749              
1750             =item * B<code_after_shebang> => I<str>
1751              
1752             Put at the very beginning of generated script, after the shebang line.
1753              
1754             =item * B<code_before_enable_logging> => I<str>
1755              
1756             =item * B<code_before_parse_cmdline_options> => I<str>
1757              
1758             =item * B<common_opts> => I<any>
1759              
1760             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1761              
1762             =item * B<completion> => I<any>
1763              
1764             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1765              
1766             =item * B<config_dirs> => I<array[str]>
1767              
1768             Where to search for configuration files.
1769              
1770             =item * B<config_filename> => I<str|hash|array[str|hash]>
1771              
1772             Configuration file name(s).
1773              
1774             =item * B<default_format> => I<any>
1775              
1776             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1777              
1778             =item * B<default_subcommand> => I<str>
1779              
1780             =item * B<description> => I<any>
1781              
1782             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1783              
1784             =item * B<env_name> => I<str>
1785              
1786             Name of environment variable name that sets default options.
1787              
1788             =item * B<exit> => I<any>
1789              
1790             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1791              
1792             =item * B<extra_urls_for_version> => I<array[riap::url]>
1793              
1794             More URLs to show version for --version.
1795              
1796             Currently not implemented in Perinci::CmdLine::Inline.
1797              
1798             =item * B<formats> => I<any>
1799              
1800             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1801              
1802             =item * B<get_subcommand_from_arg> => I<any>
1803              
1804             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1805              
1806             =item * B<include> => I<array[perl::modname]>
1807              
1808             Include extra modules.
1809              
1810             =item * B<log> => I<bool> (default: 0)
1811              
1812             Whether to enable logging.
1813              
1814             =item * B<meta> => I<hash>
1815              
1816             An alternative to specifying `url`.
1817              
1818             =item * B<meta_is_normalized> => I<bool>
1819              
1820             =item * B<output_file> => I<filename>
1821              
1822             Set output file, defaults to stdout.
1823              
1824             =item * B<overwrite> => I<bool>
1825              
1826             =item * B<pack_deps> => I<bool> (default: 1)
1827              
1828             Whether to pack dependencies into the script.
1829              
1830             By default, Perinci::CmdLine::Inline will use datapacking technique (i.e. embed
1831             dependencies into DATA section and load it on-demand using require() hook) to
1832             make the script freestanding. However, in some situation this is unwanted, e.g.
1833             when we want to produce a script that can be packaged as a Debian package
1834             (Debian policy forbids embedding convenience copy of code,
1835             https://www.debian.org/doc/debian-policy/ch-source.html#s-embeddedfiles ).
1836              
1837             =item * B<pass_cmdline_object> => I<bool> (default: 0)
1838              
1839             Whether to pass Perinci::CmdLine::Inline object.
1840              
1841             =item * B<pod> => I<bool> (default: 1)
1842              
1843             Whether to generate POD for the script.
1844              
1845             =item * B<read_config> => I<bool> (default: 1)
1846              
1847             Whether the CLI script should read configuration files.
1848              
1849             =item * B<read_env> => I<bool>
1850              
1851             Whether CLI script should read environment variable that sets default options.
1852              
1853             =item * B<riap_client> => I<any>
1854              
1855             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1856              
1857             =item * B<riap_client_args> => I<any>
1858              
1859             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1860              
1861             =item * B<riap_version> => I<any>
1862              
1863             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1864              
1865             =item * B<script_name> => I<str>
1866              
1867             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1868              
1869             =item * B<script_summary> => I<str>
1870              
1871             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1872              
1873             =item * B<script_version> => I<str>
1874              
1875             Script version (otherwise will use version from url metadata).
1876              
1877             =item * B<script_version_from_main_version> => I<bool>
1878              
1879             Use script's $main::VERSION for the version.
1880              
1881             =item * B<shebang> => I<str>
1882              
1883             Set shebang line.
1884              
1885             =item * B<skip_format> => I<bool> (default: 0)
1886              
1887             Assume that function returns raw text that need no formatting, do not offer --format, --json, --naked-res.
1888              
1889             =item * B<stripper> => I<bool> (default: 0)
1890              
1891             Whether to strip code using Perl::Stripper.
1892              
1893             =item * B<sub_name> => I<str>
1894              
1895             =item * B<subcommands> => I<hash>
1896              
1897             =item * B<tags> => I<any>
1898              
1899             Currently does nothing, provided only for compatibility with Perinci::CmdLine::Base.
1900              
1901             =item * B<url> => I<riap::url>
1902              
1903             Program URL.
1904              
1905             =item * B<use_cleanser> => I<bool> (default: 1)
1906              
1907             Whether to use data cleanser routine first before producing JSON.
1908              
1909             When a function returns result, and the user wants to display the result as
1910             JSON, the result might need to be cleansed first (e.g. using L<Data::Clean>)
1911             before it can be encoded to JSON, for example it might contain Perl objects or
1912             scalar references or other stuffs. If you are sure that your function does not
1913             produce those kinds of data, you can set this to false to produce a more
1914             lightweight script.
1915              
1916             =item * B<use_utf8> => I<bool> (default: 0)
1917              
1918             Whether to set utf8 flag on output.
1919              
1920             =item * B<validate_args> => I<bool> (default: 1)
1921              
1922             Whether the CLI script should validate arguments using schemas.
1923              
1924             =item * B<with_debug> => I<bool>
1925              
1926             Generate script with debugging outputs.
1927              
1928              
1929             =back
1930              
1931             Returns an enveloped result (an array).
1932              
1933             First element (status) is an integer containing HTTP status code
1934             (200 means OK, 4xx caller error, 5xx function error). Second element
1935             (msg) is a string containing error message, or 'OK' if status is
1936             200. Third element (payload) is optional, the actual result. Fourth
1937             element (meta) is called result metadata and is optional, a hash
1938             that contains extra information.
1939              
1940             Return value: (any)
1941              
1942             =head1 FAQ
1943              
1944             =head2 What about tab completion?
1945              
1946             Use L<App::GenPericmdCompleterScript> to generate a separate completion script.
1947             If you use L<Dist::Zilla>, see also L<Dist::Zilla::Plugin::GenPericmdScript>
1948             which lets you generate script (and its completion script) during build.
1949              
1950             =head1 HOMEPAGE
1951              
1952             Please visit the project's homepage at L<https://metacpan.org/release/Perinci-CmdLine-Inline>.
1953              
1954             =head1 SOURCE
1955              
1956             Source repository is at L<https://github.com/perlancar/perl-Perinci-CmdLine-Inline>.
1957              
1958             =head1 BUGS
1959              
1960             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-CmdLine-Inline>
1961              
1962             When submitting a bug or request, please include a test-file or a
1963             patch to an existing test-file that illustrates the bug or desired
1964             feature.
1965              
1966             =head1 SEE ALSO
1967              
1968             L<Perinci::CmdLine>, L<Perinci::CmdLine::Any>, L<Perinci::CmdLine::Lite>,
1969             L<Perinci::CmdLine::Classic>
1970              
1971             L<App::GenPericmdScript>
1972              
1973             =head1 AUTHOR
1974              
1975             perlancar <perlancar@cpan.org>
1976              
1977             =head1 COPYRIGHT AND LICENSE
1978              
1979             This software is copyright (c) 2020, 2018, 2017, 2016, 2015 by perlancar@cpan.org.
1980              
1981             This is free software; you can redistribute it and/or modify it under
1982             the same terms as the Perl 5 programming language system itself.
1983              
1984             =cut