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   61303 use 5.010001;
  3         24  
4 3     3   16 use strict;
  3         6  
  3         123  
5 3     3   16 use warnings;
  3         4  
  3         99  
6              
7 3     3   1058 use parent qw(Config::IOD::Base);
  3         912  
  3         14  
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.344'; # VERSION
13              
14             sub _merge {
15 6     6   12 my ($self, $section) = @_;
16              
17 6         10 my $res = $self->{_res};
18 6         10 for my $msect (@{ $self->{_merge} }) {
  6         23  
19 8 100       16 if ($msect eq $section) {
20             # ignore merging self
21 1         3 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         10 for my $k (keys %{ $res->{$msect} }) {
  7         21  
32 14   100     50 $res->{$section}{$k} //= $res->{$msect}{$k};
33             }
34             }
35             }
36              
37             sub _init_read {
38 64     64   111 my $self = shift;
39              
40 64         225 $self->SUPER::_init_read;
41 64         294 $self->{_res} = {};
42 64         158 $self->{_merge} = undef;
43 64         143 $self->{_num_seen_section_lines} = 0;
44 64         132 $self->{_cur_section} = $self->{default_section};
45 64         167 $self->{_arrayified} = {};
46             }
47              
48             sub _read_string {
49 68     68   178 my ($self, $str, $cb) = @_;
50              
51 68         118 my $res = $self->{_res};
52 68         143 my $cur_section = $self->{_cur_section};
53              
54             my $directive_re = $self->{allow_bang_only} ?
55 68 100       377 qr/^;?\s*!\s*(\w+)\s*/ :
56             qr/^;\s*!\s*(\w+)\s*/;
57              
58 68         111 my $_raw_val; # only to provide to callback
59              
60 68         403 my @lines = split /^/, $str;
61 68         211 local $self->{_linum} = 0;
62             LINE:
63 68         142 for my $line (@lines) {
64 699         815 $self->{_linum}++;
65              
66             # blank line
67 699 100       1481 if ($line !~ /\S/) {
68 181         215 next LINE;
69             }
70              
71             # directive line
72 518 100 100     2794 if ($self->{enable_directive} && $line =~ s/$directive_re//) {
73 25         68 my $directive = $1;
74 25 100       66 if ($self->{allow_directives}) {
75             $self->_err("Directive '$directive' is not in ".
76             "allow_directives list")
77 3         38 unless grep { $_ eq $directive }
78 3 100       4 @{$self->{allow_directives}};
  3         6  
79             }
80 24 100       61 if ($self->{disallow_directives}) {
81             $self->_err("Directive '$directive' is in ".
82             "disallow_directives list")
83 3         17 if grep { $_ eq $directive }
84 3 100       4 @{$self->{disallow_directives}};
  3         27  
85             }
86 22         82 my $args = $self->_parse_command_line($line);
87 22 100       68 if (!defined($args)) {
88 1         22 $self->_err("Invalid arguments syntax '$line'");
89             }
90              
91 21 50       38 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       77 if ($directive eq 'include') {
    100          
    100          
101 7         10 my $path;
102 7 100       20 if (! @$args) {
    50          
103 1         9 $self->_err("Missing filename to include");
104             } elsif (@$args > 1) {
105 0         0 $self->_err("Extraneous arguments");
106             } else {
107 6         12 $path = $args->[0];
108             }
109 6         16 my $res = $self->_push_include_stack($path);
110 6 100       17 if ($res->[0] != 200) {
111 1         8 $self->_err("Can't include '$path': $res->[1]");
112             }
113 5         8 $path = $res->[2];
114 5         14 $self->_read_string($self->_read_file($path, $cb), $cb);
115 4         13 $self->_pop_include_stack;
116             } elsif ($directive eq 'merge') {
117 6 100       20 $self->{_merge} = @$args ? $args : undef;
118             } elsif ($directive eq 'noop') {
119             } else {
120 3 100       9 if ($self->{ignore_unknown_directive}) {
121             # assume a regular comment
122 1         3 next LINE;
123             } else {
124 2         10 $self->_err("Unknown directive '$directive'");
125             }
126             }
127 15         33 next LINE;
128             }
129              
130             # comment line
131 493 100       1156 if ($line =~ /^\s*[;#]/) {
132              
133 64 50       104 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         97 next LINE;
141             }
142              
143             # section line
144 429 100       940 if ($line =~ /^\s*\[\s*(.+?)\s*\](?: \s*[;#].*)?/) {
145 72         172 my $prev_section = $self->{_cur_section};
146 72         167 $self->{_cur_section} = $cur_section = $1;
147 72   100     363 $res->{$cur_section} //= {};
148 72         96 $self->{_num_seen_section_lines}++;
149              
150             # previous section exists? do merging for previous section
151 72 100 66     184 if ($self->{_merge} && $self->{_num_seen_section_lines} > 1) {
152 4         13 $self->_merge($prev_section);
153             }
154              
155 72 50       118 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         169 next LINE;
164             }
165              
166             # key line
167 357 100       1234 if ($line =~ /^\s*([^=]+?)\s*=(\s*)(.*)/) {
168 352         603 my $key = $1;
169 352         434 my $space = $2;
170 352         516 my $val = $3;
171              
172 352 0 33     604 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       649 if ($val =~ /\A["!\\[\{~]/) {
180 66 50       107 $_raw_val = $val if $cb;
181 66         187 my ($err, $parse_res, $decoded_val) = $self->_parse_raw_value($val);
182 66 100       158 $self->_err("Invalid value: " . $err) if $err;
183 58         88 $val = $decoded_val;
184             } else {
185 286 50       442 $_raw_val = $val if $cb;
186 286         507 $val =~ s/\s*[#;].*//; # strip comment
187             }
188              
189 344 100       678 if (exists $res->{$cur_section}{$key}) {
190 27 100       83 if (!$self->{allow_duplicate_key}) {
    100          
191 1         5 $self->_err("Duplicate key: $key (section $cur_section)");
192             } elsif ($self->{_arrayified}{$cur_section}{$key}++) {
193 15         20 push @{ $res->{$cur_section}{$key} }, $val;
  15         43  
194             } else {
195             $res->{$cur_section}{$key} = [
196 11         33 $res->{$cur_section}{$key}, $val];
197             }
198             } else {
199 317         694 $res->{$cur_section}{$key} = $val;
200             }
201              
202 343 50       522 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         536 next LINE;
213             }
214              
215 5         28 $self->_err("Invalid syntax");
216             }
217              
218 45 100 100     146 if ($self->{_merge} && $self->{_num_seen_section_lines} > 1) {
219 2         6 $self->_merge($cur_section);
220             }
221              
222 45         249 $res;
223             }
224              
225             1;
226             # ABSTRACT: Read IOD/INI configuration files
227              
228             __END__