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