File Coverage

blib/lib/App/Cmd/Setup.pm
Criterion Covered Total %
statement 81 82 98.7
branch 9 16 56.2
condition 5 6 83.3
subroutine 22 22 100.0
pod n/a
total 117 126 92.8


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   6117 use 5.020;
  6         18  
5 6     6   25 use warnings;
  6         10  
  6         144  
6 6     6   2506 use experimental qw(postderef postderef_qq);
  6         17871  
  6         28  
7              
8             package App::Cmd::Setup 0.334;
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 and L.
42             #pod
43             #pod = when writing abstract base classes for commands
44             #pod
45             #pod That is: when you write a subclass of L that is intended for
46             #pod other commands to use as their base class, you should use App::Cmd::Setup. For
47             #pod example, if you want all the commands in MyApp to inherit from MyApp::Command,
48             #pod you may want to write that package like this:
49             #pod
50             #pod package MyApp::Command;
51             #pod use App::Cmd::Setup -command;
52             #pod
53             #pod Do not confuse this with the way you will write specific commands:
54             #pod
55             #pod package MyApp::Command::mycmd;
56             #pod use MyApp -command;
57             #pod
58             #pod Again, this form mostly performs some validation and setup behind the scenes
59             #pod for you. You can use C> if you prefer.
60             #pod
61             #pod = when writing App::Cmd plugins
62             #pod
63             #pod L is a mechanism that allows an App::Cmd class to inject code
64             #pod into all its command classes, providing them with utility routines.
65             #pod
66             #pod To write a plugin, you must use App::Cmd::Setup. As seen above, you must also
67             #pod use App::Cmd::Setup to set up your App::Cmd subclass if you wish to consume
68             #pod plugins.
69             #pod
70             #pod For more information on writing plugins, see L and
71             #pod L.
72             #pod
73             #pod =end :list
74             #pod
75             #pod =cut
76              
77 6     6   4117 use App::Cmd ();
  6         14  
  6         142  
78 6     6   3042 use App::Cmd::Command ();
  6         13  
  6         116  
79 6     6   2750 use App::Cmd::Plugin ();
  6         14  
  6         99  
80 6     6   29 use Carp ();
  6         10  
  6         72  
81 6     6   21 use Data::OptList ();
  6         11  
  6         66  
82 6     6   2538 use String::RewritePrefix ();
  6         5466  
  6         147  
83              
84             # 0.06 is needed for load_optional_class
85 6     6   34 use Class::Load 0.06 qw();
  6         105  
  6         268  
86              
87 6         36 use Sub::Exporter -setup => {
88             -as => '_import',
89             exports => [ qw(foo) ],
90             collectors => [
91             -app => \'_make_app_class',
92             -command => \'_make_command_class',
93             -plugin => \'_make_plugin_class',
94             ],
95 6     6   27 };
  6         12  
96              
97             sub import {
98 11     11   2719 goto &_import;
99             }
100              
101 6     6   19 sub _app_base_class { 'App::Cmd' }
102              
103             sub _make_app_class {
104 6     6   508 my ($self, $val, $data) = @_;
105 6         10 my $into = $data->{into};
106              
107 6   100     31 $val ||= {};
108             Carp::confess "invalid argument to -app setup"
109 6 50       17 if grep { $_ ne 'plugins' } keys %$val;
  3         21  
110              
111 6 50       73 Carp::confess "app setup requested on App::Cmd subclass $into"
112             if $into->isa('App::Cmd');
113              
114 6         16 $self->_make_x_isa_y($into, $self->_app_base_class);
115              
116 6 100       35 if ( ! Class::Load::load_optional_class( $into->_default_command_base ) ) {
117 3         1671 my $base = $self->_command_base_class;
118             Sub::Install::install_sub({
119 6     6   13 code => sub { $base },
120 3         24 into => $into,
121             as => '_default_command_base',
122             });
123             }
124              
125             # TODO Check this is right. -- kentnl, 2010-12
126             #
127             # my $want_plugin_base = $self->_plugin_base_class;
128 5         394 my $want_plugin_base = 'App::Cmd::Plugin';
129              
130 5         8 my @plugins;
131 5   100     9 for my $plugin (@{ $val->{plugins} // [] }) {
  5         25  
132 3         17 $plugin = String::RewritePrefix->rewrite(
133             {
134             '' => 'App::Cmd::Plugin::',
135             '=' => ''
136             },
137             $plugin,
138             );
139 3         164 Class::Load::load_class( $plugin );
140 3 50       460 unless( $plugin->isa( $want_plugin_base ) ){
141 0         0 die "$plugin is not a " . $want_plugin_base;
142             }
143 3         10 push @plugins, $plugin;
144             }
145              
146             Sub::Install::install_sub({
147 5     5   14 code => sub { @plugins },
148 5         33 into => $into,
149             as => '_plugin_plugins',
150             });
151              
152 5         237 return 1;
153             }
154              
155 5     5   13 sub _command_base_class { 'App::Cmd::Command' }
156              
157             sub _make_command_class {
158 2     2   175 my ($self, $val, $data) = @_;
159 2         3 my $into = $data->{into};
160              
161 2 50       17 Carp::confess "command setup requested on App::Cmd::Command subclass $into"
162             if $into->isa('App::Cmd::Command');
163              
164 2         5 $self->_make_x_isa_y($into, $self->_command_base_class);
165              
166 2         7 return 1;
167             }
168              
169             sub _make_x_isa_y {
170 11     11   22 my ($self, $x, $y) = @_;
171              
172 6     6   5564 no strict 'refs';
  6         12  
  6         1700  
173 11         15 push @{"$x\::ISA"}, $y;
  11         127  
174             }
175              
176 3     3   7 sub _plugin_base_class { 'App::Cmd::Plugin' }
177             sub _make_plugin_class {
178 3     3   282 my ($self, $val, $data) = @_;
179 3         7 my $into = $data->{into};
180              
181 3 50       35 Carp::confess "plugin setup requested on App::Cmd::Plugin subclass $into"
182             if $into->isa('App::Cmd::Plugin');
183              
184 3 50       11 Carp::confess "plugin setup requires plugin configuration" unless $val;
185              
186 3         11 $self->_make_x_isa_y($into, $self->_plugin_base_class);
187              
188             # In this special case, exporting everything by default is the sensible thing
189             # to do. -- rjbs, 2008-03-31
190 3 50       16 $val->{groups} = [ default => [ -all ] ] unless $val->{groups};
191              
192 3         4 my @exports;
193 3         10 for my $pair (Data::OptList::mkopt($val->{exports})->@*) {
194 3   50     78 push @exports, $pair->[0], ($pair->[1] || \'_faux_curried_method');
195             }
196              
197 3         9 $val->{exports} = \@exports;
198              
199 3         25 Sub::Exporter::setup_exporter({
200             %$val,
201             into => $into,
202             as => 'import_from_plugin',
203             });
204              
205 3         758 return 1;
206             }
207              
208             1;
209              
210             __END__