File Coverage

blib/lib/Collection/Categorized.pm
Criterion Covered Total %
statement 53 53 100.0
branch 5 6 83.3
condition 2 2 100.0
subroutine 13 13 100.0
pod 6 6 100.0
total 79 80 98.7


line stmt bran cond sub pod time code
1             package Collection::Categorized;
2 3     3   70681 use strict;
  3         9  
  3         111  
3 3     3   17 use warnings;
  3         5  
  3         84  
4 3     3   16 use Carp;
  3         10  
  3         317  
5 3     3   2849 use Sub::AliasedUnderscore qw/transform/;
  3         1209  
  3         221  
6              
7             our $VERSION = '0.01';
8              
9 3     3   17 use base 'Class::Accessor::Fast';
  3         6  
  3         3704  
10             __PACKAGE__->mk_accessors(qw/_sorter _data/);
11              
12             =head1 NAME
13              
14             Collection::Categorized - categorize and organize a collection of data
15              
16             =head1 SYNOPSIS
17              
18             use Collection::Categorized;
19              
20             # create a collection where elements are categorized by
21             # the class they are in
22             my $cc = Collection::Categorized->new( sub { ref $_ } );
23              
24             # add some data
25             $foo->{awesomeness} = 42;
26             $cc->add($foo); # $foo isa Foo
27             $cc->add($bar, $bar2); # $bars are Bars
28             $cc->add(@bazs); # @bazs are Bazs
29              
30             # see what we have
31             my @c = $cc->categories; # (Foo, Bar, Baz)
32              
33             # get the data by category
34             my @foos = $cc->get('Foo'); # ($foo)
35             my @bars = $cc->get('Bar'); # ($bar, $bar2)
36             my @HOOO = $cc->get('HOOO'); # undef
37              
38             # grep the data
39             $cc->edit(sub { grep { defined $_->{awesomeness} } @_ });
40             @foos = $cc->get('Foo'); # ($foo)
41             @bars = $cc->get('Bar'); # ()
42             @HOOO = $cc->get('HOOO'); # undef
43              
44             =head1 DESCRIPTION
45              
46             The idea is that, given a list of junk, you want to find order in the
47             chaos. Write some categorizers (see below), dump your data in, and
48             get it out in some sort of meaningful order.
49              
50             =head1 METHODS
51              
52             =head2 new($coderef)
53              
54             Create a categorized collection that categorizes its members
55             by the return value of C<$coderef>. Coderef is run with C<$_>
56             aliased to the element to categorize.
57              
58             =head2 new([ category => $condition, ... ])
59              
60             Create a categorized collection that categorizes its members
61             based on the passed category definition list. Example:
62              
63             new([ positive => sub { $_ < 0 },
64             zero => sub { $_ == 0 },
65             negative => sub { $_ > 0 },
66             ]);
67              
68             This example creates three categories. The conditions are run in
69             order, and the first condition to match decides the category that
70             element is in. If an element doesn't match any of the three blocks
71             (unlikely in this case), then it is silently discarded. If you want
72             some sort of fallback, just add a condition that always matches (like
73             C).
74              
75             Note that you're passing an arrayref, not a hashref, because we want
76             to preserve order.
77              
78             =cut
79              
80             sub new {
81 2     2 1 38 my ($class, $ref) = @_;
82 2         5 my $self = {};
83             my $dispatch =
84 1     1   8 { CODE => sub { $self->{_sorter} = transform $ref },
85             ARRAY => sub {
86 1     1   3 my %lookup = @$ref;
87 1         9 $lookup{$_} = transform $lookup{$_} for keys %lookup;
88              
89             # with that out of the way, setup the sorter
90             $self->{_sorter} = sub {
91 7         31 my $arg = shift;
92 7         12 foreach my $category (grep { !ref $_ } @$ref) {
  42         58  
93 13 100       56 return $category if $lookup{$category}->($arg);
94             }
95             }
96 1         29 },
97 2         25 };
98            
99 2         7 my $action = $dispatch->{ref $ref};
100 2 50       9 croak 'pass an ARRAY or CODE reference only' unless $action;
101 2         9 $action->();
102            
103 2         14 $self->{_data} = {};
104 2         21 return bless $self => $class;
105             }
106              
107             =head2 categories
108              
109             Returns a list of categories in use
110              
111             =cut
112              
113             sub categories {
114 8     8 1 19 my $self = shift;
115 8         9 return keys %{$self->{_data}};
  8         36  
116             }
117              
118             =head2 add($object)
119              
120             Add an object to the collection.
121              
122             =cut
123              
124              
125             sub add {
126 7     7 1 1234 my ($self, @objects) = @_;
127 7         13 foreach (@objects) {
128 17         83 my $class = $self->_sorter->($_);
129 17   100     179 $self->_data->{$class} ||= [];
130 17         96 push @{$self->_data->{$class}}, $_;
  17         37  
131             }
132 7         45 return;
133             }
134              
135             =head2 get($type)
136              
137             Gets all elements of a certain type
138              
139             =cut
140              
141             sub get {
142 21     21 1 8796 my ($self, $type) = @_;
143 21 100       24 return @{$self->_data->{$type}||[]};
  21         63  
144             }
145              
146             =head2 all
147              
148             Get every element in the collection
149              
150             =cut
151              
152             sub all {
153 1     1 1 2 my $self = shift;
154 1         2 return map { $self->get($_) } $self->categories;
  3         15  
155             }
156              
157             =head2 edit(sub { change @_ })
158              
159             Given a a subref, apply it to every type and change the members of the
160             type to be the return value of the sub.
161              
162             Example:
163              
164             # Input: ( category => data )
165             # { foo => [ 1 2 3 ],
166             # bar => [ 3 2 1 ],
167             # }
168              
169             $collection->edit( sub { reverse @_ } );
170              
171             # Output:
172             # { foo => [ 3 2 1 ],
173             # bar => [ 1 2 3 ],
174             # }
175              
176              
177             =cut
178              
179             sub edit {
180 2     2 1 26 my ($self, $editor) = @_;
181 2         7 foreach my $type ($self->categories) {
182 6         36 my @members = $self->get($type);
183 6         71 my @changed = $editor->(@members);
184 6         62 $self->_data->{$type} = \@changed;
185             }
186 2         50 return;
187             }
188              
189             =head1 AUTHOR
190              
191             Jonathan Rockway C<< jrockway AT cpan.org >>
192             Jeremy Wall C<< zaphar AT cpan.org >>
193              
194             We wrote this for work. Now you can have it too.
195              
196             =head1 COPYRIGHT
197              
198             This module is probably copyright (c) 2007 by Doubleclick Performics.
199             Despite the weird name of the copyright holder, you can use, modify,
200             and redistribute this module under the same terms as Perl itself.
201              
202             =cut
203              
204             1;