File Coverage

blib/lib/Iterator.pm
Criterion Covered Total %
statement 55 62 88.7
branch 19 22 86.3
condition 2 3 66.6
subroutine 12 14 85.7
pod 5 5 100.0
total 93 106 87.7


line stmt bran cond sub pod time code
1             =for gpg
2             -----BEGIN PGP SIGNED MESSAGE-----
3             Hash: SHA1
4              
5             =head1 NAME
6              
7             Iterator - A general-purpose iterator class.
8              
9             =head1 VERSION
10              
11             This documentation describes version 0.03 of Iterator.pm, October 10, 2005.
12              
13             =cut
14              
15 3     3   143762 use strict;
  3         7  
  3         109  
16 3     3   14 use warnings;
  3         7  
  3         392  
17             package Iterator;
18             our $VERSION = '0.03';
19              
20             # Declare exception classes
21             use Exception::Class
22             (
23 3         57 'Iterator::X' =>
24             {
25             description => 'Generic Iterator exception',
26             },
27             'Iterator::X::Parameter_Error' =>
28             {
29             isa => 'Iterator::X',
30             description => 'Iterator method parameter error',
31             },
32             'Iterator::X::OptionError' =>
33             {
34             isa => 'Iterator::X',
35             fields => 'name',
36             description => 'A bad option was passed to an iterator method or function',
37             },
38             'Iterator::X::Exhausted' =>
39             {
40             isa => 'Iterator::X',
41             description => 'Attempt to next_value () on an exhausted iterator',
42             },
43             'Iterator::X::Am_Now_Exhausted' =>
44             {
45             isa => 'Iterator::X',
46             description => 'Signals Iterator object that it is now exhausted',
47             },
48             'Iterator::X::User_Code_Error' =>
49             {
50             isa => 'Iterator::X',
51             fields => 'eval_error',
52             description => q{An exception was thrown within the user's code},
53             },
54             'Iterator::X::IO_Error' =>
55             {
56             isa => 'Iterator::X',
57             fields => 'os_error',
58             description => q{An I/O error occurred},
59             },
60             'Iterator::X::Internal_Error' =>
61             {
62             isa => 'Iterator::X',
63             description => 'An Iterator.pm internal error. Please contact author.',
64             },
65 3     3   3859 );
  3         111874  
66              
67             # Class method to help caller catch exceptions
68             BEGIN
69             {
70             # Dave Rolsky added this subroutine in v1.22 of Exception::Class.
71             # Thanks, Dave!
72             # We define it here so we have the functionality in pre-1.22 versions;
73             # we make it conditional so as to avoid a warning in post-1.22 versions.
74             *Exception::Class::Base::caught = sub
75             {
76 0         0 my $class = shift;
77 0         0 return Exception::Class->caught($class);
78             }
79 3 50   3   18856 if $Exception::Class::VERSION lt '1.22';
80             }
81              
82             # Croak-like location of error
83             sub Iterator::X::location
84             {
85 14     14   21 my ($pkg,$file,$line);
86 14         25 my $caller_level = 0;
87 14         17 while (1)
88             {
89 42         269 ($pkg,$file,$line) = caller($caller_level++);
90 42 100 66     262 last if $pkg !~ /\A Iterator/x && $pkg !~ /\A Exception::Class/x
91             }
92 14         69 return "at $file line $line";
93             }
94              
95             # Die-like location of error
96             sub Iterator::X::Internal_Error::location
97             {
98 0     0   0 my $self = shift;
99 0         0 return "at " . $self->file () . " line " . $self->line ()
100             }
101              
102             # Override full_message, to report location of error in caller's code.
103             sub Iterator::X::full_message
104             {
105 16     16   19817 my $self = shift;
106              
107 16         85 my $msg = $self->message;
108 16 100       129 return $msg if substr($msg,-1,1) eq "\n";
109              
110 14         60 $msg =~ s/[ \t]+\z//; # remove any trailing spaces (is this necessary?)
111 14         66 return $msg . q{ } . $self->location () . qq{\n};
112             }
113              
114              
115             ## Constructor
116              
117             # Method name: new
118             # Synopsis: $iterator = Iterator->new( $code_ref );
119             # Description: Object constructor.
120             # Created: 07/27/2005 by EJR
121             # Parameters: $code_ref - the iterator sequence generation code.
122             # Returns: New Iterator.
123             # Exceptions: Iterator::X::Parameter_Error (via _initialize)
124             sub new
125             {
126 10     10 1 6520 my $class = shift;
127 10         21 my $self = \do {my $anonymous};
  10         27  
128 10         25 bless $self, $class;
129 10         35 $self->_initialize(@_);
130 6         19 return $self;
131             }
132              
133             { # encapsulation enclosure
134              
135             # Attributes:
136             my %code_for; # The sequence code (coderef) for each object.
137             my %is_exhausted; # Boolean: is this object exhausted?
138             my %next_value_for; # One-item lookahead buffer for each object.
139             # [if you update this list of attributes, be sure to edit DESTROY]
140              
141             # Method name: _initialize
142             # Synopsis: $iterator->_initialize( $code_ref );
143             # Description: Object initializer.
144             # Created: 07/27/2005 by EJR
145             # Parameters: $code_ref - the iterator sequence generation code.
146             # Returns: Nothing.
147             # Exceptions: Iterator::X::Parameter_Error
148             # Iterator::X::User_Code_Error
149             # Notes: For internal module use only.
150             # Caches the first value of the iterator in %next_value_for.
151             sub _initialize
152             {
153 10     10   15 my $self = shift;
154              
155 10 100       60 Iterator::X::Parameter_Error->throw(q{Too few parameters to Iterator->new()})
156             if @_ < 1;
157 9 100       102 Iterator::X::Parameter_Error->throw(q{Too many parameters to Iterator->new()})
158             if @_ > 1;
159 8         17 my $code = shift;
160 8 100       39 Iterator::X::Parameter_Error->throw (q{Parameter to Iterator->new() must be code reference})
161             if ref $code ne 'CODE';
162              
163 6         30 $code_for {$self} = $code;
164              
165             # Get the next (first) value for this iterator
166             eval
167 6         17 {
168 6         19 $next_value_for{$self} = $code-> ();
169             };
170              
171 6         38 my $ex;
172 6 50       68 if ($ex = Iterator::X::Am_Now_Exhausted->caught ())
    50          
173             {
174             # Starting off exhausted is okay
175 0         0 $is_exhausted{$self} = 1;
176             }
177             elsif ($@)
178             {
179 0         0 Iterator::X::User_Code_Error->throw (message => "$@",
180             eval_error => $@);
181             }
182              
183 6         71 return;
184             }
185              
186             # Method name: DESTROY
187             # Synopsis: (none)
188             # Description: Object destructor.
189             # Created: 07/27/2005 by EJR
190             # Parameters: None.
191             # Returns: Nothing.
192             # Exceptions: None.
193             # Notes: Invoked automatically by perl.
194             # Releases the hash entries used by the object.
195             # Module would leak memory otherwise.
196             sub DESTROY
197             {
198 10     10   3707 my $self = shift;
199 10         49 delete $code_for{$self};
200 10         19 delete $is_exhausted{$self};
201 10         400 delete $next_value_for{$self};
202             }
203              
204             # Method name: value
205             # Synopsis: $next_value = $iterator->value();
206             # Description: Returns each value of the sequence in turn.
207             # Created: 07/27/2005 by EJR
208             # Parameters: None.
209             # Returns: Next value, as generated by caller's code ref.
210             # Exceptions: Iterator::X::Exhausted
211             # Notes: Keeps one forward-looking value for the iterator in
212             # %next_value_for. This is so we have something to
213             # return when user's code throws Am_Now_Exhausted.
214             sub value
215             {
216 15     15 1 42871 my $self = shift;
217              
218 15 100       89 Iterator::X::Exhausted->throw(q{Iterator is exhausted})
219             if $is_exhausted{$self};
220              
221             # The value that we'll be returning this time.
222 12         29 my $this_value = $next_value_for{$self};
223              
224             # Compute the value that we'll return next time
225             eval
226 12         18 {
227 12         47 $next_value_for{$self} = $code_for{$self}->(@_);
228             };
229 12 100       1718 if (my $ex = Iterator::X::Am_Now_Exhausted->caught ())
    100          
230             {
231             # Aha, we're done; we'll have to stop next time.
232 3         55 $is_exhausted{$self} = 1;
233             }
234             elsif ($@)
235             {
236 1         38 Iterator::X::User_Code_Error->throw (message => "$@",
237             eval_error => $@);
238             }
239              
240 11         2245 return $this_value;
241             }
242              
243             # Method name: is_exhausted
244             # Synopsis: $boolean = $iterator->is_exhausted();
245             # Description: Flag indicating that the iterator is exhausted.
246             # Created: 07/27/2005 by EJR
247             # Parameters: None.
248             # Returns: Current value of %is_exhausted for this object.
249             # Exceptions: None.
250             sub is_exhausted
251             {
252 5     5 1 9102 my $self = shift;
253              
254 5         23 return $is_exhausted{$self};
255             }
256              
257             # Method name: isnt_exhausted
258             # Synopsis: $boolean = $iterator->isnt_exhausted();
259             # Description: Flag indicating that the iterator is NOT exhausted.
260             # Created: 07/27/2005 by EJR
261             # Parameters: None.
262             # Returns: Logical NOT of %is_exhausted for this object.
263             # Exceptions: None.
264             sub isnt_exhausted
265             {
266 10     10 1 35 my $self = shift;
267              
268 10         36 return ! $is_exhausted{$self};
269             }
270              
271             } # end of encapsulation enclosure
272              
273              
274             # Function name: is_done
275             # Synopsis: Iterator::is_done ();
276             # Description: Convenience function. Throws an Am_Now_Exhausted exception.
277             # Created: 08/02/2005 by EJR, per Will Coleda's suggestion.
278             # Parameters: None.
279             # Returns: Doesn't return.
280             # Exceptions: Iterator::X::Am_Now_Exhausted
281             sub is_done
282             {
283 0     0 1   Iterator::X::Am_Now_Exhausted->throw()
284             }
285              
286              
287             1;
288             __END__