File Coverage

blib/lib/Data/Object/Cli.pm
Criterion Covered Total %
statement 139 153 90.8
branch 11 20 55.0
condition 9 14 64.2
subroutine 29 30 96.6
pod 8 14 57.1
total 196 231 84.8


line stmt bran cond sub pod time code
1             package Data::Object::Cli;
2              
3 1     1   41833 use 5.014;
  1         5  
4              
5 1     1   6 use strict;
  1         3  
  1         23  
6 1     1   5 use warnings;
  1         2  
  1         32  
7              
8 1     1   5 use registry 'Data::Object::Types';
  1         2  
  1         8  
9 1     1   245450 use routines;
  1         3  
  1         7  
10              
11 1     1   2419 use Data::Object::Class;
  1         270  
  1         23  
12 1     1   893 use Data::Object::ClassHas;
  1         10476  
  1         11  
13              
14 1     1   1370 use Data::Object::Args;
  1         31765  
  1         41  
15 1     1   1226 use Data::Object::Data;
  1         6534  
  1         35  
16 1     1   1239 use Data::Object::Opts;
  1         28435  
  1         43  
17 1     1   1226 use Data::Object::Vars;
  1         13729  
  1         143  
18              
19             our $VERSION = '2.01'; # VERSION
20              
21             # ATTRIBUTES
22              
23             has 'args' => (
24             is => 'ro',
25             isa => 'ArgsObject',
26             new => 1,
27             );
28              
29 5     5 0 3593 fun new_args($self) {
  5         11  
30 5         28 Data::Object::Args->new($self->_args_spec)
31             }
32              
33             has 'data' => (
34             is => 'ro',
35             isa => 'DataObject',
36             new => 1,
37             );
38              
39 3     3 0 3479 fun new_data($self) {
  3         6  
40 3         52 Data::Object::Data->new(from => ref $self)
41             }
42              
43             has 'opts' => (
44             is => 'ro',
45             isa => 'OptsObject',
46             new => 1,
47             );
48              
49 4     4 0 3183 fun new_opts($self) {
  4         7  
50 4         20 Data::Object::Opts->new($self->_opts_spec)
51             }
52              
53             has 'vars' => (
54             is => 'ro',
55             isa => 'VarsObject',
56             new => 1,
57             );
58              
59 3     3 0 2153 fun new_vars($self) {
  3         6  
60 3         43 Data::Object::Vars->new
61             }
62              
63             # METHODS
64              
65             sub main {
66             shift
67 0     0 1 0 }
68              
69 5     5   11 method _args_spec() {
  5         8  
70 5         15 my $args_spec = {named => {}};
71              
72 5         52 my $name = $self->name;
73 5         29 my @args = split /\s+/, $name;
74              
75 5         9 shift @args;
76              
77 5 100       64 return $args_spec if !@args;
78              
79 2         10 for (my $i=0; $i < @args; $i++) {
80 3 100       22 if (my ($token) = $args[$i] =~ /\{(\w+)\}/) {
81 2         13 $args_spec->{named}{$token} = "$i";
82             }
83             }
84              
85 2         47 return $args_spec;
86             }
87              
88 8     8   15 method _help_opts() {
  8         10  
89 8         22 my $spec = $self->spec;
90 8         17 my $size = 0;
91              
92 8         11 my @opts;
93              
94 8         26 for my $name (keys %$spec) {
95 0         0 my %seen;
96              
97 0         0 my $data = $spec->{$name};
98              
99 0         0 my $args = $data->{args};
100 0         0 my $desc = $data->{desc};
101 0   0     0 my $flag = $data->{flag} || $name;
102 0         0 my $type = $data->{type};
103              
104             my $text = join ',',
105 0 0       0 map {length > 1 ? "--$_" : "-$_"}
106 0         0 grep !$seen{$_}++, sort $name, split /\|/, $flag;
107              
108 0 0       0 $text = "${text}, +1" if defined $args;
109 0 0       0 $size = length $text if length $text > $size;
110              
111 0         0 push @opts, [$text, "[$type] $desc"];
112             }
113              
114 8         19 @opts = sort {$a->[0] cmp $b->[0]} @opts;
  0         0  
115              
116 8         35 return join "\n", map {sprintf " %-*s %s", $size, @$_} @opts;
  0         0  
117             }
118              
119 4     4   10 method _opts_spec() {
  4         6  
120 4         11 my $opts_spec = {spec => []};
121              
122 4         89 my $spec = $self->spec;
123              
124 4         25 for my $name (keys %$spec) {
125 3         7 my %seen;
126              
127 3         5 my $data = $spec->{$name};
128              
129 3         6 my $args = $data->{args};
130 3   66     11 my $flag = $data->{flag} || $name;
131 3   100     10 my $type = $data->{type} || 'string';
132              
133 3         12 my $code = { float => 'f', number => 'n', string => 's' };
134              
135 3   50     8 $code = $code->{$type} || 's';
136              
137 3         22 my @flags = grep !$seen{$_}++, reverse sort $name, split /\|/, $flag;
138              
139 3         9 $opts_spec->{named}{$name} = $flags[0];
140              
141 3         7 $flag = join '|', @flags;
142              
143 3   100     6 push @{$opts_spec->{spec}}, sprintf '%s=%s%s', $flag, $code, $args || '';
  3         21  
144             }
145              
146 4         86 return $opts_spec;
147             }
148              
149 4     4 1 25 method exit($code, $handler, @args) {
  4         9  
  4         7  
150              
151 4 50       10 $self->handle($handler, @args) if $handler;
152              
153 4   100     19 $code ||= 0;
154              
155 4         14 exit $code;
156             }
157              
158 1     1 1 10 method fail($handler, @args) {
  1         4  
  1         2  
159              
160 1         5 return $self->exit(1, $handler, @args);
161             }
162              
163 2     2 1 17 method handle($method, %args) {
  2         7  
  2         3  
164              
165 2         4 my %meta;
166              
167 2         37 $meta{args} = $self->args;
168 2         624 $meta{data} = $self->data;
169 2         521 $meta{opts} = $self->opts;
170 2         1048 $meta{vars} = $self->vars;
171              
172 2         670 return $self->$method(%meta, %args);
173             }
174              
175 8     8 1 5915 method help() {
  8         13  
176              
177 8         15 my @help;
178              
179 8         112 my $name = $self->name =~ s/\{(\w+)\}/$1/gr;
180              
181 8         48 push @help, "usage: $name", "";
182 8 100       59 push @help, $self->info, "" if $self->info;
183 8 50       76 push @help, $self->_help_opts, "" if $self->_help_opts;
184              
185 8         83 return join "\n", @help;
186             }
187              
188 11     11 0 2144 method info() {
  11         17  
189 11         18 my $class = ref $self;
190              
191 1     1   1799 do { no strict 'refs'; ${"${class}::info"} };
  1         4  
  1         74  
  11         20  
  11         13  
  11         53  
192             }
193              
194 9     9 0 856 method name() {
  9         15  
195 9         19 my $class = ref $self;
196              
197 1 100   1   88 do { no strict 'refs'; ${"${class}::name"} } || $0;
  1         4  
  1         62  
  9         18  
  9         14  
  9         68  
198             }
199              
200 1     1 1 10 method okay($handler, @args) {
  1         4  
  1         2  
201              
202 1         6 return $self->exit(0, $handler, @args);
203             }
204              
205 16     16 1 192221 method run($class: @args) {
  16         50  
  16         25  
206              
207 16         304 my $self = $class->new(@args);
208              
209 16 50       3563 return $self->handle('main') unless caller(1);
210              
211 16         203 return $self;
212             }
213              
214 11     11 1 20 method spec() {
  11         13  
215             {}
216 11         22 }
217              
218             1;
219              
220             =encoding utf8
221              
222             =head1 NAME
223              
224             Data::Object::Cli - Simple CLIs
225              
226             =cut
227              
228             =head1 ABSTRACT
229              
230             Command-line Interface Abstraction for Perl 5
231              
232             =cut
233              
234             =head1 SYNOPSIS
235              
236             package Command;
237              
238             use parent 'Data::Object::Cli';
239              
240             sub main {
241             my ($self) = @_;
242              
243             return $self->help;
244             }
245              
246             my $command = run Command;
247              
248             =cut
249              
250             =head1 DESCRIPTION
251              
252             This package provides an abstract base class for defining command-line
253             interface classes, which can be run as scripts or passed as objects in a more
254             complex system.
255              
256             =cut
257              
258             =head1 LIBRARIES
259              
260             This package uses type constraints from:
261              
262             L<Data::Object::Types>
263              
264             =cut
265              
266             =head1 ATTRIBUTES
267              
268             This package has the following attributes:
269              
270             =cut
271              
272             =head2 args
273              
274             args(ArgsObject)
275              
276             This attribute is read-only, accepts C<(ArgsObject)> values, and is optional.
277              
278             =cut
279              
280             =head2 data
281              
282             data(DataObject)
283              
284             This attribute is read-only, accepts C<(DataObject)> values, and is optional.
285              
286             =cut
287              
288             =head2 opts
289              
290             opts(OptsObject)
291              
292             This attribute is read-only, accepts C<(OptsObject)> values, and is optional.
293              
294             =cut
295              
296             =head2 vars
297              
298             vars(VarsObject)
299              
300             This attribute is read-only, accepts C<(VarsObject)> values, and is optional.
301              
302             =cut
303              
304             =head1 METHODS
305              
306             This package implements the following methods:
307              
308             =cut
309              
310             =head2 exit
311              
312             exit(Int $code, Maybe[Str] $name, Any %args) : Any
313              
314             The exit method exits the program using the exit code provided. The exit code
315             defaults to C<0>. Optionally, you can call a handler before exiting by
316             providing a method name with arguments. The handler will be called using the
317             C<handle> method so the arguments should be key/value pairs.
318              
319             =over 4
320              
321             =item exit example #1
322              
323             # given: synopsis
324              
325             $command->exit(0);
326              
327             # $command->exit($code, $method_name, %args);
328             # $command->exit($code, $method_name);
329             # $command->exit($code);
330              
331             =back
332              
333             =over 4
334              
335             =item exit example #2
336              
337             # given: synopsis
338              
339             $command->exit(1);
340              
341             # $command->exit($code, $method_name, %args);
342             # $command->exit($code, $method_name);
343             # $command->exit($code);
344              
345             =back
346              
347             =cut
348              
349             =head2 fail
350              
351             fail(Maybe[Str] $name, Any %args) : Any
352              
353             The fail method exits the program with a C<1> exit code. Optionally, you can
354             call a handler before exiting by providing a method name with arguments. The
355             handler will be called using the C<handle> method so the arguments should be
356             key/value pairs.
357              
358             =over 4
359              
360             =item fail example #1
361              
362             # given: synopsis
363              
364             $command->fail;
365              
366             # $command->fail($method_name, %args);
367             # $command->fail($method_name);
368              
369             =back
370              
371             =cut
372              
373             =head2 handle
374              
375             handle(Str $name, Any %args) : Any
376              
377             The handle method dispatches to the method whose name is provided as the first
378             argument. The forwarded method will receive arguments as key/value pairs. This
379             method injects the C<args>, C<data>, C<vars>, and C<opts> attributes as
380             arguments for convenience of use in the forwarded method. Any additional
381             arguments should be passed as key/value pairs.
382              
383             =over 4
384              
385             =item handle example #1
386              
387             # given: synopsis
388              
389             $command->handle('main');
390              
391             # $command->handle($method_name, %args);
392             # $command->handle($method_name);
393              
394             =back
395              
396             =cut
397              
398             =head2 help
399              
400             help() : Str
401              
402             The help method returns the help text documented in POD if available.
403              
404             =over 4
405              
406             =item help example #1
407              
408             package Todolist;
409              
410             use parent 'Data::Object::Cli';
411              
412             my $todolist = run Todolist;
413              
414             # $todolist->help
415              
416             =back
417              
418             =over 4
419              
420             =item help example #2
421              
422             package Todolist;
423              
424             use parent 'Data::Object::Cli';
425              
426             our $name = 'todolist';
427              
428             my $todolist = run Todolist;
429              
430             # $todolist->help
431              
432             =back
433              
434             =over 4
435              
436             =item help example #3
437              
438             package Todolist;
439              
440             use parent 'Data::Object::Cli';
441              
442             sub name {
443             'todolist'
444             }
445              
446             my $todolist = run Todolist;
447              
448             # $todolist->help
449              
450             =back
451              
452             =over 4
453              
454             =item help example #4
455              
456             package Todolist;
457              
458             use parent 'Data::Object::Cli';
459              
460             our $name = 'todolist';
461             our $info = 'manage your todo list';
462              
463             my $todolist = run Todolist;
464              
465             # $todolist->help
466              
467             =back
468              
469             =over 4
470              
471             =item help example #5
472              
473             package Todolist;
474              
475             use parent 'Data::Object::Cli';
476              
477             sub name {
478             'todolist'
479             }
480              
481             sub info {
482             'manage your todo list'
483             }
484              
485             my $todolist = run Todolist;
486              
487             # $todolist->help
488              
489             =back
490              
491             =over 4
492              
493             =item help example #6
494              
495             package Todolist::Command::Show;
496              
497             use parent 'Data::Object::Cli';
498              
499             sub name {
500             'todolist show [<{priority}>]'
501             }
502              
503             sub info {
504             'show your todo list tasks by priority levels'
505             }
506              
507             my $command = run Todolist::Command::Show;
508              
509             # $command->help
510              
511             =back
512              
513             =cut
514              
515             =head2 main
516              
517             main(Any %args) : Any
518              
519             The main method is the "main method" and entrypoint into the program. It's
520             called automatically by the C<run> method if your package is configured as
521             recommended. This method accepts arguments as key/value pairs, and if called
522             by C<run> will receive the C<args>, C<data>, C<opts>, and C<vars> objects.
523              
524             =over 4
525              
526             =item main example #1
527              
528             package Todolist;
529              
530             use parent 'Data::Object::Cli';
531              
532             sub main {
533             my ($self, %args) = @_;
534              
535             return {%args} # no args
536             }
537              
538             my $todolist = run Todolist;
539              
540             $todolist->main;
541              
542             =back
543              
544             =over 4
545              
546             =item main example #2
547              
548             package Todolist;
549              
550             use parent 'Data::Object::Cli';
551              
552             sub main {
553             my ($self, %args) = @_;
554              
555             # has $args{args}
556             # has $args{data}
557             # has $args{opts}
558             # has $args{vars}
559              
560             return {%args}
561             }
562              
563             # $args{args} = $self->args; # isa <Data::Object::Args>
564             # represents @ARGV
565              
566             # $args{data} = $self->data; # isa <Data::Object::Data>
567             # represents __DATA__
568              
569             # $args{opts} = $self->opts; # isa <Data::Object::Opts>
570             # represents Getopt::Long
571              
572             # $args{vars} = $self->vars; # isa <Data::Object::Vars>
573             # represents %ENV
574              
575             my $todolist = run Todolist;
576              
577             $todolist->handle('main'); # called automatically by run
578              
579             =back
580              
581             =cut
582              
583             =head2 okay
584              
585             okay(Maybe[Str] $name, Any %args) : Any
586              
587             The okay method exits the program with a C<0> exit code. Optionally, you can
588             call a handler before exiting by providing a method name with arguments. The
589             handler will be called using the C<handle> method so the arguments should be
590             key/value pairs.
591              
592             =over 4
593              
594             =item okay example #1
595              
596             # given: synopsis
597              
598             $command->okay;
599              
600             # $command->okay($method_name, %args);
601             # $command->okay($method_name);
602              
603             =back
604              
605             =cut
606              
607             =head2 run
608              
609             run() : Object
610              
611             The run method is designed to bootstrap the program. It detects whether the
612             package is being invoked as a script or class and behaves accordingly. It will
613             be called automatically when the package is looaded if your package is
614             configured as recommended. This method will, if invoked as a script, call the
615             main method passing the C<args>, C<data>, C<opts>, and C<vars> objects.
616              
617             =over 4
618              
619             =item run example #1
620              
621             package Todolist;
622              
623             use parent 'Data::Object::Cli';
624              
625             run Todolist;
626              
627             =back
628              
629             =cut
630              
631             =head2 spec
632              
633             spec() : HashRef[HashRef]
634              
635             The spec method returns a hashref of flag definitions used to configure
636             L<Getopt::Long>. These options are accessible as methods on the
637             L<Data::Object::Opts> object through the C<opts> attribute. Each flag
638             definition can optionally declare C<args>, C<flag>, and C<type> values as
639             follows. The C<args> property denotes that multiple flags are permitted and its
640             value can be any valid L<Getopt::Long> I<repeat> specifier. The C<type>
641             property denotes the type of data allowed and defaults to type I<string>.
642             Allowed values are C<string>, C<number>, or C<float>. The C<flag> property
643             denotes the flag aliases and should be a pipe-delimited string, e.g.
644             C<userid|id|u>, if multiple aliases are used.
645              
646             =over 4
647              
648             =item spec example #1
649              
650             package Todolist::Task;
651              
652             use parent 'Data::Object::Cli';
653              
654             our $name = 'todotask {id}';
655              
656             # id accessible as $self->args->id; alias of $ARGV[0]
657              
658             sub spec {
659             {
660             #
661             # represented in Getopt::Long as
662             # title|t=s
663             #
664             # title is accessible as $self->opts->title
665             #
666             title => {
667             type => 'string',
668             flag => 't'
669             },
670             #
671             # represented in Getopt::Long as
672             # content=s
673             #
674             # content is accessible as $self->opts->content
675             #
676             content => {
677             type => 'string',
678             },
679             #
680             # represented in Getopt::Long as
681             # attach|a=s@
682             #
683             # attach is accessible as $self->opts->attach
684             #
685             attach => {
686             flag => 'a',
687             args => '@' # allow multiple options
688             }
689             }
690             }
691              
692             my $todotask = run Todolist::Task;
693              
694             # $todotask->spec
695              
696             =back
697              
698             =cut
699              
700             =head1 AUTHOR
701              
702             Al Newkirk, C<awncorp@cpan.org>
703              
704             =head1 LICENSE
705              
706             Copyright (C) 2011-2019, Al Newkirk, et al.
707              
708             This is free software; you can redistribute it and/or modify it under the terms
709             of the The Apache License, Version 2.0, as elucidated in the L<"license
710             file"|https://github.com/iamalnewkirk/data-object-cli/blob/master/LICENSE>.
711              
712             =head1 PROJECT
713              
714             L<Wiki|https://github.com/iamalnewkirk/data-object-cli/wiki>
715              
716             L<Project|https://github.com/iamalnewkirk/data-object-cli>
717              
718             L<Initiatives|https://github.com/iamalnewkirk/data-object-cli/projects>
719              
720             L<Milestones|https://github.com/iamalnewkirk/data-object-cli/milestones>
721              
722             L<Contributing|https://github.com/iamalnewkirk/data-object-cli/blob/master/CONTRIBUTE.md>
723              
724             L<Issues|https://github.com/iamalnewkirk/data-object-cli/issues>
725              
726             =cut