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   2935224 use 5.020;
  6         24  
5 6     6   30 use warnings;
  6         10  
  6         331  
6 6     6   3052 use experimental qw(postderef postderef_qq);
  6         10760  
  6         36  
7              
8             package App::Cmd::Setup 0.340;
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   5153 use App::Cmd ();
  6         24  
  6         186  
86 6     6   3636 use App::Cmd::Command ();
  6         20  
  6         145  
87 6     6   2816 use App::Cmd::Plugin ();
  6         19  
  6         134  
88 6     6   32 use Carp ();
  6         11  
  6         81  
89 6     6   25 use Data::OptList ();
  6         12  
  6         77  
90 6     6   3100 use String::RewritePrefix ();
  6         7125  
  6         225  
91              
92             # 0.06 is needed for load_optional_class
93 6     6   37 use Class::Load 0.06 qw();
  6         136  
  6         421  
94              
95 6         50 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   30 };
  6         10  
104              
105             sub import {
106 11     11   3941 goto &_import;
107             }
108              
109 6     6   23 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   605 my ($self, $val, $data) = @_;
115 6         14 my $into = $data->{into};
116              
117 6   100     36 $val ||= {};
118             Carp::confess "invalid argument to -app setup"
119 6 50       19 if grep { ! $valid_keys{$_} } keys %$val;
  4         15  
120              
121 6 50       99 Carp::confess "app setup requested on App::Cmd subclass $into"
122             if $into->isa('App::Cmd');
123              
124 6         22 $self->_make_x_isa_y($into, $self->_app_base_class);
125              
126 6 100       55 if ( ! Class::Load::load_optional_class( $into->_default_command_base ) ) {
127 3         2030 my $base = $self->_command_base_class;
128             Sub::Install::install_sub({
129 6     6   16 code => sub { $base },
130 3         27 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         482 my $want_plugin_base = 'App::Cmd::Plugin';
139              
140 5         13 my @plugins;
141 5   100     11 for my $plugin (@{ $val->{plugins} // [] }) {
  5         30  
142 3         22 $plugin = String::RewritePrefix->rewrite(
143             {
144             '' => 'App::Cmd::Plugin::',
145             '=' => ''
146             },
147             $plugin,
148             );
149 3         208 Class::Load::load_class( $plugin );
150 3 50       494 unless( $plugin->isa( $want_plugin_base ) ){
151 0         0 die "$plugin is not a " . $want_plugin_base;
152             }
153 3         11 push @plugins, $plugin;
154             }
155              
156             Sub::Install::install_sub({
157 5     5   17 code => sub { @plugins },
158 5         43 into => $into,
159             as => '_plugin_plugins',
160             });
161              
162 5 100       294 if ($val->{getopt_conf}) {
163 1         2 my @getopt_conf = @{ $val->{getopt_conf} };
  1         2  
164              
165             Sub::Install::install_sub({
166 3     3   38 code => sub { return [ @getopt_conf ] },
167 1         4 into => $into,
168             as => '_getopt_conf',
169             });
170             }
171              
172 5         45 return 1;
173             }
174              
175 5     5   14 sub _command_base_class { 'App::Cmd::Command' }
176              
177             sub _make_command_class {
178 2     2   187 my ($self, $val, $data) = @_;
179 2         6 my $into = $data->{into};
180              
181 2 50       23 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         7 return 1;
187             }
188              
189             sub _make_x_isa_y {
190 11     11   21 my ($self, $x, $y) = @_;
191              
192 6     6   8891 no strict 'refs';
  6         12  
  6         2836  
193 11         19 push @{"$x\::ISA"}, $y;
  11         150  
194             }
195              
196 3     3   18 sub _plugin_base_class { 'App::Cmd::Plugin' }
197             sub _make_plugin_class {
198 3     3   283 my ($self, $val, $data) = @_;
199 3         20 my $into = $data->{into};
200              
201 3 50       31 Carp::confess "plugin setup requested on App::Cmd::Plugin subclass $into"
202             if $into->isa('App::Cmd::Plugin');
203              
204 3 50       8 Carp::confess "plugin setup requires plugin configuration" unless $val;
205              
206 3         17 $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       23 $val->{groups} = [ default => [ -all ] ] unless $val->{groups};
211              
212 3         25 my @exports;
213 3         29 for my $pair (Data::OptList::mkopt($val->{exports})->@*) {
214 3   50     105 push @exports, $pair->[0], ($pair->[1] || \'_faux_curried_method');
215             }
216              
217 3         8 $val->{exports} = \@exports;
218              
219 3         34 Sub::Exporter::setup_exporter({
220             %$val,
221             into => $into,
222             as => 'import_from_plugin',
223             });
224              
225 3         942 return 1;
226             }
227              
228             1;
229              
230             __END__