File Coverage

blib/lib/Class/Tangram/Generator.pm
Criterion Covered Total %
statement 137 180 76.1
branch 43 80 53.7
condition 5 19 26.3
subroutine 17 24 70.8
pod 1 6 16.6
total 203 309 65.7


line stmt bran cond sub pod time code
1             package Class::Tangram::Generator;
2              
3 6     6   3352 use strict 'vars', 'subs';
  5         89  
  5         182  
4 4     4   29 use Set::Object qw(reftype refaddr blessed);
  3         6  
  3         235  
5 3     3   15 use Carp;
  3         7  
  3         164  
6 3     3   4041 use Class::Tangram::Generator::Stub;
  3         11  
  3         81  
7              
8 3     3   2913 use IO::Handle;
  3         26609  
  3         216  
9              
10 3     3   27 use vars qw($VERSION $singleton $stub);
  3         75  
  3         263  
11             $VERSION = 0.02;
12              
13 3     3   62 BEGIN {
14 3     3   17 no warnings;
  3         5  
  3         99  
15             }
16              
17             # to re-define at run-time, use:
18             # *{Class::Tangram::Generator::DEBUG}=sub{1}
19 3     3   15 use constant DEBUG => 0;
  3         6  
  3         2820  
20              
21             sub debug_out {
22 0     0 0 0 print STDERR __PACKAGE__."[$$]: @_\n";
23             }
24              
25             $stub = $INC{'Class/Tangram/Generator/Stub.pm'};
26              
27             sub DESTROY {
28 0     0   0 my $self = shift;
29 0 0 0     0 @INC = grep { defined and
  0         0  
30             (!ref($_) or refaddr($_) ne refaddr($self)) }
31             @INC;
32             }
33              
34             sub new {
35              
36 6     6 1 5801 my ($class, $self) = (shift, undef);
37              
38 6 100       26 unless ( ref $class ) {
39              
40             # build a new Class::Tangram::Generator
41 3         6 $self = {};
42 3 50       22 $self->{_schema} = shift or croak "Must supply schema!";
43              
44             # find out what base class they want to use:
45 3   50     61 $self->{_base} = $self->{_schema}->{Base} ||
46             shift(@_) || 'Class::Tangram';
47              
48 3         338 eval "require $self->{_base}";
49 3 50       18 croak $@ if $@;
50              
51             # now extract the schema itself:
52 3 50 50     161 $self->{_schema} = ($self->{_schema}->{classes} ||
53             $self->{_schema}->{Schema}->{classes} || {}
54             ) if reftype $self->{_schema} eq "HASH";
55              
56             # convert arrayref into a hashref if necessary:
57 3 50       15 $self->{_schema} = { @{$self->{_schema}} }
  3         28  
58             if ref $self->{_schema} eq "ARRAY";
59              
60             # create load-on-demand new() constructors
61             #for my $class (grep {!ref} @{ $self->{_schema} }) {
62 3         9 while (my $class = each %{ $self->{_schema} }) {
  27         173  
63 24         24 (DEBUG>1) && debug_out("Setting up generator for $class");
64 24         46 my $ref = "${class}::new";
65 24         176 *{ $ref } = sub {
66 4     4   2504 shift;
67 4         9 (DEBUG) && do {
68             my ($pkg,$file,$line)=caller();
69             debug_out("tripped $class->new() ($pkg"
70             ." [$file:$line])");
71             };
72 4         8 undef *{ $class }; # avoid warnings
  4         27  
73 4         19 $self->load_class($class);
74 3 50 33     34 unless (blessed $_ and $_->isa(__PACKAGE__)) {
75 3         15 unshift @_, $self, $class;
76             #my $coderef = $self->can("new");
77 3         34 goto \&new;
78             }
79 24 50       28 } unless defined &{ $ref };
  24         152  
80 24         76 *{ $ref } = \42;
  24         73  
81             }
82              
83             # hash to list already handled classes
84 3         12 $self->{_done} = {};
85              
86 3         11 bless $self, $class;
87              
88 3         8 unshift @INC, $self;
89 3         9 $singleton = $self;
90              
91 3         17 return $self;
92              
93             } else {
94              
95             # setup and build a new $class object.
96 3         10 ($self, $class) = ($class, shift);
97              
98 3 50       13 unless ($class) {
99 0         0 croak "Must supply a classname or schema!";
100             }
101              
102             # make a new C::T::Gen with new schema
103 3 50       13 if(ref $class eq 'HASH') {
104 0         0 return __PACKAGE__->new($class, @_);
105             }
106              
107 3 50       14 exists $self->{_schema}->{$class} or croak "Unknown class: $class";
108 3 50       15 $self->load_class($class) unless $self->{_done}->{$class};
109              
110 3         46 my $coderef = $class->can("new");
111 3         7 unshift @_, $class;
112 3         13 goto $coderef;
113             }
114             }
115              
116             sub load_class {
117              
118 18     18 0 41 my ($self, $class, $skip_use) = @_;
119              
120 18 50       78 exists $self->{_schema}->{$class} or croak "Unknown class: $class";
121 18 50       152 unless($self->{_done}->{$class}) {
122              
123 18         18 (DEBUG) && debug_out("load_class $class");
124 3     3   20 no strict 'refs';
  3         4  
  3         16436  
125 18         24 undef *{ $class."::new" }; # avoid warnings
  18         92  
126              
127 18 100       24 for my $base (@{$self->{_schema}->{$class}->{bases} || []}) {
  18         115  
128 10 100       31 unless ($self->{_done}->{$base}) {
129 5         50 $self->load_class($base) ;
130             }
131 10         12 (DEBUG>1) && debug_out("pushing $base on to \@{ ${class}::ISA }");
132 10 100       68 push @{"${class}::ISA"}, $base
  5         161  
133             unless UNIVERSAL::isa($class, $base);
134             }
135              
136 18 100       54 if (defined $skip_use) {
137 9 100       41 if ($skip_use) {
138             #print STDERR "skip_use is $skip_use\n";
139 2         6 (DEBUG) && debug_out("loading $class from $skip_use");
140 2 50       77 open GEN, "<$skip_use" or die $!;
141 2         129641 my $code = join "", <GEN>;
142 2         61 close GEN;
143 2         394 eval $code;
144 2 100       31 die $@ if $@;
145             (DEBUG) && debug_out
146             ("symbols loaded: "
147             .join (" ", map {
148             (defined &{ $class."::$_" } ? "&" : "")
149             .(defined ${ $class."::$_" } ? "\$" : "")
150             .(defined @{ $class."::$_" } ? "\@" : "")
151             .(defined %{ $class."::$_" } ? "\%" : "")
152             ."$_"
153 1         1 } keys %{ $class."::" }));
154             (DEBUG) && debug_out
155 1         4 ("ISA is now: ".join(" ", @{ $class."::ISA" }));
156             }
157             } else {
158 9         33 (my $filename = $class) =~ s{::}{/}g;
159 9         24 $filename .= ".pm";
160 9 50       34 if ( exists $INC{$filename} ) {
161 0         0 (DEBUG) && debug_out("not loading $filename - already"
162             ." loaded");
163             } else {
164 9         12 (DEBUG>1) && debug_out("loading class via `use $class'");
165 3     3   35 eval "use $class";
  3     3   115  
  3         59  
  3         34  
  3         138  
  3         71  
  9         1684  
166             #warn "Got a warning: $@" if $@;
167 9 100 66     303 croak __PACKAGE__.": auto-include $class failed; $@"
168             if ($@ && $@ !~ /^Can't locate \Q$filename.pm\E/);
169 8         19 (DEBUG>1 && $@) && debug_out("no module for $class");
170             }
171             }
172              
173 16         59 $self->post_load($class);
174             }
175             }
176              
177             sub post_load {
178 17     17 0 2659 my $self = shift;
179 17         35 my $class = shift;
180              
181 16         25 push @{"${class}::ISA"}, $self->{_base};
  16         389  
182 8         34 ${"${class}::schema"} = $self->{_schema}->{$class}
  16         96  
183 16 100       129 unless defined ${"${class}::schema"};
184              
185             # import subroutine methods defined in schema, BEFORE
186             # Class::Tangram defines accessor methods.
187 16 100       47 while ( my ($name, $sub) =
  20         248  
188             each %{ $self->{_schema}->{$class}->{methods} || {} } ) {
189 4         7 (DEBUG>1)
190             && debug_out("inserting method into ${class}::${name}");
191 2         11 *{"${class}::${name}"} = $sub
  4         27  
192 4 100       4 unless defined &{"${class}::${name}"}
193             }
194              
195 16         30 &{"$self->{_base}::import_schema"}($class);
  16         180  
196              
197 16         88 $self->{_done}->{$class}++;
198             }
199              
200             sub Class::Tangram::Generator::INC {
201 13     13 0 29 my $self = shift;
202 13         21 my $fn = shift;
203              
204 13         41 (my $pkg = $fn) =~ s{/}{::}g;
205 13         62 $pkg =~ s{.pm$}{};
206              
207 13         151 (DEBUG>1) && debug_out "saw include for $pkg";
208              
209 13 100       83 if ($self->{_schema}->{$pkg}) {
210              
211 9         15 my $file = "";
212 9         21 for my $path (@INC) {
213 90 100       209 next if ref $path;
214 81 100       2168 if (-f "$path/$fn") {
215 2         6 $file = "$path/$fn";
216 2         6 last;
217             }
218             }
219              
220 9         63 $self->load_class($pkg, $file);
221              
222             # OK, this is getting into some pretty kooky magic, but
223             # essentially GENERATOR_HANDLE returns the file intact, but
224             # places a hoook on the end to finish up Class::Tangram
225              
226             #print STDERR "Generator: returning dummy to Perl\n";
227              
228 8 50       787 open DEVNULL, "<$stub" or die $!;
229 8         3603 return \*DEVNULL;
230              
231             } else {
232             #print STDERR "Generator: not one of mine, ignoring\n";
233 4         2109 return undef;
234             }
235             }
236              
237             #BEGIN {
238             #${__PACKAGE__."::INC"} = \&FOOINC;
239             #}
240              
241             sub READLINE {
242 0     0     my $self = shift;
243 0 0         if (wantarray) {
244 0           my @rv;
245             my $val;
246 0           while (defined ($val = $self->READLINE)) {
247 0           push @rv, $val;
248             }
249 0           return @rv;
250             }
251              
252 0 0 0       if (!$self->{fh} && $self->{source}) {
253 0 0         open GENERATOR_PM, "<$self->{source}" or die $!;
254 0           $self->{source} = IO::Handle->new_from_fd("GENERATOR_PM", "r");
255 0           *GENERATOR_PM = *GENERATOR_PM if 0;
256             }
257              
258 0           my $retval;
259              
260             AGAIN:
261 0 0 0       if (!$self->{state}) {
    0          
    0          
    0          
262              
263             # the package
264              
265 0           $self->{state} = "Package";
266 0           $retval = "package $self->{package};\n";
267              
268             } elsif ($self->{state} =~ m/Package/ && $self->{fh}) {
269              
270             # their code
271              
272 0           my $line = $self->{fh}->getline;
273 0 0         if ($line =~ m/^__END__/) {
274 0           $self->{state} = m/postamble/;
275 0           goto AGAIN;
276             }
277 0 0         if (defined($line)) {
278 0           $retval = $line;
279             } else {
280 0           $self->{state} = "postamble";
281 0           goto AGAIN;
282             }
283              
284             } elsif ($self->{state} =~ m/Package|postamble/) {
285              
286             # extra stuff normally done by load_class
287 0           $self->{state} = "END";
288 0           $retval =("\$Class::Tangram::Generator::singleton->post_load"
289             ."('$self->{package}');\n");
290              
291             } elsif ($self->{state} =~ m/END/) {
292              
293 0 0         $self->{fh}->close() if $self->{fh};
294 0           $retval = undef;
295              
296             }
297              
298 0           return $retval;
299             }
300              
301             sub GETC {
302 0     0     my $self = shift;
303 0           die "No getc!";
304             }
305              
306             sub TIEHANDLE {
307 0     0     my $class = shift;
308 0           my $package = shift;
309 0           return bless { package => $package }, $class;
310             }
311              
312             sub SOURCE {
313 0     0 0   my $self = shift;
314 0           $self->{source} = shift;
315             }
316              
317             sub READ {
318 0     0     my $self = shift;
319 0           die "No read!";
320             }
321              
322              
323             1;
324             __END__
325              
326             =head1 NAME
327              
328             Class::Tangram::Generator - Generate Class::Tangram-based objects at runtime.
329              
330             =head1 SYNOPSIS
331              
332             use Class::Tangram::Generator;
333              
334             my $schema = { ... }; # a Tangram schema definition hashref,
335             # including all classes
336             my $gen = new Class::Tangram::Generator $schema;
337              
338             my $orange = $gen->new('Orange');
339             $orange->juicyness(10); # $orange is a Class::Tangram-based Orange object
340              
341             =head1 DESCRIPTION
342              
343             The purpose of Class::Tangram::Generator is to facilitate the rapid
344             development of L<Class::Tangram|Class::Tangram>-based objects in the
345             L<Tangram|Tangram> framework. Instead of having to write class
346             modules for all your L<Tangram|Tangram> objects, many of which only
347             inherit from L<Class::Tangram|Class::Tangram> for accessor and
348             constraint checking, you use Class::Tangram::Generator to dynamically
349             instantiate each class as necessary, at runtime. This also alleviates
350             the long litany of 'use Orange; use Apple; ... ' statements in all of
351             your scripts.
352              
353             =head1 METHODS
354              
355             =over 4
356              
357             =item new($schema, [$base]) [ Class method ]
358              
359             =item new( { Schema => $schema, Base => $base } ) [ Class method ]
360              
361             Initialize and return a new Class::Tangram::Generator object, using
362             the L<Tangram> schema hashref provided. Newly generated objects will
363             have "Class::Tangram" added to their @ISA variable, unless an
364             alternative base class is specified in $base (that way you can
365             subclass L<Class::Tangram|Class::Tangram> and still use
366             Class::Tangram::Generator).
367              
368             =item new($classname) [ Object method ]
369              
370             Obtain a new object of the provided class. Additional arguments are
371             passed to L<Class::Tangram|Class::Tangram>'s new function (for
372             attribute manipulation). Any errors thrown by
373             L<Class::Tangram|Class::Tangram> will be propagated by
374             Class::Tangram::Generator.
375              
376             =back
377              
378             =head1 DISCUSSION
379              
380             =head2 Tangram Schema Extensions
381              
382             To provide custom methods for each class, add subroutine references to
383             the 'methods' key in the schema:
384              
385             Orange => {
386             fields => { int => [ qw(juicyness ripeness) ] },
387             methods => {
388             squeeze => sub {
389             my $self = shift;
390             $self->juicyness($self->juicyness() - 1);
391             },
392             eviscerate => sub {
393             my $self = shift;
394             $self->juicyness(0);
395             }
396             }
397             }
398              
399             The subroutines will be automatically installed into the class's
400             namespace.
401              
402             =head2 Interoperation with existing package files
403              
404             If a .pm module file corresponding to the requested class can be found
405             by Perl (looking in the usual places defined by @INC, PERL5LIB, etc.),
406             it will be loaded before Class::Tangram::Generator has finished
407             dynamically generating the package. This means that any schema and/or
408             methods found in the .pm module file will be overriden by those
409             specified in the schema given to Class::Tangram::Generator. For
410             example, there may be an Orange.pm module file that looks like:
411              
412             package Orange;
413              
414             sub rehydrate { shift->juicyness(10) }
415              
416             1;
417              
418             This allows the addition of more lengthy subroutines without filling
419             up the schema with lots of code. But a "rehydrate" method specified
420             in the schema would entirely replace this subroutine (and it would not
421             be available via SUPER).
422              
423             =head1 EXPORT
424              
425             Class::Tangram::Generator does not have any methods to export.
426              
427             =head1 HISTORY
428              
429             =over 4
430              
431             =item 0.01
432              
433             Initial release
434              
435             =back
436              
437             =head1 AUTHOR
438              
439             Aaron J Mackey E<lt>amackey@virginia.eduE<gt>
440              
441             =head1 SEE ALSO
442              
443             L<Class::Tangram>, L<Tangram>, L<Class::Object>, L<perl>.
444              
445             =cut