File Coverage

blib/lib/String/Lookup/PurePerl.pm
Criterion Covered Total %
statement 134 144 93.0
branch 41 58 70.6
condition 11 20 55.0
subroutine 32 35 91.4
pod 2 2 100.0
total 220 259 84.9


line stmt bran cond sub pod time code
1             package String::Lookup::PurePerl; # fake
2             package String::Lookup;
3             $VERSION= 0.12;
4              
5             # what runtime features we need
6 6     6   96 use 5.014;
  6         21  
  6         232  
7 6     6   29 use warnings;
  6         10  
  6         164  
8              
9             # constants we need
10 6     6   29 use constant OFFSET => 0; # initial / current offset
  6         10  
  6         486  
11 6     6   29 use constant INCREMENT => 1; # increment value between ID's
  6         10  
  6         292  
12 6     6   27 use constant THEHASH => 2; # hash ref with string -> id mapping
  6         9  
  6         249  
13 6     6   26 use constant THELIST => 3; # list ref with id -> string mapping
  6         8  
  6         214  
14 6     6   27 use constant INDEX => 4; # keys() index
  6         7  
  6         315  
15 6     6   39 use constant FLUSH => 5; # code that does the flush
  6         9  
  6         261  
16 6     6   28 use constant TODO => 6; # id's added
  6         11  
  6         240  
17 6     6   63 use constant AUTOFLUSH => 7; # code that determines when to autoflush
  6         11  
  6         251  
18              
19             # modules that we need
20 6     6   32 use Scalar::Util qw( reftype );
  6         8  
  6         858  
21              
22             # synonyms
23             do {
24 6     6   30 no warnings 'once';
  6         11  
  6         10262  
25             *DESTROY= \&flush;
26             *UNTIE= \&flush;
27             };
28              
29             # actions we cannot do on a lookup hash
30 0     0   0 sub CLEAR { die "Cannot clear a lookup hash" } #CLEAR
31 0     0   0 sub DELETE { die "Cannot delete strings from a lookup hash" } #DELETE
32 1     1   13 sub STORE { die "Cannot assign values to a lookup hash" } #STORE
33              
34             # satisfy -require-
35             1;
36              
37             #-------------------------------------------------------------------------------
38             #
39             # Standard Perl functionality
40             #
41             #-------------------------------------------------------------------------------
42             # TIEHASH
43             #
44             # IN: 1 class
45             # 2 .. N parameters
46             # OUT: 1 blessed object
47              
48             sub TIEHASH {
49 24     24   3830 my ( $class, %param )= @_;
50 24         43 my @errors;
51              
52             # create object
53 24         149 my $self= bless [], $class;
54              
55             # overrides
56 24   100     168 $self->[OFFSET]= delete $param{offset} || 0;
57 24   100     122 $self->[INCREMENT]= delete $param{increment} || 1;
58 24         48 my $storage= delete $param{storage};
59              
60             # sanity check
61 24 50       179 push @errors, "Offset may not be negative" if $self->[OFFSET] < 0;
62 24 50       74 push @errors, "Increment may not be negative" if $self->[INCREMENT] < 0;
63              
64             # need to initialize the lookup hash
65 24 100       62 if ( my $init= delete $param{init} ) {
66 5 50       12 push @errors, "Cannot have 'init' as well as a 'storage' parameter"
67             if $storage;
68              
69             # fill the hash
70 5 100       34 $self->init( reftype($init) eq 'HASH' ? $init : $init->() );
71             }
72              
73             # start afresh
74             else {
75 19         42 $self->[THEHASH]= {};
76 19         38 $self->[THELIST]= [];
77             }
78              
79             # need to have our own flush
80 24 100       79 if ( my $flush= delete $param{flush} ) {
81 7 50       14 push @errors, "Cannot have 'flush' as well as a 'storage' parameter"
82             if $storage;
83 7         14 $self->[FLUSH]= $flush;
84             }
85              
86             # we have a persistent backend
87 24 100       66 if ($storage) {
88 8         16 my $tag= delete $param{tag};
89 8         17 my $fork= delete $param{fork};
90              
91             # make sure we have the code
92 8 50       36 my $storage_class= $storage =~ m#::# ? $storage : "${class}::$storage";
93 8 50   1   806 eval "use $storage_class; 1" or die $@;
  1     1   630  
  1     1   3  
  1     1   16  
  1     1   9  
  1     1   2  
  1     1   13  
  1     1   7  
  1         44  
  1         13  
  1         8  
  1         3  
  1         13  
  1         9  
  1         2  
  1         17  
  1         9  
  1         2  
  1         13  
  1         6  
  1         2  
  1         14  
  1         10  
  1         4  
  1         14  
94              
95             # set up the options hash for the closures
96 8         29 my %options= ( tag => $tag ); # in closure
97 8         41 foreach my $name ( $storage_class->parameters_ok ) {
98 8 100       43 $options{$name}= delete $param{$name} if exists $param{$name};
99             }
100              
101             # some sanity checks
102 8 50 33     52 push @errors, "Must specify a 'tag'" if !defined $tag or !length $tag;
103              
104             # perform the initialization
105 8         36 $self->init( $storage_class->init( \%options ) );
106              
107             # need to fork for a flush
108 8 50       19 if ($fork) {
109             $self->[FLUSH]= sub {
110              
111             # in the parent
112 0     0   0 my $pid= fork;
113 0 0       0 return 1 if $pid;
114 0 0       0 return 0 if !defined $pid;
115              
116             # in the child process
117 0         0 exit !$storage_class->flush( \%options, @_ );
118 0         0 };
119             }
120              
121             # need to flush in this process
122             else {
123             $self->[FLUSH]= sub {
124 6     6   135 return $storage_class->flush( \%options, @_ );
125 8         50 };
126             }
127             }
128              
129             # do we flush?
130 24 100       666 if ( my $autoflush= delete $param{autoflush} ) {
131              
132             # huh?
133 6 50       46 if ( !$self->[FLUSH] ) {
    100          
    50          
134 0         0 push @errors, "Doesn't make sense to autoflush without flush";
135             }
136              
137             # autoflushing by seconds
138             elsif ( $autoflush =~ m#^([0-9]+)s$# ) {
139 1         3 my $seconds= $1;
140 1         10 my $epoch= time + $seconds;
141             $self->[AUTOFLUSH]= sub {
142 2 100   2   21 $epoch += $seconds, $_[0]->flush if time >= $epoch;
143 1         5 };
144             }
145              
146             # autoflushing by number of new ID's
147             elsif ( $autoflush =~ m#^[0-9]+$# ) {
148             $self->[AUTOFLUSH]= sub {
149 10 100   10   14 $_[0]->flush if @{ $_[0]->[TODO] } == $autoflush;
  10         170  
150 5         22 };
151             }
152              
153             # huh?
154             else {
155 0         0 push @errors, "Don't know what to do with autoflush '$autoflush'";
156             }
157             }
158              
159             # huh?
160 24 50       115 if ( my @huh= sort keys %param ) {
161 0         0 push @errors, "Don't know what to do with: @huh";
162             }
163              
164             # sorry
165 24 50       155 die join "\n", "Found the following problems:", @errors if @errors;
166              
167 24         125 return $self;
168             } #TIEHASH
169              
170             #-------------------------------------------------------------------------------
171             # FETCH
172             #
173             # IN: 1 underlying object
174             # 2 key to fetch (id or ref to string)
175             # OUT: 1 id or string
176              
177             sub FETCH {
178 78     78   2044401 my $self= shift;
179              
180             # string lookup
181 78 100       224 if ( ref $_[0] ) {
182 50   66     95 return $self->[THEHASH]->{ ${ $_[0] } } || do {
183              
184             # store string and index
185             my $index= $self->[OFFSET] += $self->[INCREMENT];
186             $self->[THEHASH]->{
187             $self->[THELIST]->[$index]= ${ $_[0] } # premature optimization
188             }= $index;
189              
190             # flushing
191             return $index if !$self->[FLUSH];
192             push @{ $self->[TODO] }, $index;
193              
194             # autoflushing
195             return $index if !$self->[AUTOFLUSH];
196             $self->[AUTOFLUSH]->($self);
197              
198             return $index;
199             };
200             }
201              
202             # id lookup
203 28         174 return $self->[THELIST]->[ $_[0] ];
204             } #FETCH
205              
206             #-------------------------------------------------------------------------------
207             # EXISTS
208             #
209             # IN: 1 underlying object
210             # 2 key to fetch (id or ref to string)
211             # OUT: 1 boolean
212              
213             sub EXISTS {
214              
215 6         27 return ref $_[1]
216 12 100   12   406 ? exists $_[0]->[THEHASH]->{ ${ $_[1] } } # string exists
217             : defined $_[0]->[THELIST]->[ $_[1] ]; # id exists
218             } #EXISTS
219              
220             #-------------------------------------------------------------------------------
221             # FIRSTKEY
222             #
223             # IN: 1 underlying object
224             # OUT: 1 first key
225              
226             sub FIRSTKEY {
227 10     10   321 my $self= shift;
228              
229             # initializations
230 10         23 my $index= $self->[INDEX]= 0;
231 10         18 my $list= $self->[THELIST];
232              
233             # find the next
234 10         101 $list->[$index] and $self->[INDEX]= $index and return $list->[$index]
235 10   33     16 while ++$index < @{$list};
      50        
236              
237             # alas
238 3         15 return undef;
239             } #FIRSTKEY
240              
241             #-------------------------------------------------------------------------------
242             # NEXTKEY
243             #
244             # IN: 1 underlying object
245             # OUT: 1 next key
246              
247             sub NEXTKEY {
248 12     12   22 my $self= shift;
249              
250             # initializations
251 12         19 my $index= $self->[INDEX];
252 12         16 my $list= $self->[THELIST];
253              
254             # find the next
255 12         90 $list->[$index] and $self->[INDEX]= $index and return $list->[$index]
256 12   33     17 while ++$index < @{$list};
      50        
257              
258             # alas
259 6         43 return undef;
260             } #NEXTKEY
261              
262             #-------------------------------------------------------------------------------
263             # SCALAR
264             #
265             # IN: 1 underlying object
266             # OUT: 1 underlying hash (for fast lookups)
267              
268 2     2   918 sub SCALAR { $_[0]->[THEHASH] } #SCALAR
269              
270             #-------------------------------------------------------------------------------
271             #
272             # Instance Methods
273             #
274             #-------------------------------------------------------------------------------
275             # flush (and DESTROY and UNTIE)
276             #
277             # IN: 1 underlying object
278             # OUT: 1 return value from flush sub
279              
280             sub flush {
281 33     33 1 14445 my $self= shift;
282              
283             # nothing to do
284 33 100       259 my $flush= $self->[FLUSH] or return;
285 24 100       802 my $todo= $self->[TODO] or return;
286              
287             # perform the flush
288 14 50       49 undef $self->[TODO]
289             if my $return= $flush->( $self->[THELIST], $todo );
290              
291 14         16808 return $return;
292             } #flush
293              
294             #-------------------------------------------------------------------------------
295             # init
296             #
297             # IN: 1 underlying object
298             # 2 hash ref to start with
299              
300             sub init {
301 13     13 1 29 my ( $self, $hash )= @_;
302              
303             # set the internal hash
304 13         65 $self->[THEHASH]= $hash;
305              
306             # make sure the internal list is set up as well
307 13         22 my @list;
308 13         22 $list[ $hash->{$_} ]= $_ foreach keys %{$hash};
  13         76  
309 13         118 $self->[THELIST]= \@list;
310              
311             # make sure offset is correct with potentially incorrectly filled hash
312 13 100       68 $self->[OFFSET]=
313             $#list +
314             $#list % $self->[INCREMENT] +
315             $self->[OFFSET] % $self->[INCREMENT]
316             if $#list > $self->[OFFSET];
317              
318 13         46 return;
319             } #init
320              
321             #-------------------------------------------------------------------------------
322              
323             __END__