File Coverage

blib/lib/Config/IOD/Reader.pm
Criterion Covered Total %
statement 104 111 93.6
branch 60 68 88.2
condition 13 14 92.8
subroutine 7 7 100.0
pod n/a
total 184 200 92.0


line stmt bran cond sub pod time code
1             package Config::IOD::Reader;
2              
3             our $DATE = '2021-06-23'; # DATE
4             our $VERSION = '0.343'; # VERSION
5              
6 3     3   57104 use 5.010001;
  3         20  
7 3     3   13 use strict;
  3         6  
  3         61  
8 3     3   11 use warnings;
  3         5  
  3         88  
9              
10 3     3   1139 use parent qw(Config::IOD::Base);
  3         798  
  3         15  
11              
12             sub _merge {
13 6     6   12 my ($self, $section) = @_;
14              
15 6         9 my $res = $self->{_res};
16 6         7 for my $msect (@{ $self->{_merge} }) {
  6         12  
17 8 100       15 if ($msect eq $section) {
18             # ignore merging self
19 1         2 next;
20             #local $self->{_linum} = $self->{_linum}-1;
21             #$self->_err("Can't merge section '$msect' to '$section': ".
22             # "Same section");
23             }
24 7 50       16 if (!exists($res->{$msect})) {
25 0         0 local $self->{_linum} = $self->{_linum}-1;
26 0         0 $self->_err("Can't merge section '$msect' to '$section': ".
27             "Section '$msect' not seen yet");
28             }
29 7         8 for my $k (keys %{ $res->{$msect} }) {
  7         19  
30 14   100     56 $res->{$section}{$k} //= $res->{$msect}{$k};
31             }
32             }
33             }
34              
35             sub _init_read {
36 64     64   89 my $self = shift;
37              
38 64         184 $self->SUPER::_init_read;
39 64         219 $self->{_res} = {};
40 64         120 $self->{_merge} = undef;
41 64         107 $self->{_num_seen_section_lines} = 0;
42 64         98 $self->{_cur_section} = $self->{default_section};
43 64         133 $self->{_arrayified} = {};
44             }
45              
46             sub _read_string {
47 68     68   139 my ($self, $str, $cb) = @_;
48              
49 68         103 my $res = $self->{_res};
50 68         106 my $cur_section = $self->{_cur_section};
51              
52             my $directive_re = $self->{allow_bang_only} ?
53 68 100       293 qr/^;?\s*!\s*(\w+)\s*/ :
54             qr/^;\s*!\s*(\w+)\s*/;
55              
56 68         93 my $_raw_val; # only to provide to callback
57              
58 68         319 my @lines = split /^/, $str;
59 68         136 local $self->{_linum} = 0;
60             LINE:
61 68         131 for my $line (@lines) {
62 699         779 $self->{_linum}++;
63              
64             # blank line
65 699 100       1472 if ($line !~ /\S/) {
66 181         216 next LINE;
67             }
68              
69             # directive line
70 518 100 100     2618 if ($self->{enable_directive} && $line =~ s/$directive_re//) {
71 25         61 my $directive = $1;
72 25 100       55 if ($self->{allow_directives}) {
73             $self->_err("Directive '$directive' is not in ".
74             "allow_directives list")
75 3         16 unless grep { $_ eq $directive }
76 3 100       4 @{$self->{allow_directives}};
  3         7  
77             }
78 24 100       42 if ($self->{disallow_directives}) {
79             $self->_err("Directive '$directive' is in ".
80             "disallow_directives list")
81 3         16 if grep { $_ eq $directive }
82 3 100       4 @{$self->{disallow_directives}};
  3         5  
83             }
84 22         81 my $args = $self->_parse_command_line($line);
85 22 100       44 if (!defined($args)) {
86 1         20 $self->_err("Invalid arguments syntax '$line'");
87             }
88              
89 21 50       33 if ($cb) {
90             $cb->(
91             event => 'directive',
92             linum=>$self->{_linum}, line=>$line, cur_section=>$self->{_cur_section},
93 0         0 directive => $directive,
94             args => $args,
95             );
96             }
97              
98 21 100       58 if ($directive eq 'include') {
    100          
    100          
99 7         8 my $path;
100 7 100       17 if (! @$args) {
    50          
101 1         17 $self->_err("Missing filename to include");
102             } elsif (@$args > 1) {
103 0         0 $self->_err("Extraneous arguments");
104             } else {
105 6         12 $path = $args->[0];
106             }
107 6         13 my $res = $self->_push_include_stack($path);
108 6 100       26 if ($res->[0] != 200) {
109 1         6 $self->_err("Can't include '$path': $res->[1]");
110             }
111 5         9 $path = $res->[2];
112 5         44 $self->_read_string($self->_read_file($path, $cb), $cb);
113 4         11 $self->_pop_include_stack;
114             } elsif ($directive eq 'merge') {
115 6 100       19 $self->{_merge} = @$args ? $args : undef;
116             } elsif ($directive eq 'noop') {
117             } else {
118 3 100       10 if ($self->{ignore_unknown_directive}) {
119             # assume a regular comment
120 1         3 next LINE;
121             } else {
122 2         8 $self->_err("Unknown directive '$directive'");
123             }
124             }
125 15         43 next LINE;
126             }
127              
128             # comment line
129 493 100       1118 if ($line =~ /^\s*[;#]/) {
130              
131 64 50       111 if ($cb) {
132             $cb->(
133             event => 'comment',
134             linum=>$self->{_linum}, line=>$line, cur_section=>$self->{_cur_section},
135 0         0 );
136             }
137              
138 64         89 next LINE;
139             }
140              
141             # section line
142 429 100       859 if ($line =~ /^\s*\[\s*(.+?)\s*\](?: \s*[;#].*)?/) {
143 72         102 my $prev_section = $self->{_cur_section};
144 72         175 $self->{_cur_section} = $cur_section = $1;
145 72   100     336 $res->{$cur_section} //= {};
146 72         100 $self->{_num_seen_section_lines}++;
147              
148             # previous section exists? do merging for previous section
149 72 100 66     155 if ($self->{_merge} && $self->{_num_seen_section_lines} > 1) {
150 4         11 $self->_merge($prev_section);
151             }
152              
153 72 50       113 if ($cb) {
154             $cb->(
155             event => 'section',
156             linum=>$self->{_linum}, line=>$line, cur_section=>$self->{_cur_section},
157 0         0 section => $cur_section,
158             );
159             }
160              
161 72         130 next LINE;
162             }
163              
164             # key line
165 357 100       1170 if ($line =~ /^\s*([^=]+?)\s*=\s*(.*)/) {
166 352         568 my $key = $1;
167 352         487 my $val = $2;
168              
169             # the common case is that value are not decoded or
170             # quoted/bracketed/braced, so we avoid calling _parse_raw_value here
171             # to avoid overhead
172 352 100       570 if ($val =~ /\A["!\\[\{~]/) {
173 66 50       131 $_raw_val = $val if $cb;
174 66         179 my ($err, $parse_res, $decoded_val) = $self->_parse_raw_value($val);
175 66 100       149 $self->_err("Invalid value: " . $err) if $err;
176 58         81 $val = $decoded_val;
177             } else {
178 286 50       410 $_raw_val = $val if $cb;
179 286         530 $val =~ s/\s*[#;].*//; # strip comment
180             }
181              
182 344 100       678 if (exists $res->{$cur_section}{$key}) {
183 27 100       85 if (!$self->{allow_duplicate_key}) {
    100          
184 1         6 $self->_err("Duplicate key: $key (section $cur_section)");
185             } elsif ($self->{_arrayified}{$cur_section}{$key}++) {
186 15         18 push @{ $res->{$cur_section}{$key} }, $val;
  15         38  
187             } else {
188             $res->{$cur_section}{$key} = [
189 11         33 $res->{$cur_section}{$key}, $val];
190             }
191             } else {
192 317         623 $res->{$cur_section}{$key} = $val;
193             }
194              
195 343 50       478 if ($cb) {
196             $cb->(
197             event => 'key',
198             linum=>$self->{_linum}, line=>$line, cur_section=>$self->{_cur_section},
199 0         0 key => $key,
200             val => $val,
201             raw_val => $_raw_val,
202             );
203             }
204              
205 343         497 next LINE;
206             }
207              
208 5         15 $self->_err("Invalid syntax");
209             }
210              
211 45 100 100     150 if ($self->{_merge} && $self->{_num_seen_section_lines} > 1) {
212 2         5 $self->_merge($cur_section);
213             }
214              
215 45         239 $res;
216             }
217              
218             1;
219             # ABSTRACT: Read IOD/INI configuration files
220              
221             __END__