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-23'; # DATE
4             our $VERSION = '0.352'; # VERSION
5              
6 15     15   347 use 5.010;
  15         53  
7 15     15   83 use strict;
  15         28  
  15         387  
8 15     15   75 use warnings;
  15         26  
  15         591  
9             #use Carp; # avoided to shave a bit of startup time
10              
11 15     15   7793 use Config::IOD::Constants qw(:ALL);
  15         36  
  15         79229  
12              
13             sub new {
14 107     107 1 400 my ($class, %attrs) = @_;
15              
16 107 50       360 if (!$attrs{_parsed}) {
17 0         0 $attrs{_parsed} = [];
18             }
19 107 50       266 if (!$attrs{_parser}) {
20 0         0 require Config::IOD;
21 0         0 $attrs{_parser} = Config::IOD->new;
22             }
23              
24 107         629 bless \%attrs, $class;
25             }
26              
27             sub empty {
28 1     1 1 4 my $self = shift;
29 1         4 $self->_discard_cache;
30 1         8 $self->{_parsed} = [];
31             }
32              
33             # all _validate_*() methods return ($err_msg, $validated_val)
34              
35             sub _validate_section {
36 46     46   83 my ($self, $name) = @_;
37 46         131 $name =~ s/\A\s+//;
38 46         108 $name =~ s/\s+\z//;
39 46 100       105 if (!length($name)) { return ("Section name must be non-zero string") }
  1         3  
40 45 100       174 if ($name =~ /\R|\]/) { return ("Section name must not contain ] or newline") }
  2         7  
41 43         123 return ("", $name);
42             }
43              
44             sub _validate_key {
45 25     25   43 my ($self, $name) = @_;
46 25         46 $name =~ s/\A\s+//;
47 25         46 $name =~ s/\s+\z//;
48 25 100       50 if (!length($name)) { return ("Key name must be non-zero string") }
  1         3  
49 24 100       70 if ($name =~ /\R|=/) { return ("Key name must not contain = or newline") }
  2         5  
50 22 100       53 if ($name =~ /\A(?:;|#|\[)/) { return ("Key name must not start with ;, #, [") }
  3         8  
51 19         42 return ("", $name);
52             }
53              
54             sub _validate_value {
55 17     17   34 my ($self, $value) = @_;
56 17         46 $value =~ s/\s+\z//;
57 17 100       49 if ($value =~ /\R/) { return ("Value must not contain newline") }
  2         7  
58 15         37 return ("", $value);
59             }
60              
61             sub _validate_comment {
62 2     2   4 my ($self, $comment) = @_;
63 2 100       7 if ($comment =~ /\R/) { return ("Comment must not contain newline") }
  1         4  
64 1         3 return ("", $comment);
65             }
66              
67             sub _validate_linum {
68 5     5   9 my ($self, $value) = @_;
69 5 100       13 if ($value < 1) { return ("linum must be at least 1") }
  1         2  
70 4 100       7 if ($value > @{$self->{_parsed}}) { return ("linum must not be larger than number of document's lines") }
  4         9  
  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   37 my $self = shift;
83 25         80 delete $self->{_dump_cache};
84             }
85              
86             sub dump {
87 21     21 1 56 my $self = shift;
88 21         31 my $opts;
89 21 50       79 if (ref($_[0]) eq 'HASH') {
90 0         0 $opts = shift;
91             } else {
92 21         50 $opts = {};
93             }
94              
95 21         105 my $parser = $self->{_parser};
96              
97 21         36 my $linum = 0;
98 21         42 my $merge;
99 21         48 my $cur_section = $parser->{default_section};
100 21         38 my $res = {};
101 21         39 my $arrayified = {};
102 21         38 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       7 unless exists $res->{$merge};
109 2         3 for my $k (keys %{ $res->{$merge} }) {
  2         8  
110 4   66     29 $res->{$cur_section}{$k} //= $res->{$merge}{$k};
111             }
112 21         146 };
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       95 local $parser->{_res} = $res if $parser->{enable_expr};
117 21 100       72 local $parser->{_cur_section} = $cur_section if $parser->{enable_expr};
118              
119 21         42 for my $line (@{ $self->{_parsed} }) {
  21         73  
120 61         92 $linum++;
121 61 50 33     163 next if defined($opts->{linum_start}) && $linum < $opts->{linum_start};
122 61 50 33     139 next if defined($opts->{linum_end} ) && $linum > $opts->{linum_end};
123              
124 61         89 my $type = $line->[COL_TYPE];
125 61 100       220 if ($type eq 'D') {
    100          
    100          
126 7         22 my $directive = $line->[COL_D_DIRECTIVE];
127 7 100       22 if ($directive eq 'merge') {
128 4         14 my $args = $parser->_parse_command_line(
129             $line->[COL_D_ARGS_RAW]);
130 4 50       160 if (!defined($args)) {
131 0         0 die "IOD document:$linum: Invalid arguments syntax '".
132             $line->[COL_D_ARGS_RAW]."'";
133             }
134 4 100       14 $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     42 $_merge->() if defined($merge) && $num_seen_section_lines > 1;
140 9         21 $cur_section = $line->[COL_S_SECTION];
141 9 100       27 $parser->{_cur_section} = $cur_section if $parser->{enable_expr}; #TMP HACK
142 9   50     47 $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         66 my $key = $line->[COL_K_KEY];
148 37         66 my $val = $line->[COL_K_VALUE_RAW];
149 37 100       126 if ($val =~ /\A["!\\[\{]/) {
150 24         112 my ($err, $parse_res, $decoded_val) =
151             $parser->_parse_raw_value($val);
152 24 100       5288 die "IOD document:$linum: Invalid value: $err" if $err;
153 18         39 $val = $decoded_val;
154             } else {
155 13         29 $val =~ s/\s*[#;].*//; # strip comment
156             }
157              
158 31 50       92 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         111 $res->{$cur_section}{$key} = $val;
170             }
171             } # ignore the other line types
172             }
173              
174 15 50 33     59 $_merge->() if defined($merge) && $num_seen_section_lines > 1;;
175              
176 15         160 $res;
177             }
178              
179             sub each_key {
180 7     7 1 2069 my $self = shift;
181 7         15 my $opts;
182 7 100       26 if (ref($_[0]) eq 'HASH') {
183 1         2 $opts = shift;
184             } else {
185 6         13 $opts = {};
186             }
187 7         16 my ($code) = @_;
188              
189 7         111 my $parser = $self->{_parser};
190              
191 7         15 my $linum = 0;
192 7         19 my $cur_section = $parser->{default_section};
193              
194 7         20 my $skip_section;
195             my %seen_sections;
196 7         0 my %seen_keys;
197 7         12 for my $line (@{ $self->{_parsed} }) {
  7         27  
198 31         59 $linum++;
199 31 50 33     78 next if defined($opts->{linum_start}) && $linum < $opts->{linum_start};
200 31 50 33     64 next if defined($opts->{linum_end} ) && $linum > $opts->{linum_end};
201              
202 31         62 my $type = $line->[COL_TYPE];
203 31 100       79 if ($type eq 'S') {
    50          
204 15         46 $cur_section = $line->[COL_S_SECTION];
205 15         27 %seen_keys = ();
206             $skip_section = $opts->{unique_section} &&
207 15   33     45 $seen_sections{$cur_section}++;
208             } elsif ($type eq 'K') {
209 16 50       38 next if $skip_section;
210 16         30 my $key = $line->[COL_K_KEY];
211 16 50 33     45 next if $opts->{unique_key} && $seen_keys{$key}++;
212 16         41 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     137 return if $opts->{early_exit} && !$res;
220             }
221             }
222             }
223              
224             sub get_value {
225 13     13 1 506 my ($self, $section, $key) = @_;
226 13 100       92 $self->{_dump_cache} = $self->dump unless $self->{_dump_cache};
227 13         70 $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         3 $opts = {};
260             }
261 2         4 my ($section) = @_;
262              
263 2         4 my @res;
264             my %mem;
265             $self->each_key(
266             sub {
267 8     8   27 my ($self, %args) = @_;
268 8 50       19 return unless $args{section} eq $section;
269 8 100 100     28 return if $opts->{unique} && $mem{$args{key}}++;
270 7         35 push @res, $args{key};
271             },
272 2         12 );
273 2         24 @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   28 my $self = shift;
296 18         27 my $opts;
297 18 100       37 if (ref($_[0]) eq 'HASH') {
298 4         7 $opts = shift;
299             } else {
300 14         22 $opts = {};
301             }
302 18         35 my ($name) = @_;
303              
304 18         25 my @res;
305              
306 18         24 my $linum = 0;
307 18         30 for my $line (@{ $self->{_parsed} }) {
  18         40  
308 45         56 $linum++;
309 45 100       98 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       43 return $linum unless $opts->{all};
314 16         30 push @res, $linum;
315             }
316 14 100       55 return undef unless $opts->{all};
317 4         14 return @res;
318             }
319              
320             sub each_section {
321 4     4 1 1878 my $self = shift;
322 4         5 my $opts;
323 4 100       14 if (ref($_[0]) eq 'HASH') {
324 3         20 $opts = shift;
325             } else {
326 1         2 $opts = {};
327             }
328 4         10 my ($code) = @_;
329              
330 4         67 my $parsed = $self->{_parsed};
331 4         15 my @linums = $self->_find_section({all=>1});
332 4         11 my %seen;
333 4         6 for my $linum (@linums) {
334 14         31 my $section = $parsed->[$linum-1][COL_S_SECTION];
335 14 100 100     44 next if $opts->{unique} && $seen{$section}++;
336              
337 13         17 my $linum_end = $linum;
338 13         16 while (1) {
339 23 100       40 last if $linum_end >= @$parsed;
340 20 100       41 last if $parsed->[$linum_end][COL_TYPE] eq 'S';
341 10         16 $linum_end++;
342             }
343              
344 13         33 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     98 return if $opts->{early_exit} && !$res;
353             }
354             }
355              
356             sub list_sections {
357 2     2 1 9 my $self = shift;
358 2         4 my $opts;
359 2 100       7 if (ref($_[0]) eq 'HASH') {
360 1         2 $opts = shift;
361             } else {
362 1         3 $opts = {};
363             }
364              
365 2         5 my @res;
366             $self->each_section(
367             $opts,
368             sub {
369 7     7   25 my ($self, %args) = @_;
370 7         33 push @res, $args{section};
371             }
372 2         13 );
373 2         24 @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   30 my $self = shift;
395 17         23 my $opts;
396 17 100       42 if (ref($_[0]) eq 'HASH') {
397 2         5 $opts = shift;
398             } else {
399 15         23 $opts = {};
400             }
401 17         33 my ($name) = @_;
402              
403 17         26 my @res;
404              
405 17         26 my $linum = 0;
406 17         35 my $cur_section = $self->{_parser}{default_section};
407 17         30 my $prev_section;
408             my $start;
409 17         25 for my $line (@{ $self->{_parsed} }) {
  17         45  
410 62         83 $linum++;
411 62 100       127 if ($line->[COL_TYPE] eq 'S') {
412 27         44 $cur_section = $line->[COL_S_SECTION];
413 27 100       58 if ($cur_section eq $name) {
414 17         26 $start = $linum+1;
415 17 100 100     52 $res[-1][1] = $linum if @res && !defined $res[-1][1];
416 17         45 push @res, [$start, undef];
417             } else {
418 10 100       24 $res[-1][1] = $linum if @res;
419 10 100 100     41 last if @res && !$opts->{all};
420             }
421             }
422             }
423 17 100 100     76 $res[-1][1] = $linum+1 if @res && !defined($res[-1][1]);
424              
425             L1:
426 17 100       51 if ($opts->{all}) { return @res } else { return $res[0] }
  2         8  
  15         80  
427             }
428              
429             sub _find_key {
430 16     16   22 my $self = shift;
431 16         21 my $opts;
432 16 100       32 if (ref($_[0]) eq 'HASH') {
433 3         6 $opts = shift;
434             } else {
435 13         22 $opts = {};
436             }
437 16         26 my ($section, $name) = @_;
438              
439 16         23 my @res;
440              
441 16         20 my $linum = 0;
442 16         30 my $cur_section = $self->{_parser}{default_section};
443 16         20 for my $line (@{ $self->{_parsed} }) {
  16         36  
444 62         99 $linum++;
445 62 100       120 if ($line->[COL_TYPE] eq 'S') {
446 23         31 $cur_section = $line->[COL_S_SECTION];
447 23         38 next;
448             }
449 39 100       70 next unless $line->[COL_TYPE] eq 'K';
450 38 100       76 next unless $cur_section eq $section;
451 26 100       58 next unless $line->[COL_K_KEY] eq $name;
452 11 100       28 return $linum unless $opts->{all};
453 7         13 push @res, $linum;
454             }
455 12 100       40 return undef unless $opts->{all};
456 3         17 return @res;
457             }
458              
459             sub _line_in_section {
460 2     2   3 my $self = shift;
461 2         3 my $opts;
462 2 50       4 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         2 my @res;
470              
471 2         3 my $linum = 0;
472 2         16 my $cur_section = $self->{_parser}{default_section};
473 2         4 for my $line (@{ $self->{_parsed} }) {
  2         5  
474 6         9 $linum++;
475 6 100       11 if ($linum == $asked_linum) {
476 2         21 return $asked_section eq $cur_section;
477             }
478 4 100       8 if ($line->[COL_TYPE] eq 'S') {
479 1         2 $cur_section = $line->[COL_S_SECTION];
480             }
481             }
482 0         0 return 0;
483             }
484              
485             sub insert_section {
486 16     16 1 76 my $self = shift;
487 16         21 my $opts;
488 16 100       41 if (ref($_[0]) eq 'HASH') {
489 8         10 $opts = shift;
490             } else {
491 8         13 $opts = {};
492             }
493              
494 16         37 my ($err, $name) = $self->_validate_section($_[0]);
495 16 100       62 die $err if $err;
496              
497 13         46 my $p = $self->{_parsed};
498              
499 13 100       28 if (defined $opts->{comment}) {
500 2         6 ($err, $opts->{comment}) = $self->_validate_comment($opts->{comment});
501 2 100       12 die $err if $err;
502             }
503              
504 12 100       40 if ($self->_find_section($name)) {
505 2 100       5 if ($opts->{ignore}) {
506 1         5 return undef;
507             } else {
508 1         11 die "Can't insert section '$name': already exists";
509             }
510             }
511              
512 10         17 my $linum;
513 10 100       26 if (defined $opts->{linum}) {
    100          
514 3         7 ($err, $opts->{linum}) = $self->_validate_linum($opts->{linum});
515 3 100       40 die $err if $err;
516 1         2 $linum = $opts->{linum};
517             } elsif ($opts->{top}) {
518 2         5 $linum = $self->_find_section;
519 2   50     6 $linum //= 1;
520             } else {
521 5         8 $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       36 "\n", # COL_S_NL
    100          
534             ];
535              
536 8         25 $self->_discard_cache;
537 8         31 $linum;
538             }
539              
540             sub insert_key {
541 19     19 1 96 my $self = shift;
542 19         29 my $opts;
543 19 100       45 if (ref($_[0]) eq 'HASH') {
544 7         11 $opts = shift;
545             } else {
546 12         14 $opts = {};
547             }
548              
549 19         33 my $err;
550 19         38 my ($err_section, $section) = $self->_validate_section($_[0]);
551 19 50       40 die $err_section if $err_section;
552 19         41 my ($err_name, $name) = $self->_validate_key($_[1]);
553 19 100       89 die $err_name if $err_name;
554 13         26 my ($err_value, $value) = $self->_validate_value($_[2]);
555 13 100       35 die $err_value if $err_value;
556              
557 12         56 my $p = $self->{_parsed};
558              
559 12         18 my $linum;
560              
561 12 100       25 if ($opts->{replace}) {
562 1         5 $self->delete_key({all=>1}, $section, $name);
563             }
564              
565             # find section
566 12         29 my $line_range = $self->_get_section_line_range($section);
567 12 100       29 if (!$line_range) {
568 2 100       12 if ($opts->{create_section}) {
569 1         3 $linum = $self->insert_section($section) + 1;
570 1         2 $line_range = [$linum, $linum];
571             } else {
572 1         22 die "Can't insert key '$name': unknown section '$section'";
573             }
574             }
575              
576 11 100       22 unless (defined $linum) {
577 10         22 $linum = $self->_find_key($section, $name);
578 10 100       22 if ($linum) {
579 3 100       16 if ($opts->{ignore}) {
    100          
    50          
580 1         5 return undef;
581             } elsif ($opts->{add}) {
582             #
583             } elsif ($opts->{replace}) {
584             # delete already done above
585             } else {
586 1         13 die "Can't insert key '$name': already exists";
587             }
588             }
589              
590 8 100       18 if ($opts->{linum}) {
591 2         5 ($err, $opts->{linum}) = $self->_validate_linum($opts->{linum});
592 2 50       5 die $err if $err;
593 2 100       5 $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       11 if ($opts->{top}) {
598 1         2 $linum = $line_range->[0];
599             } else {
600 5         7 $linum = $line_range->[1];
601 5 100       28 if ($p->[$linum-1]) {
602 1 50       4 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         26 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         38 $linum;
624             }
625              
626             sub delete_section {
627 5     5 1 46 my $self = shift;
628 5         8 my $opts;
629 5 100       18 if (ref($_[0]) eq 'HASH') {
630 2         5 $opts = shift;
631             } else {
632 3         7 $opts = {};
633             }
634              
635 5         23 my ($err, $section) = $self->_validate_section($_[0]);
636 5 50       15 die $err if $err;
637              
638 5         41 my $p = $self->{_parsed};
639              
640 5         10 my @line_ranges;
641 5 100       59 if ($opts->{all}) {
642 2         12 @line_ranges = $self->_get_section_line_range({all=>1}, $section);
643             } else {
644 3         16 @line_ranges = ($self->_get_section_line_range($section));
645 3 100       12 @line_ranges = () if !defined($line_ranges[0]);
646             }
647              
648 5 100       17 if ($opts->{cond}) {
649             @line_ranges = grep {
650 1         3 $opts->{cond}->(
  3         19  
651             $self,
652             linum_start => $_->[0],
653             linum_end => $_->[1],
654             );
655             } @line_ranges;
656             }
657              
658 5         35 my $num_deleted = 0;
659 5         13 for my $line_range (reverse @line_ranges) {
660 5 50       14 next unless defined $line_range;
661 5 50       12 my $line1 = $line_range->[0] - 1; $line1 = 1 if $line1 < 1;
  5         12  
662 5         11 my $line2 = $line_range->[1] - 1;
663 5         20 splice @$p, $line1-1, ($line2-$line1+1);
664 5         18 $num_deleted++;
665             }
666 5 100       27 $self->_discard_cache if $num_deleted;
667 5         17 $num_deleted;
668             }
669              
670             sub delete_key {
671 6     6 1 77 my $self = shift;
672 6         8 my $opts;
673 6 100       20 if (ref($_[0]) eq 'HASH') {
674 3         4 $opts = shift;
675             } else {
676 3         5 $opts = {};
677             }
678              
679 6         17 my ($err_section, $section) = $self->_validate_section($_[0]);
680 6 50       14 die $err_section if $err_section;
681 6         18 my ($err_name, $name) = $self->_validate_key($_[1]);
682 6 50       14 die $err_name if $err_name;
683              
684 6         38 my $p = $self->{_parsed};
685              
686 6         8 my @linums;
687 6 100       24 if ($opts->{all}) {
688 3         13 @linums = $self->_find_key({all=>1}, $section, $name);
689             } else {
690 3         9 @linums = ($self->_find_key($section, $name));
691 3 100       9 @linums = () if !defined($linums[0]);
692             }
693              
694 6 100       23 if ($opts->{cond}) {
695             @linums = grep {
696 1         2 my $line = $self->{_parsed}[$_-1];
  4         24  
697 4         11 $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         11 for my $linum (reverse @linums) {
710 6         14 splice @$p, $linum-1, 1;
711 6         14 $num_deleted++;
712             }
713              
714 6 100       19 $self->_discard_cache if $num_deleted;
715 6         17 $num_deleted;
716             }
717              
718             sub set_value {
719 4     4 1 31 my $self = shift;
720 4         8 my $opts;
721 4 50       14 if (ref($_[0]) eq 'HASH') {
722 0         0 $opts = shift;
723             } else {
724 4         8 $opts = {};
725             }
726              
727 4         15 my $section = $_[0];
728 4         10 my $key = $_[1];
729 4         16 my ($err_value, $value) = $self->_validate_value($_[2]);
730 4 100       28 die $err_value if $err_value;
731              
732 3         6 my $found;
733             $self->each_key(
734             sub {
735 3     3   22 my ($self, %args) = @_;
736 3 50 33     11 return if $found && !$opts->{all};
737 3 100       13 return unless $args{section} eq $section;
738 2 100       9 return unless $args{key} eq $key;
739 1         2 $found++;
740 1         4 my $l = $self->{_parsed}[ $args{linum}-1 ];
741 1         4 $l->[COL_K_VALUE_RAW] = $value;
742             },
743 3         25 );
744             }
745              
746             sub as_string {
747 60     60 1 9490 my $self = shift;
748              
749 60         151 my $abo = $self->{_parser}{allow_bang_only};
750              
751 60         130 my @str;
752 60         107 my $linum = 0;
753 60         79 for my $line (@{$self->{_parsed}}) {
  60         197  
754 747         833 $linum++;
755 747         845 my $type = $line->[COL_TYPE];
756 747 100       1496 if ($type eq 'B') {
    100          
    100          
    100          
    50          
757 182         253 push @str, $line->[COL_B_RAW];
758             } elsif ($type eq 'D') {
759 12 50       34 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         135 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     761 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         858 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         475 join "", @str;
805             }
806              
807 15     15   198 use overload '""' => \&as_string;
  15         36  
  15         172  
808              
809             1;
810             # ABSTRACT: Represent IOD document
811              
812             __END__