File Coverage

lib/Class/Usul/TraitFor/Usage.pm
Criterion Covered Total %
statement 75 92 81.5
branch 12 16 75.0
condition 1 3 33.3
subroutine 19 24 79.1
pod 8 8 100.0
total 115 143 80.4


line stmt bran cond sub pod time code
1             package Class::Usul::TraitFor::Usage;
2              
3 18     18   10810 use attributes ();
  18         38  
  18         426  
4 18     18   76 use namespace::autoclean;
  18         37  
  18         148  
5              
6 18     18   1076 use Class::Inspector;
  18         38  
  18         416  
7 18     18   88 use Class::Usul::Constants qw( FAILED FALSE NUL OK SPC TRUE );
  18         39  
  18         123  
8 18     18   20599 use Class::Usul::File;
  18         73  
  18         660  
9 18         180 use Class::Usul::Functions qw( dash2under emit emit_to ensure_class_loaded
10             find_source is_member list_attr_of pad throw
11 18     18   123 untaint_cmdline untaint_identifier );
  18         38  
12 18     18   39415 use Class::Usul::IPC;
  18         73  
  18         579  
13 18     18   150 use Class::Usul::Types qw( Bool DataEncoding DataLumper ProcCommer );
  18         39  
  18         140  
14 18     18   18154 use Scalar::Util qw( blessed );
  18         37  
  18         1446  
15 18     18   149 use Try::Tiny;
  18         49  
  18         1025  
16 18     18   105 use Moo::Role;
  18         37  
  18         140  
17 18     18   7273 use Class::Usul::Options;
  18         36  
  18         145  
18              
19             requires qw( config dumper next_argv options_usage output quiet );
20              
21             # Public attributes
22             option 'encoding' => is => 'lazy', isa => DataEncoding,
23             documentation => 'Decode/encode input/output using this encoding',
24             default => sub { $_[ 0 ]->config->encoding }, format => 's';
25              
26             option 'help_manual' => is => 'ro', isa => Bool, default => FALSE,
27             documentation => 'Displays the documentation for the program',
28             short => 'H';
29              
30             option 'help_options' => is => 'ro', isa => Bool, default => FALSE,
31             documentation => 'Describes program options and methods',
32             short => 'h';
33              
34             option 'help_usage' => is => 'ro', isa => Bool, default => FALSE,
35             documentation => 'Displays this command line usage',
36             short => '?';
37              
38             option 'show_version' => is => 'ro', isa => Bool, default => FALSE,
39             documentation => 'Displays the version number of the program class';
40              
41             has 'file' => is => 'lazy', isa => DataLumper,
42 4     4   112 builder => sub { Class::Usul::File->new( builder => $_[ 0 ] ) };
43              
44             has 'ipc' => is => 'lazy', isa => ProcCommer,
45 14     14   10542 builder => sub { Class::Usul::IPC->new( builder => $_[ 0 ] ) },
46             handles => [ 'run_cmd' ];
47              
48             # Class attributes
49             my $_can_call_cache = {}; my $_method_cache = {};
50              
51             # Private functions
52             my $_list_methods_of = sub {
53             my $class = blessed $_[ 0 ] || $_[ 0 ];
54              
55             exists $_method_cache->{ $class } or $_method_cache->{ $class }
56             = [ map { s{ \A .+ :: }{}msx; $_ }
57             grep { my $subr = $_;
58             grep { $_ eq 'method' } attributes::get( \&{ $subr } ) }
59             @{ Class::Inspector->methods( $class, 'full', 'public' ) } ];
60              
61             return $_method_cache->{ $class };
62             };
63              
64             my $_get_pod_header_for_method = sub {
65             my ($class, $method) = @_;
66              
67             my $src = find_source $class
68             or throw 'Class [_1] cannot find source', [ $class ];
69             my $ev = [ grep { $_->{content} =~ m{ (?: ^|[< ]) $method (?: [ >]|$ ) }msx}
70             grep { $_->{type} eq 'command' }
71             @{ Pod::Eventual::Simple->read_file( $src ) } ]->[ 0 ];
72             my $pod = $ev ? $ev->{content} : undef; $pod and chomp $pod;
73              
74             return $pod;
75             };
76              
77             # Private methods
78             my $_apply_stdio_encoding = sub {
79             my $self = shift; my $enc = untaint_cmdline $self->encoding;
80              
81             for (*STDIN, *STDOUT, *STDERR) {
82 18     18   140 $_->opened or next; binmode $_, ":encoding(${enc})";
  18         36  
  18         133  
83             }
84              
85             autoflush STDOUT TRUE; autoflush STDERR TRUE;
86             return;
87             };
88              
89             my $_get_classes_and_roles = sub {
90             my $self = shift; my %uniq = (); ensure_class_loaded 'mro';
91              
92             my @classes = @{ mro::get_linear_isa( blessed $self ) };
93              
94             while (my $class = shift @classes) {
95             $class = (split m{ __WITH__ }mx, $class)[ 0 ];
96             $class =~ m{ ::_BASE \z }mx and next;
97             $class =~ s{ \A Role::Tiny::_COMPOSABLE:: }{}mx;
98             $uniq{ $class } and next; $uniq{ $class }++;
99              
100             exists $Role::Tiny::APPLIED_TO{ $class }
101             and push @classes, keys %{ $Role::Tiny::APPLIED_TO{ $class } };
102             }
103              
104             return [ sort keys %uniq ];
105             };
106              
107             my $_man_page_from = sub {
108             my ($self, $src) = @_; ensure_class_loaded 'Pod::Man';
109              
110             my $conf = $self->config;
111             my $parser = Pod::Man->new( center => $conf->doc_title || NUL,
112             name => $conf->script,
113             release => 'Version '.$self->app_version,
114             section => '3m' );
115             my $cmd = $conf->man_page_cmd || [];
116             my $tempfile = $self->file->tempfile;
117              
118             $parser->parse_from_file( $src->pathname.NUL, $tempfile->pathname );
119             emit $self->run_cmd( [ @{ $cmd }, $tempfile->pathname ] )->out;
120             return OK;
121             };
122              
123             my $_usage_for = sub {
124             my ($self, $method) = @_; ensure_class_loaded 'Pod::Select';
125              
126             for my $class (@{ $self->$_get_classes_and_roles }) {
127             is_member( $method, Class::Inspector->methods( $class, 'public' ) )
128             or next;
129              
130             my $selector = Pod::Select->new(); my $tfile = $self->file->tempfile;
131              
132             $selector->select( "/(?:[A-Z][\<])?${method}.*" );
133             $selector->parse_from_file( find_source $class, $tfile->pathname );
134             $tfile->stat->{size} > 0 and return $self->$_man_page_from( $tfile );
135             }
136              
137             emit_to \*STDERR, "Method ${method} no documentation found\n";
138             return FAILED;
139             };
140              
141             my $_output_usage = sub {
142             my ($self, $verbose) = @_; my $method = $self->next_argv;
143              
144             defined $method and $method = untaint_identifier dash2under $method;
145              
146             $self->can_call( $method ) and return $self->$_usage_for( $method );
147              
148             $verbose > 1 and return $self->$_man_page_from( $self->config );
149              
150             ensure_class_loaded 'Pod::Usage'; $verbose > 0 and Pod::Usage::pod2usage
151             ( { -exitval => OK,
152             -input => $self->config->pathname.NUL,
153             -message => SPC,
154             -verbose => $verbose } ); # Never returns
155              
156             emit_to \*STDERR, $self->options_usage;
157             return FAILED;
158             };
159              
160             # Construction
161             before 'BUILD' => sub {
162             my $self = shift; $self->$_apply_stdio_encoding;
163              
164             $self->help_usage and $self->exit_usage( 0 );
165             $self->help_options and $self->exit_usage( 1 );
166             $self->help_manual and $self->exit_usage( 2 );
167             $self->show_version and $self->exit_version;
168             return;
169             };
170              
171             # Public methods
172             sub app_version {
173 4     4 1 10 my $self = shift; my $class = $self->config->appclass;
  4         71  
174              
175 4     0   72 my $ver = try { ensure_class_loaded $class; $class->VERSION } catch { '?' };
  4         204  
  4         215  
  0         0  
176              
177 4         77 return $ver;
178             }
179              
180             sub can_call {
181 10 50   10 1 28 my ($self, $wanted) = @_; $wanted or return FALSE;
  10         30  
182              
183 10 100       41 exists $_can_call_cache->{ $wanted } or $_can_call_cache->{ $wanted }
    100          
184             = (is_member $wanted, $_list_methods_of->( $self )) ? TRUE : FALSE;
185              
186 10         50 return $_can_call_cache->{ $wanted };
187             }
188              
189             sub dump_config_attr : method {
190 0     0 1 0 my $self = shift; my @except =
  0         0  
191             qw( BUILDARGS BUILD inflate_path inflate_paths inflate_symbol new secret);
192              
193 0         0 $self->dumper( [ list_attr_of $self->config, @except ] );
194              
195 0         0 return OK;
196             }
197              
198             sub dump_self : method {
199 1     1 1 3 my $self = shift;
200              
201 1         11 $self->dumper( $self ); $self->dumper( $self->config );
  1         25  
202              
203 1         13 return OK;
204             }
205              
206             sub exit_usage {
207 0     0 1 0 my ($self, $level) = @_; $self->quiet( TRUE );
  0         0  
208              
209 0         0 my $rv = $self->$_output_usage( $level );
210              
211 0 0       0 if ($level == 0) { emit "\nMethods:\n"; $self->list_methods }
  0         0  
  0         0  
212              
213 0         0 exit $rv;
214             }
215              
216             sub exit_version {
217 0     0 1 0 $_[ 0 ]->output( 'Version '.$_[ 0 ]->app_version ); exit OK;
  0         0  
218             }
219              
220             sub help : method {
221 0     0 1 0 my $self = shift; $self->$_output_usage( 1 ); return OK;
  0         0  
  0         0  
222             }
223              
224             sub list_methods : method {
225 1     1 1 4 my $self = shift; ensure_class_loaded 'Pod::Eventual::Simple';
  1         5  
226              
227 1         2 my $abstract = {}; my $max = 0; my $classes = $self->$_get_classes_and_roles;
  1         4  
  1         4  
228              
229 1         3 for my $method (@{ $_list_methods_of->( $self ) }) {
  1         4  
230 4 100       10 my $mlen = length $method; $mlen > $max and $max = $mlen;
  4         9  
231              
232 4         9 for my $class (@{ $classes }) {
  4         8  
233 36 100       141 is_member( $method, Class::Inspector->methods( $class, 'public' ) )
234             or next;
235              
236 8 100       39 my $pod = $_get_pod_header_for_method->( $class, $method ) or next;
237              
238             (not exists $abstract->{ $method }
239             or length $pod > length $abstract->{ $method })
240 4 50 33     22 and $abstract->{ $method } = $pod;
241             }
242             }
243              
244 1         3 for my $key (sort keys %{ $abstract }) {
  1         7  
245 4         25 my ($method, @rest) = split SPC, $abstract->{ $key };
246              
247 4         16 $key =~ s{ [_] }{-}gmx; emit( (pad $key, $max).SPC.(join SPC, @rest) );
  4         13  
248             }
249              
250 1         11 return OK;
251             }
252              
253             1;
254              
255             __END__
256              
257             =pod
258              
259             =encoding utf-8
260              
261             =head1 Name
262              
263             Class::Usul::TraitFor::Usage - Help and diagnostic information for command line programs
264              
265             =head1 Synopsis
266              
267             use Moo;
268              
269             extends 'Class::Usul';
270             with 'Class::Usul::TraitFor::Usage';
271              
272             =head1 Description
273              
274             Help and diagnostic information for command line programs
275              
276             =head1 Configuration and Environment
277              
278             Defines the following attributes;
279              
280             =over 3
281              
282             =item C<encoding>
283              
284             Decode/encode input/output using this encoding
285              
286             =item C<H help_manual>
287              
288             Print long help text extracted from this POD
289              
290             =item C<h help_options>
291              
292             Print short help text extracted from this POD
293              
294             =item C<? help_usage>
295              
296             Print option usage
297              
298             =item C<V show_version>
299              
300             Prints the programs version number and exits
301              
302             =back
303              
304             Requires the following;
305              
306             =over 3
307              
308             =item C<config>
309              
310             =item C<dumper>
311              
312             =item C<next_argv>
313              
314             =item C<options_usage>
315              
316             =item C<output>
317              
318             =item C<quiet>
319              
320             =back
321              
322             =head1 Subroutines/Methods
323              
324             =head2 dump_config_attr - Dumps the configuration attributes and values
325              
326             Visits the configuration object, forcing evaluation of the lazy, and printing
327             out the attributes and values
328              
329             =head2 dump_self - Dumps the program object
330              
331             Dumps out the self referential object using L<Data::Printer>
332              
333             =head2 help - Display help text about a method
334              
335             Searches the programs classes and roles to find the method implementation.
336             Displays help text from the POD that describes the method
337              
338             =head2 list_methods - Lists available command line methods
339              
340             Lists the methods (marked by the I<method> subroutine attribute) that can
341             be called via the L<run method|Class::Usul::TraitFor::RunningMethods/run>
342              
343             =head2 app_version
344              
345             $version_object = $self->app_version;
346              
347             The version number of the configured application class
348              
349             =head2 BUILD
350              
351             Called just after the object is constructed this method handles dispatching
352             to the help methods
353              
354             =head2 can_call
355              
356             $bool = $self->can_call( $method );
357              
358             Returns true if C<$self> has a method given by C<$method> that has defined
359             the I<method> method attribute
360              
361             =head2 exit_usage
362              
363             $self->exit_usage( $verbosity );
364              
365             Print out usage information from POD. The C<$verbosity> is; 0, 1 or 2
366              
367             =head2 exit_version
368              
369             $self->exit_version;
370              
371             Prints out the version of the C::U::Programs subclass and the exits
372              
373             =head1 Diagnostics
374              
375             None
376              
377             =head1 Dependencies
378              
379             =over 3
380              
381             =item L<attributes>
382              
383             =item L<Class::Inspector>
384              
385             =item L<Class::Usul::IPC>
386              
387             =item L<Class::Usul::File>
388              
389             =item L<Class::Usul::Options>
390              
391             =item L<Moo::Role>
392              
393             =back
394              
395             =head1 Incompatibilities
396              
397             There are no known incompatibilities in this module
398              
399             =head1 Bugs and Limitations
400              
401             There are no known bugs in this module. Please report problems to
402             http://rt.cpan.org/NoAuth/Bugs.html?Dist=Class-Usul.
403             Patches are welcome
404              
405             =head1 Acknowledgements
406              
407             Larry Wall - For the Perl programming language
408              
409             =head1 Author
410              
411             Peter Flanigan, C<< <pjfl@cpan.org> >>
412              
413             =head1 License and Copyright
414              
415             Copyright (c) 2017 Peter Flanigan. All rights reserved
416              
417             This program is free software; you can redistribute it and/or modify it
418             under the same terms as Perl itself. See L<perlartistic>
419              
420             This program is distributed in the hope that it will be useful,
421             but WITHOUT WARRANTY; without even the implied warranty of
422             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
423              
424             =cut
425              
426             # Local Variables:
427             # mode: perl
428             # tab-width: 3
429             # End:
430             # vim: expandtab shiftwidth=3: