File Coverage

lib/MouseX/OO_Modulino.pm
Criterion Covered Total %
statement 27 147 18.3
branch 0 66 0.0
condition 0 47 0.0
subroutine 9 28 32.1
pod 1 19 5.2
total 37 307 12.0


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             package MouseX::OO_Modulino;
3 1     1   260286 use MouseX::OO_Modulino::MOP4Import -as_base;
  1         8  
  1         8  
4              
5             our $VERSION = '0.03';
6              
7 1     1   160 use Carp ();
  1         1  
  1         19  
8 1     1   706 use Module::Runtime ();
  1         1732  
  1         31  
9              
10 1     1   6 use File::Basename ();
  1         2  
  1         25  
11 1     1   2142 use open ();
  1         1082  
  1         26  
12              
13 1     1   5 use Encode ();
  1         3  
  1         11  
14 1     1   4 use Data::Dumper ();
  1         1  
  1         10  
15              
16 1     1   506 use JSON::MaybeXS ();
  1         11221  
  1         44  
17 1     1   7 use constant USING_CPANEL_JSON_XS => JSON::MaybeXS::JSON()->isa("Cpanel::JSON::XS");
  1         2  
  1         3  
18              
19             #========================================
20              
21             has output => (is => 'rw', default => 'jsonl');
22              
23             has binary => (is => 'rw', default => 0);
24             has scalar => (is => 'rw', default => 0);
25             has quiet => (is => 'rw', default => 0);
26             has undef_as => (is => 'rw', default => 'null');
27             has no_exit_code => (is => 'rw', default => 0);
28              
29             #========================================
30              
31             sub cli_run {
32 0     0 1   my ($class, $arglist, $opt_alias) = @_;
33              
34             {
35 0           my $modFn = Module::Runtime::module_notional_filename($class);
  0            
36 0   0       $INC{$modFn} //= 1;
37             }
38              
39 0           my $self = $class->new($class->cli_parse_opts($arglist, undef, $opt_alias));
40              
41 0 0         unless (@$arglist) {
42             # Invoke help command if no arguments are given.
43 0           $self->cmd_help;
44 0           return;
45             }
46              
47 0           my $cmd = shift @$arglist;
48 0 0         if (my $sub = $self->can("cmd_$cmd")) {
    0          
49             # Invoke official command.
50              
51 0           $self->cli_precmd($cmd);
52              
53 0           $sub->($self, @$arglist);
54             }
55             elsif ($self->can($cmd)) {
56             # Invoke unofficial internal methods. Development aid.
57              
58 0           $self->cli_invoke($cmd, @$arglist);
59              
60             }
61             else {
62             # Last resort. You can implement your own subcommand interpretations here.
63              
64 0           $self->cli_unknown_subcommand($cmd, $arglist);
65             }
66             }
67              
68             sub cli_unknown_subcommand {
69 0     0 0   my ($self, $cmd, $arglist) = @_;
70              
71 0           $self->cmd_help("Error: No such subcommand '$cmd'\n");
72             }
73              
74             sub cli_invoke {
75 0     0 0   my ($self, $method, @args) = @_;
76              
77 0   0       my $no_exit_code = $self->can('no_exit_code') && $self->no_exit_code;
78              
79 0           $self->cli_precmd($method);
80              
81 0 0         my $sub = $self->can($method)
82             or Carp::croak "No such method: $method";
83              
84 0           my $list = $self->cli_invoke_sub($sub, $self, @args);
85              
86 0 0         unless ($no_exit_code) {
87 0           $self->cli_exit_for_result($list);
88             }
89             }
90              
91             sub cli_invoke_sub {
92 0     0 0   my ($self, $sub, $receiver, @args) = @_;
93              
94 0           my @res;
95 0 0         if ($self->scalar) {
96 0           $res[0] = $sub->($receiver, @args);
97 0 0 0       $self->cli_output($res[0]) if not $self->quiet and $res[0];
98             } else {
99 0           @res = $sub->($receiver, @args);
100 0 0 0       $self->cli_output(\@res) if not $self->quiet and @res;
101             }
102              
103 0           \@res;
104             }
105              
106             sub cli_output {
107 0     0 0   my ($self, $item) = @_;
108              
109 0   0       my $format = $self->output // "jsonl";
110              
111 0 0         my $emitter = $self->can("cli_write_fh_as_$format")
112             or Carp::croak "No such output format: $format";
113              
114 0 0         if ($self->scalar) {
115 0           $emitter->($self, \*STDOUT, $item);
116             } else {
117 0           $emitter->($self, \*STDOUT, $_) for @$item;
118             }
119             }
120              
121             *cli_write_fh_as_json = *cli_write_fh_as_jsonl; *cli_write_fh_as_json = *cli_write_fh_as_jsonl;
122             sub cli_write_fh_as_jsonl {
123 0     0 0   my ($self, $outFH, $item) = @_;
124 0 0 0       print $outFH (
125             ref $item ? $self->cli_encode_json($item) : $item // $self->undef_as
126             ), "\n";
127             }
128              
129             sub cli_encode_json {
130 0     0 0   my ($self, $obj) = @_;
131 0           my $json = $self->cli_encode_json_as_bytes($obj);
132 0 0         Encode::_utf8_on($json) unless $self->binary;
133 0           $json;
134             }
135              
136             sub cli_encode_json_as_bytes {
137 0     0 0   my ($self, $obj) = @_;
138 0           $self->cli_json->encode($obj);
139             }
140              
141             sub cli_write_fh_as_dump {
142 0     0 0   my ($self, $outFH, $item) = @_;
143 0           print $outFH $self->cli_encode_dump($item), "\n";
144             }
145              
146             sub cli_exit_for_result {
147 0     0 0   my ($self, $list) = @_;
148              
149 0 0         exit($self->cli_examine_result($list) ? 0 : 1);
150             }
151              
152             sub cli_examine_result {
153 0     0 0   my ($self, $list) = @_;
154 0 0         if ($self->scalar) {
155 0           $list->[0];
156             } else {
157 0           @$list;
158             }
159             }
160              
161             sub cmd_help {
162 0     0 0   my $self = shift;
163 0   0       my $pack = ref $self || $self;
164              
165             # Invoke precmd (mainly for binmode handling)
166 0           $self->cli_precmd();
167              
168 0           my @msg = (join("\n", @_, <
169 0           Usage: @{[File::Basename::basename($0)]} [--opt=value].. ARGS...
170             END
171              
172 0           my @cmds = $self->cli_describe_commands;
173 0           my @opts = $self->cli_describe_options;
174              
175 0 0         push @msg, "\nCommands:\n", @cmds if @cmds;
176              
177 0 0 0       push @msg, "\n" if @cmds && @opts;
178              
179 0 0         push @msg, "\nOptions:\n", @opts if @opts;
180              
181 0           $! = 0; $? = 0;
  0            
182 0           die join("", @msg);
183             }
184              
185             sub cli_describe_commands {
186 0     0 0   my ($self) = @_;
187 0           my $meta = $self->meta;
188             map {
189 0           my $subName = $_;
190 0           (my $name = $subName) =~ s/^cmd_//;
191 0           my $rawAtts = $meta->get_method($subName)->attributes;
192 0           my $attsDict = $self->cli_parse_attributes(@$rawAtts);
193 0 0         " $name" . (defined $attsDict->{Doc} ? " -- $attsDict->{Doc}" : "");
194 0           } sort grep {/^cmd_/} $meta->get_method_list;
  0            
195             }
196              
197             sub cli_parse_attributes {
198 0     0 0   my ($self, @atts) = @_;
199 0           my $atts = +{};
200 0           foreach my $attDesc (@atts) {
201 0 0         my ($name, $value) = $attDesc =~ m{^([^\(]+)([\(].*)?\z}
202             or Carp::croak "Can't parse attribute $attDesc";
203 0 0         $value =~ s/^\(|\)\z//g if defined $value;
204 0   0       $atts->{$name} = $value // 1;
205             }
206 0           $atts;
207             }
208              
209             sub cli_describe_options {
210 0     0 0   my ($self) = @_;
211 0           my $meta = $self->meta;
212 0           my $maxLen = 0;
213             my @spec = map {
214 0           my $att = $_;
  0            
215 0 0         if ($att->associated_class == $meta) {
216 0           my $doc = $att->{documentation};
217 0           my $name = $att->name;
218 0 0         $maxLen = length($name) > $maxLen ? length($name): $maxLen;
219 0           [$name, $doc];
220             } else {
221             ()
222 0           }
223             } $meta->get_all_attributes;
224             map {
225 0           my ($name, $doc) = @$_;
  0            
226 0   0       sprintf " --%-${maxLen}s %s\n", $name, $doc // ""
227             } @spec;
228             }
229              
230             sub cli_precmd {
231 0     0 0   my ($self) = @_;
232             #
233             # cli_precmd() may be called from $class->cmd_help.
234             #
235 0 0 0       unless (ref $self and $self->can("binary") and $self->binary) {
      0        
236 0           'open'->import(qw/:locale :std/);
237             }
238             }
239              
240             sub cli_parse_opts {
241 0     0 0   my ($class, $list, $result, $alias) = @_;
242 0           my $wantarray = wantarray;
243 0 0         unless (defined $result) {
244 0 0         $result = $wantarray ? [] : {};
245             }
246 0   0       while (@$list and defined $list->[0] and my ($n, $v) = $list->[0] =~ m{
      0        
247             ^--$
248             | ^(?:--? ([\w:\-\.]+) (?: =(.*))?)$
249             }xs) {
250 0           shift @$list;
251 0 0         last unless defined $n;
252 0 0 0       $n = $alias->{$n} if $alias and $alias->{$n};
253 0 0         $v = 1 unless defined $v;
254 0 0         if (ref $result eq 'HASH') {
255 0           $result->{$n} = $class->cli_decode_argument($v);
256             } else {
257 0           push @$result, $n, $class->cli_decode_argument($v);
258             }
259             }
260              
261 0           $_ = $class->cli_decode_argument($_) for @$list;
262              
263 0 0 0       $wantarray && ref $result ne 'HASH' ? @$result : $result;
264             }
265              
266             sub cli_decode_argument {
267 0 0 0 0 0   if ($_[1] =~ /^(?:\[.*?\]|\{.*?\})\z/s) {
    0          
268 0           my $copy = $_[1];
269 0 0         Encode::_utf8_off($copy) if Encode::is_utf8($copy);
270 0           $_[0]->cli_json->utf8->relaxed->decode($copy);
271             }
272             elsif (not Encode::is_utf8($_[1]) and $_[1] =~ /\P{ASCII}/) {
273 0           Encode::decode(utf8 => $_[1]);
274             }
275             else {
276 0           $_[1];
277             }
278             }
279              
280             sub cli_json {
281 0     0 0   JSON::MaybeXS::JSON()->new;
282             }
283              
284             __PACKAGE__->cli_run(\@ARGV) unless caller;
285              
286             1;