File Coverage

blib/lib/Class/Colon.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package Class::Colon;
2 4     4   108899 use strict; use warnings;
  4     4   8  
  4         137  
  4         18  
  4         7  
  4         246  
3              
4             our $VERSION = "0.03";
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   21 use Carp;
  4         9  
  4         658  
133              
134             our %simulated_classes;
135              
136             sub import {
137             my $class = shift;
138             my %fakes = @_;
139              
140             foreach my $fake (keys %fakes) {
141 4     4   26 no strict;
  4         6  
  4         7743  
142             *{"$fake\::NEW"} = sub { return bless {}, shift; };
143              
144             foreach my $proxy_method qw(
145             read_file read_handle objectify delim
146             write_file write_handle stringify
147             ) {
148             my $proxy_name = "$fake" . "::" . uc $proxy_method;
149             my $real_name = "$class" . "::" . $proxy_method;
150             *{"$proxy_name"} = \&{"$real_name"};
151             }
152              
153             my @attributes;
154             foreach my $col (@{$fakes{$fake}}) {
155             my ($name, $type, $constructor) = split /=/, $col;
156             *{"$fake\::$name"} = _make_accessor($name, $type, $constructor);
157             push @attributes, $name;
158             }
159             $simulated_classes{$fake} = {ATTRS => \@attributes, DELIM => ':'};
160             }
161             }
162              
163             sub _make_accessor {
164             my $attribute = shift;
165             my $type = shift;
166             my $constructor = shift || "new";
167              
168             if (defined $type) { # we need to call a constructor
169             return sub {
170             my $self = shift;
171             my $new_val = shift;
172             if (defined $new_val) {
173             $self->{$attribute} = $type->$constructor($new_val)
174             }
175             return $self->{$attribute};
176             };
177             }
178             else { # we can just dump the scalar into the attribute
179             return sub {
180             my $self = shift;
181             my $new_val = shift;
182             $self->{$attribute} = $new_val if defined $new_val;
183             return $self->{$attribute};
184             };
185             }
186             }
187              
188             =head2 DELIM
189              
190             Call this through one of the names you supplied in your use statement. Pass
191             it a string. For example, you could say
192              
193             Person->DELIM(';');
194              
195             this would change the delimiter from colon to semi-colon for Person. No
196             other classes would be affected.
197              
198             =cut
199              
200             sub delim {
201             my $fake_class = shift;
202             my $string = shift;
203              
204             if (defined $string) {
205             $simulated_classes{$fake_class}{DELIM} = $string;
206             }
207             return $simulated_classes{$fake_class}{DELIM};
208             }
209              
210             =head2 READ_FILE and READ_HANDLE
211              
212             Call these mehtods through one of the names you supplied in your use
213             statement.
214              
215             Both READ_FILE and READ_HANDLE return an array reference with one element
216             for each line in your input file. All lines are represented even if they
217             are blank or start with #. The array elements are objects of the same type
218             as the name you used to call the method. Think of these as super constructors,
219             instead of making one object at a time, they make as many as they can from
220             your input.
221              
222             READ_FILE takes the name of a file, which it opens, reads, and closes.
223              
224             READ_HANDLE takes an open handle ready for reading. You must ensure that the
225             handle is properly opened and closed.
226              
227             =cut
228              
229             sub read_file {
230             my $class = shift;
231             my $file = shift;
232              
233             open FILE, "$file" or croak "Couldn't read $file: $!";
234             my $retval = $class->READ_HANDLE(*FILE);
235             close FILE;
236              
237             return $retval;
238             }
239              
240             sub read_handle {
241             my $class = shift;
242             my $handle = shift;
243              
244             my @rows;
245             while (<$handle>) {
246             chomp;
247             push @rows, $class->OBJECTIFY($_);
248             }
249             return \@rows;
250             }
251              
252             =head2 OBJECTIFY
253              
254             If you want to control the read loop for your data, this method is here
255             to help you. Call it through a class name. Pass it one line (chomp it
256             yourself). Receive one object.
257              
258             =cut
259              
260             sub objectify {
261             my $class = shift;
262             my $string = shift;
263             my $config = $simulated_classes{$class};
264             my $col_list = $config->{ATTRS};
265              
266             my $new_object = $class->NEW();
267             my @cols = split /$config->{DELIM}/, $string;
268             foreach my $i (0 .. @cols - 1) {
269             my $method = $col_list->[$i];
270             $new_object->$method($cols[$i]);
271             }
272             return $new_object;
273             }
274              
275             =head2 WRITE_FILE and WRITE_HANDLE
276              
277             Call these mehtods through one of the names you supplied in your use
278             statement.
279              
280             Both WRITE_FILE and WRITE_HANDLE return an array reference with one element
281             for each line in your input file. The lines are made by joining the fields
282             in the order they appeared in the use statement using the current DELIM.
283              
284             WRITE_FILE takes the name of a file, which it opens, writes, and closes.
285              
286             WRITE_HANDLE takes a handle open for writing. You must ensure that the handle
287             is properly opened and closed.
288              
289             =cut
290              
291             sub write_file {
292             my $class = shift;
293             my $file = shift;
294              
295             open FILE, ">$file" or croak "Couldn't write $file: $!";
296             my $retval = $class->WRITE_HANDLE(*FILE, @_);
297             close FILE;
298              
299             return $retval;
300             }
301              
302             sub write_handle {
303             my $class = shift;
304             my $handle = shift;
305             my $rows = shift;
306              
307             foreach my $row (@$rows) {
308             print $handle $row->STRINGIFY() . "\n";
309             }
310             }
311              
312             =head2 STRINGIFY
313              
314             Call this through an object you got by using Class::Colon. Receive
315             a colon delimited string suitable for writing back to your file. The
316             string comes with no newline, unless the last field happens to have one.
317             You may need to supply a newline, especially if you chomped.
318              
319             =cut
320              
321             sub stringify {
322             my $self = shift;
323             my $type = ref($self);
324             my $config = $simulated_classes{$type};
325             my $col_list = $config->{ATTRS};
326             my $retval;
327              
328             my @fields;
329             foreach my $att (@$col_list) {
330             push @fields, $self->{$att};
331             }
332             return join $config->{DELIM}, @fields;
333             }
334              
335             =head2 accessors
336              
337             For each attribute you name in your use statement, there is a corresponding
338             dual use accessor. The names of the accessors are the same as the names
339             you used (how convenient). You can also fish directly in the hash based
340             object using the name of attribute as the key, but don't tell your OO
341             instructor.
342              
343             =cut
344              
345             =head1 BUGS and OMISSIONS
346              
347             There is no quoting. If a colon (or the DELIM of your choice) is
348             quoted, it still counts as a field separator.
349              
350             Comments and blank lines are treated as regular records.
351              
352             =head1 AUTHOR
353              
354             Phil Crow, Ephilcrow2000@yahoo.comE
355              
356             =head1 COPYRIGHT AND LICENSE
357              
358             Copyright 2003 by Phil Crow, all rights reserved.
359              
360             This library is free software; you can redistribute it and/or modify
361             it under the same terms as Perl 5.8.1 itself.
362              
363             =cut
364              
365             1;