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   164346 use strict;
  5         14  
  5         207  
4 5     5   31 use warnings;
  5         11  
  5         172  
5 5     5   33 use Carp;
  5         13  
  5         447  
6              
7 5     5   88 use vars qw{$VERSION};
  5         11  
  5         11611  
8              
9             $VERSION = '0.0.6';
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 54     54 1 90006 my $class = shift;
36 54         1436 bless {}, $class;
37             }
38              
39             sub _make_reader {
40 55     55   72 my $self = shift;
41 55         65 my $obj = shift;
42              
43 55 50       152 croak "Must have something to read from"
44             unless defined $obj;
45              
46 55 100       156 if ( my $ref = ref $obj ) {
47 53 100 0     245 if ( 'CODE' eq $ref ) {
    100          
    50          
    0          
48 51         157 return $obj;
49             }
50             elsif ( 'ARRAY' eq $ref ) {
51 1     26   6 return sub { shift @$obj };
  26         41  
52             }
53             elsif ( 'SCALAR' eq $ref ) {
54 1         6 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         25 my @lines = split( /\n/, $obj );
67 2     52   14 return sub { shift @lines };
  52         82  
68             }
69             }
70              
71             sub read {
72 54     54 1 161 my $self = shift;
73 54         66 my $obj = shift;
74              
75 54         132 $self->{reader} = $self->_make_reader( $obj );
76 54         181 $self->{capture} = [];
77              
78             # Prime the reader
79 54         175 $self->_next;
80              
81 54         129 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 50         106 my $dots = $self->_peek;
88 50 100       834 croak "Missing '...' at end of YAML"
89             unless $dots =~ $IS_END_YAML;
90              
91 47         437 delete $self->{reader};
92 47         90 delete $self->{next};
93              
94 47         164 return $doc;
95             }
96              
97             sub get_raw {
98 38     38 1 496 my $self = shift;
99              
100 38 50       100 if ( defined( my $capture = $self->{capture} ) ) {
101 38         155 return join( "\n", @$capture ) . "\n";
102             }
103              
104 0         0 return '';
105             }
106              
107             sub _peek {
108 463     463   523 my $self = shift;
109 463 100       1050 return $self->{next} unless wantarray;
110 359         516 my $line = $self->{next};
111 359         1030 $line =~ /^ (\s*) (.*) $ /x;
112 359         1235 return ( $2, length $1 );
113             }
114              
115             sub _next {
116 354     354   408 my $self = shift;
117 354 50       804 croak "_next called with no reader"
118             unless $self->{reader};
119 354         786 my $line = $self->{reader}->();
120 354         1010 $self->{next} = $line;
121 354         502 push @{ $self->{capture} }, $line;
  354         1416  
122             }
123              
124             sub _read {
125 54     54   74 my $self = shift;
126              
127 54         119 my $line = $self->_peek;
128              
129             # Do we have a document header?
130 54 100       908 if ( $line =~ /^ --- (?: \s* (.+?) \s* )? $/x ) {
131 52         303 $self->_next;
132              
133 52 100       212 return $self->_read_scalar( $1 ) if defined $1; # Inline?
134              
135 36         166 my ( $next, $indent ) = $self->_peek;
136              
137 36 100       233 if ( $next =~ /^ - /x ) {
    100          
    50          
138 18         55 return $self->_read_array( $indent );
139             }
140             elsif ( $next =~ $IS_HASH_KEY ) {
141 17         61 return $self->_read_hash( $next, $indent );
142             }
143             elsif ( $next =~ $IS_END_YAML ) {
144 1         100 croak "Premature end of YAML";
145             }
146             else {
147 0         0 croak "Unsupported YAML syntax: '$next'";
148             }
149             }
150             else {
151 2         351 croak "YAML document header not found";
152             }
153             }
154              
155             # Parse a double quoted string
156             sub _read_qq {
157 29     29   43 my $self = shift;
158 29         35 my $str = shift;
159              
160 29 50       176 unless ( $str =~ s/^ " (.*?) " $/$1/x ) {
161 0         0 die "Internal: not a quoted string";
162             }
163              
164 29         55 $str =~ s/\\"/"/gx;
165 29         97 $str =~ s/ \\ ( [tartan\\favez] | x([0-9a-fA-F]{2}) )
166 54 100       482 / (length($1) > 1) ? pack("H2", $2) : $UNESCAPES{$1} /gex;
167 29         108 return $str;
168             }
169              
170             # Parse a scalar string to the actual scalar
171             sub _read_scalar {
172 387     387   457 my $self = shift;
173 387         637 my $string = shift;
174              
175 387 100       740 return undef if $string eq '~';
176              
177 374 100 100     1542 if ( $string eq '>' || $string eq '|' ) {
178              
179 4         10 my ( $line, $indent ) = $self->_peek;
180 4 50       11 die "Multi-line scalar content missing" unless defined $line;
181              
182 4         9 my @multiline = ( $line );
183              
184 4         8 while ( 1 ) {
185 8         18 $self->_next;
186 8         16 my ( $next, $ind ) = $self->_peek;
187 8 100       19 last if $ind < $indent;
188 4         7 push @multiline, $next;
189             }
190              
191 4 100       28 return join( ( $string eq '>' ? ' ' : "\n" ), @multiline ) . "\n";
192             }
193              
194 370 100       835 if ( $string =~ /^ ' (.*) ' $/x ) {
195 14         38 ( my $rv = $1 ) =~ s/''/'/g;
196 14         45 return $rv;
197             }
198              
199 356 100       1331 if ( $string =~ $IS_QQ_STRING ) {
200 29         78 return $self->_read_qq( $string );
201             }
202              
203 327 50       769 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 327         889 return $string;
211             }
212              
213             sub _read_nested {
214 38     38   47 my $self = shift;
215              
216 38         69 my ( $line, $indent ) = $self->_peek;
217              
218 38 100       188 if ( $line =~ /^ -/x ) {
    50          
219 10         36 return $self->_read_array( $indent );
220             }
221             elsif ( $line =~ $IS_HASH_KEY ) {
222 28         84 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   45 my ( $self, $limit ) = @_;
232              
233 28         48 my $ar = [];
234              
235 28         39 while ( 1 ) {
236 104         238 my ( $line, $indent ) = $self->_peek;
237 104 100 66     922 last if $indent < $limit || !defined $line || $line =~ $IS_END_YAML;
      100        
238              
239 77 50       168 if ( $indent > $limit ) {
240 0         0 croak "Array line over-indented";
241             }
242              
243 77 100       475 if ( $line =~ /^ (- \s+) \S+ \s* : (?: \s+ | $ ) /x ) {
    100          
    50          
    0          
244 5         12 $indent += length $1;
245 5         19 $line =~ s/-\s+//;
246 5         18 push @$ar, $self->_read_hash( $line, $indent );
247             }
248             elsif ( $line =~ /^ - \s* (.+?) \s* $/x ) {
249 57 100       224 croak "Unexpected start of YAML" if $line =~ /^---/;
250 56         113 $self->_next;
251 56         135 push @$ar, $self->_read_scalar( $1 );
252             }
253             elsif ( $line =~ /^ - \s* $/x ) {
254 15         34 $self->_next;
255 15         43 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         120 return $ar;
267             }
268              
269             sub _read_hash {
270 50     50   75 my ( $self, $line, $limit ) = @_;
271              
272 50         62 my $indent;
273 50         88 my $hash = {};
274              
275 50         61 while ( 1 ) {
276 169 50       1572 croak "Badly formed hash line: '$line'"
277             unless $line =~ $HASH_LINE;
278              
279 169         359 my ( $key, $value ) = ( $self->_read_scalar( $1 ), $2 );
280 169         332 $self->_next;
281              
282 169 100       810 if ( defined $value ) {
283 146         290 $hash->{$key} = $self->_read_scalar( $value );
284             }
285             else {
286 23         61 $hash->{$key} = $self->_read_nested;
287             }
288              
289 169         355 ( $line, $indent ) = $self->_peek;
290 169 100 66     1289 last if $indent < $limit || !defined $line || $line =~ $IS_END_YAML;
      100        
291             }
292              
293 50         150 return $hash;
294             }
295              
296             1;
297              
298             __END__
299              
300             =head1 NAME
301              
302             Data::YAML::Reader - Parse YAML created by Data::YAML::Writer
303              
304             =head1 VERSION
305              
306             This document describes Data::YAML::Reader version 0.0.6
307              
308             =head1 SYNOPSIS
309              
310             use Data::YAML::Reader;
311              
312             my $yr = Data::YAML::Reader->new;
313            
314             # Read from an array...
315             my $from_array = $yr->read( \@some_array );
316            
317             # ...an open file handle...
318             my $from_handle = $yr->read( $some_file );
319            
320             # ...a string containing YAML...
321             my $from_string = $yr->read( $some_string );
322            
323             # ...or a closure
324             my $from_code = $yr->read( sub { return get_next_line() } );
325              
326             =head1 DESCRIPTION
327              
328             In the spirit of L<YAML::Tiny> this is a lightweight, dependency-free
329             YAML reader. While C<YAML::Tiny> is designed principally for working
330             with configuration files C<Data::YAML> concentrates on the transparent
331             round-tripping of YAML serialized Perl data structures.
332              
333             The syntax accepted by C<Data::YAML::Reader> is a subset of YAML.
334             Specifically it is the same subset of YAML that L<Data::YAML::Writer>
335             produces. See L<Data::YAML> for more information.
336              
337             =head1 INTERFACE
338              
339             =over
340              
341             =item C<< new >>
342              
343             Creates and returns an empty C<Data::YAML::Reader> object. No options may be passed.
344              
345             =item C<< read( $source ) >>
346              
347             Read YAML and return the data structure it represents. The YAML data may be supplied by a
348              
349             =over
350              
351             =item * scalar string containing YAML source
352              
353             =item * the handle of an open file
354              
355             =item * a reference to an array of lines
356              
357             =item * a code reference
358              
359             =back
360              
361             In the case of a code reference a subroutine (most likely a closure)
362             that returns successive lines of YAML must be supplied. Lines should
363             have no trailing newline. When the YAML is exhausted the subroutine must
364             return undef.
365              
366             Returns the data structure (specifically either a scalar, hash ref or
367             array ref) that results from decoding the YAML.
368              
369             =item C<< get_raw >>
370              
371             Return the raw YAML source from the most recent C<read>.
372              
373             =back
374              
375             =head1 BUGS AND LIMITATIONS
376              
377             No bugs have been reported.
378              
379             Please report any bugs or feature requests to
380             C<data-yaml@rt.cpan.org>, or through the web interface at
381             L<http://rt.cpan.org>.
382              
383             =head1 SEE ALSO
384              
385             L<YAML::Tiny>, L<YAML>, L<YAML::Syck>, L<Config::Tiny>, L<CSS::Tiny>
386              
387             =head1 AUTHOR
388              
389             Andy Armstrong C<< <andy@hexten.net> >>
390              
391             Adam Kennedy wrote L<YAML::Tiny> which provided the template and many of
392             the YAML matching regular expressions for this module.
393              
394             =head1 LICENCE AND COPYRIGHT
395              
396             Copyright (c) 2007, Andy Armstrong C<< <andy@hexten.net> >>. All rights reserved.
397              
398             This module is free software; you can redistribute it and/or
399             modify it under the same terms as Perl itself. See L<perlartistic>.
400              
401             =head1 DISCLAIMER OF WARRANTY
402              
403             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
404             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
405             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
406             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
407             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
408             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
409             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
410             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
411             NECESSARY SERVICING, REPAIR, OR CORRECTION.
412              
413             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
414             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
415             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
416             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
417             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
418             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
419             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
420             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
421             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
422             SUCH DAMAGES.