File Coverage

blib/lib/Embedix/ECD.pm
Criterion Covered Total %
statement 192 206 93.2
branch 37 50 74.0
condition 4 11 36.3
subroutine 37 39 94.8
pod 21 23 91.3
total 291 329 88.4


line stmt bran cond sub pod time code
1             package Embedix::ECD;
2              
3 5     5   3841 use strict;
  5         8  
  5         186  
4 5     5   23 use vars qw($AUTOLOAD $VERSION);
  5         10  
  5         389  
5              
6             $VERSION = '0.09';
7              
8             # different classes of nodes
9 5     5   2765 use Embedix::ECD::Autovar;
  5         12  
  5         146  
10 5     5   3228 use Embedix::ECD::Component;
  5         14  
  5         153  
11 5     5   2804 use Embedix::ECD::Group;
  5         12  
  5         143  
12 5     5   2638 use Embedix::ECD::Option;
  5         11  
  5         162  
13              
14             # misc info
15 5         656 use Embedix::ECD::Util qw(
16             indent
17             unindent_and_aggregate
18             %default
19             @attribute_order
20 5     5   28 );
  5         9  
21              
22             # damian conway is the man
23 5     5   14691 use Parse::RecDescent;
  5         326273  
  5         42  
24              
25             # for debugging
26 5     5   8087 use Data::Dumper;
  5         61577  
  5         522  
27              
28             # for preserving insertion order
29 5     5   6208 use Tie::IxHash;
  5         38575  
  5         6482  
30              
31             # for when the grammar is giving me trouble
32             # $::RD_HINT = 1;
33              
34             # the grammar
35             $Embedix::ECD::__grammar = q(
36              
37             {
38             *Parse::RecDescent::unindent_and_aggregate =
39             \&Embedix::ECD::Util::unindent_and_aggregate;
40             }
41              
42             ecd_arrayref:
43             statement(s?)
44             {
45             # return syntax tree as nested arrayrefs
46             $return = $item[1];
47             }
48              
49             statement:
50             comment
51             | node
52             | attribute
53             |
54              
55             comment_line:
56             /^#(.*)\n/
57             {
58             $return = $1;
59             }
60              
61             comment:
62             comment_line(s)
63             {
64             $return = [ 'Comment', $item[1] ];
65             }
66              
67             rawtext:
68             #m{[^<]+}
69             #m{(?:[^<]|<(?!(?:/[a-zA-Z_]+|[a-zA-Z_]+/?)>))+}
70             m{(?:[^<]|<(?!(/)?[a-zA-Z_]+(?(1)|/?)>))+}
71             {
72             $return = $item[1];
73             }
74              
75             attribute:
76             # one line w/o tags
77             m{(\w+)[ \t]*=(.*)\n}
78             {
79             my $attr = $1;
80             my $value = $2;
81             $value =~ s/^\s*//;
82             $value =~ s/\s*$//;
83             $return = [ $attr, $value ];
84             }
85             | tag_open rawtext(s?) tag_close
86             {
87             if ($item[1] ne $item[3]) {
88             die "$thisline: was expecting $item[1], " .
89             "but found $item[3] instead\n";
90             }
91             my $value = ref($item[2]) && $item[2][0] || '';
92             $value = unindent_and_aggregate($value);
93             $return = [ $item[1], $value ];
94             }
95              
96             tag_open:
97             m{<([a-zA-Z_]+)>}
98             {
99             $return = $1;
100             }
101              
102             tag_close:
103             m{}
104             {
105             $return = $1;
106             }
107              
108             node:
109             node_start node_item(s?) node_end
110             {
111             if ($item[1][0] ne $item[3]) {
112             die "$thisline: was expecting $item[1][0], " .
113             "but found $item[3] instead\n";
114             }
115             $return = [ $item[1], $item[2] ];
116             }
117              
118             node_item:
119             statement
120             | attribute
121              
122             node_start:
123             m{<(\w+)\s+(.*?)>}
124             {
125             #print STDERR "node $1 $2\n";
126             my $nodetype = ucfirst lc $1;
127             my $name = $2;
128             $return = [ $nodetype, $name ];
129             }
130              
131             node_end:
132             m{}
133             {
134             $return = ucfirst lc $1;
135             }
136             );
137              
138             $Embedix::ECD::__parser = undef;
139              
140             # the parser as a singleton
141             #_______________________________________
142             sub parser {
143 9 100   9 1 58 if (defined $Embedix::ECD::__parser) {
144 6         20 return $Embedix::ECD::__parser;
145             } else {
146             # construct a new parser
147 3         10 my $g = \$Embedix::ECD::__grammar;
148 3         33 my $p = Parse::RecDescent->new($$g);
149 3         263334 return $Embedix::ECD::__parser = $p;
150             }
151             }
152              
153             # constructor, basic
154             #_______________________________________
155             sub new {
156 35 50   35 1 127 my $class = shift; (@_ & 1) && die "Odd number of parameters.\n";
  35         98  
157 35         106 my %opt = @_;
158 35         48 my %child;
159 35         199 tie %child, "Tie::IxHash";
160 35   50     1082 my $self = {
161             name => $opt{name} || die("name attribute is mandatory\n"),
162             parent => undef,
163             child => \%child,
164              
165             # FIXME : these are the attributes that have occurred in nodes
166             # so far. I need to find out if certain attributes are not
167             # allowed in certain node types.
168             attribute => {
169             # scalar values
170             type => undef,
171             value => undef,
172             default_value => undef,
173             range => undef,
174             help => undef,
175             prompt => undef,
176             license => undef,
177             srpm => undef,
178             specpatch => undef,
179              
180             static_size => undef, # XXX < not a good indicator of
181             min_dynamic_size => undef, # XXX < true memory consumption
182             storage_size => undef, # XXX platform dependent
183             startup_time => undef, # XXX platform dependent
184              
185             # These options have been observed to contain aggregate values.
186             build_vars => undef,
187             conflicts => undef,
188             provides => undef,
189             requires => undef,
190             keeplist => undef,
191             choicelist => undef,
192             trideps => undef,
193              
194             # a syntax of its own
195             requiresexpr => undef,
196             'if' => undef,
197             },
198             };
199 35         90 delete($opt{name});
200 35         77 @{$self->{attribute}}{keys %opt} = values %opt;
  35         81  
201 35         205 return bless($self => $class);
202             }
203              
204             # constructor, object
205             #_______________________________________
206             sub newFromCons {
207 27     27 1 53 my $proto = shift;
208 27         45 my $cons = shift;
209 27         35 my $self = undef;
210 27         35 my $i;
211              
212             # self
213 27 100       94 $self = $proto if (ref($proto));
214              
215             # root node
216 27 100       108 $self = Embedix::ECD->new(name => 'ecd') unless ($self);
217              
218             # add kids recursively
219 27         80 while ($i = shift(@$cons)) {
220 158 100       3208 if (ref($i->[0])) {
221             # node
222 21         67 my $node_class = "Embedix::ECD::" . $i->[0][0];
223 21         164 my $child = $node_class->new(name => $i->[0][1]);
224 21         88 $self->addChild($child);
225 21         125 $child->newFromCons($i->[1]);
226             } else {
227             # attribute
228 137 100       270 if ($i->[0] eq "Comment") {
229             # comment
230             # throw them away for now
231             } else {
232             # attribute
233 131         760 $self->setAttribute(lc $i->[0], $i->[1]);
234             }
235             }
236             }
237 27         99 return $self;
238             }
239              
240             # constructor, object
241             #_______________________________________
242             sub newFromString {
243 6     6 1 13 my $class = shift;
244 6         13 my $s = shift;
245 6         37 my $p = Embedix::ECD->parser();
246 6         83 my $cons = $p->ecd_arrayref($s);
247 6         55643 my $self = $class->newFromCons($cons);
248 6         23 return $self;
249             }
250              
251             # constructor, object
252             #_______________________________________
253             sub newFromFile {
254 6     6 1 103 my $class = shift;
255 6         12 my $filename = shift;
256 6 50       425 open(ECD, $filename) || die "$!";
257 6         613 my $s = join('', );
258 6         142 my $self = $class->newFromString($s);
259 6         11440 close(ECD);
260 6         71 return $self;
261             }
262              
263             # constructor, arrayref
264             #_______________________________________
265             sub consFromString {
266 2     2 1 5 my $class = shift;
267 2         2 my $s = shift;
268 2         163 my $p = Embedix::ECD->parser();
269 2         25 my $cons = $p->ecd_arrayref($s);
270 2         68772 return $cons;
271             }
272              
273             # constructor, arrayref
274             #_______________________________________
275             sub consFromFile {
276 2     2 1 21 my $class = shift;
277 2         4 my $filename = shift;
278 2 50       199 open(ECD, $filename) || die "$!";
279 2         63 my $s = join('', );
280 2         13 my $cons = $class->consFromString($s);
281 2         75 close(ECD);
282 2         11 return $cons;
283             }
284              
285             # destructor
286             #_______________________________________
287 0     0   0 sub DESTROY {
288              
289             }
290              
291             # accessor for name
292             #_______________________________________
293             sub name {
294 15     15 1 22 my $self = shift;
295 15 50       87 if (@_) {
296 0         0 $self->{name} = +shift;
297             } else {
298 15         80 return $self->{name};
299             }
300             }
301              
302             # general attribute getter
303             #_______________________________________
304             sub getAttribute {
305 22     22 1 22 my $self = shift;
306 22         24 my $attr = shift;
307 22         57 return $self->{attribute}{$attr};
308             }
309              
310             # general attribute setter
311             #_______________________________________
312             sub setAttribute {
313 131     131 1 176 my $self = shift;
314 131         158 my $attr = shift;
315 131         167 my $val = shift;
316 131         560 $self->{attribute}{$attr} = $val;
317             }
318              
319             # maybe do some glob + closure magic for attribute getters and setters
320             #_______________________________________
321             sub make_accessor_method {
322 5     5 0 15 my $package = caller;
323 5         11 my $method;
324 5         17 foreach $method (@_) {
325 5     5   49 no strict 'refs';
  5         10  
  5         1114  
326 110         1268 *{$package . "::$method"} = sub {
327 4     4   45 my $self = shift;
328 4 100       14 if (@_) {
329 2         8 $self->{attribute}{$method} = +shift;
330             } else {
331 2         13 return $self->{attribute}{$method};
332             }
333             }
334 110         615 }
335             }
336              
337             # *_size attributes can be mathematical expressions.
338             #_______________________________________
339             sub make_evaluating_getter_method {
340 5     5 0 13 my $package = caller;
341 5         9 my $method;
342 5         102 foreach $method (@_) {
343 5     5   29 no strict 'refs';
  5         11  
  5         7946  
344 20         210 *{$package . "::eval_$method"} = sub {
345 2     2   23 my $self = shift;
346 2         20 my @x = split (
347             /(?<=\d)\s+(?=\d)/,
348             $self->{attribute}{$method}
349             );
350 2 100       7 push @x, 0 if (@x == 1);
351 2         6 return map { eval } @x;
  4         194  
352             }
353 20         102 }
354             }
355              
356             # in the future, subclasses will be more specific.
357             Embedix::ECD::make_accessor_method(qw(
358             type
359             value
360             default_value
361             range
362             help
363             prompt
364             license
365             srpm
366             specpatch
367              
368             static_size
369             min_dynamic_size
370             storage_size
371             startup_time
372              
373             build_vars
374             conflicts
375             provides
376             requires
377             keeplist
378             choicelist
379             trideps
380              
381             requiresexpr
382             if
383             ));
384              
385             Embedix::ECD::make_evaluating_getter_method(qw(
386             static_size
387             min_dynamic_size
388             storage_size
389             startup_time
390             ));
391              
392             # get child node objects
393             #_______________________________________
394             sub getChild {
395 4     4 1 6 my $self = shift;
396 4         5 my $name = shift;
397              
398 4 100       20 if (defined $self->{child}{$name}) {
399 3         29 return $self->{child}{$name};
400             } else {
401 1         9 return undef;
402             }
403             }
404              
405             # an alias for getChild()
406             #_______________________________________
407             *Embedix::ECD::n = \&getChild; # node
408              
409             # set child node objects
410             #_______________________________________
411             sub addChild {
412 28     28 1 75 my $self = shift;
413 28         38 my $obj = shift;
414 28 50 33     289 die "$obj is not an instance of Embedix::ECD.\n"
415             unless (ref($obj) && $obj->isa('Embedix::ECD'));
416 28         103 my $name = $obj->{name};
417 28         50 $obj->{parent} = $self;
418 28         194 $self->{child}{$name} = $obj;
419 28         454 return $obj;
420             }
421              
422             # delete a child from a node
423             #_______________________________________
424             sub delChild {
425 0     0 1 0 my $self = shift;
426 0         0 my $obj = shift;
427 0         0 my $name;
428 0 0       0 if (ref($obj)) {
429 0         0 $name = $obj->name;
430             } else {
431 0         0 $name = $obj;
432             }
433 0 0       0 if (defined $self->{child}{$name}) {
434 0         0 return delete($self->{child}{$name});
435             } else {
436 0         0 carp("Child $name does not exist");
437 0         0 return undef;
438             }
439             }
440              
441             # return list of children of node
442             #_______________________________________
443             sub getChildren {
444 7     7 1 12 my $self = shift;
445 7         10 return values %{$self->{child}};
  7         284  
446             }
447              
448             # true if node has children
449             #_______________________________________
450             sub hasChildren {
451 2     2 1 33 my $self = shift;
452 2         3 return scalar values %{$self->{child}};
  2         11  
453             }
454              
455             # get child node objects automagically
456             #_______________________________________
457             sub AUTOLOAD {
458 18     18   196 my $self = shift;
459 18         26 my $name = $AUTOLOAD;
460              
461 18         73 $name =~ s/.*://;
462              
463 18 100       95 if (defined $self->{child}{$name}) {
464 17         166 return $self->{child}{$name};
465             } else {
466             # my (undef, $f, $l) = caller();
467             # die "$f\[$l\] => ", ref($self), "::$name() is not a valid method.\n";
468 1         19 return undef;
469             }
470             }
471              
472             # merge another Embedix::ECD object together with $self
473             #_______________________________________
474             sub mergeWith {
475 5     5 1 15 my $self = shift;
476 5         8 my $ecd = shift;
477              
478             # restrict
479 5 50 33     11 unless ( ($self->name eq $ecd->name)
480             && (ref($self) eq ref($ecd)) )
481             {
482 0         0 die "ecd needs to have the same name and class to be merged\n";
483             }
484              
485             # copy attributes
486 5         8 foreach (keys %{$ecd->{attribute}}) {
  5         33  
487 110 100       213 $self->{attribute}{$_} = $ecd->{attribute}{$_}
488             if defined ($ecd->{attribute}{$_});
489             }
490              
491             # merge children
492 5         14 my $sibling;
493 5         20 foreach $sibling ($ecd->getChildren) {
494 4         112 my $name = $sibling->name;
495 4         14 my $child = $self->getChild($name);
496              
497 4 100       34 unless ($child) {
498 1         4 $self->addChild($sibling);
499             } else {
500 3         43 my $evil_twin = $sibling;
501 3         22 $child->mergeWith($evil_twin); # bad poetry?
502             }
503             }
504             }
505              
506             # get depth in tree
507             #_______________________________________
508             sub getDepth {
509 2     2 1 2 my $self = shift;
510 2         5 my $parent = $self->{parent};
511 2 100       8 unless (ref($parent)) {
512 1         10 return 0;
513             } else {
514 1         6 return ($parent->getDepth() + 1);
515             }
516             }
517              
518             # return the class of the node as a string
519             #_______________________________________
520             sub getNodeClass {
521 1     1 1 11 my $self = shift;
522 1         2 my $type = ref($self);
523 1         6 return substr($type, rindex($type, ':') + 1);
524             }
525              
526             # calculate spaces
527             #_______________________________________
528             sub getFormatOptions {
529 1 50   1 1 2 my $self = shift; (@_ & 1) && die "Odd number of parameters.\n";
  1         7  
530 1         6 my %opt = @_;
531              
532 1 50       6 $opt{indent} = 0 unless(defined($opt{indent}));
533 1   33     6 $opt{sw} = $opt{shiftwidth} || $default{shiftwidth};
534 1         12 $opt{space} = " " x $opt{indent} . indent($self->getDepth, $opt{sw});
535 1         5 $opt{space2} = $opt{space} . " " x $opt{sw};
536 1         3 $opt{order} = \@attribute_order;
537              
538 1         3 return \%opt;
539             }
540              
541             # render the attributes of a node
542             # It's rare for me to nest this much.
543             #_______________________________________
544             sub attributeToString {
545 1     1 1 2 my $self = shift;
546 1         3 my $opt = shift;
547 1         4 my ($sw, $space, $space2) = map { $opt->{$_} } qw(sw space space2);
  3         12  
548 1         3 my $a;
549 22         44 return join '', map {
550 1         4 $a = $self->getAttribute($_);
551 22 100       70 if (defined($a)) {
552 9 100       16 if (ref($a)) {
553 2 50       9 if (scalar(@$a)) {
554             # an aggregate attribute
555 3         41 $space2 . "<" . uc($_) . ">\n" .
556 2         10 join('', map { $space2 . " " x $sw . "$_\n" } @$a) .
557             $space2 . "\n";
558             } else {
559             # an empty aggregate attribute
560 0         0 "";
561             }
562             } else {
563             # a scalar attribute
564 7         29 $space2 . uc($_) . "=" . "$a\n";
565             }
566             }
567 1         2 } @{$opt->{order}};
568             }
569              
570             # render $self in ECD format
571             # Embedix::ECD itself doesn't have a textual representation
572             # but its subclasses should.
573             #_______________________________________
574             sub toString {
575 2     2 1 33 my $self = shift;
576 2         14 return join('', map { $_->toString(@_) } $self->getChildren());
  1         49  
577             }
578              
579             1;
580              
581             __END__