File Coverage

blib/lib/MooX/Attributes/Shadow.pm
Criterion Covered Total %
statement 70 70 100.0
branch 34 34 100.0
condition 6 9 66.6
subroutine 13 13 100.0
pod 3 3 100.0
total 126 129 97.6


line stmt bran cond sub pod time code
1             # --8<--8<--8<--8<--
2             #
3             # Copyright (C) 2012 Smithsonian Astrophysical Observatory
4             #
5             # This file is part of MooX-Attributes-Shadow
6             #
7             # MooX-Attributes-Shadow is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 3 of the License, or (at
10             # your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20             # -->8-->8-->8-->8--
21              
22             package MooX::Attributes::Shadow;
23              
24 10     10   3847314 use strict;
  10         27  
  10         375  
25 10     10   60 use warnings;
  10         20  
  10         645  
26              
27             our $VERSION = '0.03';
28              
29 10     10   54 use Carp;
  10         74  
  10         1713  
30 10     10   11204 use Params::Check qw[ check last_error ];
  10         65489  
  10         951  
31 10     10   124 use Scalar::Util qw[ blessed ];
  10         22  
  10         997  
32              
33 10     10   62 use Exporter 'import';
  10         21  
  10         5415  
34              
35             our %EXPORT_TAGS = ( all => [ qw( shadow_attrs shadowed_attrs xtract_attrs ) ],
36             );
37             Exporter::export_ok_tags('all');
38              
39             my %MAP;
40              
41             ## no critic (ProhibitAccessOfPrivateData)
42              
43             sub shadow_attrs {
44              
45 16     16 1 1613 my $contained = shift;
46              
47 16         71 my $container = caller;
48              
49             my $args = check( {
50             fmt => {
51 11     11   915 allow => sub { ref $_[0] eq 'CODE' }
52             },
53              
54 7 100 66 7   525 attrs => { allow => sub { 'ARRAY' eq ref $_[0] && @{ $_[0] }
  5         34  
55             or 'HASH' eq ref $_[0] },
56             },
57 16 100       545 private => { default => 1 },
58             instance => {},
59             },
60             {@_} ) or croak( "error parsing arguments: ", last_error, "\n" );
61              
62              
63 15 100       552 unless ( exists $args->{attrs} ) {
64              
65 9         17 $args->{attrs} = [ eval { $contained->shadowable_attrs } ];
  9         77  
66              
67 9 100       94 croak( "must specify attrs or call shadowable_attrs in shadowed class" )
68             if $@;
69              
70             }
71              
72 14 100       236 my $has = $container->can( 'has' )
73             or croak( "container class $container does not have a 'has' function.",
74             " Is it really a Moo class?" );
75              
76 20         68 my %attr =
77 12         29 'ARRAY' eq ref $args->{attrs} ? ( map { $_ => undef } @{$args->{attrs}} )
  1         6  
78 13 100       71 : %{$args->{attrs}};
79              
80 13         27 my %map;
81 13         63 while( my ( $attr, $alias ) = each %attr ) {
82              
83 22 100       4533 $alias = $args->{fmt} ? $args->{fmt}->( $attr ) : $attr
    100          
84             unless defined $alias;
85              
86 22 100       114 my $priv = $args->{private} ? "_shadow_${contained}_${alias}" : $alias;
87 22         106 $priv =~ s/::/_/g;
88 22         115 $map{$attr} = { priv => $priv, alias => $alias };
89              
90             ## no critic (ProhibitNoStrict)
91 10     10   94 no strict 'refs';
  10         22  
  10         10610  
92 22         113 $has->(
93             $priv => (
94             is => 'ro',
95             init_arg => $alias,
96             predicate => "_has_${priv}",
97             ) );
98              
99             }
100              
101 13 100       5246 if ( defined $args->{instance} ) {
102              
103 6         27 $MAP{$contained}{$container}{instance}{ $args->{instance} } = \%map;
104              
105             }
106              
107             else {
108              
109 7         120 $MAP{$contained}{$container}{default} = \%map;
110              
111             }
112              
113 13         103 return;
114             }
115              
116             sub _resolve_attr_env {
117              
118 28     28   53 my ( $contained, $container, $options ) = @_;
119              
120             # contained should be resolved into a class name
121 28   66     166 my $containedClass = blessed $contained || $contained;
122              
123             # allow $container to be either a class or an object
124 28   66     209 my $containerClass = blessed $container || $container;
125              
126 28 100       126 my $map = defined $options->{instance}
127             ? $MAP{$containedClass}{$containerClass}{instance}{$options->{instance}}
128             : $MAP{$containedClass}{$containerClass}{default};
129              
130 28 100       92 croak( "attributes must first be shadowed using ${containedClass}::shadow_attrs\n" )
131             unless defined $map;
132              
133 27         100 return $map;
134             }
135              
136             # call as
137             # shadowed_attrs( $ContainedClass, [ $container ], \%options)
138              
139             sub shadowed_attrs {
140              
141 8     8 1 5792 my $containedClass = shift;
142 8 100       28 my $options = 'HASH' eq ref $_[-1] ? pop() : {};
143              
144 8 100       25 my $containerClass = @_ ? shift : caller();
145              
146 8         111 my $map = _resolve_attr_env( $containedClass, $containerClass, $options );
147              
148 8         25 return { map { $map->{$_}{alias}, $_ } keys %$map }
  16         134  
149             }
150              
151             # call as
152             # xtract_attrs( $ContainedClass, $container_obj, \%options)
153             sub xtract_attrs {
154              
155 21     21 1 26649 my $containedClass = shift;
156 21 100       192 my $options = 'HASH' eq ref $_[-1] ? pop() : {};
157 21         38 my $container = shift;
158 21 100       138 my $containerClass = blessed $container or
159             croak( "container_obj parameter is not a container object\n" );
160              
161 20         74 my $map = _resolve_attr_env( $containedClass, $containerClass, $options );
162              
163 19         75 my %attr;
164 19         128 while( my ($attr, $names) = each %$map ) {
165              
166 34         107 my $priv = $names->{priv};
167 34         91 my $has = "_has_${priv}";
168              
169 34 100       281 $attr{$attr} = $container->$priv
170             if $container->$has;
171             }
172              
173 19         321 return %attr;
174             }
175              
176             1;
177             __END__