File Coverage

blib/lib/MasonX/Component/Registry.pm
Criterion Covered Total %
statement 36 164 21.9
branch 0 56 0.0
condition n/a
subroutine 12 31 38.7
pod 18 18 100.0
total 66 269 24.5


line stmt bran cond sub pod time code
1             package MasonX::Component::Registry;
2              
3 1     1   2105 use strict;
  1         2  
  1         48  
4 1     1   6 use warnings;
  1         2  
  1         46  
5 1     1   6 use Carp;
  1         1  
  1         248  
6 1     1   939 use Clone ();
  1         18101  
  1         190  
7 1     1   1419 use IO::Dir;
  1         31582  
  1         60  
8 1     1   10 use IO::File;
  1         2  
  1         244  
9 1     1   2005 use Hash::Merge;
  1         6323  
  1         80  
10 1     1   14 use File::Path ();
  1         2  
  1         17  
11 1     1   1205 use File::ShareDir ();
  1         3503  
  1         29  
12 1     1   9 use Class::Inspector;
  1         2  
  1         31  
13 1     1   1200 use File::Spec::Functions qw/catdir catfile/;
  1         1001  
  1         2096  
14              
15             =head1 METHODS
16              
17             =head2 register_component
18              
19             =over 4
20              
21             =item Arguments: $component_name, \%attributes
22              
23             =item Return Value:
24              
25             =back
26              
27             =cut
28              
29             sub register_component {
30 0     0 1   my ($self, $component, $attrs) = @_;
31              
32 0           my $component_path = $self->build_config_path($component);
33              
34 0 0         if (-f $component_path) {
35 0           croak "Component $component already registered";
36             }
37              
38 0 0         my $fh = IO::File->new($component_path, 'w')
39             or croak "Failed to open component config `$component_path' for writing: $!";
40              
41 0           eval {
42 0           $self->writer->write_handle({
43             $component => $attrs,
44             }, $fh);
45             };
46              
47 0 0         if (my $error = $@) {
48 0           $fh->close;
49 0           unlink $component_path;
50              
51 0           croak $error;
52             }
53              
54 0           $fh->close;
55              
56 0           return;
57             }
58              
59             =head2 install_component
60              
61             =over 4
62              
63             =item Arguments: $component_name, \%attrs
64              
65             =item Return Value: none
66              
67             =back
68              
69             =cut
70              
71             sub install_component {
72 0     0 1   my ($self, $component, $attrs) = @_;
73              
74 0           eval {
75 0           $self->unregister_component($component);
76             };
77              
78             #TODO: better error handling
79              
80 0           $self->register_component($component, $attrs);
81              
82 0           return;
83             }
84              
85             =head2 set_component_attributes
86              
87             =over 4
88              
89             =item Arguments: $component_name, \%attributes
90              
91             =item Return Value: none
92              
93             =back
94              
95             =cut
96              
97             sub set_component_attributes {
98 0     0 1   my ($self, $component, $attrs) = @_;
99              
100 0           my $overrides = $self->get_overrides;
101 0           my $new_overrides = Hash::Merge::merge({ $component => $attrs }, $overrides);
102              
103 0           eval {
104 0           $self->set_overrides($new_overrides);
105             };
106              
107 0 0         if (my $error = $@) {
108 0           eval {
109 0           $self->set_overrides($overrides);
110             };
111              
112 0 0         croak $@ if $@;
113             }
114              
115 0           return;
116             }
117              
118             =head2 unset_component_attributes
119              
120             =over 4
121              
122             =item Arguments: $component_name, \@attribute_names
123              
124             =item Return Value: none
125              
126             =back
127              
128             =cut
129              
130             sub unset_component_attributes {
131 0     0 1   my ($self, $component, $keys) = @_;
132              
133 0           my $overrides = $self->get_overrides;
134              
135 0 0         if (exists $overrides->{$component}) {
136 0           my $new_overrides = Clone::clone($overrides);
137              
138 0           delete $new_overrides->{$component}->{$_}
139 0           for @{ $keys };
140              
141 0           eval {
142 0           $self->set_overrides($new_overrides);
143             };
144              
145 0 0         if (my $error = $@) {
146 0           eval {
147 0           $self->set_overrides($overrides);
148             };
149              
150 0 0         croak $@ if $@;
151             }
152             }
153              
154 0           return;
155             }
156              
157             =head2 get_overrides
158              
159             =over 4
160              
161             =item Arguments: none
162              
163             =item Return Value: \%attributes
164              
165             =back
166              
167             =cut
168              
169             sub get_overrides {
170 0     0 1   my ($self) = @_;
171              
172             return +{}
173 0 0         if !-f $self->conf_file;
174              
175 0 0         my $fh = IO::File->new($self->conf_file, 'r')
176             or croak "Failed to open config file for reading: $!";
177              
178 0           my $overrides = $self->reader->read_handle($fh);
179              
180 0           $fh->close;
181 0           return $overrides;
182             }
183              
184             =head2 set_overrides
185              
186             =over 4
187              
188             =item Arguments: \%attributes
189              
190             =item Return Value: none
191              
192             =back
193              
194             =cut
195              
196             sub set_overrides {
197 0     0 1   my ($self, $overrides) = @_;
198              
199 0 0         my $fh = IO::File->new($self->conf_file, 'w')
200             or croak "Failed to open config file for writing: $!";
201              
202 0           eval {
203 0           $self->writer->write_handle($overrides, $fh);
204             };
205              
206 0           my $error = $@;
207 0           $fh->close;
208              
209 0 0         croak $error if $error;
210              
211 0           return;
212             }
213              
214             =head2 comp_roots
215              
216             =over 4
217              
218             =item Arguments: none
219              
220             =item Return Value: @comp_roots
221              
222             =back
223              
224             =cut
225              
226             sub comp_roots {
227 0     0 1   my ($self) = @_;
228              
229 0 0         my $dir = IO::Dir->new($self->conf_dir)
230             or croak "Failed to open config directory: $!";
231              
232 0           my @comp_roots;
233              
234 0           while (defined (my $file = $dir->read)) {
235 0 0         next if $file =~ /^\.{1,2}$/;
236              
237 0           my $attrs;
238 0           eval {
239 0           $attrs = $self->component_info($file);
240             };
241              
242 0 0         if (my $error = $@) {
243 0           warn $error;
244 0           next;
245             }
246              
247 0 0         next unless defined $attrs;
248 0 0         next unless defined $attrs->{comp_root};
249              
250 0           my $moniker = $attrs->{moniker};
251              
252 0 0         if (!$moniker) {
253 0           ($moniker = lc $file) =~ s/.*:://;
254             }
255              
256             #TODO: sort by priority field
257 0           push @comp_roots, [ $moniker => $attrs->{comp_root} ],
258             }
259              
260 0           $dir->close;
261              
262 0           return @comp_roots;
263             }
264              
265             =head2 unregister_component
266              
267             =over 4
268              
269             =item Arguments: $component_name
270              
271             =item Return Value:
272              
273             =back
274              
275             =cut
276              
277             sub unregister_component {
278 0     0 1   my ($self, $component) = @_;
279              
280 0           my $component_path = $self->build_config_path($component);
281 0 0         if (!unlink $component_path) {
282 0           croak "Failed to remove component config: $!";
283             }
284              
285 0           return;
286             }
287              
288             =head2 component_info
289              
290             =over 4
291              
292             =item Arguments: $component_name
293              
294             =item Return Value: \%component_attributes
295              
296             =back
297              
298             =cut
299              
300             sub component_info {
301 0     0 1   my ($self, $component) = @_;
302              
303 0           my $component_path = $self->build_config_path($component);
304              
305 0 0         if (!-f $component_path) {
306 0           croak "Component $component not registered";
307             }
308              
309 0           my $attrs = $self->read_file($component_path);
310 0           $attrs = $self->apply_overrides($attrs);
311              
312 0 0         if (!exists $attrs->{$component}) {
313 0           croak "Component config didn't contain any information on requested component $component";
314             }
315              
316 0           return $attrs->{$component};
317             }
318              
319             =head2 apply_overrides
320              
321             =over 4
322              
323             =item Arguments: \%attributes
324              
325             =item Return Value: \%modified_attributes
326              
327             =back
328              
329             =cut
330              
331             sub apply_overrides {
332 0     0 1   my ($self, $attrs) = @_;
333              
334 0           my $overrides = $self->get_overrides;
335              
336 0           return Hash::Merge::merge($overrides, $attrs);
337             }
338              
339             =head2 read_file
340              
341             =over 4
342              
343             =item Arguments: $path
344              
345             =item Return Value: \%attributes
346              
347             =back
348              
349             =cut
350              
351             sub read_file {
352 0     0 1   my ($self, $path) = @_;
353              
354 0 0         my $fh = IO::File->new($path, 'r')
355             or croak "Failed to open component config `$path' for reading: $!";
356              
357 0           my $attrs = $self->reader->read_handle($fh);
358              
359 0           $fh->close;
360              
361 0           return $attrs;
362             }
363              
364             =head2 conf_prefix
365              
366             =over 4
367              
368             =item Arguments: none
369              
370             =item Return Value: $path
371              
372             =back
373              
374             =cut
375              
376             sub conf_prefix {
377 0     0 1   my ($self) = @_;
378              
379 0           my $dir;
380              
381 0           eval {
382 0           $dir = File::ShareDir::module_dir($self);
383             };
384              
385 0 0         if (my $error = $@) { #FIME: fragile
386 0           ($dir) = $error =~ /Directory '(.*)', does not exist/;
387             }
388              
389 0 0         if (!-d $dir) {
390 0           File::Path::mkpath($dir);
391             }
392              
393 0           return $dir;
394             }
395              
396             =head2 conf_file
397              
398             =over 4
399              
400             =item Arguments: none
401              
402             =item Return Value: $path
403              
404             =back
405              
406             =cut
407              
408             sub conf_file {
409 0     0 1   my ($self) = @_;
410              
411 0           return catfile($self->conf_prefix, 'registry.ini');
412             }
413              
414             =head2 conf_dir
415              
416             =over 4
417              
418             =item Arguments: none
419              
420             =item Return Value: $path
421              
422             =back
423              
424             =cut
425              
426             sub conf_dir {
427 0     0 1   my ($self) = @_;
428              
429 0           my $dir = catdir($self->conf_prefix, 'registry.d');
430              
431 0 0         if (!-d $dir) {
432 0           File::Path::mkpath($dir);
433             }
434              
435 0           return $dir;
436             }
437              
438             =head2 build_config_path
439              
440             =over 4
441              
442             =item Arguments: $component_name
443              
444             =item Return Value: $path
445              
446             =back
447              
448             =cut
449              
450             sub build_config_path {
451 0     0 1   my ($self, $component) = @_;
452              
453 0           return catfile($self->conf_dir, $component);
454             }
455              
456             =head2 reader_class
457              
458             =over 4
459              
460             =item Arguments: none
461              
462             =item Return Value: $class
463              
464             =back
465              
466             =cut
467              
468             sub reader_class {
469 0     0 1   return 'MasonX::Component::Registry::Reader';
470             }
471              
472             =head2 writer_class
473              
474             =over 4
475              
476             =item Arguments: none
477              
478             =item Return Value: $class
479              
480             =back
481              
482             =cut
483              
484             sub writer_class {
485 0     0 1   return 'MasonX::Component::Registry::Writer';
486             }
487              
488             =head2 reader
489              
490             =over 4
491              
492             =item Arguments: none
493              
494             =item Return Value: $class
495              
496             =back
497              
498             =head2 writer
499              
500             =over 4
501              
502             =item Arguments: none
503              
504             =item Return Value: $class
505              
506             =back
507              
508             =cut
509              
510             {
511             for my $meth (qw/reader writer/) {
512 1     1   8 no strict 'refs'; ## no critic (ProhibitNoStrict)
  1         3  
  1         275  
513              
514             *{ $meth } = sub {
515 0     0     my ($self) = @_;
516              
517 0           my $get_class = "${meth}_class";
518 0           my $class = $self->$get_class;
519              
520 0           $self->ensure_class_loaded($class);
521              
522 0           return $class;
523             };
524             }
525             }
526              
527             =head2 ensure_class_loaded
528              
529             =over 4
530              
531             =item Arguments: none
532              
533             =item Return Value: none
534              
535             =back
536              
537             =cut
538              
539             sub ensure_class_loaded {
540 0     0 1   my ($self, $class) = @_;
541              
542 0 0         croak "Invalid class name $class"
543             if $class =~ /(?:\b:\b|\:{3,})/;
544              
545 0 0         return if Class::Inspector->loaded($class);
546              
547 0           eval "require $class";
548 0 0         if (my $error = $@) {
549 0           croak $error;
550             }
551              
552 0           return;
553             }
554              
555             1;