File Coverage

blib/lib/Config/IOD/Reader.pm
Criterion Covered Total %
statement 106 114 92.9
branch 60 70 85.7
condition 15 20 75.0
subroutine 7 7 100.0
pod n/a
total 188 211 89.1


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