File Coverage

blib/lib/Bolts/Util.pm
Criterion Covered Total %
statement 94 104 90.3
branch 37 54 68.5
condition 15 28 53.5
subroutine 12 12 100.0
pod 4 4 100.0
total 162 202 80.2


line stmt bran cond sub pod time code
1             package Bolts::Util;
2             $Bolts::Util::VERSION = '0.143171';
3             # ABSTRACT: Utilities helpful for use with Bolts
4              
5 11     11   31375 use Moose ();
  11         692582  
  11         234  
6 11     11   49 use Moose::Exporter;
  11         12  
  11         80  
7              
8 11     11   4122 use Bolts::Locator;
  11         114453  
  11         395  
9 11     11   88 use Moose::Util;
  11         16  
  11         102  
10 11     11   2005 use Safe::Isa;
  11         23  
  11         1356  
11 11     11   7356 use Hash::Util::FieldHash 'fieldhash';
  11         9032  
  11         617  
12              
13 11     11   3758 use Bolts::Meta::Initializer;
  11         2871  
  11         11766  
14              
15             Moose::Exporter->setup_import_methods(
16             as_is => [ qw( bolts_init locator_for meta_locator_for ) ],
17             );
18              
19             fieldhash my %locator;
20             fieldhash my %meta_locator;
21              
22              
23             sub _injector {
24 38     38   74 my ($meta, $where, $type, $key, $params) = @_;
25              
26 38         37 my %params;
27              
28 38 50 33     106 if ($params->$_can('does') and $params->$_does('Bolts::Blueprint')) {
29 0         0 %params = { blueprint => $params };
30             }
31             else {
32 38         348 %params = %$params;
33             }
34              
35 38 50 33     100 Carp::croak("invalid blueprint in $where $key")
36             unless $params{blueprint}->$_can('does')
37             and $params{blueprint}->$_does('Bolts::Blueprint::Role::Injector');
38              
39 38 100       3521 $params{isa} = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($params{isa})
40             if defined $params{isa};
41 38 50       4203 $params{does} = Moose::Util::TypeConstraints::find_or_create_does_type_constraint($params{does})
42             if defined $params{does};
43              
44 38         71 $params{key} = $key;
45              
46 38         142 return $meta->acquire('injector', $type, \%params);
47             }
48              
49             # TODO This sugar requires special knowledge of the built-in blueprint
50             # types. It would be slick if this was not required. On the other hand, that
51             # sounds like very deep magic and that might just be taking the magic too far.
52             sub artifact {
53 45     45 1 65 my $meta = shift;
54 45         62 my $name = shift;
55              
56             # No arguments means it's acquired with given parameters
57 45         55 my $blueprint_name;
58             my %params;
59 45 100       161 if (@_ == 0) {
    100          
60 2         4 $blueprint_name = 'acquired';
61 2         8 $params{path} = [ "__auto_$name" ];
62 2         23 $meta->add_attribute("__auto_$name" =>
63             is => 'ro',
64             init_arg => $name,
65             );
66             }
67              
68             # One argument means it's a literal or an artifact object
69             elsif (@_ == 1) {
70              
71             # If it is an artifact, just return it as is
72 6 100 66     23 return { $name => $_[0] }
73             if $_[0]->$_can('does') && $_[0]->$_does('Bolts::Role::Artifact');
74              
75 3         29 $blueprint_name = 'literal';
76 3         7 $params{value} = $_[0];
77             }
78              
79             # Otherwise, we gotta figure out what it is...
80             else {
81 37         105 %params = @_;
82              
83             # Is the service class named?
84 37 50 66     336 if (defined $params{blueprint}) {
    100          
    50          
    100          
    50          
85 0         0 $blueprint_name = delete $params{blueprint};
86             }
87              
88             # Is it an acquired?
89             elsif (defined $params{path} && $params{path}) {
90 2         4 $blueprint_name = 'acquired';
91              
92 2 50       8 $params{path} = [ $params{path} ] unless ref $params{path} eq 'ARRAY';
93              
94 2         3 my @path = ('__top', @{ $params{path} });
  2         4  
95              
96 2         5 $params{path} = \@path;
97             }
98              
99             # Is it a literal?
100             elsif (exists $params{value}) {
101 0         0 $blueprint_name = 'literal';
102             }
103              
104             # Is it a factory blueprint?
105             elsif (defined $params{class}) {
106 12         18 $blueprint_name = 'factory';
107             }
108              
109             # Is it a builder blueprint?
110             elsif (defined $params{builder}) {
111 23         36 $blueprint_name = 'built';
112             }
113              
114             else {
115 0         0 Carp::croak("unable to determine what kind of service $name is in ", $meta->name);
116             }
117             }
118              
119 42         4556 my @injectors;
120 42 100       104 if (defined $params{parameters}) {
121 6         20 my $parameters = delete $params{parameters};
122              
123 6 50       22 if ($parameters->$_does('Bolts::Blueprint')) {
    100          
    50          
124 0         0 push @injectors, _injector(
125             $meta, 'parameters', 'parameter_position',
126             '0', { blueprint => $parameters },
127             );
128             }
129             elsif (ref $parameters eq 'HASH') {
130 5         51 for my $key (keys %$parameters) {
131 12         38 push @injectors, _injector(
132             $meta, 'parameters', 'parameter_name',
133             $key, $parameters->{$key},
134             );
135             }
136             }
137             elsif (ref $parameters eq 'ARRAY') {
138 1         11 my $key = 0;
139 1         1 for my $params (@$parameters) {
140 3         9 push @injectors, _injector(
141             $meta, 'parameters', 'parameter_position',
142             $key++, $params,
143             );
144             }
145             }
146             else {
147 0         0 Carp::croak("parameters must be a blueprint, an array of blueprints, or a hash with blueprint values");
148             }
149             }
150              
151 42 100       112 if (defined $params{setters}) {
152 1         3 my $setters = delete $params{setters};
153              
154 1         4 for my $key (keys %$setters) {
155 1         3 push @injectors, _injector(
156             $meta, 'setters', 'setter',
157             $key, $setters->{$key},
158             );
159             }
160             }
161              
162 42 100       108 if (defined $params{indexes}) {
163 1         3 my $indexes = delete $params{indexes};
164              
165 1         5 while (my ($index, $def) = splice @$indexes, 0, 2) {
166 4 50 33     29 if (!Scalar::Util::blessed($def) && Scalar::Util::reftype($def) eq 'HASH') {
167 4   33     16 $def->{position} //= $index;
168             }
169              
170 4         7 push @injectors, _injector(
171             $meta, 'indexes', 'store_array',
172             $index, $def,
173             );
174             }
175             }
176              
177 42 100       105 if (defined $params{push}) {
178 9         24 my $push = delete $params{push};
179              
180 9         11 my $i = 0;
181 9         20 for my $def (@$push) {
182 14   66     51 my $key = $def->{key} // $i;
183              
184 14         103 push @injectors, _injector(
185             $meta, 'push', 'store_array',
186             $key, $def,
187             );
188              
189 14         52 $i++;
190             }
191             }
192              
193 42 100       125 if (defined $params{keys}) {
194 1         6 my $keys = delete $params{keys};
195              
196 1         5 for my $key (keys %$keys) {
197 4         12 push @injectors, _injector(
198             $meta, 'keys', 'store_hash',
199             $key, $keys->{$key},
200             );
201             }
202             }
203              
204             # TODO Remember the service for introspection
205              
206 42   100     199 my $scope_name = delete $params{scope} // '_';
207 42   100     160 my $infer = delete $params{infer} // 'none';
208              
209 42         205 my $scope = $meta->acquire('scope', $scope_name);
210              
211 42         171 my $blueprint = $meta->acquire('blueprint', $blueprint_name, \%params);
212              
213             return {
214 42         1200 $name => Bolts::Artifact->new(
215             meta_locator => $meta,
216             name => $name,
217             blueprint => $blueprint,
218             scope => $scope,
219             infer => $infer,
220             injectors => \@injectors,
221             ),
222             };
223             }
224              
225              
226             sub locator_for {
227 231     231 1 261 my ($bag) = @_;
228              
229 231 50       510 if ($bag->$_does('Bolts::Role::Locator')) {
    0          
230 231         9869 return $bag;
231             }
232             elsif (defined $locator{ $bag }) {
233 0         0 return $locator{ $bag };
234             }
235             else {
236 0         0 return $locator{ $bag } = Bolts::Locator->new($bag);
237             }
238             }
239              
240              
241             sub meta_locator_for {
242 57     57 1 69 my ($bag) = @_;
243              
244 57         151 my $meta = Moose::Util::find_meta($bag);
245 57 50       431 if (defined $meta) {
    0          
246 57         97 my $meta_meta = Moose::Util::find_meta($meta);
247 57 50 33     374 if ($meta_meta->$_can('does_role') && $meta_meta->does_role('Bolts::Meta::Class::Trait::Locator')) {
248 57         5928 return $meta->locator;
249             }
250             }
251              
252             elsif (defined $meta_locator{ $bag }) {
253 0         0 return $meta_locator{ $bag };
254             }
255              
256 0         0 return $meta_locator{ $bag } = $Bolts::GLOBAL_FALLBACK_META_LOCATOR->new;
257             }
258              
259              
260 2     2 1 4014 sub bolts_init { Bolts::Meta::Initializer->new(@_) }
261              
262             1;
263              
264             __END__
265              
266             =pod
267              
268             =encoding UTF-8
269              
270             =head1 NAME
271              
272             Bolts::Util - Utilities helpful for use with Bolts
273              
274             =head1 VERSION
275              
276             version 0.143171
277              
278             =head1 SYNOPSIS
279              
280             use Bolts::Util qw( bolts_init locator_for meta_locator_for );
281              
282             my $loc = locator_for($bag);
283             my $thing = $loc->acquire('path', 'to', 'thing');
284              
285             my $metaloc = meta_locator_for($bag);
286             my $blueprint = $metaloc->acquire('blueprint', 'factory', {
287             class => 'MyApp::Thing',
288             method => 'fetch',
289             });
290              
291             # See Bolts::Role::Initializer for a better synopsis
292             my $obj = MyApp::Thing->new(
293             foo => bolts_init('path', 'to', 'foo'),
294             );
295              
296             =head1 DESCRIPTION
297              
298             This provides some helpful utility methods for use with Bolts.
299              
300             =head1 EXPORTED FUNCTIONS
301              
302             =head2 artifact
303              
304             my %artifact = %{ artifact($bag, $name, %definition) };
305              
306             # For example:
307             my %artifact = %{ artifact($bag, thing => ( class => 'MyApp::Thing' ) ) };
308              
309             This contains the internal implementation for building L<Bolt::Artifact> objects used by the sugar methods in L<Bolts> and L<Bolts::Role>. See the documentation L<there|Bolts/artifact> for more details on how to call it.
310              
311             The C<$bag> must be the metaclass or reference to which the artifact is being attached. The C<$name> is the name to give the artifact and teh C<%definition> is the remainder of the definition.
312              
313             This function returns a hash with a single key, which is the name of the artifact. The value on that key is an object that implements L<Bolts::Role::Artifact>.
314              
315             =head2 locator_for
316              
317             my $loc = locator_for($bag);
318              
319             Given a bag, it will return a L<Bolts::Role::Locator> for acquiring artifacts from it. If the bag provides it's own locator, the bag will be returned. If it doesn't (e.g., if it's a hash or an array or just some other object that doesn't have a locator built-in), then a new locator will be built to locate within the bag and returned on the first call. Subsequent calls using the same reference will return the same locator object.
320              
321             =head2 meta_locator_for
322              
323             my $metaloc = meta_locator_for($bag);
324              
325             Attempts to find the meta locator for the bag. It returns a L<Bolts::Role::Locator> that is able to return artifacts used to manage a collection of bolts bags and artifacts. If the bag itself does not have such a locator associated with it, one is constructed using the L<Bolts/$Bolts::GLOBAL_FALLBACK_META_LOCATOR> class, which is L<Bolts::Meta::Locator> by default. After the first call, the object created the first time for each reference will be reused.
326              
327             =head2 bolts_init
328              
329             my $init = bolts_init(@path, \%params);
330              
331             This is shorthand for:
332              
333             my $init = Bolts::Meta::Initializer->new(@path, \%params);
334              
335             This returns an initializer object that may be used with L<Bolts::Role::Initializer> to automatically initialize attributes from a built-in locator.
336              
337             =head1 AUTHOR
338              
339             Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
340              
341             =head1 COPYRIGHT AND LICENSE
342              
343             This software is copyright (c) 2014 by Qubling Software LLC.
344              
345             This is free software; you can redistribute it and/or modify it under
346             the same terms as the Perl 5 programming language system itself.
347              
348             =cut