File Coverage

blib/lib/Config/IOD/Document.pm
Criterion Covered Total %
statement 405 454 89.2
branch 224 274 81.7
condition 41 63 65.0
subroutine 33 40 82.5
pod 17 17 100.0
total 720 848 84.9


line stmt bran cond sub pod time code
1             package Config::IOD::Document;
2              
3 15     15   307 use 5.010;
  15         45  
4 15     15   70 use strict;
  15         20  
  15         290  
5 15     15   60 use warnings;
  15         23  
  15         468  
6             #use Carp; # avoided to shave a bit of startup time
7              
8 15     15   6528 use Config::IOD::Constants qw(:ALL);
  15         31  
  15         66668  
9              
10             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
11             our $DATE = '2022-05-02'; # DATE
12             our $DIST = 'Config-IOD'; # DIST
13             our $VERSION = '0.353'; # VERSION
14              
15             sub new {
16 107     107 1 350 my ($class, %attrs) = @_;
17              
18 107 50       312 if (!$attrs{_parsed}) {
19 0         0 $attrs{_parsed} = [];
20             }
21 107 50       229 if (!$attrs{_parser}) {
22 0         0 require Config::IOD;
23 0         0 $attrs{_parser} = Config::IOD->new;
24             }
25              
26 107         517 bless \%attrs, $class;
27             }
28              
29             sub empty {
30 1     1 1 6 my $self = shift;
31 1         3 $self->_discard_cache;
32 1         11 $self->{_parsed} = [];
33             }
34              
35             # all _validate_*() methods return ($err_msg, $validated_val)
36              
37             sub _validate_section {
38 46     46   84 my ($self, $name) = @_;
39 46         121 $name =~ s/\A\s+//;
40 46         102 $name =~ s/\s+\z//;
41 46 100       111 if (!length($name)) { return ("Section name must be non-zero string") }
  1         3  
42 45 100       146 if ($name =~ /\R|\]/) { return ("Section name must not contain ] or newline") }
  2         15  
43 43         115 return ("", $name);
44             }
45              
46             sub _validate_key {
47 25     25   46 my ($self, $name) = @_;
48 25         55 $name =~ s/\A\s+//;
49 25         40 $name =~ s/\s+\z//;
50 25 100       48 if (!length($name)) { return ("Key name must be non-zero string") }
  1         3  
51 24 100       68 if ($name =~ /\R|=/) { return ("Key name must not contain = or newline") }
  2         5  
52 22 100       63 if ($name =~ /\A(?:;|#|\[)/) { return ("Key name must not start with ;, #, [") }
  3         8  
53 19         48 return ("", $name);
54             }
55              
56             sub _validate_value {
57 17     17   33 my ($self, $value) = @_;
58 17         35 $value =~ s/\s+\z//;
59 17 100       46 if ($value =~ /\R/) { return ("Value must not contain newline") }
  2         5  
60 15         37 return ("", $value);
61             }
62              
63             sub _validate_comment {
64 2     2   5 my ($self, $comment) = @_;
65 2 100       7 if ($comment =~ /\R/) { return ("Comment must not contain newline") }
  1         2  
66 1         3 return ("", $comment);
67             }
68              
69             sub _validate_linum {
70 5     5   10 my ($self, $value) = @_;
71 5 100       12 if ($value < 1) { return ("linum must be at least 1") }
  1         2  
72 4 100       5 if ($value > @{$self->{_parsed}}) { return ("linum must not be larger than number of document's lines") }
  4         10  
  1         3  
73 3         8 return ("", $value);
74             }
75              
76             sub _blank_line {
77 0     0   0 ["B", "\n"];
78             }
79              
80             # cache is used for get_value() and get_raw_value() to avoid re-scanning the
81             # files on every invocation. but whenever one of document-modifying methods is
82             # called, we discard the cache
83             sub _discard_cache {
84 25     25   35 my $self = shift;
85 25         64 delete $self->{_dump_cache};
86             }
87              
88             sub dump {
89 21     21 1 37 my $self = shift;
90 21         25 my $opts;
91 21 50       47 if (ref($_[0]) eq 'HASH') {
92 0         0 $opts = shift;
93             } else {
94 21         28 $opts = {};
95             }
96              
97 21         77 my $parser = $self->{_parser};
98              
99 21         27 my $linum = 0;
100 21         29 my $merge;
101 21         34 my $cur_section = $parser->{default_section};
102 21         25 my $res = {};
103 21         27 my $arrayified = {};
104 21         23 my $num_seen_section_lines = 0;
105              
106             my $_merge = sub {
107 2 50   2   6 return if $cur_section eq $merge;
108             die "IOD document:$linum: Can't merge section '$merge' to ".
109             "'$cur_section': Section '$merge' not seen yet"
110 2 50       5 unless exists $res->{$merge};
111 2         3 for my $k (keys %{ $res->{$merge} }) {
  2         6  
112 4   66     14 $res->{$cur_section}{$k} //= $res->{$merge}{$k};
113             }
114 21         84 };
115              
116             # TMP HACK. for _decode_expr, this is currently rather hackish because
117             # Config::IOD::Base expects some state in $parser
118 21 100       48 local $parser->{_res} = $res if $parser->{enable_expr};
119 21 100       41 local $parser->{_cur_section} = $cur_section if $parser->{enable_expr};
120              
121 21         26 for my $line (@{ $self->{_parsed} }) {
  21         45  
122 61         72 $linum++;
123 61 50 33     106 next if defined($opts->{linum_start}) && $linum < $opts->{linum_start};
124 61 50 33     108 next if defined($opts->{linum_end} ) && $linum > $opts->{linum_end};
125              
126 61         78 my $type = $line->[COL_TYPE];
127 61 100       147 if ($type eq 'D') {
    100          
    100          
128 7         13 my $directive = $line->[COL_D_DIRECTIVE];
129 7 100       16 if ($directive eq 'merge') {
130 4         15 my $args = $parser->_parse_command_line(
131             $line->[COL_D_ARGS_RAW]);
132 4 50       143 if (!defined($args)) {
133 0         0 die "IOD document:$linum: Invalid arguments syntax '".
134             $line->[COL_D_ARGS_RAW]."'";
135             }
136 4 100       11 $merge = @$args ? $args->[0] : undef;
137             } # ignore the other directives
138             } elsif ($type eq 'S') {
139 9         18 $num_seen_section_lines++;
140             # merge previous section
141 9 100 66     29 $_merge->() if defined($merge) && $num_seen_section_lines > 1;
142 9         20 $cur_section = $line->[COL_S_SECTION];
143 9 100       23 $parser->{_cur_section} = $cur_section if $parser->{enable_expr}; #TMP HACK
144 9   50     43 $res->{$cur_section} //= {};
145             } elsif ($type eq 'K') {
146             # the common case is that value are not decoded or
147             # quoted/bracketed/braced, so we avoid calling _parse_raw_value here
148             # to avoid overhead
149 37         52 my $key = $line->[COL_K_KEY];
150 37         50 my $val = $line->[COL_K_VALUE_RAW];
151 37 100       95 if ($val =~ /\A["!\\[\{]/) {
152 24         85 my ($err, $parse_res, $decoded_val) =
153             $parser->_parse_raw_value($val);
154 24 100       4332 die "IOD document:$linum: Invalid value: $err" if $err;
155 18         31 $val = $decoded_val;
156             } else {
157 13         23 $val =~ s/\s*[#;].*//; # strip comment
158             }
159              
160 31 50       68 if (exists $res->{$cur_section}{$key}) {
161 0 0       0 if (!$parser->{allow_duplicate_key}) {
    0          
162 0         0 die "IOD document:$linum: Duplicate key: $key ".
163             "(section $cur_section)";
164             } elsif ($arrayified->{$cur_section}{$key}++) {
165 0         0 push @{ $res->{$cur_section}{$key} }, $val;
  0         0  
166             } else {
167             $res->{$cur_section}{$key} = [
168 0         0 $res->{$cur_section}{$key}, $val];
169             }
170             } else {
171 31         72 $res->{$cur_section}{$key} = $val;
172             }
173             } # ignore the other line types
174             }
175              
176 15 50 33     39 $_merge->() if defined($merge) && $num_seen_section_lines > 1;;
177              
178 15         112 $res;
179             }
180              
181             sub each_key {
182 7     7 1 1421 my $self = shift;
183 7         11 my $opts;
184 7 100       15 if (ref($_[0]) eq 'HASH') {
185 1         2 $opts = shift;
186             } else {
187 6         19 $opts = {};
188             }
189 7         14 my ($code) = @_;
190              
191 7         81 my $parser = $self->{_parser};
192              
193 7         12 my $linum = 0;
194 7         12 my $cur_section = $parser->{default_section};
195              
196 7         16 my $skip_section;
197             my %seen_sections;
198 7         0 my %seen_keys;
199 7         9 for my $line (@{ $self->{_parsed} }) {
  7         17  
200 31         37 $linum++;
201 31 50 33     54 next if defined($opts->{linum_start}) && $linum < $opts->{linum_start};
202 31 50 33     48 next if defined($opts->{linum_end} ) && $linum > $opts->{linum_end};
203              
204 31         40 my $type = $line->[COL_TYPE];
205 31 100       56 if ($type eq 'S') {
    50          
206 15         22 $cur_section = $line->[COL_S_SECTION];
207 15         19 %seen_keys = ();
208             $skip_section = $opts->{unique_section} &&
209 15   33     31 $seen_sections{$cur_section}++;
210             } elsif ($type eq 'K') {
211 16 50       28 next if $skip_section;
212 16         22 my $key = $line->[COL_K_KEY];
213 16 50 33     28 next if $opts->{unique_key} && $seen_keys{$key}++;
214 16         33 my $res = $code->(
215             $self,
216             linum => $linum,
217             section => $cur_section,
218             key => $key,
219             raw_value => $line->[COL_K_VALUE_RAW],
220             );
221 16 100 100     95 return if $opts->{early_exit} && !$res;
222             }
223             }
224             }
225              
226             sub get_value {
227 13     13 1 371 my ($self, $section, $key) = @_;
228 13 100       92 $self->{_dump_cache} = $self->dump unless $self->{_dump_cache};
229 13         69 $self->{_dump_cache}{$section}{$key};
230             }
231              
232             sub get_directive_before_key {
233 0     0 1 0 my ($self, $section, $key) = @_;
234              
235 0         0 my $found;
236             $self->each_key(
237             sub {
238 0     0   0 my ($self, %args) = @_;
239 0 0       0 return if $found;
240 0 0       0 return unless $args{linum} > 1;
241 0 0       0 return unless $args{section} eq $section;
242 0 0       0 return unless $args{key} eq $key;
243 0         0 my $l = $self->{_parsed}[ $args{linum}-1-1 ];
244 0 0       0 return unless $l->[COL_TYPE] eq 'D';
245 0         0 my $p = $self->{_parser};
246             $found = [
247             $l->[COL_D_DIRECTIVE],
248 0   0     0 @{ $p->_parse_command_line($l->[COL_D_ARGS_RAW]) // [] },
  0         0  
249             ];
250             },
251 0         0 );
252 0         0 $found;
253             }
254              
255             sub list_keys {
256 2     2 1 8 my $self = shift;
257 2         3 my $opts;
258 2 100       7 if (ref($_[0]) eq 'HASH') {
259 1         2 $opts = shift;
260             } else {
261 1         2 $opts = {};
262             }
263 2         4 my ($section) = @_;
264              
265 2         3 my @res;
266             my %mem;
267             $self->each_key(
268             sub {
269 8     8   23 my ($self, %args) = @_;
270 8 50       17 return unless $args{section} eq $section;
271 8 100 100     23 return if $opts->{unique} && $mem{$args{key}}++;
272 7         17 push @res, $args{key};
273             },
274 2         12 );
275 2         22 @res;
276             }
277              
278             sub key_exists {
279 0     0 1 0 my $self = shift;
280 0         0 my ($section, $key) = @_;
281              
282 0         0 my $found;
283             $self->each_key(
284             {early_exit=>1},
285             sub {
286 0     0   0 my ($self, %args) = @_;
287 0 0       0 return 1 unless $args{section} eq $section;
288 0 0       0 return 1 unless $args{key} eq $key;
289 0         0 $found++;
290 0         0 return 0;
291             },
292 0         0 );
293 0         0 $found;
294             }
295              
296             sub _find_section {
297 18     18   22 my $self = shift;
298 18         23 my $opts;
299 18 100       39 if (ref($_[0]) eq 'HASH') {
300 4         5 $opts = shift;
301             } else {
302 14         21 $opts = {};
303             }
304 18         28 my ($name) = @_;
305              
306 18         20 my @res;
307              
308 18         21 my $linum = 0;
309 18         22 for my $line (@{ $self->{_parsed} }) {
  18         36  
310 45         49 $linum++;
311 45 100       78 next unless $line->[COL_TYPE] eq 'S';
312 24 100       43 if (defined $name) {
313 6 100       29 next unless $line->[COL_S_SECTION] eq $name;
314             }
315 20 100       36 return $linum unless $opts->{all};
316 16         21 push @res, $linum;
317             }
318 14 100       46 return undef unless $opts->{all}; ## no critic: Subroutines::ProhibitExplicitReturnUndef
319 4         9 return @res;
320             }
321              
322             sub each_section {
323 4     4 1 1610 my $self = shift;
324 4         5 my $opts;
325 4 100       12 if (ref($_[0]) eq 'HASH') {
326 3         4 $opts = shift;
327             } else {
328 1         2 $opts = {};
329             }
330 4         7 my ($code) = @_;
331              
332 4         54 my $parsed = $self->{_parsed};
333 4         15 my @linums = $self->_find_section({all=>1});
334 4         9 my %seen;
335 4         59 for my $linum (@linums) {
336 14         26 my $section = $parsed->[$linum-1][COL_S_SECTION];
337 14 100 100     35 next if $opts->{unique} && $seen{$section}++;
338              
339 13         16 my $linum_end = $linum;
340 13         14 while (1) {
341 23 100       34 last if $linum_end >= @$parsed;
342 20 100       35 last if $parsed->[$linum_end][COL_TYPE] eq 'S';
343 10         10 $linum_end++;
344             }
345              
346 13         28 my $res = $code->(
347             $self,
348             linum => $linum,
349             linum_start => $linum,
350             linum_end => $linum_end,
351             parsed => $parsed->[$linum-1],
352             section => $section,
353             );
354 13 100 100     78 return if $opts->{early_exit} && !$res;
355             }
356             }
357              
358             sub list_sections {
359 2     2 1 7 my $self = shift;
360 2         3 my $opts;
361 2 100       5 if (ref($_[0]) eq 'HASH') {
362 1         1 $opts = shift;
363             } else {
364 1         1 $opts = {};
365             }
366              
367 2         4 my @res;
368             $self->each_section(
369             $opts,
370             sub {
371 7     7   16 my ($self, %args) = @_;
372 7         15 push @res, $args{section};
373             }
374 2         9 );
375 2         15 @res;
376             }
377              
378             sub section_exists {
379 0     0 1 0 my $self = shift;
380 0         0 my ($section) = @_;
381              
382 0         0 my $found;
383             $self->each_section(
384             {early_exit=>1},
385             sub {
386 0     0   0 my ($self, %args) = @_;
387 0 0       0 return 1 unless $args{section} eq $section;
388 0         0 $found++;
389 0         0 return 0;
390             },
391 0         0 );
392 0         0 $found;
393             }
394              
395             sub _get_section_line_range {
396 17     17   29 my $self = shift;
397 17         40 my $opts;
398 17 100       42 if (ref($_[0]) eq 'HASH') {
399 2         2 $opts = shift;
400             } else {
401 15         32 $opts = {};
402             }
403 17         31 my ($name) = @_;
404              
405 17         23 my @res;
406              
407 17         24 my $linum = 0;
408 17         39 my $cur_section = $self->{_parser}{default_section};
409 17         31 my $prev_section;
410             my $start;
411 17         23 for my $line (@{ $self->{_parsed} }) {
  17         44  
412 62         101 $linum++;
413 62 100       139 if ($line->[COL_TYPE] eq 'S') {
414 27         36 $cur_section = $line->[COL_S_SECTION];
415 27 100       61 if ($cur_section eq $name) {
416 17         26 $start = $linum+1;
417 17 100 100     46 $res[-1][1] = $linum if @res && !defined $res[-1][1];
418 17         39 push @res, [$start, undef];
419             } else {
420 10 100       20 $res[-1][1] = $linum if @res;
421 10 100 100     42 last if @res && !$opts->{all};
422             }
423             }
424             }
425 17 100 100     83 $res[-1][1] = $linum+1 if @res && !defined($res[-1][1]);
426              
427             L1:
428 17 100       38 if ($opts->{all}) { return @res } else { return $res[0] }
  2         7  
  15         44  
429             }
430              
431             sub _find_key {
432 16     16   22 my $self = shift;
433 16         33 my $opts;
434 16 100       32 if (ref($_[0]) eq 'HASH') {
435 3         6 $opts = shift;
436             } else {
437 13         20 $opts = {};
438             }
439 16         28 my ($section, $name) = @_;
440              
441 16         19 my @res;
442              
443 16         22 my $linum = 0;
444 16         25 my $cur_section = $self->{_parser}{default_section};
445 16         66 for my $line (@{ $self->{_parsed} }) {
  16         35  
446 62         65 $linum++;
447 62 100       113 if ($line->[COL_TYPE] eq 'S') {
448 23         37 $cur_section = $line->[COL_S_SECTION];
449 23         66 next;
450             }
451 39 100       69 next unless $line->[COL_TYPE] eq 'K';
452 38 100       62 next unless $cur_section eq $section;
453 26 100       46 next unless $line->[COL_K_KEY] eq $name;
454 11 100       27 return $linum unless $opts->{all};
455 7         11 push @res, $linum;
456             }
457 12 100       41 return undef unless $opts->{all}; ## no critic: Subroutines::ProhibitExplicitReturnUndef
458 3         7 return @res;
459             }
460              
461             sub _line_in_section {
462 2     2   4 my $self = shift;
463 2         3 my $opts;
464 2 50       5 if (ref($_[0]) eq 'HASH') {
465 0         0 $opts = shift;
466             } else {
467 2         3 $opts = {};
468             }
469 2         4 my ($asked_linum, $asked_section) = @_;
470              
471 2         15 my @res;
472              
473 2         4 my $linum = 0;
474 2         4 my $cur_section = $self->{_parser}{default_section};
475 2         3 for my $line (@{ $self->{_parsed} }) {
  2         5  
476 6         7 $linum++;
477 6 100       10 if ($linum == $asked_linum) {
478 2         25 return $asked_section eq $cur_section;
479             }
480 4 100       9 if ($line->[COL_TYPE] eq 'S') {
481 1         3 $cur_section = $line->[COL_S_SECTION];
482             }
483             }
484 0         0 return 0;
485             }
486              
487             sub insert_section {
488 16     16 1 93 my $self = shift;
489 16         18 my $opts;
490 16 100       34 if (ref($_[0]) eq 'HASH') {
491 8         10 $opts = shift;
492             } else {
493 8         11 $opts = {};
494             }
495              
496 16         30 my ($err, $name) = $self->_validate_section($_[0]);
497 16 100       80 die $err if $err;
498              
499 13         40 my $p = $self->{_parsed};
500              
501 13 100       24 if (defined $opts->{comment}) {
502 2         12 ($err, $opts->{comment}) = $self->_validate_comment($opts->{comment});
503 2 100       11 die $err if $err;
504             }
505              
506 12 100       25 if ($self->_find_section($name)) {
507 2 100       5 if ($opts->{ignore}) {
508 1         4 return undef; ## no critic: Subroutines::ProhibitExplicitReturnUndef
509             } else {
510 1         8 die "Can't insert section '$name': already exists";
511             }
512             }
513              
514 10         12 my $linum;
515 10 100       20 if (defined $opts->{linum}) {
    100          
516 3         7 ($err, $opts->{linum}) = $self->_validate_linum($opts->{linum});
517 3 100       19 die $err if $err;
518 1         1 $linum = $opts->{linum};
519             } elsif ($opts->{top}) {
520 2         5 $linum = $self->_find_section;
521 2   50     5 $linum //= 1;
522             } else {
523 5         9 $linum = @$p + 1;
524             }
525              
526             splice @$p, $linum-1, 0, [
527             'S',
528             '', # COL_S_WS1
529             '', # COL_S_WS2
530             $name, # COL_S_SECTION
531             '', # COL_S_WS3
532             defined($opts->{comment}) ? ' ' : undef, # COL_S_WS4
533             defined($opts->{comment}) ? ';' : undef, # COL_S_COMMENT_CHAR
534             $opts->{comment}, # COL_S_COMMENT
535 8 100       49 "\n", # COL_S_NL
    100          
536             ];
537              
538 8         20 $self->_discard_cache;
539 8         27 $linum;
540             }
541              
542             sub insert_key {
543 19     19 1 131 my $self = shift;
544 19         31 my $opts;
545 19 100       53 if (ref($_[0]) eq 'HASH') {
546 7         10 $opts = shift;
547             } else {
548 12         22 $opts = {};
549             }
550              
551 19         27 my $err;
552 19         52 my ($err_section, $section) = $self->_validate_section($_[0]);
553 19 50       50 die $err_section if $err_section;
554 19         49 my ($err_name, $name) = $self->_validate_key($_[1]);
555 19 100       121 die $err_name if $err_name;
556 13         39 my ($err_value, $value) = $self->_validate_value($_[2]);
557 13 100       34 die $err_value if $err_value;
558              
559 12         70 my $p = $self->{_parsed};
560              
561 12         21 my $linum;
562              
563 12 100       28 if ($opts->{replace}) {
564 1         7 $self->delete_key({all=>1}, $section, $name);
565             }
566              
567             # find section
568 12         78 my $line_range = $self->_get_section_line_range($section);
569 12 100       30 if (!$line_range) {
570 2 100       6 if ($opts->{create_section}) {
571 1         3 $linum = $self->insert_section($section) + 1;
572 1         3 $line_range = [$linum, $linum];
573             } else {
574 1         13 die "Can't insert key '$name': unknown section '$section'";
575             }
576             }
577              
578 11 100       26 unless (defined $linum) {
579 10         29 $linum = $self->_find_key($section, $name);
580 10 100       21 if ($linum) {
581 3 100       10 if ($opts->{ignore}) {
    100          
    50          
582 1         5 return undef; ## no critic: Subroutines::ProhibitExplicitReturnUndef
583             } elsif ($opts->{add}) {
584             #
585             } elsif ($opts->{replace}) {
586             # delete already done above
587             } else {
588 1         16 die "Can't insert key '$name': already exists";
589             }
590             }
591              
592 8 100       41 if ($opts->{linum}) {
593 2         8 ($err, $opts->{linum}) = $self->_validate_linum($opts->{linum});
594 2 50       7 die $err if $err;
595 2 100       6 $self->_line_in_section($opts->{linum}, $section)
596             or die "Invalid linum $opts->{linum}: not inside section '$section'";
597 1         3 $linum = $opts->{linum};
598             } else {
599 6 100       34 if ($opts->{top}) {
600 1         2 $linum = $line_range->[0];
601             } else {
602 5         6 $linum = $line_range->[1];
603 5 100       17 if ($p->[$linum-1]) {
604 1 50       5 if ($p->[$linum-1][COL_TYPE] eq 'S') {
605             } else {
606 0         0 $linum++;
607             }
608             }
609             }
610             }
611             }
612              
613             #XXX implement option: replace
614              
615 8         39 splice @$p, $linum-1, 0, [
616             'K',
617             '', # COL_K_WS1
618             $name, # COL_K_KEY
619             '', # COL_K_WS2
620             '', # COL_K_WS3
621             $value, # COL_K_VALUE_RAW
622             "\n", # COL_K_NL
623             ];
624 8         26 $self->_discard_cache;
625 8         36 $linum;
626             }
627              
628             sub delete_section {
629 5     5 1 28 my $self = shift;
630 5         7 my $opts;
631 5 100       11 if (ref($_[0]) eq 'HASH') {
632 2         3 $opts = shift;
633             } else {
634 3         5 $opts = {};
635             }
636              
637 5         12 my ($err, $section) = $self->_validate_section($_[0]);
638 5 50       10 die $err if $err;
639              
640 5         31 my $p = $self->{_parsed};
641              
642 5         7 my @line_ranges;
643 5 100       10 if ($opts->{all}) {
644 2         8 @line_ranges = $self->_get_section_line_range({all=>1}, $section);
645             } else {
646 3         7 @line_ranges = ($self->_get_section_line_range($section));
647 3 100       8 @line_ranges = () if !defined($line_ranges[0]);
648             }
649              
650 5 100       10 if ($opts->{cond}) {
651             @line_ranges = grep {
652 1         3 $opts->{cond}->(
  3         18  
653             $self,
654             linum_start => $_->[0],
655             linum_end => $_->[1],
656             );
657             } @line_ranges;
658             }
659              
660 5         11 my $num_deleted = 0;
661 5         9 for my $line_range (reverse @line_ranges) {
662 5 50       9 next unless defined $line_range;
663 5 50       9 my $line1 = $line_range->[0] - 1; $line1 = 1 if $line1 < 1;
  5         6  
664 5         7 my $line2 = $line_range->[1] - 1;
665 5         12 splice @$p, $line1-1, ($line2-$line1+1);
666 5         9 $num_deleted++;
667             }
668 5 100       46 $self->_discard_cache if $num_deleted;
669 5         20 $num_deleted;
670             }
671              
672             sub delete_key {
673 6     6 1 32 my $self = shift;
674 6         9 my $opts;
675 6 100       17 if (ref($_[0]) eq 'HASH') {
676 3         5 $opts = shift;
677             } else {
678 3         5 $opts = {};
679             }
680              
681 6         15 my ($err_section, $section) = $self->_validate_section($_[0]);
682 6 50       12 die $err_section if $err_section;
683 6         30 my ($err_name, $name) = $self->_validate_key($_[1]);
684 6 50       17 die $err_name if $err_name;
685              
686 6         35 my $p = $self->{_parsed};
687              
688 6         9 my @linums;
689 6 100       12 if ($opts->{all}) {
690 3         14 @linums = $self->_find_key({all=>1}, $section, $name);
691             } else {
692 3         8 @linums = ($self->_find_key($section, $name));
693 3 100       9 @linums = () if !defined($linums[0]);
694             }
695              
696 6 100       18 if ($opts->{cond}) {
697             @linums = grep {
698 1         3 my $line = $self->{_parsed}[$_-1];
  4         23  
699 4         9 $opts->{cond}->(
700             $self,
701             linum => $_,
702             parsed => $line,
703             key => $line->[COL_K_KEY],
704             raw_value => $line->[COL_K_VALUE_RAW],
705             # XXX value
706             );
707             } @linums;
708             }
709              
710 6         11 my $num_deleted = 0;
711 6         12 for my $linum (reverse @linums) {
712 6         13 splice @$p, $linum-1, 1;
713 6         11 $num_deleted++;
714             }
715              
716 6 100       21 $self->_discard_cache if $num_deleted;
717 6         16 $num_deleted;
718             }
719              
720             sub set_value {
721 4     4 1 19 my $self = shift;
722 4         4 my $opts;
723 4 50       9 if (ref($_[0]) eq 'HASH') {
724 0         0 $opts = shift;
725             } else {
726 4         4 $opts = {};
727             }
728              
729 4         6 my $section = $_[0];
730 4         6 my $key = $_[1];
731 4         7 my ($err_value, $value) = $self->_validate_value($_[2]);
732 4 100       17 die $err_value if $err_value;
733              
734 3         4 my $found;
735             $self->each_key(
736             sub {
737 3     3   11 my ($self, %args) = @_;
738 3 50 33     6 return if $found && !$opts->{all};
739 3 100       15 return unless $args{section} eq $section;
740 2 100       22 return unless $args{key} eq $key;
741 1         4 $found++;
742 1         3 my $l = $self->{_parsed}[ $args{linum}-1 ];
743 1         4 $l->[COL_K_VALUE_RAW] = $value;
744             },
745 3         14 );
746             }
747              
748             sub as_string {
749 60     60 1 11802 my $self = shift;
750              
751 60         140 my $abo = $self->{_parser}{allow_bang_only};
752              
753 60         76 my @str;
754 60         83 my $linum = 0;
755 60         69 for my $line (@{$self->{_parsed}}) {
  60         119  
756 747         812 $linum++;
757 747         815 my $type = $line->[COL_TYPE];
758 747 100       1716 if ($type eq 'B') {
    100          
    100          
    100          
    50          
759 182         260 push @str, $line->[COL_B_RAW];
760             } elsif ($type eq 'D') {
761 12 50       38 push @str, join(
762             "",
763             ($abo ? $line->[COL_D_COMMENT_CHAR] : ";"),
764             $line->[COL_D_WS1], "!",
765             $line->[COL_D_WS2],
766             $line->[COL_D_DIRECTIVE],
767             $line->[COL_D_WS3],
768             $line->[COL_D_ARGS_RAW],
769             $line->[COL_D_NL],
770             );
771             } elsif ($type eq 'C') {
772 63         129 push @str, join(
773             "",
774             $line->[COL_C_WS1],
775             $line->[COL_C_COMMENT_CHAR],
776             $line->[COL_C_COMMENT],
777             $line->[COL_C_NL],
778             );
779             } elsif ($type eq 'S') {
780 113   100     707 push @str, join(
      100        
      100        
781             "",
782             $line->[COL_S_WS1], "[",
783             $line->[COL_S_WS2],
784             $line->[COL_S_SECTION],
785             $line->[COL_S_WS3], "]",
786             $line->[COL_S_WS4] // '',
787             $line->[COL_S_COMMENT_CHAR] // '',
788             $line->[COL_S_COMMENT] // '',
789             $line->[COL_S_NL],
790             );
791             } elsif ($type eq 'K') {
792 377         767 push @str, join(
793             "",
794             $line->[COL_K_WS1],
795             $line->[COL_K_KEY],
796             $line->[COL_K_WS2], "=",
797             $line->[COL_K_WS3],
798             $line->[COL_K_VALUE_RAW],
799             $line->[COL_K_NL],
800             );
801             } else {
802 0         0 die "BUG: Unknown type '$type' in line $linum";
803             }
804             }
805              
806 60         369 join "", @str;
807             }
808              
809 15     15   179 use overload '""' => \&as_string;
  15         27  
  15         206  
810              
811             1;
812             # ABSTRACT: Represent IOD document
813              
814             __END__