File Coverage

blib/lib/Circle/Collection.pm
Criterion Covered Total %
statement 106 201 52.7
branch 34 80 42.5
condition 17 49 34.6
subroutine 8 18 44.4
pod n/a
total 165 348 47.4


line stmt bran cond sub pod time code
1             # You may distribute under the terms of the GNU General Public License
2             #
3             # (C) Paul Evans, 2008-2014 -- leonerd@leonerd.org.uk
4              
5             package Circle::Collection;
6              
7 4     4   24 use strict;
  4         7  
  4         95  
8 4     4   18 use warnings;
  4         6  
  4         120  
9              
10             our $VERSION = '0.173320';
11              
12 4     4   18 use Carp;
  4         11  
  4         220  
13             require attributes;
14              
15 4     4   21 use Attribute::Storage qw( apply_subattrs_for_pkg );
  4         8  
  4         29  
16 4     4   228 use Class::Method::Modifiers qw( install_modifier );
  4         8  
  4         6135  
17              
18             # A template role to merge
19             sub import
20             {
21 16     16   39 my $pkg = shift;
22 16         199 my %args = @_;
23              
24 16         37 my $caller = caller;
25              
26 16 50       50 my $name = $args{name} or croak "Need a collection name";
27              
28 16 50       44 my $attrs = $args{attrs} or croak "Need attrs";
29 16 50       59 ref $attrs eq "ARRAY" or croak "Expected 'attrs' to be an ARRAY";
30              
31 16   33     63 my $desc2 = $args{desc_plural} || $name;
32 16   33     45 my $desc1 = $args{desc_single} || do { $_ = $name; s/s$//; $_ };
33              
34 16 50       43 my $storage = $args{storage} or croak "Need a storage type";
35 16         26 my $config = $args{config};
36              
37             # Now parse it down to several fields
38 16         37 my @attrs_all;
39             my @attrs_persisted;
40 16         0 my %attrs;
41              
42 16         43 for( my $i = 0; $i < @$attrs; $i += 2 ) {
43 48         61 my $name = $attrs->[$i];
44 48         60 my $a = $attrs->[$i+1];
45              
46 48         64 push @attrs_all, $name;
47 48 50       88 push @attrs_persisted, $name unless $a->{transient};
48              
49 48         108 $attrs{$name} = $a;
50             }
51              
52 16         24 my $keyattr = $attrs_all[0];
53              
54 16         23 my %commands;
55 16 100       40 %commands = %{ $args{commands} } if $args{commands};
  4         21  
56              
57             # Data access code
58              
59 16         33 my ( $method_list, $method_get, $method_set, $method_add, $method_del );
60              
61 16 100       57 if( ref $storage eq "HASH" ) {
    100          
    50          
62 8         11 $method_list = $storage->{list};
63 8         9 $method_get = $storage->{get};
64 8         11 $method_set = $storage->{set};
65 8         11 $method_add = $storage->{add};
66 8         10 $method_del = $storage->{del};
67             }
68             elsif( $storage eq "methods" ) {
69 4         12 $method_list = "${name}_list";
70 4         9 $method_get = "${name}_get";
71 4         10 $method_set = "${name}_set";
72 4         9 $method_add = "${name}_add";
73 4         7 $method_del = "${name}_del";
74             }
75             elsif( $storage eq "array" ) {
76             $method_list = sub {
77 0     0   0 my $self = shift;
78 0         0 return @{ $self->{$name} }
  0         0  
79 4         19 };
80              
81             $method_get = sub {
82 0     0   0 my $self = shift;
83 0         0 my ( $key ) = @_;
84 0         0 return ( grep { $_->{$keyattr} eq $key } @{ $self->{$name} } )[0];
  0         0  
  0         0  
85 4         18 };
86              
87             $method_add = sub {
88 0     0   0 my $self = shift;
89 0         0 my ( $key, $item ) = @_;
90             # TODO: something with key
91 0         0 push @{ $self->{$name} }, $item;
  0         0  
92 4         15 };
93              
94             $method_del = sub {
95 0     0   0 my $self = shift;
96 0         0 my ( $key, $item ) = @_;
97              
98 0         0 my $items = $self->{$name};
99 0         0 my ( $idx ) = grep { $items->[$_] == $item } 0 .. $#$items;
  0         0  
100              
101 0 0       0 return 0 unless defined $idx;
102              
103 0         0 splice @$items, $idx, 1, ();
104 0         0 return 1;
105 4         28 };
106             }
107             else {
108 0         0 croak "Unrecognised storage type $storage";
109             }
110              
111             # Manipulation commands
112              
113 16 50       42 unless( exists $commands{list} ) {
114 16 50       36 defined $method_list or croak "No list method defined for list subcommand";
115              
116             $commands{list} = apply_subattrs_for_pkg( $caller,
117             Command_description => qq("List the $desc2"),
118             Command_subof => qq('$name'),
119             Command_default => qq(),
120             sub {
121 0     0   0 my $self = shift;
122 0         0 my ( $cinv ) = @_;
123              
124 0         0 my @items = $self->$method_list;
125              
126 0 0       0 unless( @items ) {
127 0         0 $cinv->respond( "No $desc2" );
128 0         0 return;
129             }
130              
131 0         0 my @table;
132              
133 0         0 foreach my $item ( @items ) {
134 0         0 my @shown_item;
135 0         0 foreach my $attr ( @attrs_all ) {
136 0         0 my $value = $item->{$attr};
137 0 0       0 push @shown_item, exists $attrs{$attr}{show} ? $attrs{$attr}{show}->( local $_ = $value ) : $value;
138             }
139 0         0 push @table, \@shown_item;
140             }
141              
142 0         0 $cinv->respond_table( \@table, headings => \@attrs_all );
143 0         0 return;
144             }
145 16         171 );
146             }
147              
148 16         1044 my @opts_add;
149             my @opts_mod;
150              
151 16         39 foreach ( @attrs_persisted ) {
152 48 100       93 next if $_ eq $keyattr;
153              
154 32   66     97 my $desc = $attrs{$_}{desc} || $_;
155              
156 32 100       80 $desc .= qq[ (default \\"$attrs{$_}{default}\\")] if exists $attrs{$_}{default};
157              
158 32         68 push @opts_add, qq('$_=\$', desc => "$desc");
159              
160             push @opts_mod, qq('$_=\$', desc => "$desc"),
161 32 100       121 qq('no-$_=+', desc => "remove $_") unless $attrs{$_}{nomod};
162             }
163              
164 16 100       38 unless( exists $commands{add} ) {
165 12 50       143 defined $method_add or croak "No add method defined for add subcommand";
166              
167             $commands{add} = apply_subattrs_for_pkg( $caller,
168             Command_description => qq("Add a $desc1"),
169             Command_subof => qq('$name'),
170             Command_arg => qq('$keyattr'),
171 28         119 ( map { +Command_opt => $_ } @opts_add ),
172             sub {
173 2     2   4 my $self = shift;
174 2         11 my ( $key, $opts, $cinv ) = @_;
175              
176 2 50       9 if( $self->$method_get( $key ) ) {
177 0         0 $cinv->responderr( "Already have a $desc1 '$key'" );
178 0         0 return;
179             }
180              
181 2         47 my $item = { $keyattr => $key };
182 2   66     17 exists $attrs{$_}{default} and $item->{$_} = $attrs{$_}{default} for @attrs_persisted;
183              
184 2   66     11 defined $opts->{$_} and $item->{$_} = $opts->{$_} for @attrs_persisted;
185              
186 2 50       3 unless( eval { $self->$method_add( $key, $item ); 1 } ) {
  2         16  
  2         6  
187 0         0 my $err = "$@"; chomp $err;
  0         0  
188 0         0 $cinv->responderr( "Cannot add $desc1 '$key' - $err" );
189 0         0 return;
190             }
191              
192 2         16 $cinv->respond( "Added $desc1 '$key'" );
193 2         12 return;
194             }
195 12         50 );
196             }
197              
198 16 100       641 unless( exists $commands{mod} ) {
199 12 50       32 defined $method_get or croak "No get method defined for mod subcommand";
200              
201             $commands{mod} = apply_subattrs_for_pkg( $caller,
202             Command_description => qq("Modify an existing $desc1"),
203             Command_subof => qq('$name'),
204             Command_arg => qq('$keyattr'),
205 48         110 ( map { +Command_opt => $_ } @opts_mod ),
206             sub {
207 0     0   0 my $self = shift;
208 0         0 my ( $key, $opts, $cinv ) = @_;
209              
210 0         0 my $item = $self->$method_get( $key );
211              
212 0 0       0 unless( $item ) {
213 0         0 $cinv->responderr( "No such $desc1 '$key'" );
214 0         0 return;
215             }
216              
217 0         0 my %mod;
218 0   0     0 exists $opts->{$_} and $mod{$_} = $opts->{$_} for @attrs_persisted;
219 0   0     0 exists $opts->{"no-$_"} and $mod{$_} = $attrs{$_}{default} for @attrs_persisted;
220              
221 0 0       0 if( $method_set ) {
222 0         0 $self->$method_set( $key, \%mod );
223             }
224             else {
225 0         0 $item->{$_} = $mod{$_} for keys %mod;
226             }
227              
228 0         0 $cinv->respond( "Modified $desc1 '$key'" );
229 0         0 return;
230             }
231 12         60 );
232             }
233              
234 16 100       634 unless( exists $commands{del} ) {
235 12 50       28 defined $method_del or croak "No del method defined for del subcommand";
236              
237             $commands{del} = apply_subattrs_for_pkg( $caller,
238             Command_description => qq("Delete a $desc1"),
239             Command_subof => qq('$name'),
240             Command_arg => qq('$keyattr'),
241             sub {
242 0     0   0 my $self = shift;
243 0         0 my ( $key, $cinv ) = @_;
244              
245 0         0 my $item = $self->$method_get( $key );
246              
247 0 0       0 unless( $item ) {
248 0         0 $cinv->responderr( "No such $desc1 '$key'" );
249 0         0 return;
250             }
251              
252 0 0       0 unless( eval { $self->$method_del( $key, $item ); 1 } ) {
  0         0  
  0         0  
253 0         0 my $err = "$@"; chomp $err;
  0         0  
254 0         0 $cinv->responderr( "Cannot delete $desc1 '$key' - $err" );
255 0         0 return;
256             }
257              
258 0         0 $cinv->respond( "Removed $desc1 '$key'" );
259 0         0 return;
260             }
261 12         91 );
262             }
263              
264             # Now delete present-but-undef ones; these are where the caller vetoed the
265             # above autogeneration
266 16   100     829 defined $commands{$_} or delete $commands{$_} for keys %commands;
267              
268 16         27 my %subs;
269 16         103 $subs{"command_${name}_$_"} = $commands{$_} for keys %commands;
270              
271             $subs{"command_${name}"} = apply_subattrs_for_pkg( $caller,
272             Command_description => qq("Display or manipulate $desc2"),
273             # body matters not but it needs to be a cloned closure
274 16     0   35 do { my $dummy; sub { undef $dummy } }
  16         140  
  16         73  
  0         0  
275             );
276              
277             {
278 4     4   28 no strict 'refs';
  4         277  
  4         2201  
  16         872  
279 16         52 *{"${caller}::$_"} = $subs{$_} for keys %subs;
  68         235  
280             }
281              
282 16 100 100     231 if( !defined $config or $config ) {
283 12   100     46 my $config_type = $config->{type} || "array";
284 12         26 my $type_array = $config_type eq "array";
285 12         21 my $type_hash = $config_type eq "hash";
286 12 50 66     39 $type_array or $type_hash or
287             die "Expected config type either 'array' or 'hash'";
288              
289             # Optional config-related methods
290 12         30 my $method_store = $config->{store};
291 12         19 my $method_load = $config->{load};
292              
293             # Configuration load/store
294             install_modifier $caller, after => load_configuration => sub {
295 0     0   0 my $self = shift;
296 0         0 my ( $ynode ) = @_;
297              
298 0 0       0 my $ynodes = $ynode->{$name} or return;
299              
300 0 0       0 foreach my $this ( $type_array ? @$ynodes : keys %$ynodes ) {
301 0         0 my $item = {};
302 0 0       0 my $n = $type_array ? $this : $ynodes->{$this};
303              
304 0         0 $item->{$_} = $n->{$_} for @attrs_persisted;
305 0 0       0 $item->{$keyattr} = $this if $type_hash;
306              
307 0         0 $self->$method_add( $item->{$keyattr}, $item );
308              
309 0 0 0     0 if( ref $method_load or $method_load && $self->can( $method_load ) ) {
      0        
310 0         0 $self->$method_load( $item->{$keyattr}, $n );
311             }
312             }
313 12         91 };
314              
315             install_modifier $caller, after => store_configuration => sub {
316 0     0     my $self = shift;
317 0           my ( $ynode ) = @_;
318              
319 0 0 0       my $ynodes = $ynode->{$name} ||= $type_array ? [] : YAML::Node->new({});
320 0 0         $type_array ? ( @$ynodes = () ) : ( %$ynodes = () );
321              
322 0           foreach my $item ( $self->$method_list ) {
323 0           my $n = YAML::Node->new({});
324              
325 0   0       defined $item->{$_} and $n->{$_} = $item->{$_} for @attrs_persisted;
326              
327 0 0 0       if( ref $method_store or $method_store && $self->can( $method_store ) ) {
      0        
328 0           $self->$method_store( $item->{$keyattr}, $n );
329             }
330              
331             $type_array ?
332             ( push @$ynodes, $n ) :
333 0 0         do { $ynodes->{$n->{$keyattr}} = $n; delete $n->{$keyattr} };
  0            
  0            
334             }
335 12         2221 };
336             }
337             }
338              
339             0x55AA;