File Coverage

blib/lib/RPSL/Parser.pm
Criterion Covered Total %
statement 78 81 96.3
branch 16 22 72.7
condition 1 3 33.3
subroutine 14 14 100.0
pod 4 4 100.0
total 113 124 91.1


line stmt bran cond sub pod time code
1             package RPSL::Parser;
2             require 5.006_001;
3 2     2   18497 use strict;
  2         4  
  2         53  
4 2     2   5 use warnings;
  2         3  
  2         52  
5 2     2   18 use base qw( Class::Accessor );
  2         2  
  2         1090  
6             __PACKAGE__->mk_accessors(
7             qw( text type tokens key comment
8             object omit_key order )
9             );
10              
11             our $VERSION = "0.039_001";
12              
13             # Public Interface Methods
14              
15             # Constructor
16             sub new {
17 2     2 1 616 my $class = shift;
18 2         11 my $self = bless {
19             __META => {
20             comment => {},
21             object => {},
22             }
23             }, $class;
24 2         5 return $self;
25             }
26              
27             # service method
28             sub parse {
29 1     1 1 32 my $self = shift;
30 1 50       7 unless ( UNIVERSAL::isa( $self, q{RPSL::Parser} ) ) {
31 0         0 $self = RPSL::Parser->new;
32             }
33 1         5 return $self->_read_text(@_)->_tokenize->_build_parse_tree->_parse_tree;
34             }
35              
36             # Private Interface Methods
37              
38             # Overriding Class::Accessor::get
39             sub get {
40 39     39 1 137 my ( $self, @keys ) = @_;
41             return wantarray
42 6         21 ? @{ $self->{__META} }{@keys}
43 39 100       41 : ${ $self->{__META} }{ $keys[0] };
  33         98  
44             }
45              
46             # Overriding Class::Accessor::set
47             sub set {
48 6     6 1 28 my ( $self, $key, $value ) = @_;
49 6         23 return $self->{__META}{$key} = $value;
50             }
51              
52             # Other private methods
53             sub _read_text {
54 1     1   3 my ( $self, @input ) = @_;
55 1         3 my $data;
56 1 50 33     26 if ( UNIVERSAL::isa( $input[0], 'GLOB' )
57             or UNIVERSAL::isa( $input[0], 'IO::Handle' ) )
58             {
59 0         0 local $/;
60 0         0 $data = <$input[0]>;
61             }
62             else {
63 1         5 $data = join '', @input;
64             }
65 1         8 $self->text($data);
66 1         5 return $self;
67             }
68              
69             sub _cleanup_attribute {
70 13     13   20 my ( $self, $value ) = @_;
71 13 50       20 return unless $value;
72 13         15 $value =~ s/\n\s+/\n/gosm;
73 13         55 $value =~ s/^\s+|\s+$//go;
74 13         19 return $value;
75             }
76              
77             sub _tokenize {
78 1     1   2 my $self = shift;
79 1         3 my $text = $self->text;
80 1         2 study $text;
81 1         73 my @tokens = $text =~ m{
82             ^(?:
83             # Look for an attribute name ...
84             ( [a-z0-9][a-z0-9_-]+[a-z0-9] ):
85             # ... followed by zero or more horizontal spaces ...
86             [\t ]*
87             # ... followed by a value ...
88             ( .*?
89             # ... and all valid continuation lines.
90             (?: \n [\s+] .* ? )*
91             )
92             )$
93             }mixg;
94 1         8 $self->tokens( \@tokens );
95 1         5 return $self;
96             }
97              
98             sub _store_attribute {
99 12     12   14 my ( $self, $key, $value ) = @_;
100 12         22 $value = $self->_cleanup_attribute($value);
101              
102             # Store the value
103 12 100       40 if ( exists $self->object->{$key} ) {
104 3 100       7 if ( !UNIVERSAL::isa( $self->object->{$key}, 'ARRAY' ) ) {
105 1         3 $self->object->{$key} = [ $self->object->{$key} ];
106             }
107 3         3 push @{ $self->object->{$key} }, $value;
  3         6  
108             }
109             else {
110 9         17 $self->object->{$key} = $value;
111             }
112 12         31 return $self;
113             }
114              
115             sub _store_comment {
116 12     12   21 my ( $self, $order, $value ) = @_;
117 12 50       22 return unless defined $value;
118 12 100       24 if ( $value =~ s{#(.*)}{} ) {
119 1         3 $self->comment->{$order} = $self->_cleanup_attribute($1);
120             }
121 12         18 return $value;
122             }
123              
124             sub _build_parse_tree {
125 1     1   2 my $self = shift;
126 1         1 my @tokens = @{ $self->tokens };
  1         3  
127 1         2 my ( @order, @omit_key );
128 1         10 while ( my ( $key, $value ) = splice @tokens, 0, 2 ) {
129              
130             # Save the order
131 12         17 push @order, $key;
132              
133             # Handle multi-line comments
134 12 50       20 if ( defined $value ) {
135 12         63 my @parts = split qr{\n\+?\s*}, $value;
136 12 100       32 if ( @parts > 1 ) { # too much, put it back.
137 1         6 unshift @tokens, $key, $_ for reverse @parts[ 1 .. $#parts ];
138 1         2 $value = $parts[0];
139 1         2 my $count = $#order;
140 1         3 map { push @omit_key, $count + $_ } 1 .. $#parts;
  1         4  
141             }
142             }
143              
144 12         21 $value = $self->_store_comment( $#order, $value );
145 12         32 $self->_store_attribute( $key, $value );
146             } # end while
147              
148             # Fill in the object's meta-attributes
149 1         4 $self->order( \@order );
150 1         3 $self->omit_key( \@omit_key );
151 1         3 $self->type( $order[0] );
152              
153             # Stores the object primary key value
154 1         3 my $primary_key = $self->object->{ $order[0] };
155 1 50       5 $primary_key = $primary_key->[0]
156             if UNIVERSAL::isa( $primary_key, 'ARRAY' );
157 1         3 $primary_key =~ s{\s*\#.*$}{};
158 1         3 $self->key($primary_key);
159              
160             # Done!
161 1         3 return $self;
162             }
163              
164             sub _parse_tree {
165 1     1   2 my $self = shift;
166             return {
167 1         2 data => $self->object,
168             type => $self->type,
169             key => $self->key,
170             meta => {
171             order => $self->order,
172             comment => $self->comment,
173             omit_key => $self->omit_key,
174             },
175             };
176             }
177              
178             1;
179             __END__