File Coverage

blib/lib/Data/YAML/Reader.pm
Criterion Covered Total %
statement 123 137 89.7
branch 58 76 76.3
condition 13 18 72.2
subroutine 18 19 94.7
pod 3 3 100.0
total 215 253 84.9


line stmt bran cond sub pod time code
1             package Data::YAML::Reader;
2              
3 5     5   80612 use strict;
  5         7  
  5         145  
4 5     5   19 use warnings;
  5         5  
  5         94  
5 5     5   15 use Carp;
  5         8  
  5         220  
6              
7 5     5   16 use vars qw{$VERSION};
  5         6  
  5         6558  
8              
9             $VERSION = '0.0.7';
10              
11             # TODO:
12             # Handle blessed object syntax
13              
14             # Printable characters for escapes
15             my %UNESCAPES = (
16             z => "\x00",
17             a => "\x07",
18             t => "\x09",
19             n => "\x0a",
20             v => "\x0b",
21             f => "\x0c",
22             r => "\x0d",
23             e => "\x1b",
24             '\\' => '\\',
25             );
26              
27             my $QQ_STRING = qr{ " (?:\\. | [^"])* " }x;
28             my $HASH_LINE = qr{ ^ ($QQ_STRING|\S+) \s* : (?: \s+ (.+?) \s* )? $ }x;
29             my $IS_HASH_KEY = qr{ ^ [\w\'\"] }x;
30             my $IS_END_YAML = qr{ ^ [.][.][.] \s* $ }x;
31             my $IS_QQ_STRING = qr{ ^ $QQ_STRING $ }x;
32              
33             # Create an empty Data::YAML::Reader object
34             sub new {
35 58     58 1 51427 my $class = shift;
36 58         228 bless {}, $class;
37             }
38              
39             sub _make_reader {
40 59     59   39 my $self = shift;
41 59         47 my $obj = shift;
42              
43 59 50       91 croak "Must have something to read from"
44             unless defined $obj;
45              
46 59 100       108 if ( my $ref = ref $obj ) {
47 57 100 0     74 if ( 'CODE' eq $ref ) {
    100          
    50          
    0          
48 55         111 return $obj;
49             }
50             elsif ( 'ARRAY' eq $ref ) {
51 1     26   6 return sub { shift @$obj };
  26         24  
52             }
53             elsif ( 'SCALAR' eq $ref ) {
54 1         5 return $self->_make_reader( $$obj );
55             }
56             elsif ( 'GLOB' eq $ref || 'IO::Handle' eq $ref ) {
57             return sub {
58 0     0   0 my $line = <$obj>;
59 0 0       0 chomp $line if defined $line;
60 0         0 return $line;
61 0         0 };
62             }
63 0         0 croak "Don't know how to read $ref";
64             }
65             else {
66 2         22 my @lines = split( /\n/, $obj );
67 2     52   15 return sub { shift @lines };
  52         53  
68             }
69             }
70              
71             sub read {
72 58     58 1 114 my $self = shift;
73 58         43 my $obj = shift;
74              
75 58         70 $self->{reader} = $self->_make_reader( $obj );
76 58         82 $self->{capture} = [];
77              
78             # Prime the reader
79 58         85 $self->_next;
80              
81 58         75 my $doc = $self->_read;
82              
83             # The terminator is mandatory otherwise we'd consume a line from the
84             # iterator that doesn't belong to us. If we want to remove this
85             # restriction we'll have to implement look-ahead in the iterators.
86             # Which might not be a bad idea.
87 54         65 my $dots = $self->_peek;
88 54 100       416 croak "Missing '...' at end of YAML"
89             unless $dots =~ $IS_END_YAML;
90              
91 51         78 delete $self->{reader};
92 51         44 delete $self->{next};
93              
94 51         93 return $doc;
95             }
96              
97             sub get_raw {
98 38     38 1 315 my $self = shift;
99              
100 38 50       58 if ( defined( my $capture = $self->{capture} ) ) {
101 38         100 return join( "\n", @$capture ) . "\n";
102             }
103              
104 0         0 return '';
105             }
106              
107             sub _peek {
108 487     487   353 my $self = shift;
109 487 100       645 return $self->{next} unless wantarray;
110 375         294 my $line = $self->{next};
111 375         615 $line =~ /^ (\s*) (.*) $ /x;
112 375         716 return ( $2, length $1 );
113             }
114              
115             sub _next {
116 370     370   271 my $self = shift;
117 370 50       532 croak "_next called with no reader"
118             unless $self->{reader};
119 370         468 my $line = $self->{reader}->();
120 370         651 $self->{next} = $line;
121 370         234 push @{ $self->{capture} }, $line;
  370         523  
122             }
123              
124             sub _read {
125 58     58   43 my $self = shift;
126              
127 58         68 my $line = $self->_peek;
128              
129             # Do we have a document header?
130 58 100       240 if ( $line =~ /^ --- (?: \s* (.+?) \s* )? $/x ) {
131 56         65 $self->_next;
132              
133 56 100       122 return $self->_read_scalar( $1 ) if defined $1; # Inline?
134              
135 40         52 my ( $next, $indent ) = $self->_peek;
136              
137 40 100       158 if ( $next =~ /^ - /x ) {
    100          
    50          
138 18         27 return $self->_read_array( $indent );
139             }
140             elsif ( $next =~ $IS_HASH_KEY ) {
141 21         40 return $self->_read_hash( $next, $indent );
142             }
143             elsif ( $next =~ $IS_END_YAML ) {
144 1         68 croak "Premature end of YAML";
145             }
146             else {
147 0         0 croak "Unsupported YAML syntax: '$next'";
148             }
149             }
150             else {
151 2         165 croak "YAML document header not found";
152             }
153             }
154              
155             # Parse a double quoted string
156             sub _read_qq {
157 31     31   24 my $self = shift;
158 31         24 my $str = shift;
159              
160 31 50       127 unless ( $str =~ s/^ " (.*?) " $/$1/x ) {
161 0         0 die "Internal: not a quoted string";
162             }
163              
164 31         38 $str =~ s/\\"/"/gx;
165 31         56 $str =~ s/ \\ ( [tartan\\favez] | x([0-9a-fA-F]{2}) )
166 54 100       143 / (length($1) > 1) ? pack("H2", $2) : $UNESCAPES{$1} /gex;
167 31         77 return $str;
168             }
169              
170             # Parse a scalar string to the actual scalar
171             sub _read_scalar {
172 399     399   290 my $self = shift;
173 399         354 my $string = shift;
174              
175 399 100       508 return undef if $string eq '~';
176              
177 386 100 100     974 if ( $string eq '>' || $string eq '|' ) {
178              
179 4         5 my ( $line, $indent ) = $self->_peek;
180 4 50       6 die "Multi-line scalar content missing" unless defined $line;
181              
182 4         5 my @multiline = ( $line );
183              
184 4         5 while ( 1 ) {
185 8         10 $self->_next;
186 8         9 my ( $next, $ind ) = $self->_peek;
187 8 100       14 last if $ind < $indent;
188 4         3 push @multiline, $next;
189             }
190              
191 4 100       16 return join( ( $string eq '>' ? ' ' : "\n" ), @multiline ) . "\n";
192             }
193              
194 382 100       563 if ( $string =~ /^ ' (.*) ' $/x ) {
195 14         21 ( my $rv = $1 ) =~ s/''/'/g;
196 14         32 return $rv;
197             }
198              
199 368 100       876 if ( $string =~ $IS_QQ_STRING ) {
200 31         51 return $self->_read_qq( $string );
201             }
202              
203 337 50       506 if ( $string =~ /^['"]/ ) {
204              
205             # A quote with folding... we don't support that
206 0         0 die __PACKAGE__ . " does not support multi-line quoted scalars";
207             }
208              
209             # Regular unquoted string
210 337         515 return $string;
211             }
212              
213             sub _read_nested {
214 42     42   32 my $self = shift;
215              
216 42         50 my ( $line, $indent ) = $self->_peek;
217              
218 42 100       133 if ( $line =~ /^ -/x ) {
    50          
219 10         19 return $self->_read_array( $indent );
220             }
221             elsif ( $line =~ $IS_HASH_KEY ) {
222 32         56 return $self->_read_hash( $line, $indent );
223             }
224             else {
225 0         0 croak "Unsupported YAML syntax: '$line'";
226             }
227             }
228              
229             # Parse an array
230             sub _read_array {
231 28     28   23 my ( $self, $limit ) = @_;
232              
233 28         26 my $ar = [];
234              
235 28         21 while ( 1 ) {
236 104         113 my ( $line, $indent ) = $self->_peek;
237 104 100 66     538 last if $indent < $limit || !defined $line || $line =~ $IS_END_YAML;
      100        
238              
239 77 50       99 if ( $indent > $limit ) {
240 0         0 croak "Array line over-indented";
241             }
242              
243 77 100       246 if ( $line =~ /^ (- \s+) \S+ \s* : (?: \s+ | $ ) /x ) {
    100          
    50          
    0          
244 5         5 $indent += length $1;
245 5         12 $line =~ s/-\s+//;
246 5         12 push @$ar, $self->_read_hash( $line, $indent );
247             }
248             elsif ( $line =~ /^ - \s* (.+?) \s* $/x ) {
249 57 100       150 croak "Unexpected start of YAML" if $line =~ /^---/;
250 56         64 $self->_next;
251 56         69 push @$ar, $self->_read_scalar( $1 );
252             }
253             elsif ( $line =~ /^ - \s* $/x ) {
254 15         22 $self->_next;
255 15         23 push @$ar, $self->_read_nested;
256             }
257             elsif ( $line =~ $IS_HASH_KEY ) {
258 0         0 $self->_next;
259 0         0 push @$ar, $self->_read_hash( $line, $indent, );
260             }
261             else {
262 0         0 croak "Unsupported YAML syntax: '$line'";
263             }
264             }
265              
266 27         45 return $ar;
267             }
268              
269             sub _read_hash {
270 58     58   64 my ( $self, $line, $limit ) = @_;
271              
272 58         33 my $indent;
273 58         69 my $hash = {};
274              
275 58         41 while ( 1 ) {
276 177 50       795 croak "Badly formed hash line: '$line'"
277             unless $line =~ $HASH_LINE;
278              
279 177         220 my ( $key, $value ) = ( $self->_read_scalar( $1 ), $2 );
280 177         213 $self->_next;
281              
282 177 100       203 if ( defined $value ) {
283 150         161 $hash->{$key} = $self->_read_scalar( $value );
284             }
285             else {
286 27         39 $hash->{$key} = $self->_read_nested;
287             }
288              
289 177         203 ( $line, $indent ) = $self->_peek;
290 177 100 66     845 last if $indent < $limit || !defined $line || $line =~ $IS_END_YAML;
      100        
291             }
292              
293 58         103 return $hash;
294             }
295              
296             1;
297              
298             __END__