1 package WebPAC::Parser;
9 use Data::Dump qw/dump/;
12 use base qw/WebPAC::Common WebPAC::Normalize/;
16 WebPAC::Parser - parse perl normalization configuration files and mungle it
24 our $VERSION = '0.04';
28 This module will parse L<WebPAC::Normalize/lookup> directives and generate source
29 to produce lookups and normalization.
31 It's written using L<PPI>, pure-perl parser for perl and heavily influenced by
32 reading about LISP. It might be a bit over-the board, but at least it removed
33 separate configuration files for lookups.
35 This is experimental code, but it replaces all older formats which where,
36 at one point in time, available in WebPAC.
44 Create new parser object.
46 my $parser = new WebPAC::Parser(
47 config => new WebPAC::Config(),
48 base_path => '/optional/path/to/conf',
58 my $log = $self->_get_logger();
60 $log->logdie("config isn't WebPAC::Config") unless ($self->{config} && $self->{config}->isa('WebPAC::Config'));
62 $log->logdie("can't iterate_inputs over this config object") unless ($self->{config}->can('iterate_inputs'));
66 $self ? return $self : return undef;
71 my $source_files = $parser->read_sources;
80 my $log = $self->_get_logger();
86 $self->{config}->iterate_inputs( sub {
87 my ($input, $database) = @_;
89 $log->debug("database: $database input = ", dump($input));
91 foreach my $normalize (@{ $input->{normalize} }) {
93 my $path = $normalize->{path};
95 my $full = $self->{base_path} ? $self->{base_path} . '/' . $path : $path;
97 $log->logdie("normalization input $full doesn't exist") unless (-e $full);
99 my $s = read_file( $full ) || $log->logdie("can't read $full: $!");
101 my $input_name = _input_name($input) || $log->logdie("can't deduce name of input: ", dump( $input ));
103 $log->debug("$database/$input_name: adding $path");
105 $self->{valid_inputs}->{$database}->{$input_name}++;
108 $self->parse_lookups( $database, $input_name, $full, $s );
115 $log->debug("found $nr source files");
118 $_->() foreach (@lookups);
125 $parser->parse_lookups($database,$input,$path,$source);
127 Called for each normalize source in each input by L</new>
129 It will report invalid databases and inputs in error log after parsing.
135 my ($database, $input, $path, $source) = @_;
137 $input = _input_name($input);
139 my $log = $self->_get_logger();
141 $log->logdie("invalid database $database" ) unless $self->valid_database( $database );
142 $log->logdie("invalid input $input of database $database", ) unless $self->valid_database_input( $database, $input );
144 $log->logdie("no source found for database $database input $input path $path") unless ($source);
146 $log->info("parsing lookups for $database/$input from $path (",length($source)," bytes)");
148 my $Document = PPI::Document->new( \$source ) || $log->logdie("can't parse source:\n", $self->{source});
150 $Document->prune('PPI::Token::Whitespace');
151 #$Document->prune('PPI::Token::Operator');
153 # Find all the named subroutines
155 $self->{_lookup_errors} = ();
160 $self->_get_logger->logconfess("error without message?") unless ($msg);
161 push @{ $self->{_lookup_errors} }, $msg;
165 $Document->find( sub {
166 my ($Document,$Element) = @_;
168 $Element->isa('PPI::Token::Word') or return '';
169 $Element->content eq 'lookup' or return '';
171 $log->debug("expansion: ", $Element->snext_sibling);
173 my $args = $Element->snext_sibling;
175 my @e = $args->child(0)->elements;
176 $log->logdie("hum, expect at least 8 elements, got ", scalar @e, " in $args") if ($#e < 8);
178 if ($log->is_debug) {
179 my $report = "found " . scalar @e . " elements:\n";
181 foreach my $i ( 0 .. $#e ) {
182 $report .= sprintf("# %-2d: %-30s %s\n", $i, ( $e[$i] || 'undef' ), $e[$i]->class );
185 $log->debug($report);
188 my $key_element = $e[8]->clone;
190 $log->logdie("key element must be PPI::Structure::Block") unless $key_element->isa('PPI::Structure::Block');
192 $log->debug("key part: ", $key_element);
196 $key_element->find( sub {
197 my $e = $_[1] || die "no element?";
198 $e->isa('PPI::Token::Word') or return '';
199 $e->content eq 'rec' or return '';
201 my $kf = $e->snext_sibling;
203 $log->debug("key fragment = $kf");
206 $log->logdie("can't eval { $kf }: $@") if ($@);
211 my $key = join('-', @key ) || $log->logdie("no key found!");
213 $log->debug("key = $key");
216 $coderef = ' . $e[7] . $e[8] . ';
217 foreach my $v ($coderef->()) {
218 next unless (defined($v) && $v ne \'\');
219 push @{ $lookup->{\'' . $key . '\'}->{$v} }, $mfn;
223 $log->debug("create: $create");
225 return $self->_lookup_error("invalid database $e[3] in $path" ) unless $self->valid_database( $e[3] );
226 return $self->_lookup_error("invalid input $e[5] of database $e[3] in $path", ) unless $self->valid_database_input( $e[3], $e[5] );
228 # save code to create this lookup
229 $self->{_lookup_create}->{ _q($e[3]) }->{ _q($e[5]) }->{ _q($key) } .= $create;
232 if (defined( $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) } )) {
233 my $dep_key = $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) };
234 $log->warn("dependency of $database/$input on $e[3]/$e[5] allready recorded as $dep_key, now changed to $key") if ($dep_key ne $key);
237 # save this dependency
238 $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) } .= $key;
241 $e[8]->insert_after( $e[8]->clone );
242 $e[8]->insert_after( $e[7]->clone );
243 $e[8]->insert_after( $e[6]->clone );
247 $e[8]->insert_before( new PPI::Token::Quote::Single( "'$key'" ) );
251 $log->debug(">>> ", $Element->snext_sibling);
254 my $normalize_source = $Document->serialize;
255 $log->debug("create: ", dump($self->{_lookup_create}) );
256 $log->debug("normalize: $normalize_source");
258 $self->{_normalize_source}->{$database}->{$input} = $normalize_source;
260 if ($self->{debug}) {
261 my $Dumper = PPI::Dumper->new( $Document );
265 $log->error("Parser errors:\n", join("\n",@{ $self->{_lookup_errors} }) ) if ($self->{_lookup_errors});
271 =head2 lookup_create_rules
273 my $source = $parser->lookup_create_rules($database, $input);
277 sub lookup_create_rules {
279 my ($database,$input) = @_;
280 return $self->{_lookup_create}->{ _q($database) }->{ _q($input) };
283 =head2 valid_database
285 my $ok = $parse->valid_database('key');
292 my $database = shift || return;
294 return defined($self->{valid_inputs}->{ _q($database) });
297 =head2 valid_database_input
299 my $ok = $parse->valid_database('database_key','input_name');
303 sub valid_database_input {
306 my ($database,$input) = @_;
307 return defined($self->{valid_inputs}->{ _q($database) }->{ _q($input) });
312 Return all databases and inputs on which specified one depends
314 $depends_on = $parser->depends('database','input');
320 my ($database,$input) = @_;
321 $self->_get_logger->debug("depends($database,$input)");
323 defined( $self->{depends}->{ _q($database) } ) &&
324 defined( $self->{depends}->{ _q($database) }->{ _q($input) } )
326 return $self->{depends}->{ _q($database) }->{ _q($input) };
333 Strip single or double quotes around value
340 my $v = shift || return;
348 Return C<name> value if HASH or arg if scalar
355 my $input = shift || return;
356 if (ref($input) eq 'HASH') {
357 die "can't find 'name' value in ", dump($input) unless defined($input->{name});
358 return $input->{name};
367 Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>
369 =head1 COPYRIGHT & LICENSE
371 Copyright 2006 Dobrica Pavlinusic, All Rights Reserved.
373 This program is free software; you can redistribute it and/or modify it
374 under the same terms as Perl itself.
378 1; # End of WebPAC::Parser