/[webpac2]/trunk/lib/WebPAC/Parser.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /trunk/lib/WebPAC/Parser.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 702 - (show annotations)
Mon Sep 25 13:08:17 2006 UTC (17 years, 7 months ago) by dpavlin
File size: 8602 byte(s)
 r998@llin:  dpavlin | 2006-09-25 15:06:05 +0200
 first cuts at depends

1 package WebPAC::Parser;
2
3 use warnings;
4 use strict;
5
6
7 use PPI;
8 use PPI::Dumper;
9 use Data::Dump qw/dump/;
10 use File::Slurp;
11
12 use base qw/WebPAC::Common WebPAC::Normalize/;
13
14 =head1 NAME
15
16 WebPAC::Parser - parse perl normalization configuration files and mungle it
17
18 =head1 VERSION
19
20 Version 0.04
21
22 =cut
23
24 our $VERSION = '0.04';
25
26 =head1 SYNOPSIS
27
28 This module will parse L<WebPAC::Normalize/lookup> directives and generate source
29 to produce lookups and normalization.
30
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.
34
35 This is experimental code, but it replaces all older formats which where,
36 at one point in time, available in WebPAC.
37
38 FIXME
39
40 =head1 FUNCTIONS
41
42 =head2 new
43
44 Create new parser object.
45
46 my $parser = new WebPAC::Parser(
47 config => new WebPAC::Config(),
48 base_path => '/optional/path/to/conf',
49 );
50
51 =cut
52
53 sub new {
54 my $class = shift;
55 my $self = {@_};
56 bless($self, $class);
57
58 my $log = $self->_get_logger();
59
60 $log->logdie("config isn't WebPAC::Config") unless ($self->{config} && $self->{config}->isa('WebPAC::Config'));
61
62 $log->logdie("can't iterate_inputs over this config object") unless ($self->{config}->can('iterate_inputs'));
63
64 $self->read_sources;
65
66 $self ? return $self : return undef;
67 }
68
69 =head2 read_sources
70
71 my $source_files = $parser->read_sources;
72
73 Called by L</new>.
74
75 =cut
76
77 sub read_sources {
78 my $self = shift;
79
80 my $log = $self->_get_logger();
81
82 my $nr = 0;
83
84 my @lookups;
85
86 $self->{config}->iterate_inputs( sub {
87 my ($input, $database) = @_;
88
89 $log->debug("database: $database input = ", dump($input));
90
91 foreach my $normalize (@{ $input->{normalize} }) {
92
93 my $path = $normalize->{path};
94 return unless($path);
95 my $full = $self->{base_path} ? $self->{base_path} . '/' . $path : $path;
96
97 $log->logdie("normalization input $full doesn't exist") unless (-e $full);
98
99 my $s = read_file( $full ) || $log->logdie("can't read $full: $!");
100
101 my $input_name = _input_name($input) || $log->logdie("can't deduce name of input: ", dump( $input ));
102
103 $log->debug("$database/$input_name: adding $path");
104
105 $self->{valid_inputs}->{$database}->{$input_name}++;
106
107 push @lookups, sub {
108 $self->parse_lookups( $database, $input_name, $full, $s );
109 };
110
111 $nr++;
112 }
113 } );
114
115 $log->debug("found $nr source files");
116
117 # parse all lookups
118 $_->() foreach (@lookups);
119
120 return $nr;
121 }
122
123 =head2 parse_lookups
124
125 $parser->parse_lookups($database,$input,$path,$source);
126
127 Called for each normalize source in each input by L</new>
128
129 It will report invalid databases and inputs in error log after parsing.
130
131 =cut
132
133 sub parse_lookups {
134 my $self = shift;
135 my ($database, $input, $path, $source) = @_;
136
137 $input = _input_name($input);
138
139 my $log = $self->_get_logger();
140
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 );
143
144 $log->logdie("no source found for database $database input $input path $path") unless ($source);
145
146 $log->info("parsing lookups for $database/$input from $path (",length($source)," bytes)");
147
148 my $Document = PPI::Document->new( \$source ) || $log->logdie("can't parse source:\n", $self->{source});
149
150 $Document->prune('PPI::Token::Whitespace');
151 #$Document->prune('PPI::Token::Operator');
152
153 # Find all the named subroutines
154
155 $self->{_lookup_errors} = ();
156
157 sub _lookup_error {
158 my $self = shift;
159 my $msg = shift;
160 $self->_get_logger->logconfess("error without message?") unless ($msg);
161 push @{ $self->{_lookup_errors} }, $msg;
162 return '';
163 }
164
165 $Document->find( sub {
166 my ($Document,$Element) = @_;
167
168 $Element->isa('PPI::Token::Word') or return '';
169 $Element->content eq 'lookup' or return '';
170
171 $log->debug("expansion: ", $Element->snext_sibling);
172
173 my $args = $Element->snext_sibling;
174
175 my @e = $args->child(0)->elements;
176 $log->logdie("hum, expect at least 8 elements, got ", scalar @e, " in $args") if ($#e < 8);
177
178 if ($log->is_debug) {
179 my $report = "found " . scalar @e . " elements:\n";
180
181 foreach my $i ( 0 .. $#e ) {
182 $report .= sprintf("# %-2d: %-30s %s\n", $i, ( $e[$i] || 'undef' ), $e[$i]->class );
183 }
184
185 $log->debug($report);
186 }
187
188 my $key_element = $e[8]->clone;
189
190 $log->logdie("key element must be PPI::Structure::Block") unless $key_element->isa('PPI::Structure::Block');
191
192 $log->debug("key part: ", $key_element);
193
194 my @key;
195
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 '';
200
201 my $kf = $e->snext_sibling;
202
203 $log->debug("key fragment = $kf");
204
205 push @key, eval $kf;
206 $log->logdie("can't eval { $kf }: $@") if ($@);
207
208 return 1;
209 });
210
211 my $key = join('-', @key ) || $log->logdie("no key found!");
212
213 $log->debug("key = $key");
214
215 my $create = '
216 $coderef = ' . $e[7] . $e[8] . ';
217 foreach my $v ($coderef->()) {
218 next unless (defined($v) && $v ne \'\');
219 push @{ $lookup->{\'' . $key . '\'}->{$v} }, $mfn;
220 }
221 ';
222
223 $log->debug("create: $create");
224
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] );
227
228 # save code to create this lookup
229 $self->{_lookup_create}->{ _q($e[3]) }->{ _q($e[5]) }->{ _q($key) } .= $create;
230
231
232 if (defined( $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) }->{ $key } )) {
233 $log->warn("dependency of $database/$input on $e[3]/$e[5] with $key allready recorded");
234 }
235
236 # save this dependency
237 $self->{depends}->{ $database }->{ $input }->{ _q($e[3]) }->{ _q($e[5]) }->{$key}++;
238
239 if ($#e < 10) {
240 $e[8]->insert_after( $e[8]->clone );
241 $e[8]->insert_after( $e[7]->clone );
242 $e[8]->insert_after( $e[6]->clone );
243 }
244
245 $e[7]->remove;
246 $e[8]->insert_before( new PPI::Token::Quote::Single( "'$key'" ) );
247 $e[8]->remove;
248
249
250 $log->debug(">>> ", $Element->snext_sibling);
251 });
252
253 my $normalize_source = $Document->serialize;
254 $log->debug("create: ", dump($self->{_lookup_create}) );
255 $log->debug("normalize: $normalize_source");
256
257 $self->{_normalize_source}->{$database}->{$input} = $normalize_source;
258
259 if ($self->{debug}) {
260 my $Dumper = PPI::Dumper->new( $Document );
261 $Dumper->print;
262 }
263
264 $log->error("Parser errors:\n", join("\n",@{ $self->{_lookup_errors} }) ) if ($self->{_lookup_errors});
265
266 return 1;
267 }
268
269
270 =head2 lookup_create_rules
271
272 my $source = $parser->lookup_create_rules($database, $input);
273
274 =cut
275
276 sub lookup_create_rules {
277 my $self = shift;
278 my ($database,$input) = @_;
279 return $self->{_lookup_create}->{ _q($database) }->{ _q($input) };
280 }
281
282 =head2 valid_database
283
284 my $ok = $parse->valid_database('key');
285
286 =cut
287
288 sub valid_database {
289 my $self = shift;
290
291 my $database = shift || return;
292
293 return defined($self->{valid_inputs}->{ _q($database) });
294 }
295
296 =head2 valid_database_input
297
298 my $ok = $parse->valid_database('database_key','input_name');
299
300 =cut
301
302 sub valid_database_input {
303 my $self = shift;
304
305 my ($database,$input) = @_;
306 return defined($self->{valid_inputs}->{ _q($database) }->{ _q($input) });
307 }
308
309 =head2 depends
310
311 Return all databases and inputs on which specified one depends
312
313 $depends_on = $parser->depends('database','input');
314
315 =cut
316
317 sub depends {
318 my $self = shift;
319 my ($database,$input) = @_;
320 $self->_get_logger->debug("depends($database,$input)");
321 return unless (
322 defined( $self->{depends}->{ _q($database) } ) &&
323 defined( $self->{depends}->{ _q($database) }->{ _q($input) } )
324 );
325 return $self->{depends}->{ _q($database) }->{ _q($input) };
326 }
327
328 =head1 PRIVATE
329
330 =head2 _q
331
332 Strip single or double quotes around value
333
334 _q(qq/'foo'/) -> foo
335
336 =cut
337
338 sub _q {
339 my $v = shift || return;
340 $v =~ s/^['"]*//g;
341 $v =~ s/['"]*$//g;
342 return $v;
343 }
344
345 =head2 _input_name
346
347 Return C<name> value if HASH or arg if scalar
348
349 _input_name($input)
350
351 =cut
352
353 sub _input_name {
354 my $input = shift || return;
355 if (ref($input) eq 'HASH') {
356 die "can't find 'name' value in ", dump($input) unless defined($input->{name});
357 return $input->{name};
358 } else {
359 return $input;
360 }
361 }
362
363
364 =head1 AUTHOR
365
366 Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>
367
368 =head1 COPYRIGHT & LICENSE
369
370 Copyright 2006 Dobrica Pavlinusic, All Rights Reserved.
371
372 This program is free software; you can redistribute it and/or modify it
373 under the same terms as Perl itself.
374
375 =cut
376
377 1; # End of WebPAC::Parser

  ViewVC Help
Powered by ViewVC 1.1.26