File Coverage

blib/lib/Bolts/Util.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


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