File Coverage

blib/lib/Code/Style/Kit.pm
Criterion Covered Total %
statement 112 119 94.1
branch 19 26 73.0
condition 11 15 73.3
subroutine 19 19 100.0
pod 3 3 100.0
total 164 182 90.1


line stmt bran cond sub pod time code
1             package Code::Style::Kit;
2 2     2   112988 use strict;
  2         5  
  2         58  
3 2     2   11 use warnings;
  2         4  
  2         46  
4 2     2   881 use Data::OptList;
  2         14288  
  2         13  
5 2     2   952 use Import::Into;
  2         3116  
  2         103  
6 2     2   15 use Carp;
  2         5  
  2         102  
7 2     2   12 use mro ();
  2         5  
  2         30  
8 2     2   910 use Package::Stash;
  2         10512  
  2         71  
9 2     2   12 use Module::Runtime qw(use_module);
  2         4  
  2         12  
10              
11 2     2   101 use constant DEBUG => $ENV{CODE_STYLE_KIT_DEBUG};
  2         5  
  2         2760  
12              
13             our $VERSION = '1.0.3'; # VERSION
14             # ABSTRACT: build composable bulk exporters
15              
16              
17             sub import {
18 11     11   47550 my $class = shift;
19 11         25 my $caller = caller();
20              
21 11         42 my $self = $class->_new($caller,@_);
22 11         52 $self->_export_features;
23 9         440 return;
24             }
25              
26              
27             sub is_feature_requested {
28 4     4 1 17 my ($self, $feature) = @_;
29 4         22 return !! $self->{requested_features}{$feature};
30             }
31              
32              
33             sub also_export {
34 1     1 1 16 my ($self, $feature, $args) = @_;
35 1   50     6 local $self->{requested_features}{$feature} = $args || [];
36 1         6 $self->_export_one_feature($feature, 1);
37 1         3 return;
38             }
39              
40              
41             sub maybe_also_export {
42 5     5 1 21 my ($self, $feature, $args) = @_;
43 5   100     39 local $self->{requested_features}{$feature} = $args || [];
44 5         26 $self->_export_one_feature($feature, 0);
45 5         14 return;
46             }
47              
48             # private constructor, invoked from C
49             sub _new {
50 11     11   28 my ($class,$caller,@args) = @_;
51              
52             # get all the parts in C<@ISA> order
53 11         37 my $parent_stashes = $class->_get_parent_stashes;
54             # get the features they provide by default
55 11         40 my %feature_set = map { $_ => [] } $class->_default_features($parent_stashes);
  8         34  
56              
57 11         56 my $args = Data::OptList::mkopt(\@args,{
58             moniker => $class,
59             must_be => 'ARRAY',
60             require_unique => 1,
61             });
62             # interpret the import arguments
63 11         572 for my $arg (@{$args}) {
  11         21  
64 10         19 my ($key, $value) = @{$arg};
  10         21  
65              
66 10 50       59 if ($key =~ /^-(\w+)$/) {
    50          
67 0 0       0 if ($value) {
68 0         0 croak "providing import arguments (@{$value}) when removing a feature ($1) makes no sense";
  0         0  
69             }
70 0         0 print STDERR "$class - removing feature $1\n" if DEBUG;
71 0         0 delete $feature_set{$1};
72             }
73             elsif ($key =~ /^\w+$/) {
74 10         20 print STDERR "$class - adding feature $key\n" if DEBUG;
75 10   100     43 $feature_set{$key} = $value // [];
76             }
77             else {
78 0         0 croak "malformed feature <$key> when importing $class";
79             }
80             }
81              
82             # build the instance
83 11         54 return bless {
84             caller => $caller,
85             feature_list => [ $class->_sort_features(keys %feature_set) ],
86             requested_features => \%feature_set,
87             exported_features => {},
88             # we save this, so ->_export_one_feature doesn't have to scan
89             # the parts again
90             parent_stashes => $parent_stashes,
91             }, $class;
92             }
93              
94             sub _export_features {
95 11     11   21 my ($self) = @_;
96              
97 11         15 for my $feature (@{$self->{feature_list}}) {
  11         37  
98 16         36 $self->_export_one_feature($feature, 1);
99             }
100             }
101              
102             # all the magic is from here to the end
103              
104             # the actual exporting
105             sub _export_one_feature {
106 22     22   44 my ($self, $feature, $croak_if_not_implemented) = @_;
107 22         40 my $class = ref($self);
108 22 50       30 my @import_args = @{ $self->{requested_features}{$feature} || [] };
  22         64  
109              
110 22         29 print STDERR "$class - exporting $feature to $self->{caller} with arguments (@import_args)\n"
111             if DEBUG;
112              
113             # do nothing if we've exported it already
114 22 50       49 return if $self->{exported_features}{$feature};
115              
116 22         46 my $list_method = "feature_${feature}_export_list";
117 22         40 my $direct_method = "feature_${feature}_export";
118              
119 22         41 my $arguments_method = "feature_${feature}_takes_arguments";
120 22   66     119 my $takes_arguments = $self->can($arguments_method) && $self->$arguments_method;
121 22 100 100     67 if (@import_args && !$takes_arguments) {
122 1         212 croak "feature $feature does not take arguments, but (@import_args) were provided";
123             }
124              
125 21         33 my $provided = 0;
126             # loop over the parts in @ISA order
127 21         30 for my $parent_stash (@{$self->{parent_stashes}}) {
  21         41  
128 110         219 my $parent_class = $parent_stash->name;
129 110         140 my $method_ref;
130             # does this part provide a *_export_list sub?
131 110 100       681 if ($method_ref = $parent_stash->get_symbol("&$list_method")) {
    100          
132 1         2 print STDERR " calling ${parent_class}->$list_method\n"
133             if DEBUG;
134             # import all the packages that the sub returns
135 1         3 for my $module ($self->$method_ref) {
136 1         6 use_module($module)->import::into($self->{caller}, @import_args);
137             }
138 1         638 $provided = 1;
139             }
140             # does this part provide a *_export sub?
141             elsif ($method_ref = $parent_stash->get_symbol("&$direct_method")) {
142 18         24 print STDERR " calling ${parent_class}->$direct_method\n"
143             if DEBUG;
144             # call it and let it do whatever it needs to
145 18         58 $self->$method_ref($self->{caller},@import_args);
146 17         7222 $provided = 1;
147             }
148             }
149              
150             # did we find the feature?
151 20 100       44 if ($provided) {
    50          
152             # mark it as exported
153 18         38 $self->{exported_features}{$feature} = 1;
154             }
155             elsif ($croak_if_not_implemented) {
156             # croak if asked to
157 0         0 croak "feature <$feature> is not implemented by $class";
158             }
159              
160 20         56 return;
161             }
162              
163             # use the *_order subs
164             sub _sort_features {
165 11     11   28 my ($class, @features) = @_;
166              
167             my %feature_sort_key = map {
168 11         20 my $method = "feature_${_}_order";
  18         39  
169 18 100       133 $_ => ( $class->can($method) ? $class->$method : 100 )
170             } @features;
171              
172             @features = sort {
173 11         40 $feature_sort_key{$a} <=> $feature_sort_key{$b}
  8         23  
174             } @features;
175              
176 11         22 print "$class - sorted features: (@features)\n" if DEBUG;
177              
178 11         83 return @features;
179             }
180              
181             # use the *_default subs
182             sub _default_features {
183 11     11   26 my ($class, $parent_stashes) = @_;
184              
185 11         16 my @features;
186             # loop over the parts in @ISA order
187 11         13 for my $parent_stash (@{$parent_stashes}) {
  11         27  
188 59         376 my @subs = $parent_stash->list_all_symbols('CODE');
189 59         122 for my $sub (@subs) {
190             # we only care about sub names of this form
191 286 100       589 next unless $sub =~ /^feature_(\w+)_default$/;
192              
193 32         71 my $feature = $1;
194 32         103 my $is_default = $class->$sub;
195              
196 32         98 if (DEBUG) {
197             my $parent_class = $parent_stash->name;
198             print STDERR "$class - $parent_class provides $feature, by default ",
199             ($is_default ? 'enabled' : 'disabled' ),
200             "\n";
201             }
202              
203 32 100       79 push @features, $feature if $is_default;
204             }
205             }
206              
207 11         30 return @features;
208             }
209              
210             sub _get_parent_stashes {
211 11     11   22 my ($class) = @_;
212              
213 11   33     48 $class = ref($class) || $class;
214 11         17 return [ map { Package::Stash->new($_) } @{ mro::get_linear_isa($class) } ];
  59         314  
  11         44  
215             }
216              
217             1;
218              
219             __END__