File Coverage

blib/lib/Text/KDL/XS/Parser.pm
Criterion Covered Total %
statement 39 42 92.8
branch 17 26 65.3
condition 7 13 53.8
subroutine 9 9 100.0
pod 0 2 0.0
total 72 92 78.2


line stmt bran cond sub pod time code
1             package Text::KDL::XS::Parser;
2              
3 7     7   48 use strict;
  7         17  
  7         236  
4 7     7   29 use warnings;
  7         9  
  7         345  
5              
6 7     7   40 use Scalar::Util ();
  7         11  
  7         142  
7 7     7   46 use Carp ();
  7         9  
  7         4165  
8              
9             # Constructor:
10             # Text::KDL::XS::Parser->new($source, %opts)
11             # $source : string | filehandle | coderef returning chunks
12             # %opts : version => 'detect'|'1'|'2' (default: detect)
13             # emit_comments => 0|1 (default: 0)
14             sub new {
15 20     20 0 46 my ($class, $source, %opts) = @_;
16              
17 20 100       197 Carp::croak("Text::KDL::XS::Parser: source is required")
18             unless defined $source;
19              
20 19         62 my $opts_int = _build_opt_flags(\%opts);
21              
22 19 100       52 if (my $reftype = ref $source) {
23 2 100       31 return $class->_new_stream_parser($source, $opts_int)
24             if $reftype eq 'CODE';
25              
26 1         4 my $reader = _make_io_reader($source);
27 1 50       7 return $class->_new_stream_parser($reader, $opts_int)
28             if $reader;
29              
30 0         0 Carp::croak("Text::KDL::XS::Parser: unsupported source ref type '$reftype'");
31             }
32              
33 17         139 return $class->_new_string_parser($source, $opts_int);
34             }
35              
36             # Returns the next event hashref, or undef at EOF. Dies on parse error.
37             sub next_event {
38 195     195 0 263 my ($self) = @_;
39 195         1117 return $self->_next_event;
40             }
41              
42             # --- internals -------------------------------------------------------------
43              
44             sub _build_opt_flags {
45 19     19   34 my ($opts) = @_;
46              
47 19   100     84 my $version = lc($opts->{version} // 'detect');
48 19 50       119 my $flags
    50          
    100          
49             = $version eq 'detect' ? Text::KDL::XS::_OPT_DETECT()
50             : $version eq '1' ? Text::KDL::XS::_OPT_V1()
51             : $version eq '2' ? Text::KDL::XS::_OPT_V2()
52             : Carp::croak("unknown version '$version' (expected 'detect', '1', or '2')");
53              
54 19 50       49 $flags |= Text::KDL::XS::_OPT_EMIT_COMMENTS() if $opts->{emit_comments};
55 19         33 return $flags;
56             }
57              
58             # Wrap a filehandle / IO object as a Perl sub the XS layer can call.
59             sub _make_io_reader {
60 1     1   2 my ($source) = @_;
61              
62 1   50     4 my $reftype = Scalar::Util::reftype($source) // '';
63 1 50 33     4 return undef unless $reftype eq 'GLOB' || Scalar::Util::blessed($source);
64              
65             # Duck-type: must support sysread or read.
66 1   33     5 my $can_sysread = $reftype eq 'GLOB' || (Scalar::Util::blessed($source)
67             && ($source->can('sysread') || $source->can('read')));
68 1 50       3 return undef unless $can_sysread;
69              
70             return sub {
71 2     2   3 my ($want) = @_;
72 2         4 my $buf = '';
73 2         1 my $n;
74 2 50       5 if ($reftype eq 'GLOB') {
    0          
75 2         13 $n = sysread($source, $buf, $want);
76             }
77             elsif ($source->can('sysread')) {
78 0         0 $n = $source->sysread($buf, $want);
79             }
80             else {
81 0         0 $n = $source->read($buf, $want);
82             }
83 2 100 66     16 return defined $n && $n > 0 ? $buf : '';
84 1         5 };
85             }
86              
87             1;
88              
89             __END__