Class::AutoGenerate::Declare - Declarations for an auto-generating class loader
# Create a customized class loader (auto-generator) package My::ClassLoader; use Class::AutoGenerate -base; # Define a matching rule that generates some code... requiring 'Some::**::Class' => generates { qq{ sub print_my_middle_names { print $1,"\n" } } };
EXPERIMENTAL. I'm trying this idea out. Please let me know what you think by contacting me using the information listed under "AUTHOR". 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.
You do not use this class directly, but it contains the documentation for how to declare a new auto-generating class loader. To use this class, just tell Class::AutoGenerate that you are building a base class:
package My::ClassLoader; use Class::AutoGenerate -base;
This will then import the declarations described here into your class loader so that you can define your auto-generation rules.
The requiring rule tests the pattern against the given package name and runs the generates block if there's a match. The pattern can be any of the following:
requiring
generates
If you provide an exact package name (one containing only letters, numbers, underscores, and colons), then only that exact name will be matched.
For example:
requiring 'TestApp::Model::Flooble' => ...
would only match when exactly TestApp::Model::Flooble was required or used.
TestApp::Model::Flooble
If you provide a pattern string containing one or more wildcards, the pattern will match any package matching the wildcard pattern. This is very similar to how file globs work, but we use "::" instead of "/" as our divider. There are three different wildcards available:
requiring '*::Model::*Collection' => ...
will match TestApp::Model::Collection and TestApp::Model::FloobleCollection and SomeOtherApp::Model::WakkaCollection.
TestApp::Model::Collection
TestApp::Model::FloobleCollection
SomeOtherApp::Model::WakkaCollection
requiring '**::Model::**Collection' => ...
will match TestApp::Plugin::Charts::Model::Deep::Model::NameCollection and TestApp::Model::FloobleCollection and SomeOtherApp::Model::Collection.
TestApp::Plugin::Charts::Model::Deep::Model::NameCollection
SomeOtherApp::Model::Collection
requiring 'TestApp??::Record' => ...
will match TestAppAA::Record and TestApp12::Record.
TestAppAA::Record
TestApp12::Record
Each occurrence of a wildcard will be captured for use in the "generates" block. The first wildcard will be $1, the second $2, etc.
$1
$2
requiring 'TestApp??::**::*' => ...
would match TestApp38::A::Package::Name::Blah and would have the following values available in generates:
TestApp38::A::Package::Name::Blah
$1 = '3'; $2 = '8'; $3 = 'A::Package::Name'; $4 = 'Blah';
You may use a regular expression to match anything more complicated than this. (In fact, the previous matching mechanism are converted to regular expressions, but are convenient for handling the common cases.)
requiring qr/^(.*)::(\w+)::(\w+)(\d{2})$/ => ...
Any captures performed in the regular expression will be available as $1, $2, etc. in the "generates" block.
Finally, you may also place a series of matches into an array. The given generates block will be used if any of the matches match a given module name.
requiring [ 'App', 'App::**', qr/^SomeOther::(Thing|Whatsit)$/ ] => ...
This handles the second half of the requiring/generates statement. The code block may contain any code you need, but you'll probably want it to contain statements for generating code to go into the required class.
requiring 'My::*' => generates { my $name = $1; extends "My::Base::$name"; uses 'Scalar::Util', 'looks_like_number'; defines '$scalar' => 14; defines '@array' => [ 1, 2, 3 ]; defines '%hash' => { x => 1, y => 2 }; defines 'package_name' => sub { $package }; defines 'short_name' => sub { $name }; };
If we included the rule above, intantiated the class loader, and then ran:
use My::Flipper;
A class would be generated named My::Flipper that uses My::Base::Flipper as its only base class, imports the looks_like_number function from Scalar::Util, defines a scalar package variable $scalar set to 14, an array package variable, @array, set to (1, 2, 3), a hash package variable named %hash set to (x = 1, y => 2)>, and two subroutines named package_name and short_name.
My::Flipper
My::Base::Flipper
looks_like_number
$scalar
@array
(1, 2, 3)
%hash
(x =
package_name
short_name
A declare block may be used to wrap your class loader code, but is not required. The block will be passed a single argument, $self, which is the initialized class loader object. It is helpful if you need a reference to your $self.
$self
For example,
package My::Classloader; use Class::Autogenerate -base; declare { my $self = shift; my $base = $self->{base}; requiring "$base::**' => generates {}; }; 1; # later... use My::Classloader; BEGIN { My::Classloader->new( base => 'Foo' ) };
You may have multiple declare blocks in your class loader.
declare
It is important to note that the declare block modifies the semantics of how the class loader is built. Normally, the requiring rules are all generated and associated with the class loader package immediately. A declare block causes all rules inside the block to be held until the class loader is constructed. During construction, the requiring rules in declare blocks are built and associated with the constructed class loader instance directly.
This subroutine is used with "generates" to mark the generated class as extending the named class or classes. This pushes the named classes into the @ISA array for the class when it is generated.
@ISA
N.B. You need to ask Perl to include this class on your own. This is not exactly equivalent to <use base qw/$class/> in this regard. If a class might not be included already, you may wish to do something like the following:
require My::Parent::Class; extends 'My::Parent::Class';
This subroutine states that the generated class uses another package. The first argument is the class to use and the remaining arguments are passed to the import method of the used class (the first argument may also be a version number, see "use" in perlfunc).
This is similar to "uses", but uses "require" in perlfunc instead of use.
use
This is the general purpose definition declaration. If the given name starts with a dollar sign ($), then a scalar value is created. If the given name starts with an at sign (@), then an array value is added to the class. If the given starts with a percent sign (%), then a hash value will be generated. Finally, if it starts with a letter, underscore, or ampersand (&), a subroutine is added to the package.
The given value must be appropriate for the type of definition being generated.
If you need to inject code directly into the package generated, this is the general purpose way to do it. Just pass a string (or use one of the helpers "source_file" or "source_file" below) and that code will be evaluated within the new package.
requiring 'Some::Class' => generates { extends 'Class::Access::Fast'; generate_from source_code qq{ __PACKAGE__->mk_accessors( qw/ name title description / ); }; };
Caution: If user input has any effect on the code generated, you should make certain that all input is carefully validated to prevent code injection.
This is a special helper used in place of "generate_from" for code that could cause a loop during code generation. This can occur because Perl does not realize that the generated module has been loaded until after the "generates" block has been completely executed. Therefore, the use of require and use might cause a loop under certain conditions.
require
Rather than try to explain who to contrive such a situation, here's a contrived example where conclude_with is helpful:
conclude_with
package My::Util; use UNIVERSAL::require; # helper that makes "Any::Class"->require; work sub require_helpers { my $class = shift; my $module = shift; for my $name ( qw( Bob Larry ) ) { my $helper = "My::Thing::${module}::Helper::$name"; $helper->require; } } package My::ClassLoader; use Class::AutoGenerate -base; use UNIVERSAL::require; requiring 'My::Thing::*' => generates { my $module = $1; defines 'do_something' => sub { ... }; conclude_with source_code "My::Util->require_helpers('$module');"; }; requiring 'My::Thing::*::Helper::*' => generates { my $module = $1; my $name = $2; # We only make helpers for something that exists! my $thing = "My::Thing::$module"; $thing->require or next_rule; defines 'help_with_something' => sub { ... }; };
If we had used generate_from rather than conclude_with in the code above, a loop would have been generated upon calling require My::Thing::Flup. This would have resulted in a call to require_helpers in the sample, which would have resulted in a called to require My::Thing::Flup::Helper::Bob, which would have resulted in another call to require My::Thing::Flup to see if such a module exists. Unfortunately, since Perl hasn't yet recorded that "My::Thing::Flup" has already been loaded, this will fail.
generate_from
require My::Thing::Flup
require_helpers
require My::Thing::Flup::Helper::Bob
By using conclude_with, the code given is not executed until Perl has already noted that the class is loaded, so the loop stops and this code should execute successfully.
This method is purely for use with making your code a little easier to read. It doesn't do anything but return the argument passed to it.
Given a file name, this evalutes the Perl in that file within the context of the package.
requiring 'Another::Class' => generates { generate_from source_file 'code_base.pl'; };
Caution: If user input has any effect on this file included, you should make certain that all input is carefully validated to prevent code injection.
By calling the next_rule statement, you will prevent the current "generates" statement from finishing. Instead, it will quit and the next "requirng" rule will be tried.
next_rule
The last_rule statement causes the class loader to stop completely and return that it found no matching Perl modules.
last_rule
UNIVERSAL::require
Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
<hanenkamp@cpan.org>
Copyright 2007 Boomer Consulting, Inc.
This program is free software and may be modified and distributed under the same terms as Perl itself.
To install Class::AutoGenerate, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Class::AutoGenerate
CPAN shell
perl -MCPAN -e shell install Class::AutoGenerate
For more information on module installation, please visit the detailed CPAN module installation guide.