NAME
Class::AutoGenerate::Declare - Declarations for an auto-generating class loader
SYNOPSIS
# 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" }
} };
DESCRIPTION
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.
DECLARATIONS
requiring PATTERN => generates { ... };
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:
- Package Name
-
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. - Package Glob
-
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:
- 1 Single Asterisk (*). A single asterisk will match zero or more characters of a single package name component.
-
For example:
requiring '*::Model::*Collection' => ...
will match
TestApp::Model::Collection
andTestApp::Model::FloobleCollection
andSomeOtherApp::Model::WakkaCollection
. - 1 Double Asterisk (**). A double asterisk will match zero or more chaters of a package name, possibly spanning multiple double-colon (::) separators.
-
For example:
requiring '**::Model::**Collection' => ...
will match
TestApp::Plugin::Charts::Model::Deep::Model::NameCollection
andTestApp::Model::FloobleCollection
andSomeOtherApp::Model::Collection
. - 1 Question mark (?). A question mark will match exactly one character in a package name.
-
For example:
requiring 'TestApp??::Record' => ...
will match
TestAppAA::Record
andTestApp12::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.For example:
requiring 'TestApp??::**::*' => ...
would match
TestApp38::A::Package::Name::Blah
and would have the following values available ingenerates
:$1 = '3'; $2 = '8'; $3 = 'A::Package::Name'; $4 = 'Blah';
- Regular Expression
-
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.)
For example:
requiring qr/^(.*)::(\w+)::(\w+)(\d{2})$/ => ...
Any captures performed in the regular expression will be available as
$1
,$2
, etc. in the "generates" block. - Array of Matches
-
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)$/ ] => ...
generates { ... }
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
.
declare { ... };
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
.
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.
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.
extends CLASSES
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.
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';
uses CLASS, ARGS
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).
requires EXPR
This is similar to "uses", but uses "require" in perlfunc instead of use
.
defines NAME => VALUE
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.
generate_from SOURCE
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.
conclude_with SOURCE
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.
Rather than try to explain who to contrive such a situation, here's a contrived example where conclude_with
is helpful:
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.
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.
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.
source_code SOURCE
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.
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.
source_file FILENAME
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.
next_rule
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.
last_rule
The last_rule
statement causes the class loader to stop completely and return that it found no matching Perl modules.
SEE ALSO
AUTHOR
Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
COPYRIGHT AND LICENSE
Copyright 2007 Boomer Consulting, Inc.
This program is free software and may be modified and distributed under the same terms as Perl itself.