r1018@llin: dpavlin | 2006-09-26 12:20:52 +0200
[webpac2] / lib / WebPAC / Parser.pm
index 08eb855..667a930 100644 (file)
@@ -3,13 +3,13 @@ package WebPAC::Parser;
 use warnings;
 use strict;
 
-use base qw/WebPAC::Common WebPAC::Normalize/;
 
 use PPI;
 use PPI::Dumper;
 use Data::Dump qw/dump/;
 use File::Slurp;
 
+use base qw/WebPAC::Common/;
 
 =head1 NAME
 
@@ -17,14 +17,24 @@ WebPAC::Parser - parse perl normalization configuration files and mungle it
 
 =head1 VERSION
 
-Version 0.01
+Version 0.05
 
 =cut
 
-our $VERSION = '0.01';
+our $VERSION = '0.05';
 
 =head1 SYNOPSIS
 
+This module will parse L<WebPAC::Normalize/lookup> directives and generate source
+to produce lookups and normalization.
+
+It's written using L<PPI>, pure-perl parser for perl and heavily influenced by
+reading about LISP. It might be a bit over-the board, but at least it removed
+separate configuration files for lookups.
+
+This is experimental code, but it replaces all older formats which where,
+at one point in time, available in WebPAC.
+
 FIXME
 
 =head1 FUNCTIONS
@@ -51,44 +61,190 @@ sub new {
 
        $log->logdie("can't iterate_inputs over this config object") unless ($self->{config}->can('iterate_inputs'));
 
-       my $source;
+       $self->_read_sources;
+
+       $self ? return $self : return undef;
+}
+
+=head2 valid_database
+
+  my $ok = $parse->valid_database('key');
+
+=cut
+
+sub valid_database {
+       my $self = shift;
+
+       my $database = shift || return;
+
+       return defined($self->{valid_inputs}->{ _q($database) });
+}
+
+=head2 valid_database_input
+
+  my $ok = $parse->valid_database('database_key','input_name');
+
+=cut
+
+sub valid_database_input {
+       my $self = shift;
+       my ($database,$input) = @_;
+       $input = _input_name($input);
+       return defined($self->{valid_inputs}->{ _q($database) }->{ _q($input) });
+}
+
+=head2 depends
+
+Return all databases and inputs on which specified one depends
+
+  $depends_on = $parser->depends('database','input');
+
+=cut
+
+sub depends {
+       my $self = shift;
+       my ($database,$input) = @_;
+       $input = _input_name($input);
+       $self->_get_logger->debug("depends($database,$input)");
+       return unless (
+               defined( $self->{depends}->{ _q($database) } ) &&
+               defined( $self->{depends}->{ _q($database) }->{ _q($input) } )
+       );
+       return $self->{depends}->{ _q($database) }->{ _q($input) };
+}
+
+=head2 have_lookup_create
+
+  my @keys = $parser->have_lookup_create($database, $input);
+
+=cut
+
+sub have_lookup_create {
+       my $self = shift;
+       my ($database,$input) = @_;
+       $input = _input_name($input);
+       return unless (
+               defined( $self->{_lookup_create_key}->{ _q($database) } ) &&
+               defined( $self->{_lookup_create_key}->{ _q($database) }->{ _q($input) } )
+       );
+       return keys %{ $self->{_lookup_create_key}->{ _q($database) }->{ _q($input) } };
+}
+
+
+=head2 lookup_create_rules
+
+  my $source = $parser->lookup_create_rules($database, $input);
+
+=cut
+
+sub lookup_create_rules {
+       my $self = shift;
+       my ($database,$input) = @_;
+       $input = _input_name($input);
+       return unless (
+               defined( $self->{_lookup_create}->{ _q($database) } ) &&
+               defined( $self->{_lookup_create}->{ _q($database) }->{ _q($input) } )
+       );
+       return $self->{_lookup_create}->{ _q($database) }->{ _q($input) };
+}
+
+=head1 PRIVATE
+
+=head2 _read_sources
+
+  my $source_files = $parser->_read_sources;
+
+Called by L</new>.
+
+=cut
+
+sub _read_sources {
+       my $self = shift;
+
+       my $log = $self->_get_logger();
+
+       my $nr = 0;
+
+       my @lookups;
 
        $self->{config}->iterate_inputs( sub {
-               my $input = shift;
-               my $path = $input->{normalize}->{path} || return;
-               my $full = $self->{base_path} ? $self->{base_path} . '/' . $path : $path;
-               $log->logdie("normalization input $full doesn't exist") unless (-e $full);
-               my $s = read_file( $full ) || $log->logdie("can't read $full: $!");
-               $log->debug("adding $path to parser [",length($s)," bytes]");
-               $source .= $s;
+               my ($input, $database) = @_;
+
+               $log->debug("database: $database input = ", dump($input));
+
+               foreach my $normalize (@{ $input->{normalize} }) {
+
+                       my $path = $normalize->{path};
+                       return unless($path);
+                       my $full = $self->{base_path} ? $self->{base_path} . '/' . $path : $path;
+
+                       $log->logdie("normalization input $full doesn't exist") unless (-e $full);
+
+                       my $s = read_file( $full ) || $log->logdie("can't read $full: $!");
+
+                       my $input_name = _input_name($input) || $log->logdie("can't deduce name of input: ", dump( $input ));
+
+                       $log->debug("$database/$input_name: adding $path");
+
+                       $self->{valid_inputs}->{$database}->{$input_name}++;
+
+                       push @lookups, sub {
+                               $self->_parse_lookups( $database, $input_name, $full, $s );
+                       };
+
+                       $nr++;
+               }
        } );
 
-       $log->debug("collected ", length($source), " bytes of source");
+       $log->debug("found $nr source files");
 
-       $self->{source} = $source;
+       # parse all lookups
+       $_->() foreach (@lookups);
 
-       $self ? return $self : return undef;
+       return $nr;
 }
 
-=head2 parse
+=head2 _parse_lookups
+
+  $parser->_parse_lookups($database,$input,$path,$source);
+
+Called for each normalize source (rules) in each input by L</_read_sources>
+
+It will report invalid databases and inputs in error log after parsing.
 
 =cut
 
-sub parse {
+sub _parse_lookups {
        my $self = shift;
+       my ($database, $input, $path, $source) = @_;
+
+       $input = _input_name($input);
 
        my $log = $self->_get_logger();
 
-       $log->logdie('no source found in object') unless ($self->{source});
+       $log->logdie("invalid database $database" ) unless $self->valid_database( $database );
+       $log->logdie("invalid input $input of database $database", ) unless $self->valid_database_input( $database, $input );
+
+       $log->logdie("no source found for database $database input $input path $path") unless ($source);
 
-       my $Document = PPI::Document->new( \$self->{source} ) || $log->logdie("can't parse source:\n", $self->{source});
+       $log->info("parsing lookups for $database/$input from $path (",length($source)," bytes)");
+
+       my $Document = PPI::Document->new( \$source ) || $log->logdie("can't parse source:\n", $self->{source});
 
        $Document->prune('PPI::Token::Whitespace');
        #$Document->prune('PPI::Token::Operator');
 
        # Find all the named subroutines
 
-       my $eval_create;
+       $self->{_lookup_errors} = ();
+
+       sub _lookup_error {
+               my $self = shift;
+               my $msg = shift;
+               $self->_get_logger->logconfess("error without message?") unless ($msg);
+               push @{ $self->{_lookup_errors} }, $msg;
+               return '';
+       }
 
        $Document->find( sub {
                        my ($Document,$Element) = @_;
@@ -140,18 +296,26 @@ sub parse {
 
                        $log->debug("key = $key");
 
-                       my $create = ' 
-                               $coderef = ' . $e[7] . $e[8] . ';
-                               foreach my $v ($coderef->()) {
-                                       next unless (defined($v) && $v ne \'\');
-                                       push @{ $lookup->{\'' . $key . '\'}->{$v} }, $mfn;
-                               }
-                       ';
+                       return $self->_lookup_error("invalid database $e[3] in $path" ) unless $self->valid_database( $e[3] );
+                       return $self->_lookup_error("invalid input $e[5] of database $e[3] in $path", ) unless $self->valid_database_input( $e[3], $e[5] );
+
+                       my $create = qq{
+                               save_into_lookup($e[3],$e[5],'$key', $e[7] $e[8] );
+                       };
 
                        $log->debug("create: $create");
 
-                       $create =~ s/\s+/ /gs;
-                       $eval_create->{ $e[3] }->{ $e[5] } .= $create;
+                       # save code to create this lookup
+                       $self->{_lookup_create}->{ _q($e[3]) }->{ _q($e[5]) } .= $create;
+                       $self->{_lookup_create_key}->{ _q($e[3]) }->{ _q($e[5]) }->{ _q($key) }++;
+
+
+                       if (defined( $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) }->{ $key } )) {
+                               $log->warn("dependency of $database/$input on $e[3]/$e[5] with $key allready recorded");
+                       }
+
+                       # save this dependency
+                       $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) }->{$key}++;
 
                        if ($#e < 10) {
                                $e[8]->insert_after( $e[8]->clone );
@@ -167,17 +331,57 @@ sub parse {
                        $log->debug(">>> ", $Element->snext_sibling);
        });
 
-       $log->info("create: ", dump($eval_create) );
-       $log->info("lookup: ", $Document->serialize );
+       my $normalize_source = $Document->serialize;
+       $log->debug("create: ", dump($self->{_lookup_create}) );
+       $log->debug("normalize: $normalize_source");
+
+       $self->{_normalize_source}->{$database}->{$input} = $normalize_source;
 
        if ($self->{debug}) {
                my $Dumper = PPI::Dumper->new( $Document );
                $Dumper->print;
        }
 
+       $log->error("Parser errors:\n", join("\n",@{ $self->{_lookup_errors} }) ) if ($self->{_lookup_errors});
+
        return 1;
 }
 
+
+=head2 _q
+
+Strip single or double quotes around value
+
+  _q(qq/'foo'/) -> foo
+
+=cut
+
+sub _q {
+       my $v = shift || return;
+       $v =~ s/^['"]*//g;
+       $v =~ s/['"]*$//g;
+       return $v;
+}
+
+=head2 _input_name
+
+Return C<name> value if HASH or arg if scalar
+
+  _input_name($input)
+
+=cut
+
+sub _input_name {
+       my $input = shift || return;
+       if (ref($input) eq 'HASH') {
+               die "can't find 'name' value in ", dump($input) unless defined($input->{name});
+               return $input->{name};
+       } else {
+               return $input;
+       }
+}
+
+
 =head1 AUTHOR
 
 Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>