File Coverage

blib/lib/Class/AutoGenerate.pm
Criterion Covered Total %
statement 122 123 99.1
branch 30 34 88.2
condition 11 20 55.0
subroutine 20 20 100.0
pod 4 4 100.0
total 187 201 93.0


line stmt bran cond sub pod time code
1 18     18   697470 use strict;
  18         56  
  18         1095  
2 18     18   100 use warnings;
  18         37  
  18         1391  
3              
4             package Class::AutoGenerate;
5              
6 18     18   16650 use Class::AutoGenerate::Declare ();
  18         49  
  18         474  
7 18     18   153 use Scalar::Util qw/ blessed reftype /;
  18         32  
  18         3947  
8              
9             our $VERSION = 0.05;
10              
11             our %AUTOGENERATED;
12              
13             =head1 NAME
14              
15             Class::AutoGenerate - Automatically generate code upon require or use
16              
17             =head1 SYNOPSIS
18              
19             # Create a customized class loader (auto-generator)
20             package My::ClassLoader;
21             use Class::AutoGenerate -base;
22              
23             # Define a matching rule that generates some code...
24             requiring 'Some::**::Class' => generates { qq{
25             sub print_my_middle_names { print $1,"\n" }
26             } };
27              
28             # In some other file, let's use the class loader
29             package main;
30              
31             # Create the class loader, which adds itself to @INC
32             use My::ClassLoader;
33             BEGIN { My::ClassLoader->new( match_only => '**::Freaking::Class' ); }
34              
35             # These class will be generated on the fly...
36             use Some::Freaking::Class;
37             use Some::Other::Freaking::Class;
38              
39             Some::Freaking::Class->print_my_middle_names;
40             Some::Other::Freaking::Class->print_my_middle_names;
41              
42             # Output is:
43             # Freaking
44             # Other::Freaking
45              
46             =head1 DESCRIPTION
47              
48             B I'm trying this idea out. Please let me know what you think by contacting me using the information listed under L. This is an experiment and any and all aspects of the API are up for revision at this point and I'm not even sure I'll maintain it, but I hope it will be found useful to myself and others.
49              
50             Sometimes it's nice to be able to generate code on the fly. This tool does just that. You declare a few rules that can be used to define the class names you want to auto-generate and then the code that is to be built from it. Later you create your auto-generator object and start using the auto-generated classes.
51              
52             This is a generalization baed upon L. If this experiment is successful in the way I'm testing it out for, it may be used to re-implement that class.
53              
54             =head1 METHODS
55              
56             =head2 import
57              
58             When you are creating a new auto-generating class loader, you will include this statement in your package definition:
59              
60             package My::ClassLoader;
61             use Class::AutoGenerate -base;
62              
63             This statement tells L to import all the subroutines in L into the current package so that a new class loader can be declared.
64              
65             Later, when you use your class loader, you will use the undecorated form:
66              
67             use My::ClassLoader;
68              
69             In this case, the import method does nothing special.
70              
71             =cut
72              
73             sub import {
74 21     21   879 my $class = shift;
75 21         52 my $base = shift;
76              
77 21         83 my $package = caller;
78              
79 21 100 66     306 if (defined $base and $base eq '-base') {
80 18         4309 Class::AutoGenerate::Declare->export_to_level(1, undef);
81            
82 18     18   100 no strict 'refs';
  18         31  
  18         596  
83 18     18   122 no warnings 'once';
  18         33  
  18         71041  
84 18         43 push @{ $package . '::ISA' }, $class;
  18         690  
85 18         44 @{ $package . '::RULES' } = ();
  18         157  
86              
87 18         41 *{ $package . '::autogenerated' } = *autogenerated;
  18         201  
88 18         41 *{ $package . '::autogenerator_of' } = *autogenerator_of;
  18         101  
89             }
90              
91 21         63615 return 1;
92             }
93              
94             =head2 new
95              
96             Creates a new instance of the auto-generating class loader object you've built. The class loader automatically adds itself to the C<@INC> array to start loading classes.
97              
98             If you want to immediately start using the class loader at compile time, you may wish to call this method within a C block:
99              
100             use My::Custom::ClassLoader;
101             BEGIN { My::Custom::ClassLoader->new };
102              
103             The constructor also recognizes the following options, passed in a hash, that can modify the behavior of the class loader.
104              
105             =over
106              
107             =item match_only
108              
109             This argument may be passed as anything that would be accepted in a L clause and is used to prequalify which classes may actually be generated by this class loader. Using this, you can build one generic class loader that may be limited in how it is applied.
110              
111             A module will only be generated if it first matches at least one of the patterns provided to "match_only".
112              
113             For example,
114              
115             package My::ClassLoader;
116             use Class::AutoGenerate -base;
117              
118             requiring '**' => generates {};
119              
120             1;
121              
122             BEGIN {
123             My::ClassLoader->new( match_only => [ 'Prefix1::**', 'Prefix2::*' ] );
124             }
125              
126             use Prefix1::Thing;
127             use Prefix2::Thing;
128             use Prefix3::Thing; # <--- ERROR: does not match the match_only clause
129              
130             =back
131              
132             =cut
133              
134             sub new {
135 21     21 1 84 my $class = shift;
136 21         67 my %args = @_;
137              
138             # Create the class
139 21         78 my $self = bless \%args, $class;
140              
141             # if the match_only is given, remember that
142 21 100       219 if (defined $self->{match_only}) {
143 7 100       735 $self->{match_only} = [ $self->{match_only} ]
144             unless ref $self->{match_only};
145              
146 7         42 $_ = Class::AutoGenerate::Declare::_compile_glob_pattern($_)
147 7         12 foreach (@{ $self->{match_only} });
148              
149 7 50       38 $self->{match_only} = $self->{match_only}
150             if defined $self->{match_only};
151             }
152              
153             # Now, load the rule set from the declarations
154 21         75 for my $declaration (@{ $self->_declarations }) {
  21         73  
155              
156             # Handle declare { ... } blocks
157 47 100       198 if (reftype $declaration eq 'CODE') {
158 2         7 my @declarations = $declaration->($self);
159 2         3 push @{ $self->{rules} }, @declarations;
  2         11  
160             }
161              
162             # Handle top-level requiring ... => rules
163             else {
164 45         60 push @{ $self->{rules} }, $declaration;
  45         162  
165             }
166             }
167              
168             # place ourself into @INC
169 21         60 push @INC, $self;
170              
171 21         63 return $self;
172             }
173              
174             =head2 INC
175              
176             This is the subroutine called by Perl during a L or L and evaluates the rules defined in your class loader. See L (towards the end) to see how this works.
177              
178             It should be noted, however, that we cheat the system a little bit. According ot the require hook API, this method should return either a filehandle containing the code to be read or C indicating that the hook does not know about the file being required.
179              
180             This is done, except that only an empty stub package like this is ever returned when a class is auto-generated:
181              
182             use strict;
183             use warnings;
184              
185             package The::Included::Package::Name;
186              
187             1;
188              
189             Instead of having the import mechanism within Perl compile the code, most of the work is handled through symbol table manipulations and code evaluation before the file handle is returned. This allows for some earlier compile-time checking via closures and the like.
190              
191             =cut
192              
193             # Use the fully-qualified name since Perl ignores "sub INC"
194             # (see perldoc require)
195             sub Class::AutoGenerate::INC {
196 104     104 1 145802 my $self = shift;
197 104         322 my $module = shift;
198              
199             # Canonicalize $module to :: style rather than / and .pm style
200 104         721 $module =~ s{\.pm$}{};
201 104         446 $module =~ s{/}{::}g;
202              
203             # Pass off control to _match_and_generate() to do the real work
204 104         600 return $self->_match_and_generate($module);
205             }
206              
207             =head2 autogenerated MODULE
208              
209             This method may be called in any of the following ways:
210              
211             Class::AutoGenerate::autogenerated 'Some::Module';
212             Class::AutoGenerate->autogenerated('Some::Module');
213              
214             # Where My::AutoGenerator->isa('Class::AutoGenerate')
215             My::AutoGenerator::autogenerated 'Some::Module';
216             My::AutoGenerator->autogenerated('Some::Module');
217              
218             # Where $autogenerator->isa('Class::AutoGenerate');
219             $autogenerator->autogenerated('Some::Module');
220              
221             Returns true if the package named was autogenerated by a L class loader. Returns C in any other case.
222              
223             =cut
224              
225             sub autogenerated($) {
226 18     18 1 862 my $class = shift;
227 18 50 33     152 if (blessed $class or (not ref $class and $class =~ /^[:\w]+$/)) {
      66        
228 18 100       118 $class = shift if $class->isa('Class::AutoGenerate');
229             }
230              
231 18         88 return exists $AUTOGENERATED{ $class };
232             }
233              
234             =head2 autogenerator_of MODULE
235              
236             This method may be called in any of the following ways:
237              
238             Class::AutoGenerate::autogenerator_of 'Some::Module';
239             Class::AutoGenerate->autogenerator_of('Some::Module');
240              
241             # Where My::AutoGenerator->isa('Class::AutoGenerate')
242             My::AutoGenerator::autogenerator_of 'Some::Module';
243             My::AutoGenerator->autogenerator_of('Some::Module');
244              
245             # Where $autogenerator->isa('Class::AutoGenerate');
246             $autogenerator->autogenerator_of('Some::Module');
247              
248             Returns the object that was used to autogenerate the module. This is really just a shortcut for looking up the information in C<%INC>, but saves some work of converting Perl module names into package file names and the cryptic use of the C<%INC> variable.
249              
250             =cut
251              
252             sub autogenerator_of($) {
253 18     18 1 42 my $class = shift;
254 18 50 33     172 if (blessed $class or (not ref $class and $class =~ /^[:\w]+$/)) {
      66        
255 18 100       110 $class = shift if $class->isa('Class::AutoGenerate');
256             }
257              
258             # Convert the module name into a package file, Some::Thing -> Some/Thing.pm
259 18         64 my $package_file = $class;
260 18         254 $package_file =~ s{::}{/}g;
261 18         27 $package_file .= '.pm';
262              
263 18 100       126 return exists($INC{ $package_file }) ? $INC{ $package_file } : undef;
264             }
265              
266             =head2 _match_and_generate MODULE
267              
268             This method is used internally to match L statements and automatically generate code upon a match.
269              
270             =cut
271              
272             sub _match_and_generate {
273 104     104   175 my $self = shift;
274 104         301 my $module = shift;
275              
276             # If match_only is specified, make sure it matches that first
277 104 100       509 if (defined $self->{match_only}) {
278 59         170 return unless grep { $self->_match_requiring($module, $_) }
  34         99  
279 34 100       49 @{ $self->{match_only} };
280             }
281              
282             # Get the requiring/generates rules
283 101         381 my $rules = $self->_rules;
284             #use Data::Dumper;
285             #$Data::Dumper::Deparse = 1;
286             #Test::More::diag(Dumper($rules));
287              
288             # Iterate through the rules
289             RULE:
290 101         254 for my $rule (@$rules) {
291              
292             # Does it match? First match wins...
293 252 100       764 if ($self->_match_requiring($module, $rule->[0])) {
294 86         182 my $conclude_with = eval {
295 86         636 $self->_autogenerate($module, $rule->[0], $rule->[1]);
296             };
297              
298             # Handle a call to next_rule
299 86 100       443 if ($@ eq "NEXT_RULE\n") {
    100          
    50          
300 1         5 next RULE;
301             }
302              
303             # Handle a call to last_rule
304             elsif ($@ eq "LAST_RULE\n") {
305 1         3 last RULE;
306             }
307              
308             # Handle a regular exception
309             elsif ($@) {
310 0         0 die $@;
311             }
312              
313             # Return the empty stub to signal class found
314 84         386 return $self->_stub_file_handle($module, $conclude_with);
315             }
316             }
317              
318             # Return undef to signal no such file found
319 17         549 return;
320             }
321              
322             =head2 _declarations
323              
324             Used internally to reference the L blocks and top-level L rules in the auto-generating class loader's definition.
325              
326             These are, then, instantiated to build the L for the object when L is called.
327              
328             =cut
329              
330             sub _declarations {
331 65     65   115 my $self = shift;
332 65   66     386 my $package = blessed $self || $self;
333              
334 18     18   303 no strict 'refs';
  18         55  
  18         792  
335 18     18   108 no warnings 'once';
  18         56  
  18         8201  
336 65         93 return \@{ $package . '::DECLARATIONS' };
  65         627  
337             }
338              
339             =head2 _rules
340              
341             Used internally to reference the rules declared and instantiated within the auto-generating class loader.
342              
343             =cut
344              
345             sub _rules {
346 101     101   151 my $self = shift;
347 101         285 return $self->{rules};
348             }
349              
350             =head2 _match_requiring MODULE, PATTERN
351              
352             Used internally to match a L declaration to a package name. Returns true if there's a match, or false otherwise.
353              
354             =cut
355              
356             sub _match_requiring {
357 311     311   422 my $self = shift;
358 311         370 my $module = shift;
359 311         353 my $pattern = shift;
360              
361 311 100       1873 if ($module =~ $pattern) {
362             #Test::More::diag("$module matches $pattern");
363 117         451 return 1;
364             }
365             else {
366             #Test::More::diag("$module misses $pattern");
367 194         609 return;
368             }
369             }
370              
371             =head2 _autogenerate MODULE, PATTERN, GENERATES
372              
373             This method performs the action of taking the work in the generates declration and stuffing that work into the named package.
374              
375             =cut
376              
377             our ($package, $conclude_with);
378             sub _autogenerate {
379 86     86   144 my $self = shift;
380 86         137 my $module = shift;
381 86         118 my $pattern = shift;
382 86         133 my $generates = shift;
383              
384             # match again to setup $1, $2, etc...
385 86         387 $module =~ $pattern;
386              
387             # Setup the $package variable used inside the various declarations
388 86         191 local $package = $module;
389 86         283 local $conclude_with = [];
390              
391             # Call the code to generate the various codes
392 86         287 $generates->();
393              
394             # Remember that it was generated
395 84         31483 $AUTOGENERATED{ $module } = 1;
396              
397 84         318 return $conclude_with;
398             }
399              
400             =head2 _stub_file_handle MODULE, CONCLUSIONS
401              
402             Returns a basic stub class that is handed off to the import infrastructure of Perl to let it know that we succeeded, even though we already did most of the work for it.
403              
404             =cut
405              
406             sub _stub_file_handle {
407 84     84   135 my $self = shift;
408 84         127 my $module = shift;
409 84   50     239 my $conclusions = shift || [];
410              
411             # Here's the stub code...
412 84         234 my $code = qq{use strict; use warnings; package $module; };
413 84         217 for my $conclusion (@$conclusions) {
414 2         9 $code .= "{ $conclusion } ";
415             }
416 84         249 $code .= "1; ";
417              
418             # Magick that code into a file handle
419 18     18   144 open my $fh, '<', \$code;
  18         34  
  18         136  
  84         1779  
420 84         63217 return $fh;
421             }
422              
423             =head1 SEE ALSO
424              
425             L
426              
427             =head1 AUTHOR
428              
429             Andrew Sterling Hanenkamp C<< >>
430              
431             =head1 COPYRIGHT AND LICENSE
432              
433             Copyright 2007 Boomer Consulting, Inc.
434              
435             This program is free software and may be modified and distributed under the same terms as Perl itself.
436              
437             =cut
438              
439             1;