1 package WebPAC::Parser;
9 use Data::Dump qw/dump/;
12 use base qw/WebPAC::Common/;
16 WebPAC::Parser - parse perl normalization configuration files and mungle it
24 our $VERSION = '0.06';
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 have_lookup_create
118 my @keys = $parser->have_lookup_create($database, $input);
122 sub have_lookup_create {
124 my ($database,$input) = @_;
125 $input = _input_name($input);
127 defined( $self->{_lookup_create_key}->{ _q($database) } ) &&
128 defined( $self->{_lookup_create_key}->{ _q($database) }->{ _q($input) } )
130 return keys %{ $self->{_lookup_create_key}->{ _q($database) }->{ _q($input) } };
134 =head2 lookup_create_rules
136 my $source = $parser->lookup_create_rules($database, $input);
140 sub lookup_create_rules {
142 my ($database,$input) = @_;
143 $input = _input_name($input);
145 defined( $self->{_lookup_create}->{ _q($database) } ) &&
146 defined( $self->{_lookup_create}->{ _q($database) }->{ _q($input) } )
148 return $self->{_lookup_create}->{ _q($database) }->{ _q($input) };
151 =head2 normalize_rules
153 my $source = $parser->normalize_rules($database, $input);
157 sub normalize_rules {
159 my ($database,$input) = @_;
160 $input = _input_name($input);
162 defined( $self->{_normalize_source}->{ _q($database) } ) &&
163 defined( $self->{_normalize_source}->{ _q($database) }->{ _q($input) } )
165 return $self->{_normalize_source}->{ _q($database) }->{ _q($input) };
172 my $source_files = $parser->_read_sources;
181 my $log = $self->_get_logger();
187 $self->{config}->iterate_inputs( sub {
188 my ($input, $database) = @_;
190 $log->debug("database: $database input = ", dump($input));
192 foreach my $normalize (@{ $input->{normalize} }) {
194 my $path = $normalize->{path};
195 return unless($path);
196 my $full = $self->{base_path} ? $self->{base_path} . '/' . $path : $path;
198 $log->logdie("normalization input $full doesn't exist") unless (-e $full);
200 my $s = read_file( $full ) || $log->logdie("can't read $full: $!");
202 my $input_name = _input_name($input) || $log->logdie("can't deduce name of input: ", dump( $input ));
204 $log->debug("$database/$input_name: adding $path");
206 $self->{valid_inputs}->{$database}->{$input_name}++;
209 $self->_parse_lookups( $database, $input_name, $full, $s );
216 $log->debug("found $nr source files");
219 $_->() foreach (@lookups);
224 =head2 _parse_lookups
226 $parser->_parse_lookups($database,$input,$path,$source);
228 Called for each normalize source (rules) in each input by L</_read_sources>
230 It will report invalid databases and inputs in error log after parsing.
236 my ($database, $input, $path, $source) = @_;
238 $input = _input_name($input);
240 my $log = $self->_get_logger();
242 $log->logdie("invalid database $database" ) unless $self->valid_database( $database );
243 $log->logdie("invalid input $input of database $database", ) unless $self->valid_database_input( $database, $input );
245 $log->logdie("no source found for database $database input $input path $path") unless ($source);
247 $log->info("parsing lookups for $database/$input from $path (",length($source)," bytes)");
249 my $Document = PPI::Document->new( \$source ) || $log->logdie("can't parse source:\n", $self->{source});
251 $Document->prune('PPI::Token::Whitespace');
252 $Document->prune('PPI::Token::Comment');
253 #$Document->prune('PPI::Token::Operator');
255 # Find all the named subroutines
257 $self->{_lookup_errors} = ();
262 $self->_get_logger->logconfess("error without message?") unless ($msg);
263 push @{ $self->{_lookup_errors} }, $msg;
267 $Document->find( sub {
268 my ($Document,$Element) = @_;
270 $Element->isa('PPI::Token::Word') or return '';
271 $Element->content eq 'lookup' or return '';
273 $log->debug("expansion: ", $Element->snext_sibling);
275 my $args = $Element->snext_sibling;
277 my @e = $args->child(0)->elements;
278 $log->logdie("hum, expect at least 8 elements, got ", scalar @e, " in $args") if ($#e < 8);
280 if ($log->is_debug) {
281 my $report = "found " . scalar @e . " elements:\n";
283 foreach my $i ( 0 .. $#e ) {
284 $report .= sprintf("# %-2d: %-30s %s\n", $i, ( $e[$i] || 'undef' ), $e[$i]->class );
287 $log->debug($report);
290 my $key_element = $e[8]->clone;
292 $log->logdie("key element must be PPI::Structure::Block") unless $key_element->isa('PPI::Structure::Block');
294 $log->debug("key part: ", $key_element);
298 $key_element->find( sub {
299 my $e = $_[1] || die "no element?";
300 $e->isa('PPI::Token::Word') or return '';
301 $e->content eq 'rec' or return '';
303 my $kf = $e->snext_sibling;
305 $log->debug("key fragment = $kf");
308 $log->logdie("can't eval { $kf }: $@") if ($@);
313 my $key = join('-', @key ) || $log->logdie("no key found!");
315 $log->debug("key = $key");
317 return $self->_lookup_error("invalid database $e[3] in $path" ) unless $self->valid_database( $e[3] );
318 return $self->_lookup_error("invalid input $e[5] of database $e[3] in $path", ) unless $self->valid_database_input( $e[3], $e[5] );
321 save_into_lookup($e[3],$e[5],'$key', $e[7] $e[8] );
324 $log->debug("create: $create");
326 # save code to create this lookup
327 $self->{_lookup_create}->{ _q($e[3]) }->{ _q($e[5]) } .= $create;
328 $self->{_lookup_create_key}->{ _q($e[3]) }->{ _q($e[5]) }->{ _q($key) }++;
331 if (defined( $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) }->{ $key } )) {
332 $log->warn("dependency of $database/$input on $e[3]/$e[5] with $key allready recorded");
335 # save this dependency
336 $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) }->{$key}++;
339 $e[8]->insert_after( $e[8]->clone );
340 $e[8]->insert_after( $e[7]->clone );
341 $e[8]->insert_after( $e[6]->clone );
345 $e[8]->insert_before( new PPI::Token::Quote::Single( "'$key'" ) );
349 $log->debug(">>> ", $Element->snext_sibling);
352 my $normalize_source = $Document->serialize;
353 $log->debug("create: ", dump($self->{_lookup_create}) );
354 $log->debug("normalize: $normalize_source");
356 $self->{_normalize_source}->{$database}->{$input} .= $normalize_source;
358 if ($self->{debug}) {
359 my $Dumper = PPI::Dumper->new( $Document );
363 $log->error("Parser errors:\n", join("\n",@{ $self->{_lookup_errors} }) ) if ($self->{_lookup_errors});
371 Strip single or double quotes around value
378 my $v = shift || return;
386 Return C<name> value if HASH or arg if scalar
393 my $input = shift || return;
394 if (ref($input) eq 'HASH') {
395 die "can't find 'name' value in ", dump($input) unless defined($input->{name});
396 return $input->{name};
405 Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>
407 =head1 COPYRIGHT & LICENSE
409 Copyright 2006 Dobrica Pavlinusic, All Rights Reserved.
411 This program is free software; you can redistribute it and/or modify it
412 under the same terms as Perl itself.
416 1; # End of WebPAC::Parser