File Coverage

blib/lib/AI/MicroStructure/MultiList.pm
Criterion Covered Total %
statement 110 124 88.7
branch 20 22 90.9
condition 13 21 61.9
subroutine 20 23 86.9
pod 7 7 100.0
total 170 197 86.2


line stmt bran cond sub pod time code
1             package AI::MicroStructure::MultiList;
2 1     1   569 use strict;
  1         2  
  1         24  
3 1     1   5 use AI::MicroStructure (); # do not export metaname and friends
  1         2  
  1         18  
4 1     1   612 use AI::MicroStructure::RemoteList;
  1         2  
  1         34  
5 1     1   5 use List::Util qw( shuffle );
  1         2  
  1         95  
6 1     1   5 use Carp;
  1         1  
  1         112  
7              
8             our @ISA = qw( AI::MicroStructure::RemoteList );
9             our $VERSION = '1.000';
10              
11             sub init {
12 1     1 1 26 my ($self, $data) = @_;
13 1         3 my $class = caller(0);
14              
15 1   33     23 $data ||= AI::MicroStructure->load_data($class);
16 1     1   5 no strict 'refs';
  1         1  
  1         484  
17              
18             # note: variables mentioned twice to avoid a warning
19              
20 1   50     2 my $sep = ${"$class\::Separator"} = ${"$class\::Separator"} ||= '/';
  1         4  
  1         12  
21 1         37 my $tail = qr/$sep?[^$sep]*$/;
22              
23             # compute all categories
24 1         6 my @categories = ( [ $data->{names}, '' ] );
25 1 100       2 while ( my ( $h, $k ) = @{ shift @categories or []} ) {
  13         54  
26 12 100       24 if ( ref $h eq 'HASH' ) {
27             push @categories,
28 5 100       13 map { [ $h->{$_}, ( $k ? "$k$sep$_" : $_ ) ] } keys %$h;
  11         48  
29             }
30             else { # leaf
31 7         22 my @items = split /\s+/, $h;
32 7         15 while ($k) {
33 15         18 push @{ ${"$class\::MultiList"}{$k} }, @items;
  15         15  
  15         74  
34 15         101 $k =~ s!$tail!!;
35             }
36             }
37             }
38              
39 1   50     5 ${"$class\::Default"} = ${"$class\::Default"} = $data->{default} || ':all';
  1         3  
  1         7  
40 1         4 ${"$class\::Theme"} = ${"$class\::Theme"} = ( split /::/, $class )[-1];
  1         3  
  1         7  
41              
42 1         5 *{"$class\::import"} = sub {
43 0     0   0 my $callpkg = caller(0);
44 0         0 my $structure = ${"$class\::Theme"};
  0         0  
45 0         0 my $meta = $class->new;
46 0     0   0 *{"$callpkg\::meta$structure"} = sub { $meta->name(@_) };
  0         0  
  0         0  
47 1         7 };
48              
49 1         7 ${"$class\::meta"} = ${"$class\::meta"} = $class->new();
  1         6  
  1         12  
50             }
51              
52             sub name {
53 42     42 1 5386 my ( $self, $count ) = @_;
54 42         84 my $class = ref $self;
55              
56 42 50       96 if ( !$class ) { # called as a class method!
57 0         0 $class = $self;
58 1     1   5 no strict 'refs';
  1         1  
  1         56  
59 0         0 $self = ${"$class\::meta"};
  0         0  
60             }
61              
62 42 100 100     166 if ( defined $count && $count == 0 ) {
63 1     1   5 no strict 'refs';
  1         2  
  1         160  
64             return wantarray
65 14         112 ? shuffle @{ $self->{base} }
66 14 50       30 : scalar @{ $self->{base} };
  0         0  
67             }
68              
69 28   100     76 $count ||= 1;
70 28         48 my $list = $self->{cache};
71 28 100       31 if ( @{ $self->{base} } ) {
  28         76  
72 26         67 push @$list, shuffle @{ $self->{base} } while @$list < $count;
  36         213  
73             }
74 28         114 splice( @$list, 0, $count );
75             }
76              
77             sub new {
78 17     17 1 8803 my $class = shift;
79              
80 1     1   5 no strict 'refs';
  1         2  
  1         151  
81 17         64 my $self = bless { @_, cache => [] }, $class;
82              
83             # compute some defaults
84 17   66     54 $self->{category} ||= ${"$class\::Default"};
  3         15  
85              
86             # fall back to last resort (FIXME should we carp()?)
87 1         5 $self->{category} = ${"$class\::Default"}
88             if $self->{category} ne ':all'
89 17 100 100     51 && !exists ${"$class\::MultiList"}{ $self->{category} };
  16         96  
90              
91 17         39 $self->_compute_base();
92 17         35 return $self;
93             }
94              
95             sub _compute_base {
96 17     17   23 my ($self) = @_;
97 17         29 my $class = ref $self;
98              
99             # compute the base list for this category
100 1     1   6 no strict 'refs';
  1         1  
  1         158  
101 17         23 my %seen;
102             $self->{base} = [
103 78         207 grep { !$seen{$_}++ }
104 27         29 map { @{ ${"$class\::MultiList"}{$_} } }
  27         32  
  27         128  
105             $self->{category} eq ':all'
106 1         6 ? ( keys %{"$class\::MultiList"} )
107             : ( $self->{category} )
108 17 100       51 ];
109 17         51 return;
110             }
111              
112 14     14 1 95 sub category { $_[0]->{category} }
113              
114             sub categories {
115 2     2 1 224 my $class = shift;
116 2 100       7 $class = ref $class if ref $class;
117              
118 1     1   5 no strict 'refs';
  1         1  
  1         81  
119 2         3 return keys %{"$class\::MultiList"};
  2         17  
120             }
121              
122             sub has_category {
123 6     6 1 612 my ($class, $category) = @_;
124 6 100       17 $class = ref $class if ref $class;
125              
126 1     1   5 no strict 'refs';
  1         2  
  1         73  
127 6         7 return exists ${"$class\::MultiList"}{$category};
  6         38  
128             }
129              
130             sub structure {
131 0   0 0 1   my $class = ref $_[0] || $_[0];
132 1     1   5 no strict 'refs';
  1         2  
  1         55  
133 0           return ${"$class\::Theme"};
  0            
134             }
135              
136             1;
137              
138             __END__
139              
140             =head1 NAME
141              
142             AI::MicroStructure::MultiList - Base class for structures with multiple lists
143              
144             =head1 SYNOPSIS
145              
146             package AI::MicroStructure::digits;
147             use AI::MicroStructure::MultiList;
148             our @ISA = ( AI::MicroStructure::MultiList );
149             __PACKAGE__->init();
150             1;
151              
152             =head1 NAME
153            
154             AI::MicroStructure::digits - The numbers structure
155            
156             =head1 DESCRIPTION
157            
158             You can count on this module. Almost.
159              
160             =cut
161            
162             __DATA__
163             # default
164             :all
165             # names primes even
166             two
167             # names primes odd
168             three five seven
169             # names composites even
170             four six eight
171             # names composites odd
172             nine
173             # names other
174             zero one
175              
176             =head1 DESCRIPTION
177              
178             C<AI::MicroStructure::MultiList> is the base class for all structures
179             that are meant to return a random excerpt from a predefined list
180             I<divided in categories>.
181              
182             The category is selected at construction time from:
183              
184             =over 4
185              
186             =item 1.
187              
188             the given C<category> parameter,
189              
190             =item 2.
191              
192             the default category for the selected structure.
193              
194             =back
195              
196             Categories and sub-categories are separated by a C</> character.
197              
198             =head1 METHODS
199              
200             AI::MicroStructure::MultiList offers several methods, so that the subclasses
201             are easy to write (see full example in L<SYNOPSIS>):
202              
203             =over 4
204              
205             =item new( category => $category )
206              
207             The constructor of a single instance. An instance will not repeat items
208             until the list is exhausted.
209              
210             $meta = AI::MicroStructure::digits->new( category => 'primes' );
211             $meta = AI::MicroStructure::digits->new( category => 'primes/odd' );
212              
213             The special category C<:all> will use all the items in all categories.
214              
215             $meta = AI::MicroStructure::digits->new( category => ':all' );
216              
217             If no C<category> parameter is given, C<AI::MicroStructure::MultiList>
218             will use the class default. If the class doesn't define a default,
219             then C<:all> is used.
220              
221             =item init()
222              
223             init() must be called when the subclass is loaded, so as to read the
224             __DATA__ section and fully initialise it.
225              
226             =item name( $count )
227              
228             Return $count names (default: C<1>).
229              
230             Using C<0> will return the whole list in list context, and the size of the
231             list in scalar context (according to the C<category> parameter passed to the
232             constructor).
233              
234             =item category()
235              
236             Return the selected category for this instance.
237              
238             =item categories()
239              
240             Return the categories supported by the structure (except C<:all>).
241              
242             =item has_category( $category )
243              
244             Return a boolean value indicating if the structure contains the given category.
245              
246             =item structure()
247              
248             Return the structure name.
249              
250             =back
251              
252             =head1 AUTHOR
253              
254             Philippe 'BooK' Bruhat, C<< <book@cpan.org> >>
255              
256             =head1 COPYRIGHT & LICENSE
257              
258             Copyright 2006 Philippe 'BooK' Bruhat, All Rights Reserved.
259              
260             This program is free software; you can redistribute it and/or modify it
261             under the same terms as Perl itself.
262              
263             =cut
264