File Coverage

blib/lib/Class/Colon.pm
Criterion Covered Total %
statement 95 95 100.0
branch 8 12 66.6
condition 2 2 100.0
subroutine 16 16 100.0
pod 0 7 0.0
total 121 132 91.6


line stmt bran cond sub pod time code
1             package Class::Colon;
2 4     4   228596 use strict; use warnings;
  4     4   30  
  4         97  
  4         16  
  4         5  
  4         270  
3              
4             our $VERSION = "0.04";
5              
6             =head1 NAME
7              
8             Class::Colon - Makes objects out of colon delimited records and vice versa
9              
10             =head1 VERSION
11              
12             This document covers version 0.03 of C.
13              
14             =head1 SYNOPSIS
15              
16             use Date;
17             use Class::Colon
18             Person => [ qw ( first middle family date_of_birth=Date=new ) ],
19             Address => [ qw ( street city province code country ) ];
20              
21             Person->DELIM(','); # change from colon to comma for delimeter
22             my $names = Person->READ_FILE($file_name);
23             foreach my $name (@$names) {
24             print $name->family, ",", $name->first, $name->middle, "\n";
25             }
26              
27             open ADDRESS_FILE, "addresses.dat" or die "...\n";
28             my $addresses = Address->READ_HANDLE(*ADDRESS_FILE);
29             foreach my $address (@$addresses) {
30             print $address->street . "\n"
31             print $address->city . ", " . $address->province . "\n";
32             print $address->country, "\n" if $address->country;
33             }
34             close ADDRESS_FILE;
35              
36             my $sample_address = Address->OBJECTIFY(
37             "1313 Mocking Bird Ln:Adamstown:PA:12345:USA"
38             ); # convert one string to an object
39              
40             my $first_address = $addresses->[0]->STRINGIFY();
41             # puts it back in delimited form
42              
43             Address->WRITE_FILE("output.dat", $addresses);
44            
45             open ADDRESS_FILE, ">newaddr.dat" or die "...\n";
46             Address->WRITE_HANDLE(*ADDRESS_FILE, $addresses);
47             close ADDRESS_FILE;
48              
49             =head1 DESCRIPTION
50              
51             To turn your colon delimited file into a list of objects, use C,
52             giving it the name you want to use for the class and an anonymous array of
53             column names which will become attributes of the objects in the class. List
54             the names in the order they appear in the input. Missing fields will be set
55             to "". Extra fields will be ignored. Use lower case names for the fields.
56             Upper case names are reserved for use as methods of the class.
57              
58             Most fields will be simple scalars, but if one of the fields should be an
59             object, its entry should be of the form
60              
61             attribute_name=package_name=constructor_name
62              
63             as shown above for C which is of type C whose constructor
64             is C. In that example, I could have omitted the constructor name, since
65             C is the default.
66              
67             You may objectify as many different record types as you like in one use
68             statement. You may have multiple use statements throughout your program
69             or module. If you are using this package from another package, you should
70             worry a little about namespace collision. There is only one list of classes
71             made by this package. The names must be unique or Bad Things will happen.
72             Feel free to include your module name in the names of the fabricated classes
73             as in:
74              
75             package YourModule;
76             use Class::Colon YourModule::Person => [ qw( field names here ) ];
77              
78             You wouldn't have to use the double colon, but it makes sense to me.
79              
80             If your delimiter is not colon, call DELIM on I class I calling
81             C. Pass it as a string. It can be any length, but is taken
82             literally.
83              
84             Feel free to add code to the generated package(s) before or after using
85             Class::Colon. But, keep in mind possible name conflicts. As pointed out
86             below (under METHODS), all ALL_CAPS names are reserved.
87              
88             =head1 ABSTRACT
89              
90             This module turns colon separated data files into lists of objects.
91              
92             =head2 EXPORT
93              
94             None, this is object oriented.
95              
96             =head1 METHODS
97              
98             There are currently only a few methods. There are two class methods
99             for reading, READ_FILE and READ_HANDLE, (these work for every class you
100             requested in your use Class::Colon statement). There are corresponding
101             class methods for writing, WRITE_FILE and WRITE_HANDLE. If you want to handle
102             the I/O manually (or maybe you don't need I/O), there are two methods to help,
103             OBJECTIFY (takes a string returns an object) and STRINGIFY (the opposite).
104             There is also a set of dual use accessors, one for each field in each class.
105             You name these yourself in the use statement. Finally, there is a DELIM
106             method which allows you to set the delimiter. This can be any literal string,
107             it applies to all fields in the file. There is a separate delimiter for
108             each class. It defaults to colon.
109              
110             You should consider every ALL_CAPS name reserved. I reserve the right to
111             add methods in the future, their names will be ALL_CAPS, as the current
112             method names are. Therefore, don't use ALL_CAPS for field names.
113              
114             In addition to retrieving the attributes through accessor methods, you
115             could peek directly at the data. It is stored in a hash so the following
116             are equivalent:
117              
118             my $country = $address->country();
119              
120             and
121              
122             my $country = $address->{country};
123              
124             Using this fact might make some things neater in your code (like print
125             statements). It also saves a tiny amount of time. Our OO teachers
126             will smack our hands, if they hear about this little arrangement, so keep
127             quite about it :-). I have no plans to change the implementation, but
128             they tell me never to make such promises.
129              
130             =cut
131              
132 4     4   22 use Carp;
  4         17  
  4         344  
133              
134             our %simulated_classes;
135              
136             sub import {
137 5     5   394 my $class = shift;
138 5         15 my %fakes = @_;
139              
140 5         19 foreach my $fake (keys %fakes) {
141 4     4   22 no strict;
  4         6  
  4         3446  
142 4     12   17 *{"$fake\::NEW"} = sub { return bless {}, shift; };
  4         25  
  12         98  
143              
144 4         12 my @proxy_method_names = qw(
145             read_file read_handle objectify delim
146             write_file write_handle stringify
147             );
148 4         7 foreach my $proxy_method (@proxy_method_names) {
149 28         48 my $proxy_name = "$fake" . "::" . uc $proxy_method;
150 28         38 my $real_name = "$class" . "::" . $proxy_method;
151 28         29 *{"$proxy_name"} = \&{"$real_name"};
  28         93  
  28         48  
152             }
153              
154 4         12 my @attributes;
155 4         4 foreach my $col (@{$fakes{$fake}}) {
  4         9  
156 15         34 my ($name, $type, $constructor) = split /=/, $col;
157 15         28 *{"$fake\::$name"} = _make_accessor($name, $type, $constructor);
  15         44  
158 15         30 push @attributes, $name;
159             }
160 4         5150 $simulated_classes{$fake} = {ATTRS => \@attributes, DELIM => ':'};
161             }
162             }
163              
164             sub _make_accessor {
165 15     15   18 my $attribute = shift;
166 15         17 my $type = shift;
167 15   100     45 my $constructor = shift || "new";
168              
169 15 100       26 if (defined $type) { # we need to call a constructor
170             return sub {
171 2     2   3 my $self = shift;
172 2         3 my $new_val = shift;
173 2 50       34 if (defined $new_val) {
174 2         9 $self->{$attribute} = $type->$constructor($new_val)
175             }
176 2         17 return $self->{$attribute};
177 1         3 };
178             }
179             else { # we can just dump the scalar into the attribute
180             return sub {
181 44     44   528 my $self = shift;
182 44         53 my $new_val = shift;
183 44 100       102 $self->{$attribute} = $new_val if defined $new_val;
184 44         70 return $self->{$attribute};
185 14         34 };
186             }
187             }
188              
189             =head2 DELIM
190              
191             Call this through one of the names you supplied in your use statement. Pass
192             it a string. For example, you could say
193              
194             Person->DELIM(';');
195              
196             this would change the delimiter from colon to semi-colon for Person. No
197             other classes would be affected.
198              
199             =cut
200              
201             sub delim {
202 1     1 0 771 my $fake_class = shift;
203 1         1 my $string = shift;
204              
205 1 50       4 if (defined $string) {
206 1         2 $simulated_classes{$fake_class}{DELIM} = $string;
207             }
208 1         3 return $simulated_classes{$fake_class}{DELIM};
209             }
210              
211             =head2 READ_FILE and READ_HANDLE
212              
213             Call these mehtods through one of the names you supplied in your use
214             statement.
215              
216             Both READ_FILE and READ_HANDLE return an array reference with one element
217             for each line in your input file. All lines are represented even if they
218             are blank or start with #. The array elements are objects of the same type
219             as the name you used to call the method. Think of these as super constructors,
220             instead of making one object at a time, they make as many as they can from
221             your input.
222              
223             READ_FILE takes the name of a file, which it opens, reads, and closes.
224              
225             READ_HANDLE takes an open handle ready for reading. You must ensure that the
226             handle is properly opened and closed.
227              
228             =cut
229              
230             sub read_file {
231 3     3 0 772 my $class = shift;
232 3         7 my $file = shift;
233              
234 3 50       88 open FILE, "$file" or croak "Couldn't read $file: $!";
235 3         18 my $retval = $class->READ_HANDLE(*FILE);
236 3         22 close FILE;
237              
238 3         11 return $retval;
239             }
240              
241             sub read_handle {
242 4     4 0 885 my $class = shift;
243 4         11 my $handle = shift;
244              
245 4         6 my @rows;
246 4         61 while (<$handle>) {
247 8         15 chomp;
248 8         25 push @rows, $class->OBJECTIFY($_);
249             }
250 4         16 return \@rows;
251             }
252              
253             =head2 OBJECTIFY
254              
255             If you want to control the read loop for your data, this method is here
256             to help you. Call it through a class name. Pass it one line (chomp it
257             yourself). Receive one object.
258              
259             =cut
260              
261             sub objectify {
262 11     11 0 952 my $class = shift;
263 11         18 my $string = shift;
264 11         15 my $config = $simulated_classes{$class};
265 11         15 my $col_list = $config->{ATTRS};
266              
267 11         27 my $new_object = $class->NEW();
268 11         59 my @cols = split /$config->{DELIM}/, $string;
269 11         34 foreach my $i (0 .. @cols - 1) {
270 42         56 my $method = $col_list->[$i];
271 42         75 $new_object->$method($cols[$i]);
272             }
273 11         60 return $new_object;
274             }
275              
276             =head2 WRITE_FILE and WRITE_HANDLE
277              
278             Call these mehtods through one of the names you supplied in your use
279             statement.
280              
281             Both WRITE_FILE and WRITE_HANDLE return an array reference with one element
282             for each line in your input file. The lines are made by joining the fields
283             in the order they appeared in the use statement using the current DELIM.
284              
285             WRITE_FILE takes the name of a file, which it opens, writes, and closes.
286              
287             WRITE_HANDLE takes a handle open for writing. You must ensure that the handle
288             is properly opened and closed.
289              
290             =cut
291              
292             sub write_file {
293 1     1 0 692 my $class = shift;
294 1         2 my $file = shift;
295              
296 1 50       48 open FILE, ">$file" or croak "Couldn't write $file: $!";
297 1         5 my $retval = $class->WRITE_HANDLE(*FILE, @_);
298 1         25 close FILE;
299              
300 1         4 return $retval;
301             }
302              
303             sub write_handle {
304 2     2 0 109 my $class = shift;
305 2         6 my $handle = shift;
306 2         3 my $rows = shift;
307              
308 2         5 foreach my $row (@$rows) {
309 4         13 print $handle $row->STRINGIFY() . "\n";
310             }
311             }
312              
313             =head2 STRINGIFY
314              
315             Call this through an object you got by using Class::Colon. Receive
316             a colon delimited string suitable for writing back to your file. The
317             string comes with no newline, unless the last field happens to have one.
318             You may need to supply a newline, especially if you chomped.
319              
320             =cut
321              
322             sub stringify {
323 5     5 0 10 my $self = shift;
324 5         8 my $type = ref($self);
325 5         10 my $config = $simulated_classes{$type};
326 5         6 my $col_list = $config->{ATTRS};
327 5         7 my $retval;
328              
329             my @fields;
330 5         7 foreach my $att (@$col_list) {
331 15         25 push @fields, $self->{$att};
332             }
333 5         45 return join $config->{DELIM}, @fields;
334             }
335              
336             =head2 accessors
337              
338             For each attribute you name in your use statement, there is a corresponding
339             dual use accessor. The names of the accessors are the same as the names
340             you used (how convenient). You can also fish directly in the hash based
341             object using the name of attribute as the key, but don't tell your OO
342             instructor.
343              
344             =cut
345              
346             =head1 BUGS and OMISSIONS
347              
348             There is no quoting. If a colon (or the DELIM of your choice) is
349             quoted, it still counts as a field separator.
350              
351             Comments and blank lines are treated as regular records.
352              
353             =head1 AUTHOR
354              
355             Phil Crow, Ephilcrow2000@yahoo.comE
356              
357             =head1 COPYRIGHT AND LICENSE
358              
359             Copyright 2003 by Phil Crow, all rights reserved.
360              
361             This library is free software; you can redistribute it and/or modify
362             it under the same terms as Perl 5.8.1 itself.
363              
364             =cut
365              
366             1;