File Coverage

blib/lib/RPSL/Parser.pm
Criterion Covered Total %
statement 79 82 96.3
branch 18 24 75.0
condition 1 3 33.3
subroutine 14 14 100.0
pod 4 4 100.0
total 116 127 91.3


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