File Coverage

blib/lib/Games/Object/Common.pm
Criterion Covered Total %
statement 107 128 83.5
branch 85 120 70.8
condition 14 22 63.6
subroutine 8 9 88.8
pod 0 3 0.0
total 214 282 75.8


line stmt bran cond sub pod time code
1             package Games::Object::Common;
2              
3 10     10   61 use strict;
  10         19  
  10         665  
4 10     10   54 use Exporter;
  10         19  
  10         564  
5              
6 10     10   56 use Carp qw(carp croak confess);
  10         18  
  10         1022  
7              
8 10     10   330 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS @ISA);
  10         286  
  10         1426  
9              
10             $VERSION = "0.10";
11             @ISA = qw(Exporter);
12             @EXPORT_OK = qw(ANAME_MANAGER FetchParams LoadData SaveData);
13             %EXPORT_TAGS = (
14             attrnames => [qw(ANAME_MANAGER)],
15             functions => [qw(FetchParams LoadData SaveData)],
16             );
17              
18 10     10   60 use constant ANAME_MANAGER => "_MANAGER";
  10         26  
  10         24976  
19              
20             ####
21             ## PUBLIC FUNCTIONS
22              
23             # Save an item of data to a file.
24              
25             sub SaveData
26             {
27 769     769 0 1029 my ($file, $data) = @_;
28              
29             # Check for undef, as this takes special handling.
30 769 50       1510 if (!defined($data)) {
31 0         0 print $file "U\n";
32 0         0 return 1;
33             }
34              
35             # Now handle everything else.
36 769         1013 my $ref = ref($data);
37 769 50 66     3461 if ($ref eq '' && $data =~ /\n/) {
    100 33        
    100          
    100          
    50          
38             # Multiline text scalar
39 0         0 my @lines = split(/\n/, $data);
40 0         0 print $file "M " . scalar(@lines) . "\n" .
41             join("\n", @lines) . "\n";
42             } elsif ($ref eq '') {
43             # Simple scalar.
44 534         983 print $file "S $data\n";
45             } elsif ($ref eq 'ARRAY') {
46             # Array.
47 50         101 print $file "A " . scalar(@$data) . "\n";
48 50         92 foreach my $item (@$data) {
49 94         170 SaveData($file, $item);
50             }
51             } elsif ($ref eq 'HASH') {
52             # Hash. WARNING: Hash keys cannot have newlines in them!
53 176         593 my @keys = keys %$data;
54 176         438 print $file "H " . scalar(@keys) . "\n";
55 176         350 foreach my $key (@keys) {
56 655         1086 print $file "$key\n";
57 655         1406 SaveData($file, $data->{$key});
58             }
59             } elsif ($ref && UNIVERSAL::can($ref, 'save')) {
60             # Pass along to save method of this object's class.
61 9         19 print $file "O $ref\n";
62 9         31 $data->save($file);
63             } else {
64             # SOL
65 0         0 croak("Cannot save reference to $ref object");
66             }
67 769         1595 1;
68             }
69              
70             # Load data from a file. This can take an optional second parameter. If present,
71             # this is taken to be a reference to a variable that will hold the data, rather
72             # than creating our own and returning the result (this applies only to
73             # non-scalar data). WARNING!! No check is made to insure that the reference
74             # type is compatible with what is in the file!
75              
76             sub LoadData
77             {
78 874     874 0 1033 my ($file, $store) = @_;
79 874         1209 my $line = <$file>;
80              
81             # The caller is responsible for calling this routine only when there
82             # is data to read.
83 874 50       1667 croak("Unexpected EOF") if (!defined($line));
84              
85             # Check for something we recognize.
86 874         1106 chomp $line;
87 874         1203 my $tag = substr($line, 0, 1);
88 874 50       1835 my $val = substr($line, 2) if ($tag ne 'U'); # Avoid substr warning
89 874 50       2271 if ($tag eq 'U') {
    100          
    50          
    100          
    100          
    50          
90             # Undef.
91 0         0 undef;
92             } elsif ($tag eq 'S') {
93             # Simple scalar value
94 610         1984 $val;
95             } elsif ($tag eq 'M') {
96             # Multiline text, to be returned as scalar.
97 0         0 my @text = ();
98 0         0 foreach my $i (1 .. $val) {
99 0         0 my $line2 = <$file>;
100 0 0       0 croak("Unexpected EOF") if (!defined($line2));
101 0         0 push @text, $line2;
102             }
103 0         0 join("\n", @text);
104             } elsif ($tag eq 'A') {
105             # Build an array.
106 58   50     212 my $ary = $store || [];
107 58         125 foreach my $i (1 .. $val) {
108 115         211 push @$ary, LoadData($file);
109             }
110 58         171 $ary;
111             } elsif ($tag eq 'H') {
112             # Reconstruct a hash.
113 196   100     675 my $hsh = $store || {};
114 196         404 foreach my $i (1 .. $val) {
115 738         1018 my $key = <$file>;
116 738         1053 chomp $key;
117 738         1371 $hsh->{$key} = LoadData($file);
118             }
119 196         621 $hsh;
120             } elsif ($tag eq 'O') {
121             # Object reference. We first make sure this has the proper method
122             # and then call it.
123 10 50       78 if (UNIVERSAL::can($val, 'load')) {
124 10         41 my $obj = $val->load($file);
125 10         113 $obj;
126             } else {
127 0         0 croak "Cannot load object of class '$val' (no load method)";
128             }
129             } else {
130             # Anything else is unrecognized.
131 0         0 croak("Unknown tag '$tag' in file, file may be corrupted");
132             }
133              
134             }
135              
136             # Fetch parameters, checking for required params and validating the values.
137              
138             sub FetchParams
139             {
140 1421     1421 0 2429 my ($args, $res, $opts, $del) = @_;
141 1421 100       7547 $del = 0 if (!defined($del));
142              
143             # If the first item is the name of this class, shift it off.
144 1421 50 66     6472 shift @$args if (@$args && $args->[0] =~ /^Games::Object/);
145              
146             # Now go down the opts list and see what parameters are needed.
147             # Return the results in a hash.
148 1421         5199 my %args = @$args;
149 1421         3492 while (my $spec = shift @$opts) {
150              
151             # Fetch the values for this spec. Note that not all may be present,
152             # depending on the type.
153 6868         11435 my ($type, $name, $dflt, $rstr) = @$spec;
154              
155             # Philosophy conflict: Many CPAN modules like args to be passed
156             # with '-' prefixing them. I don't. Useless use of an extra
157             # keystroke. However, I want to be consistent. Thus a compromise:
158             # I allow args to be passed with or without the '-', but it always
159             # gets stored internally without the '-'.
160 6868         8368 my $oname = $name;
161 6868 100       21229 $name = '-' . $name if (defined($args{"-${name}"}));
162              
163             # Is the attribute name a pattern? If so, here's what we do: we
164             # search the list of args for attribute names that match this
165             # and automagically generate specific options that we tack on
166             # to the end of the list.
167 6868 100       15876 if ($name =~ /[\^\$\.\+\*\[\{]/) {
168 104         1050 my @amatches = grep { /$name/ }
  104         377  
169 126         273 map { s/^\-//g; $_; }
  104         252  
170             keys %args;
171 126         265 foreach my $amatch (@amatches) {
172 16         72 push @$opts, [ $type, $amatch, $dflt, $rstr ];
173             }
174 126         413 next;
175             }
176              
177             # Check the type.
178 6742 100       21086 if ($type eq 'req') {
    50          
179              
180             # Required parameter, so it must be provided.
181 1085 50       2405 croak("Missing required argument '$name'")
182             unless (defined($args{$name}));
183 1085         2429 $res->{$oname} = $args{$name};
184              
185             } elsif ($type eq 'opt') {
186              
187             # Optional parameter. If not there and a default is specified,
188             # then set it to that.
189 5657 100       13406 if (defined($args{$name})) { $res->{$oname} = $args{$name}; }
  2407 100       5257  
190 1061         2223 elsif (defined($dflt)) { $res->{$oname} = $dflt; }
191              
192             }
193              
194             # Delete item from args if requested.
195 6742 100       13155 delete $args{$name} if ($del);
196              
197             # Stop here if we wound up with undef anyway or there are no
198             # restrictions on the parameter.
199 6742 100 66     24667 next if (!defined($res->{$oname}) || !defined($rstr));
200              
201             # Check for additional restrictions.
202 4334 100       22093 if (ref($rstr) eq 'CODE') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    50          
203              
204             # User defining own validation code.
205 260 50       834 croak("Invalid value '$res->{$oname}' for param '$name'")
206             if (! &$rstr($res->{$oname}) );
207              
208             } elsif (ref($rstr) eq 'ARRAY') {
209              
210             # Value must be one of these
211 231         284 my $found = 0;
212 231         392 foreach my $item (@$rstr) {
213 388         666 $found = ( $item eq $res->{$oname} );
214 388 100       956 last if $found;
215             }
216 231 50       964 croak("Invalid value '$res->{$oname}' for param '$name'")
217             unless ($found);
218              
219             } elsif ($rstr eq 'any') {
220              
221             # Automatically succeeds.
222              
223             } elsif ($rstr =~ /^(.+)ref$/) {
224              
225 529         1088 my $reftype = uc($1);
226 529 50       2508 croak("Parameter '$name' must be $reftype ref")
227             if (ref($res->{$oname}) ne $reftype);
228              
229             } elsif ($rstr eq 'int') {
230              
231             # Must be an integer.
232 1024 50       6367 croak("Param '$name' must be an integer")
233             if ($res->{$oname} !~ /^[\+\-\d]\d*$/);
234              
235             } elsif ($rstr eq 'number') {
236              
237             # Must be a number. Rather than trying to match against a
238             # heinously long regexp, we'll intercept the warning for
239             # a non-numeric when we try to int() it. TMTOWTDI.
240 99         137 my $not_number = 0;
241             local $SIG{__WARN__} = sub {
242 0     0   0 my $msg = shift;
243 0 0       0 if ($msg =~ /isn't numeric in int/) {
244 0         0 $not_number = 1;
245             } else {
246 0         0 warn $msg;
247             }
248 99         651 };
249 99         207 my $x = int($res->{$oname});
250 99 50       767 croak("Param '$name' must be a number") if ($not_number);
251              
252             } elsif ($rstr eq 'boolean') {
253              
254             # Must be a boolean. We simply convert to a 0 or 1.
255 429 50       1135 my $bool = ( $res->{$oname} eq '0' ? 0 :
    100          
256             $res->{$oname} eq '' ? 0 :
257             1 );
258 429         1452 $res->{$oname} = $bool;
259              
260             } elsif ($rstr eq 'string') {
261              
262             # Must not be a reference
263 602 50       2534 croak("Param '$name' must be a string, not a reference")
264             if (ref($res->{$oname}));
265              
266             } elsif ($rstr eq 'callback') {
267              
268             # Must be a callback definition, which is minimally an
269             # array with two items. Note that we can have lists of
270             # callbacks as well; so if this is not already such a list,
271             # make it one with a single entry for the purposes of checking
272             # it here.
273 234         365 my $list = $res->{$oname};
274 234 50       555 croak "Param '$name' must be a callback array or list of " .
275             "callback arrays" if (ref($list) ne 'ARRAY');
276 234 100 66     1242 $list = [ $list ]
277             if (@$list == 0 || ref($list->[0]) ne 'ARRAY');
278 234         433 foreach my $cbk (@$list) {
279 327 100 66     819 next if (!ref($cbk) && $cbk eq 'FAIL');
280 320 50       634 croak "Param '$name' must be a callback or list of " .
281             "callbacks" if (ref($cbk) ne 'ARRAY');
282 320 50       607 croak "Param '$name' callback must contain at least two " .
283             "parameters" if (@$cbk < 2);
284 320         451 foreach my $item (@$cbk) {
285 1578 50       3731 croak "Param '$name' callback args must be simple " .
286             "scalars" if (ref($item));
287             }
288             }
289              
290             } elsif ($rstr eq 'file') {
291              
292             # Must be reference to an IO::File or FileHandle object, or
293             # a GLOB.
294 36 50       228 croak("Param '$name' must be a file (IO::File/" .
295             "FileHandler object or GLOB reference acceptable)")
296             if (ref($res->{$oname}) !~ /^(IO::File|FileHandle|GLOB)$/);
297              
298             } elsif ($rstr eq 'readable_filename' ) {
299              
300             # Must be the name of a file that exists and is readable.
301 0 0       0 croak("Filename '$res->{$oname}' does not exist")
302             if (! -f $res->{$oname});
303 0 0       0 croak("Filename '$res->{$oname}' is not readable")
304             if (! -r $res->{$oname});
305              
306             } elsif ($rstr eq 'object') {
307              
308             # Must be an object reference
309 488         900 my $ref = ref($res->{$oname});
310 488 50       2649 croak("Param '$name' must be an object reference, not a " .
311             "'$ref' reference")
312             if ($ref =~ /^(SCALAR|ARRAY|HASH|CODE|REF|GLOB|LVALUE)$/);
313             } else {
314              
315 0         0 croak("'$rstr' is an invalid datatype");
316              
317             }
318             }
319              
320             # Set args to trimmed amount if delete option requested.
321 1421 100       3433 @$args = %args if ($del);
322              
323 1421         4439 $res;
324             }
325              
326             1;