File Coverage

blib/lib/Data/Object/Cli.pm
Criterion Covered Total %
statement 150 165 90.9
branch 15 26 57.6
condition 9 15 60.0
subroutine 31 32 96.8
pod 9 15 60.0
total 214 253 84.5


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