Andrew Sterling Hanenkamp > Class-AutoGenerate > Class::AutoGenerate::Declare

Download:
Class-AutoGenerate-0.05.tar.gz

Dependencies

Annotate this POD

CPAN RT

New  1
Open  0
View/Report Bugs
Module Version: 0.05   Source  

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 and TestApp::Model::FloobleCollection and SomeOtherApp::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 and TestApp::Model::FloobleCollection and SomeOtherApp::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 and 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.

For example:

  requiring 'TestApp??::**::*' => ...

would match TestApp38::A::Package::Name::Blah and would have the following values available in generates:

  $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 ^

UNIVERSAL::require

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.

syntax highlighting: