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.05';
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 $ok = $parse->valid_database('key');
78 my $database = shift || return;
80 return defined($self->{valid_inputs}->{ _q($database) });
83 =head2 valid_database_input
85 my $ok = $parse->valid_database('database_key','input_name');
89 sub valid_database_input {
91 my ($database,$input) = @_;
92 $input = _input_name($input);
93 return defined($self->{valid_inputs}->{ _q($database) }->{ _q($input) });
98 Return all databases and inputs on which specified one depends
100 $depends_on = $parser->depends('database','input');
106 my ($database,$input) = @_;
107 $input = _input_name($input);
108 $self->_get_logger->debug("depends($database,$input)");
110 defined( $self->{depends}->{ _q($database) } ) &&
111 defined( $self->{depends}->{ _q($database) }->{ _q($input) } )
113 return $self->{depends}->{ _q($database) }->{ _q($input) };
116 =head2 lookup_create_rules
118 my $source = $parser->lookup_create_rules($database, $input);
122 sub lookup_create_rules {
124 my ($database,$input) = @_;
125 $input = _input_name($input);
127 defined( $self->{_lookup_create}->{ _q($database) } ) &&
128 defined( $self->{_lookup_create}->{ _q($database) }->{ _q($input) } )
130 return $self->{_lookup_create}->{ _q($database) }->{ _q($input) };
137 my $source_files = $parser->_read_sources;
146 my $log = $self->_get_logger();
152 $self->{config}->iterate_inputs( sub {
153 my ($input, $database) = @_;
155 $log->debug("database: $database input = ", dump($input));
157 foreach my $normalize (@{ $input->{normalize} }) {
159 my $path = $normalize->{path};
160 return unless($path);
161 my $full = $self->{base_path} ? $self->{base_path} . '/' . $path : $path;
163 $log->logdie("normalization input $full doesn't exist") unless (-e $full);
165 my $s = read_file( $full ) || $log->logdie("can't read $full: $!");
167 my $input_name = _input_name($input) || $log->logdie("can't deduce name of input: ", dump( $input ));
169 $log->debug("$database/$input_name: adding $path");
171 $self->{valid_inputs}->{$database}->{$input_name}++;
174 $self->_parse_lookups( $database, $input_name, $full, $s );
181 $log->debug("found $nr source files");
184 $_->() foreach (@lookups);
189 =head2 _parse_lookups
191 $parser->_parse_lookups($database,$input,$path,$source);
193 Called for each normalize source (rules) in each input by L</read_sources>
195 It will report invalid databases and inputs in error log after parsing.
201 my ($database, $input, $path, $source) = @_;
203 $input = _input_name($input);
205 my $log = $self->_get_logger();
207 $log->logdie("invalid database $database" ) unless $self->valid_database( $database );
208 $log->logdie("invalid input $input of database $database", ) unless $self->valid_database_input( $database, $input );
210 $log->logdie("no source found for database $database input $input path $path") unless ($source);
212 $log->info("parsing lookups for $database/$input from $path (",length($source)," bytes)");
214 my $Document = PPI::Document->new( \$source ) || $log->logdie("can't parse source:\n", $self->{source});
216 $Document->prune('PPI::Token::Whitespace');
217 #$Document->prune('PPI::Token::Operator');
219 # Find all the named subroutines
221 $self->{_lookup_errors} = ();
226 $self->_get_logger->logconfess("error without message?") unless ($msg);
227 push @{ $self->{_lookup_errors} }, $msg;
231 $Document->find( sub {
232 my ($Document,$Element) = @_;
234 $Element->isa('PPI::Token::Word') or return '';
235 $Element->content eq 'lookup' or return '';
237 $log->debug("expansion: ", $Element->snext_sibling);
239 my $args = $Element->snext_sibling;
241 my @e = $args->child(0)->elements;
242 $log->logdie("hum, expect at least 8 elements, got ", scalar @e, " in $args") if ($#e < 8);
244 if ($log->is_debug) {
245 my $report = "found " . scalar @e . " elements:\n";
247 foreach my $i ( 0 .. $#e ) {
248 $report .= sprintf("# %-2d: %-30s %s\n", $i, ( $e[$i] || 'undef' ), $e[$i]->class );
251 $log->debug($report);
254 my $key_element = $e[8]->clone;
256 $log->logdie("key element must be PPI::Structure::Block") unless $key_element->isa('PPI::Structure::Block');
258 $log->debug("key part: ", $key_element);
262 $key_element->find( sub {
263 my $e = $_[1] || die "no element?";
264 $e->isa('PPI::Token::Word') or return '';
265 $e->content eq 'rec' or return '';
267 my $kf = $e->snext_sibling;
269 $log->debug("key fragment = $kf");
272 $log->logdie("can't eval { $kf }: $@") if ($@);
277 my $key = join('-', @key ) || $log->logdie("no key found!");
279 $log->debug("key = $key");
282 $coderef = ' . $e[7] . $e[8] . ';
283 foreach my $v ($coderef->()) {
284 next unless (defined($v) && $v ne \'\');
285 push @{ $lookup->{\'' . $key . '\'}->{$v} }, $mfn;
289 $log->debug("create: $create");
291 return $self->_lookup_error("invalid database $e[3] in $path" ) unless $self->valid_database( $e[3] );
292 return $self->_lookup_error("invalid input $e[5] of database $e[3] in $path", ) unless $self->valid_database_input( $e[3], $e[5] );
294 # save code to create this lookup
295 $self->{_lookup_create}->{ _q($e[3]) }->{ _q($e[5]) }->{ _q($key) } .= "# lookup for $e[3]/$e[5]/$key\n\n$create";
298 if (defined( $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) }->{ $key } )) {
299 $log->warn("dependency of $database/$input on $e[3]/$e[5] with $key allready recorded");
302 # save this dependency
303 $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) }->{$key}++;
306 $e[8]->insert_after( $e[8]->clone );
307 $e[8]->insert_after( $e[7]->clone );
308 $e[8]->insert_after( $e[6]->clone );
312 $e[8]->insert_before( new PPI::Token::Quote::Single( "'$key'" ) );
316 $log->debug(">>> ", $Element->snext_sibling);
319 my $normalize_source = $Document->serialize;
320 $log->debug("create: ", dump($self->{_lookup_create}) );
321 $log->debug("normalize: $normalize_source");
323 $self->{_normalize_source}->{$database}->{$input} = $normalize_source;
325 if ($self->{debug}) {
326 my $Dumper = PPI::Dumper->new( $Document );
330 $log->error("Parser errors:\n", join("\n",@{ $self->{_lookup_errors} }) ) if ($self->{_lookup_errors});
338 Strip single or double quotes around value
345 my $v = shift || return;
353 Return C<name> value if HASH or arg if scalar
360 my $input = shift || return;
361 if (ref($input) eq 'HASH') {
362 die "can't find 'name' value in ", dump($input) unless defined($input->{name});
363 return $input->{name};
372 Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>
374 =head1 COPYRIGHT & LICENSE
376 Copyright 2006 Dobrica Pavlinusic, All Rights Reserved.
378 This program is free software; you can redistribute it and/or modify it
379 under the same terms as Perl itself.
383 1; # End of WebPAC::Parser