package DateTime::Format::Builder; { $DateTime::Format::Builder::VERSION = '0.81'; } use strict; use warnings; use 5.005; use Carp; use DateTime 1.00; use Params::Validate 0.72 qw( validate SCALAR ARRAYREF HASHREF SCALARREF CODEREF GLOB GLOBREF UNDEF ); use vars qw( %dispatch_data ); my $parser = 'DateTime::Format::Builder::Parser'; sub verbose { warn "Use of verbose() deprecated for the interim."; 1; } sub import { my $class = shift; $class->create_class( @_, class => (caller)[0] ) if @_; } sub create_class { my $class = shift; my %args = validate( @_, { class => { type => SCALAR, default => (caller)[0] }, version => { type => SCALAR, optional => 1 }, verbose => { type => SCALAR | GLOBREF | GLOB, optional => 1 }, parsers => { type => HASHREF }, groups => { type => HASHREF, optional => 1 }, constructor => { type => UNDEF | SCALAR | CODEREF, optional => 1 }, } ); verbose( $args{verbose} ) if exists $args{verbose}; my $target = $args{class}; # where we're writing our methods and such. # Create own lovely new package { no strict 'refs'; ${"${target}::VERSION"} = $args{version} if exists $args{version}; $class->create_constructor( $target, exists $args{constructor}, $args{constructor} ); # Turn groups of parser specs in to groups of parsers { my $specs = $args{groups}; my %groups; for my $label ( keys %$specs ) { my $parsers = $specs->{$label}; my $code = $class->create_parser($parsers); $groups{$label} = $code; } $dispatch_data{$target} = \%groups; } # Write all our parser methods, creating parsers as we go. while ( my ( $method, $parsers ) = each %{ $args{parsers} } ) { my $globname = $target . "::$method"; croak "Will not override a preexisting method $method()" if defined &{$globname}; *$globname = $class->create_end_parser($parsers); } } } sub create_constructor { my $class = shift; my ( $target, $intended, $value ) = @_; my $new = $target . "::new"; $value = 1 unless $intended; return unless $value; return if not $intended and defined &$new; croak "Will not override a preexisting constructor new()" if defined &$new; no strict 'refs'; return *$new = $value if ref $value eq 'CODE'; return *$new = sub { my $class = shift; croak "${class}->new takes no parameters." if @_; my $self = bless {}, ref($class) || $class; # If called on an object, clone, but we've nothing to # clone $self; }; } sub create_parser { my $class = shift; my @common = ( maker => $class ); if ( @_ == 1 ) { my $parsers = shift; my @parsers = ( ( ref $parsers eq 'HASH' ) ? %$parsers : ( ( ref $parsers eq 'ARRAY' ) ? @$parsers : $parsers ) ); $parser->create_parser( \@common, @parsers ); } else { $parser->create_parser( \@common, @_ ); } } sub create_end_parser { my ( $class, $parsers ) = @_; $class->create_method( $class->create_parser($parsers) ); } sub create_method { my ( $class, $parser ) = @_; return sub { my $self = shift; $parser->parse( $self, @_ ); } } sub on_fail { my ( $class, $input ) = @_; my $pkg; my $i = 0; while ( ($pkg) = caller( $i++ ) ) { last if ( !UNIVERSAL::isa( $pkg, 'DateTime::Format::Builder' ) && !UNIVERSAL::isa( $pkg, 'DateTime::Format::Builder::Parser' ) ); } local $Carp::CarpLevel = $i; croak "Invalid date format: $input"; } sub new { my $class = shift; croak "Constructor 'new' takes no parameters" if @_; my $self = bless { parser => sub { croak "No parser set." } }, ref($class) || $class; if ( ref $class ) { # If called on an object, clone $self->set_parser( $class->get_parser ); # and that's it. we don't store that much info per object } return $self; } sub parser { my $class = shift; my $parser = $class->create_end_parser( \@_ ); # Do we need to instantiate a new object for return, # or are we modifying an existing object? my $self; $self = ref $class ? $class : $class->new(); $self->set_parser($parser); $self; } sub clone { my $self = shift; croak "Calling object method as class method!" unless ref $self; return $self->new(); } sub set_parser { my ( $self, $parser ) = @_; croak "set_parser given something other than a coderef" unless $parser and ref $parser eq 'CODE'; $self->{parser} = $parser; $self; } sub get_parser { my ($self) = @_; return $self->{parser}; } sub parse_datetime { my $self = shift; croak "parse_datetime is an object method, not a class method." unless ref $self and $self->isa(__PACKAGE__); croak "No date specified." unless @_; return $self->{parser}->( $self, @_ ); } sub format_datetime { croak __PACKAGE__ . "::format_datetime not implemented."; } require DateTime::Format::Builder::Parser; 1; # ABSTRACT: Create DateTime parser classes and objects. __END__ =pod =head1 NAME DateTime::Format::Builder - Create DateTime parser classes and objects. =head1 VERSION version 0.81 =head1 SYNOPSIS package DateTime::Format::Brief; use DateTime::Format::Builder ( parsers => { parse_datetime => [ { regex => qr/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/, params => [qw( year month day hour minute second )], }, { regex => qr/^(\d{4})(\d\d)(\d\d)$/, params => [qw( year month day )], }, ], } ); =head1 DESCRIPTION DateTime::Format::Builder creates DateTime parsers. Many string formats of dates and times are simple and just require a basic regular expression to extract the relevant information. Builder provides a simple way to do this without writing reams of structural code. Builder provides a number of methods, most of which you'll never need, or at least rarely need. They're provided more for exposing of the module's innards to any subclasses, or for when you need to do something slightly beyond what I expected. This creates the end methods. Coderefs die on bad parses, return C objects on good parse. =head1 TUTORIAL See L. =head1 ERROR HANDLING AND BAD PARSES Often, I will speak of C being returned, however that's not strictly true. When a simple single specification is given for a method, the method isn't given a single parser directly. It's given a wrapper that will call C if the single parser returns C. The single parser must return C so that a multiple parser can work nicely and actual errors can be thrown from any of the callbacks. Similarly, any multiple parsers will only call C right at the end when it's tried all it could. C (see L) is defined, by default, to throw an error. Multiple parser specifications can also specify C with a coderef as an argument in the options block. This will take precedence over the inheritable and over-ridable method. That said, don't throw real errors from callbacks in multiple parser specifications unless you really want parsing to stop right there and not try any other parsers. In summary: calling a B will result in either a C object being returned or an error being thrown (unless you've overridden C or C, or you've specified a C key to a multiple parser specification). Individual B (be they multiple parsers or single parsers) will return either the C object or C. =head1 SINGLE SPECIFICATIONS A single specification is a hash ref of instructions on how to create a parser. The precise set of keys and values varies according to parser type. There are some common ones though: =over 4 =item * B is an optional parameter that can be used to specify that this particular I is only applicable to strings of a certain fixed length. This can be used to make parsers more efficient. It's strongly recommended that any parser that can use this parameter does. You may happily specify the same length twice. The parsers will be tried in order of specification. You can also specify multiple lengths by giving it an arrayref of numbers rather than just a single scalar. If doing so, please keep the number of lengths to a minimum. If any specifications without Is are given and the particular I parser fails, then the non-I parsers are tried. This parameter is ignored unless the specification is part of a multiple parser specification. =item * B