File Coverage

blib/lib/App/Cmd/Setup.pm
Criterion Covered Total %
statement 86 87 98.8
branch 11 18 61.1
condition 5 6 83.3
subroutine 23 23 100.0
pod n/a
total 125 134 93.2


line stmt bran cond sub pod time code
1             # The "experimental" below is not actually scary. The feature went on to be
2             # de-experimental-ized with no changes and is now on by default in perl v5.24
3             # and later. -- rjbs, 2021-03-14
4 6     6   8848 use 5.020;
  6         20  
5 6     6   32 use warnings;
  6         11  
  6         170  
6 6     6   2667 use experimental qw(postderef postderef_qq);
  6         20891  
  6         31  
7              
8             package App::Cmd::Setup 0.336;
9              
10             # ABSTRACT: helper for setting up App::Cmd classes
11              
12             #pod =head1 OVERVIEW
13             #pod
14             #pod App::Cmd::Setup is a helper library, used to set up base classes that will be
15             #pod used as part of an App::Cmd program. For the most part you should refer to
16             #pod L for how you should use this library.
17             #pod
18             #pod This class is useful in three scenarios:
19             #pod
20             #pod =begin :list
21             #pod
22             #pod = when writing your App::Cmd subclass
23             #pod
24             #pod Instead of writing:
25             #pod
26             #pod package MyApp;
27             #pod use parent 'App::Cmd';
28             #pod
29             #pod ...you can write:
30             #pod
31             #pod package MyApp;
32             #pod use App::Cmd::Setup -app;
33             #pod
34             #pod The benefits of doing this are mostly minor, and relate to sanity-checking your
35             #pod class. The significant benefit is that this form allows you to specify
36             #pod plugins, as in:
37             #pod
38             #pod package MyApp;
39             #pod use App::Cmd::Setup -app => { plugins => [ 'Prompt' ] };
40             #pod
41             #pod Plugins are described in L.
42             #pod
43             #pod Doing this also allows you to override the default configuration passed to
44             #pod L. By default, this configuration includes C,
45             #pod which allows subdispatch to work correctly. If you are not using subdispatch,
46             #pod and want your command to exit on unknown options, you can say:
47             #pod
48             #pod package MyApp;
49             #pod use App::Cmd::Setup -app => { getopt_conf => [] };
50             #pod
51             #pod = when writing abstract base classes for commands
52             #pod
53             #pod That is: when you write a subclass of L that is intended for
54             #pod other commands to use as their base class, you should use App::Cmd::Setup. For
55             #pod example, if you want all the commands in MyApp to inherit from MyApp::Command,
56             #pod you may want to write that package like this:
57             #pod
58             #pod package MyApp::Command;
59             #pod use App::Cmd::Setup -command;
60             #pod
61             #pod Do not confuse this with the way you will write specific commands:
62             #pod
63             #pod package MyApp::Command::mycmd;
64             #pod use MyApp -command;
65             #pod
66             #pod Again, this form mostly performs some validation and setup behind the scenes
67             #pod for you. You can use C> if you prefer.
68             #pod
69             #pod = when writing App::Cmd plugins
70             #pod
71             #pod L is a mechanism that allows an App::Cmd class to inject code
72             #pod into all its command classes, providing them with utility routines.
73             #pod
74             #pod To write a plugin, you must use App::Cmd::Setup. As seen above, you must also
75             #pod use App::Cmd::Setup to set up your App::Cmd subclass if you wish to consume
76             #pod plugins.
77             #pod
78             #pod For more information on writing plugins, see L and
79             #pod L.
80             #pod
81             #pod =end :list
82             #pod
83             #pod =cut
84              
85 6     6   4441 use App::Cmd ();
  6         14  
  6         137  
86 6     6   2846 use App::Cmd::Command ();
  6         20  
  6         118  
87 6     6   2593 use App::Cmd::Plugin ();
  6         14  
  6         116  
88 6     6   32 use Carp ();
  6         11  
  6         78  
89 6     6   24 use Data::OptList ();
  6         12  
  6         121  
90 6     6   2611 use String::RewritePrefix ();
  6         5883  
  6         159  
91              
92             # 0.06 is needed for load_optional_class
93 6     6   40 use Class::Load 0.06 qw();
  6         111  
  6         333  
94              
95 6         46 use Sub::Exporter -setup => {
96             -as => '_import',
97             exports => [ qw(foo) ],
98             collectors => [
99             -app => \'_make_app_class',
100             -command => \'_make_command_class',
101             -plugin => \'_make_plugin_class',
102             ],
103 6     6   32 };
  6         11  
104              
105             sub import {
106 11     11   3107 goto &_import;
107             }
108              
109 6     6   19 sub _app_base_class { 'App::Cmd' }
110              
111             my %valid_keys = map {; $_ => 1 } qw(plugins getopt_conf);
112              
113             sub _make_app_class {
114 6     6   565 my ($self, $val, $data) = @_;
115 6         12 my $into = $data->{into};
116              
117 6   100     41 $val ||= {};
118             Carp::confess "invalid argument to -app setup"
119 6 50       22 if grep { ! $valid_keys{$_} } keys %$val;
  4         17  
120              
121 6 50       78 Carp::confess "app setup requested on App::Cmd subclass $into"
122             if $into->isa('App::Cmd');
123              
124 6         19 $self->_make_x_isa_y($into, $self->_app_base_class);
125              
126 6 100       35 if ( ! Class::Load::load_optional_class( $into->_default_command_base ) ) {
127 3         1465 my $base = $self->_command_base_class;
128             Sub::Install::install_sub({
129 6     6   15 code => sub { $base },
130 3         23 into => $into,
131             as => '_default_command_base',
132             });
133             }
134              
135             # TODO Check this is right. -- kentnl, 2010-12
136             #
137             # my $want_plugin_base = $self->_plugin_base_class;
138 5         415 my $want_plugin_base = 'App::Cmd::Plugin';
139              
140 5         10 my @plugins;
141 5   100     9 for my $plugin (@{ $val->{plugins} // [] }) {
  5         28  
142 3         15 $plugin = String::RewritePrefix->rewrite(
143             {
144             '' => 'App::Cmd::Plugin::',
145             '=' => ''
146             },
147             $plugin,
148             );
149 3         184 Class::Load::load_class( $plugin );
150 3 50       475 unless( $plugin->isa( $want_plugin_base ) ){
151 0         0 die "$plugin is not a " . $want_plugin_base;
152             }
153 3         12 push @plugins, $plugin;
154             }
155              
156             Sub::Install::install_sub({
157 5     5   14 code => sub { @plugins },
158 5         34 into => $into,
159             as => '_plugin_plugins',
160             });
161              
162 5 100       285 if ($val->{getopt_conf}) {
163 1         2 my @getopt_conf = @{ $val->{getopt_conf} };
  1         2  
164              
165             Sub::Install::install_sub({
166 3     3   19 code => sub { return [ @getopt_conf ] },
167 1         4 into => $into,
168             as => '_getopt_conf',
169             });
170             }
171              
172 5         60 return 1;
173             }
174              
175 5     5   17 sub _command_base_class { 'App::Cmd::Command' }
176              
177             sub _make_command_class {
178 2     2   231 my ($self, $val, $data) = @_;
179 2         5 my $into = $data->{into};
180              
181 2 50       20 Carp::confess "command setup requested on App::Cmd::Command subclass $into"
182             if $into->isa('App::Cmd::Command');
183              
184 2         6 $self->_make_x_isa_y($into, $self->_command_base_class);
185              
186 2         9 return 1;
187             }
188              
189             sub _make_x_isa_y {
190 11     11   22 my ($self, $x, $y) = @_;
191              
192 6     6   7328 no strict 'refs';
  6         12  
  6         2026  
193 11         15 push @{"$x\::ISA"}, $y;
  11         163  
194             }
195              
196 3     3   9 sub _plugin_base_class { 'App::Cmd::Plugin' }
197             sub _make_plugin_class {
198 3     3   293 my ($self, $val, $data) = @_;
199 3         6 my $into = $data->{into};
200              
201 3 50       33 Carp::confess "plugin setup requested on App::Cmd::Plugin subclass $into"
202             if $into->isa('App::Cmd::Plugin');
203              
204 3 50       9 Carp::confess "plugin setup requires plugin configuration" unless $val;
205              
206 3         8 $self->_make_x_isa_y($into, $self->_plugin_base_class);
207              
208             # In this special case, exporting everything by default is the sensible thing
209             # to do. -- rjbs, 2008-03-31
210 3 50       20 $val->{groups} = [ default => [ -all ] ] unless $val->{groups};
211              
212 3         7 my @exports;
213 3         25 for my $pair (Data::OptList::mkopt($val->{exports})->@*) {
214 3   50     101 push @exports, $pair->[0], ($pair->[1] || \'_faux_curried_method');
215             }
216              
217 3         8 $val->{exports} = \@exports;
218              
219 3         18 Sub::Exporter::setup_exporter({
220             %$val,
221             into => $into,
222             as => 'import_from_plugin',
223             });
224              
225 3         922 return 1;
226             }
227              
228             1;
229              
230             __END__