File Coverage

blib/lib/Config/INI/RefVars.pm
Criterion Covered Total %
statement 173 175 98.8
branch 74 84 88.1
condition 16 18 88.8
subroutine 23 23 100.0
pod 10 10 100.0
total 296 310 95.4


line stmt bran cond sub pod time code
1             package Config::INI::RefVars;
2 31     31   3162371 use 5.010;
  31         102  
3 31     31   141 use strict;
  31         94  
  31         1079  
4 31     31   250 use warnings;
  31         94  
  31         1922  
5 31     31   180 use feature ":5.10";
  31         74  
  31         4917  
6              
7 31     31   176 use Carp;
  31         44  
  31         2017  
8 31     31   159 use Config;
  31         60  
  31         1301  
9 31     31   134 use Cwd qw(abs_path);
  31         43  
  31         1641  
10 31     31   12715 use File::Spec::Functions qw(catdir catfile file_name_is_absolute splitpath);
  31         22378  
  31         2404  
11 31     31   16233 use Config::INI::RefVars::Builtins;
  31         97  
  31         1772  
12              
13             our $VERSION = '1.02';
14              
15 31     31   193 use constant DFLT_TOCOPY_SECTION => "__TOCOPY__";
  31         49  
  31         3252  
16 31     31   159 use constant FLD_KEY_PREFIX => __PACKAGE__ . ' __ ';
  31         48  
  31         3930  
17              
18             use constant {
19 31         263943 EXPANDED => FLD_KEY_PREFIX . 'EXPANDED',
20             CMNT_VL => FLD_KEY_PREFIX . 'CMNT_VL',
21             TOCOPY_SECTION => FLD_KEY_PREFIX . 'TOCOPY_SECTION',
22             CURR_TOCP_SECTION => FLD_KEY_PREFIX . 'CURR_TOCP_SECTION',
23             TOCOPY_VARS => FLD_KEY_PREFIX . 'TOCOPY_VARS',
24             NOT_TOCOPY => FLD_KEY_PREFIX . 'NOT_TOCOPY',
25             SECTIONS => FLD_KEY_PREFIX . 'SECTIONS',
26             SECTIONS_H => FLD_KEY_PREFIX . 'SECTIONS_H',
27             SRC_NAME => FLD_KEY_PREFIX . 'SRC_NAME',
28             VARIABLES => FLD_KEY_PREFIX . 'VARIABLES',
29             FUNCTIONS => FLD_KEY_PREFIX . 'FUNCTIONS',
30             GLOBAL_VARS => FLD_KEY_PREFIX . 'GLOBAL_VARS',
31             GLOBAL_MODE => FLD_KEY_PREFIX . 'GLOBAL_MODE',
32             VREF_RE => FLD_KEY_PREFIX . 'VREF_RE',
33             SEPARATOR => FLD_KEY_PREFIX . 'SEPARATOR',
34             BACKUP => FLD_KEY_PREFIX . 'BACKUP',
35             VARNAME_CHK_RE => FLD_KEY_PREFIX . 'VARNAME_CHK_RE',
36             DISPATCH_TABLE => FLD_KEY_PREFIX . 'DISPATCH_TABLE',
37 31     31   158 };
  31         51  
38              
39             my %Globals = ('=:' => catdir("", ""),
40             '=::' => $Config{path_sep},
41             '=VERSION' => $VERSION,
42             '=devnull' => File::Spec::Functions::devnull(),
43             '=rootdir' => File::Spec::Functions::rootdir(),
44             '=tmpdir' => File::Spec::Functions::tmpdir(),
45             );
46              
47             # Match punctuation chars, but not the underscores.
48             my $Modifier_Char = '[^_[:^punct:]]';
49              
50             my ($_look_up, $_x_var_name, $_expand_vars, $_user_function_call, $_function_body, $_parse_ini);
51              
52             my $_dispatch_sub = sub {
53             my ($self, $name) = @_;
54              
55             my $dispatch = $self->{+DISPATCH_TABLE}
56             or croak("Internal error: dispatch table is not initialized");
57              
58             my $sub = $dispatch->{$name}
59             or croak("unknown function '$name'");
60              
61             return $sub;
62             };
63              
64             my $_split_dispatch_spec = sub {
65             my ($self, $spec) = @_;
66              
67             $spec =~ s/^\s+//;
68             $spec =~ s/\s+$//;
69              
70             croak("empty function call") if $spec eq "";
71              
72             my @parts;
73             my $buf = "";
74             my $level = 0;
75              
76             foreach my $token (split(/(\$\(|\))/, $spec)) {
77             if ($token eq '$(') {
78             ++$level;
79             $buf .= $token;
80             }
81             elsif ($token eq ')') {
82             croak("unterminated variable reference") if !$level;
83             --$level;
84             $buf .= $token;
85             }
86             else {
87             foreach my $subtok (split(/(,)/, $token)) {
88             if ($subtok eq ',' && !$level) {
89             push(@parts, $buf);
90             $buf = "";
91             }
92             else {
93             $buf .= $subtok;
94             }
95             }
96             }
97             }
98             croak("unterminated variable reference") if $level;
99              
100             push(@parts, $buf);
101              
102             my $name = shift(@parts) // "";
103             $name =~ s/^\s+//;
104             $name =~ s/\s+$//;
105             croak("empty function name") if $name eq "";
106              
107             @parts = map {
108             s/^\s+//;
109             s/\s+$//;
110             $_;
111             } @parts;
112              
113             return ($name, @parts);
114             };
115              
116              
117             my $_dispatch_call = sub {
118             my ($self, $curr_sect, $spec, $seen) = @_;
119              
120             my ($name, @args) = $self->$_split_dispatch_spec($spec);
121             @args = map { $self->$_expand_vars($curr_sect, undef, $_, $seen, 1) } @args;
122              
123             my $sub = $self->$_dispatch_sub($name);
124             return $sub->(@args) // "";
125             };
126              
127              
128             $_function_body = sub {
129             my ($self, $curr_sect, $name) = @_;
130             my $functions = $self->{+FUNCTIONS} // {};
131              
132             if ($name =~ $self->{+VREF_RE}) {
133             my ($section, $basename) = ($1, $2);
134             return (exists($functions->{$section}) && exists($functions->{$section}{$basename})) ?
135             ($section, $basename, $functions->{$section}{$basename}) : ();
136             }
137             return ($curr_sect, $name, $functions->{$curr_sect}{$name})
138             if exists($functions->{$curr_sect}) && exists($functions->{$curr_sect}{$name});
139              
140             my $tocopy_section = $self->{+TOCOPY_SECTION};
141             return ($tocopy_section, $name, $functions->{$tocopy_section}{$name})
142             if ($curr_sect ne $tocopy_section
143             && exists($functions->{$tocopy_section})
144             && exists($functions->{$tocopy_section}{$name}));
145             return;
146             };
147              
148              
149             $_user_function_call = sub {
150             my ($self, $curr_sect, $spec, $seen) = @_;
151              
152             my ($name, @args) = $self->$_split_dispatch_spec($spec);
153             @args = map { $self->$_expand_vars($curr_sect, undef, $_, $seen, 1) } @args;
154              
155             my ($func_section, $func_name, $body) = $self->$_function_body($curr_sect, $name);
156              
157             if (!defined($body)) {
158             croak("unknown function '$name'") if $name =~ $self->{+VREF_RE};
159             return $self->$_dispatch_sub($name)->(@args) // "";
160             }
161              
162             my $x_func_name = "[$func_section]#=$func_name";
163              
164             croak("recursive function '$x_func_name' calls itself") if exists($seen->{$x_func_name});
165              
166             $seen->{$x_func_name} = undef;
167              
168             my $variables = $self->{+VARIABLES};
169             my $sect_vars = $variables->{$curr_sect} //= {};
170             my (%had_arg, %old_arg, %param);
171              
172             for my $i (1 .. @args) {
173             $param{$i} = $args[$i - 1];
174             }
175              
176             while ($body =~ /\$\((\d+)\)/g) {
177             $param{$1} //= "";
178             }
179              
180             foreach my $arg (keys(%param)) {
181             $had_arg{$arg} = exists($sect_vars->{$arg});
182             $old_arg{$arg} = $sect_vars->{$arg} if $had_arg{$arg};
183             $sect_vars->{$arg} = $param{$arg};
184             }
185             my $result;
186             eval { $result = $self->$_expand_vars($curr_sect, undef, $body, $seen, 1); 1; } or die($@);
187              
188             foreach my $arg (keys(%param)) {
189             if ($had_arg{$arg}) {
190             $sect_vars->{$arg} = $old_arg{$arg};
191             }
192             else {
193             delete($sect_vars->{$arg});
194             }
195             }
196             delete($seen->{$x_func_name});
197             return $result;
198             };
199              
200              
201             my $_check_tocopy_vars = sub {
202             my ($self, $tocopy_vars, $set) = @_;
203              
204             croak("'tocopy_vars': expected HASH ref") if ref($tocopy_vars) ne 'HASH';
205             $tocopy_vars = { %$tocopy_vars };
206              
207             while (my ($var, $value) = each(%$tocopy_vars)) {
208             croak("'tocopy_vars': value of '$var' is a ref, expected scalar") if ref($value);
209             if (!defined($value)) {
210             carp("'tocopy_vars': value '$var' is undef - treated as empty string");
211             $tocopy_vars->{$var} = "";
212             }
213             croak("'tocopy_vars': variable '$var': name is not permitted")
214             if ($var =~ /^\s*$/ || $var =~ /^[[=;]/);
215             }
216             $self->{+TOCOPY_VARS} = {%$tocopy_vars} if $set;
217             return $tocopy_vars;
218             };
219              
220              
221             my $_check_not_tocopy = sub {
222             my ($self, $not_tocopy, $set) = @_;
223             my $ref = ref($not_tocopy);
224             if ($ref eq 'ARRAY') {
225             foreach my $v (@$not_tocopy) {
226             croak("'not_tocopy': undefined value in array") if !defined($v);
227             croak("'not_tocopy': unexpected ref value in array") if ref($v);
228             }
229             $not_tocopy = {map {$_ => undef} @$not_tocopy};
230             }
231             elsif ($ref eq 'HASH') {
232             $not_tocopy = %{$not_tocopy};
233             }
234             else {
235             croak("'not_tocopy': unexpected type: must be ARRAY or HASH ref");
236             }
237             $self->{+NOT_TOCOPY} = $not_tocopy if $set;
238             return $not_tocopy;
239             };
240              
241              
242             sub new {
243 189     189 1 5484087 my ($class, %args) = @_;
244              
245 189         1053 state $allowed_keys = { map { $_ => undef } qw(builtins
  232         575  
246             cmnt_vl
247             global_mode
248             not_tocopy
249             separator
250             tocopy_section
251             tocopy_vars
252             varname_chk_re
253             )};
254              
255 189         702 _check_args(\%args, $allowed_keys);
256              
257 188   100     1026 my $builtins = delete($args{builtins}) // {};
258 188 50       606 croak("builtins must be a hash reference") if ref($builtins) ne 'HASH';
259 188         732 my $dispatch = Config::INI::RefVars::Builtins::default_dispatch_table();
260 188         474 foreach my $name (keys(%$builtins)) {
261 4 50       10 croak("builtin '$name' is not a CODE reference") if ref($builtins->{$name}) ne 'CODE';
262 4         6 $dispatch->{$name} = $builtins->{$name};
263             }
264 188         441 my $self = {
265             +DISPATCH_TABLE() => $dispatch,
266             };
267              
268 188 100       519 croak("'tocopy_section': must not be a reference") if ref($args{tocopy_section});
269              
270 187 100       435 if (exists($args{separator})) {
271 7         16 state $allowed_sep_chars = "#!%&',./:~\\";
272 7         16 my $sep = $args{separator};
273 7 100       44 croak("'separator': unexpected ref type, must be a scalar") if ref($sep);
274 6 100       257 croak("'separator': invalid value. Allowed chars: $allowed_sep_chars")
275             if $sep !~ m{^[\Q$allowed_sep_chars\E]+$};
276 5         15 $self->{+SEPARATOR} = $sep;
277 5         117 $self->{+VREF_RE} = qr/^(.*?)(?:\Q$sep\E)(.*)$/;
278             }
279             else {
280 180         751 $self->{+VREF_RE} = qr/^\[\s*(.*?)\s*\](.*)$/;
281             }
282 185         382 $self->{+CMNT_VL} = $args{cmnt_vl};
283 185   100     777 $self->{+TOCOPY_SECTION} = $args{tocopy_section} // DFLT_TOCOPY_SECTION;
284 185 100       428 $self->$_check_tocopy_vars($args{tocopy_vars}, 1) if exists($args{tocopy_vars});
285 180 100       418 $self->$_check_not_tocopy($args{not_tocopy}, 1) if exists($args{not_tocopy});
286              
287 177         493 $self->{+GLOBAL_MODE} = !!$args{global_mode};
288              
289 177 100       409 if (exists($args{varname_chk_re})) {
290             croak("'varname_chk_re': must be a compiled regex")
291 1 50       4 if ref($args{varname_chk_re}) ne 'Regexp';
292 1         2 $self->{+VARNAME_CHK_RE} = $args{varname_chk_re};
293             }
294 177         904 return bless($self, $class);
295             }
296              
297              
298             my $_expand_value = sub { return $_[0]->$_expand_vars($_[1], undef, $_[2]); };
299              
300             #
301             # We assume that this is called when the target section is still empty and if
302             # tocopy vars exist.
303             #
304             my $_cp_tocopy_vars = sub {
305             my ($self, $to_sect_name) = @_;
306              
307             my $comm_sec = $self->{+VARIABLES}{$self->{+TOCOPY_SECTION}} // die("no tocopy vars");
308             my $not_tocopy = $self->{+NOT_TOCOPY};
309             my $to_sec = $self->{+VARIABLES}{$to_sect_name} //= {};
310             my $expanded = $self->{+EXPANDED};
311              
312             foreach my $comm_var (keys(%$comm_sec)) {
313             next if exists($not_tocopy->{$comm_var});
314             $to_sec->{$comm_var} = $comm_sec->{$comm_var};
315             my $comm_x_var_name = "[" . $self->{+TOCOPY_SECTION} . "]$comm_var";
316             $expanded->{"[$to_sect_name]$comm_var"} = undef
317             if exists($expanded->{$comm_x_var_name});
318             }
319             };
320              
321              
322             my $_read_ini_file = sub {
323             my ($path) = @_;
324              
325             open(my $fh, '<', $path) or croak("'$path': cannot open file: $!");
326             my @lines = <$fh>;
327             close($fh) or croak("'$path': cannot close file: $!");
328              
329             return \@lines;
330             };
331              
332              
333             $_parse_ini = sub {
334             my ($self, $src, $curr_section, $include_stack, $src_dir, $src_name) = @_;
335              
336             croak("Internal error: argument is not an ARRAY ref") if ref($src) ne 'ARRAY';
337              
338             $src_name //= $self->{+SRC_NAME};
339             $src_dir //= '.';
340             $include_stack //= {};
341              
342             my $cmnt_vl = $self->{+CMNT_VL};
343             my $sections = $self->{+SECTIONS};
344             my $sections_h = $self->{+SECTIONS_H};
345             my $expanded = $self->{+EXPANDED};
346             my $variables = $self->{+VARIABLES};
347             my $functions = $self->{+FUNCTIONS};
348             my $tocopy_sec = $self->{+TOCOPY_SECTION};
349             my $tocopy_vars = $variables->{$tocopy_sec}; # hash key need not to exist!
350             my $global_mode = $self->{+GLOBAL_MODE};
351             my $vnm_chk_re = $self->{+VARNAME_CHK_RE};
352              
353             my $tocopy_sec_declared;
354              
355             my $i; # index in for() loop
356             my $_fatal = sub { croak("'$src_name': ", $_[0], " at line ", $i + 1); };
357              
358             my $_include_file = sub {
359             my ($include) = @_;
360              
361             $include =~ s/^\s+//;
362             $include =~ s/\s+$//;
363             $_fatal->("missing file name in include directive") if $include eq "";
364              
365             $include = $self->$_expand_vars(defined($curr_section) ? $curr_section : $tocopy_sec,
366             undef,
367             $include,
368             undef,
369             1,
370             );
371              
372             my $path = file_name_is_absolute($include) ? $include : catfile($src_dir, $include);
373              
374             my $abs_path = abs_path($path) or $_fatal->("'$include': cannot resolve include file");
375              
376             $_fatal->("'$include': recursive include") if exists($include_stack->{$abs_path});
377              
378             local $include_stack->{$abs_path} = undef;
379              
380             my ($vol, $dirs) = splitpath($abs_path);
381             my $inc_dir = catdir(length($vol // "") ? $vol : (), $dirs);
382              
383             my ($inc_tocopy_declared, $inc_curr_section) =
384             $self->$_parse_ini($_read_ini_file->($abs_path),
385             $curr_section,
386             $include_stack,
387             $inc_dir,
388             $abs_path,
389             );
390              
391             $tocopy_sec_declared ||= $inc_tocopy_declared;
392             $curr_section = $inc_curr_section if defined($inc_curr_section);
393             };
394              
395             my $set_curr_section = sub {
396             $curr_section = shift;
397             if ($curr_section eq $tocopy_sec) {
398             $_fatal->("tocopy section '$tocopy_sec' must be first section") if @$sections;
399             $tocopy_vars = $variables->{$tocopy_sec} = {} if !$tocopy_vars;
400             $functions->{$tocopy_sec} //= {};
401             $tocopy_sec_declared = 1;
402             }
403             elsif ($tocopy_vars && !$global_mode) {
404             $self->$_cp_tocopy_vars($curr_section);
405             }
406             else {
407             $variables->{$curr_section} = {};
408             }
409             $functions->{$curr_section} //= {};
410              
411             $_fatal->("'$curr_section': duplicate header") if exists($sections_h->{$curr_section});
412             $sections_h->{$curr_section} = @$sections; # Index!
413             push(@$sections, $curr_section);
414             };
415              
416             for ($i = 0; $i < @$src; ++$i) {
417             my $line = $src->[$i];
418             $line =~ s/\s+$//;
419             next if $line eq "";
420              
421             if ($line =~ /^=include(?:\s+(.+))?\z/) {
422             $_include_file->($1 // "");
423             next;
424             }
425              
426             if (index($line, ";!") == 0 || index($line, "=") == 0) {
427             $_fatal->("directives are not yet supported");
428             }
429              
430             $line =~ s/^\s+//;
431             next if $line eq "" || $line =~ /^[;#]/;
432              
433             # section header
434             if (index($line, "[") == 0) {
435             $line =~ s/\s*[#;][^\]]*$//;
436             $line =~ /^\[\s*(.*?)\s*\]$/ or $_fatal->("invalid section header");
437             $set_curr_section->($1);
438             next;
439             }
440              
441             # var = val
442             $line =~ s/\s+;.*$// if $cmnt_vl;
443             $set_curr_section->($tocopy_sec) if !defined($curr_section);
444              
445             $line =~ /^(.*?)\s*($Modifier_Char*?)=(?:\s*)(.*)/
446             or $_fatal->("neither section header nor key definition");
447              
448             my ($var_name, $modifier, $value) = ($1, $2, $3);
449              
450             if ($modifier =~ s/\\\z//) {
451             while ($value =~ s/\\\z//) {
452             last if $i + 1 >= @$src;
453             my $next_line = $src->[++$i];
454             $next_line =~ s/\s+$//;
455              
456             if (index($next_line, "=") == 0) {
457             $_fatal->("directive in line continuation");
458             }
459             $value .= $next_line;
460             }
461             }
462             if ($vnm_chk_re) {
463             croak("'$var_name': var name does not match varname_chk_re") if $var_name !~ $vnm_chk_re;
464             }
465              
466             my $x_var_name = $self->$_x_var_name($curr_section, $var_name);
467             my $exp_flag = exists($expanded->{$x_var_name});
468              
469             $_fatal->("empty variable name") if $var_name eq "";
470              
471             my $sect_vars = $variables->{$curr_section} //= {};
472             my $sect_funcs = $functions->{$curr_section} //= {};
473              
474             if ($modifier eq '#') {
475             $sect_funcs->{$var_name} = $value;
476             }
477             elsif ($modifier eq "") {
478             delete $expanded->{$x_var_name} if $exp_flag;
479             $sect_vars->{$var_name} = $value;
480             }
481             elsif ($modifier eq '?') {
482             $sect_vars->{$var_name} = $value if !exists($sect_vars->{$var_name});
483             }
484             elsif ($modifier eq '??') {
485             $sect_vars->{$var_name} = $value
486             if (!exists($sect_vars->{$var_name}) || $sect_vars->{$var_name} eq "");
487             }
488             elsif ($modifier eq '+') {
489             if (exists($sect_vars->{$var_name})) {
490             $sect_vars->{$var_name} .= " "
491             . ($exp_flag ? $self->$_expand_value($curr_section, $value) : $value);
492             }
493             else {
494             $sect_vars->{$var_name} = $value;
495             }
496             }
497             elsif ($modifier eq '.') {
498             $sect_vars->{$var_name} = ($sect_vars->{$var_name} // "")
499             . ($exp_flag ? $self->$_expand_value($curr_section, $value) : $value);
500             }
501             elsif ($modifier eq ':') {
502             delete $expanded->{$x_var_name} if $exp_flag;
503             $sect_vars->{$var_name} = $self->$_expand_vars($curr_section, $var_name, $value, undef, 1);
504             }
505             elsif ($modifier eq '+>') {
506             if (exists($sect_vars->{$var_name})) {
507             $sect_vars->{$var_name} =
508             ($exp_flag ? $self->$_expand_value($curr_section, $value) : $value)
509             . ' ' . $sect_vars->{$var_name};
510             }
511             else {
512             $sect_vars->{$var_name} = $value;
513             }
514             }
515             elsif ($modifier eq '.>') {
516             $sect_vars->{$var_name} = ($exp_flag ? $self->$_expand_value($curr_section, $value) : $value)
517             . ($sect_vars->{$var_name} // "");
518             }
519             else {
520             $_fatal->("'$modifier': unsupported modifier");
521             }
522             }
523             return ($tocopy_sec_declared, $curr_section);
524             };
525              
526              
527             sub parse_ini {
528 239     239 1 207588 my $self = shift;
529 239         808 my %args = (cleanup => 1, @_);
530              
531             state $allowed_keys = {
532 239         380 map { $_ => undef } qw(cleanup src src_name tocopy_section tocopy_vars not_tocopy)
  174         397  
533             };
534 239         414 state $dflt_src_name = "INI data";
535              
536 239         669 _check_args(\%args, $allowed_keys);
537              
538 238         484 foreach my $scalar_arg (qw(tocopy_section src_name)) {
539 475 100       1096 croak("'$scalar_arg': must not be a reference") if ref($args{$scalar_arg});
540             }
541              
542 236 100       726 delete $self->{+SRC_NAME} if exists($self->{+SRC_NAME});
543 236 100       575 $self->{+SRC_NAME} = $args{src_name} if exists($args{src_name});
544              
545             my ($cleanup, $src, $tocopy_section, $tocopy_vars, $not_tocopy)
546 236         806 = @args{qw(cleanup src tocopy_section tocopy_vars not_tocopy)};
547              
548 236 100       542 croak("'src': missing mandatory argument") if !defined($src);
549              
550 235   100     984 my $backup = $self->{+BACKUP} //= {};
551              
552 235 100       491 if (defined($tocopy_section)) {
553 8         23 $backup->{tocopy_section} = $self->{+TOCOPY_SECTION};
554 8         19 $self->{+TOCOPY_SECTION} = $tocopy_section;
555             }
556             else {
557 227         433 $tocopy_section = $self->{+TOCOPY_SECTION};
558             }
559              
560 235         496 $self->{+CURR_TOCP_SECTION} = $tocopy_section;
561 235         470 $Globals{'=TO_CP_SEC'} = $tocopy_section;
562              
563 235 100       1024 if ($tocopy_vars) {
564 16         34 $backup->{tocopy_vars} = $self->{+TOCOPY_VARS};
565 16         41 $self->$_check_tocopy_vars($tocopy_vars, 1);
566             }
567              
568 230 100       449 if ($not_tocopy) {
569 10         19 $backup->{not_tocopy} = $self->{+NOT_TOCOPY};
570 10         28 $self->$_check_not_tocopy($not_tocopy, 1);
571             }
572              
573 227         542 $self->{+SECTIONS} = [];
574 227         479 $self->{+SECTIONS_H} = {};
575 227         497 $self->{+EXPANDED} = {};
576             $self->{+VARIABLES} = {
577 227 100       1009 $tocopy_section => ($self->{+TOCOPY_VARS} ? {%{$self->{+TOCOPY_VARS}}} : {})
  24         113  
578             };
579 227         638 $self->{+FUNCTIONS} = {};
580              
581 227         1782 my $global_vars = $self->{+GLOBAL_VARS} = {%Globals};
582 227         526 my $variables = $self->{+VARIABLES};
583 227         374 my $tocopy_sec_vars = $variables->{$tocopy_section};
584              
585 227 100       590 if (my $ref_src = ref($src)) {
586 88 100       281 $self->{+SRC_NAME} = $dflt_src_name if !exists($self->{+SRC_NAME});
587              
588 88 100       219 if ($ref_src eq 'ARRAY') {
589 87         237 $src = [@$src];
590 87         230 foreach my $entry (@$src) {
591 525 100       861 croak("'src': unexpected ref type in array") if ref($entry);
592 524 50       856 if (!defined($entry)) {
593 0         0 carp("'src': undef entry - treated as empty string");
594 0         0 $entry = "";
595             }
596             }
597             }
598             else {
599 1         10 croak("'src': $ref_src: ref type not allowed");
600             }
601             }
602             else {
603 139 100       381 if (index($src, "\n") < 0) {
604 14         22 my $path = $src;
605 14 50       557 my $abs_path = abs_path($path) or croak("'$path': cannot resolve file name");
606 14         42 $src = $_read_ini_file->($abs_path);
607 14 50       54 $self->{+SRC_NAME} = $path if !exists($self->{+SRC_NAME});
608              
609 14         58 my ($vol, $dirs, $file) = splitpath($abs_path);
610 14 50 50     323 @{$global_vars}{'=INIfile', '=INIdir'} = ($file,
  14         55  
611             catdir(length($vol // "") ? $vol : (), $dirs),
612             );
613             }
614             else {
615 125         631 $src = [split(/\n/, $src)];
616 125 50       501 $self->{+SRC_NAME} = $dflt_src_name if !exists($self->{+SRC_NAME});
617             }
618             }
619              
620 225         525 $global_vars->{'=srcname'} = $self->{+SRC_NAME};
621              
622 225         504 my $src_dir = '.';
623 225         415 my $include_stack = {};
624              
625 225 100 100     941 if (!ref($args{src}) && index($args{src}, "\n") < 0) {
626             my $abs_path = abs_path($args{src})
627 14 50       382 or croak("'$args{src}': cannot resolve file name");
628 14         69 $include_stack->{$abs_path} = undef;
629 14         31 my ($vol, $dirs) = splitpath($abs_path);
630 14 50 50     177 $src_dir = catdir(length($vol // "") ? $vol : (), $dirs);
631             }
632              
633             my ($tocopy_sec_declared, undef) = $self->$_parse_ini($src,
634             undef,
635             $include_stack,
636             $src_dir,
637 225         824 $self->{+SRC_NAME},
638             );
639              
640             my @sections = ((exists($self->{+SECTIONS_H}{$tocopy_section}) ? () : $tocopy_section),
641 200 100       598 @{$self->{+SECTIONS}},
  200         600  
642             );
643              
644 200         386 foreach my $section (@sections) {
645 487         743 my $sec_vars = $variables->{$section};
646              
647 487         1040 foreach my $variable (keys(%$sec_vars)) {
648 926         1336 my $value = $sec_vars->{$variable};
649 926         1396 $sec_vars->{$variable} = $self->$_expand_vars($section, $variable, $value);
650             }
651             }
652              
653 172 100       377 if ($cleanup) {
654 167         384 foreach my $section (keys(%$variables)) {
655 414         567 my $sec_vars = $variables->{$section};
656 414         688 foreach my $var (keys(%$sec_vars)) {
657 810 100       1576 delete $sec_vars->{$var} if index($var, '=') >= 0;
658             }
659             }
660              
661 167 100 100     806 delete $variables->{$self->{+TOCOPY_SECTION}} if (!$tocopy_sec_declared && !%$tocopy_sec_vars);
662             }
663             else {
664 5 100       17 if ($self->{+GLOBAL_MODE}) {
665 2         7 foreach my $section (keys(%$variables)) {
666 6         12 my $sec_vars = $variables->{$section};
667 6         13 $sec_vars->{'='} = $section;
668             }
669 2         11 @{$tocopy_sec_vars}{keys(%$global_vars)} = values(%$global_vars);
  2         21  
670             }
671             else {
672 3         7 foreach my $section (keys(%$variables)) {
673 9         10 my $sec_vars = $variables->{$section};
674 9         12 $sec_vars->{'='} = $section;
675 9         22 @{$sec_vars}{keys(%$global_vars)} = values(%$global_vars);
  9         39  
676             }
677             }
678             }
679              
680 172 100       415 $self->{+TOCOPY_SECTION} = $backup->{tocopy_section} if exists($backup->{tocopy_section});
681 172 100       407 $self->{+TOCOPY_VARS} = $backup->{tocopy_vars} if exists($backup->{tocopy_vars});
682 172 100       439 $self->{+NOT_TOCOPY} = $backup->{not_tocopy} if exists($backup->{not_tocopy});
683 172         281 $backup = {};
684              
685 172         1134 return $self;
686             }
687              
688              
689 11     11 1 60 sub current_tocopy_section { $_[0]->{+CURR_TOCP_SECTION} }
690 22     22 1 95 sub tocopy_section { $_[0]->{+TOCOPY_SECTION} }
691 20     20 1 3785 sub global_mode { $_[0]->{+GLOBAL_MODE} }
692              
693             sub sections {
694 23 100   23 1 1210 return defined($_[0]->{+SECTIONS}) ? [@{$_[0]->{+SECTIONS}}] : undef;
  22         129  
695             }
696              
697             sub sections_h {
698 22 100   22 1 7193 return defined($_[0]->{+SECTIONS_H}) ? +{ %{$_[0]->{+SECTIONS_H}} } : undef;
  21         116  
699             }
700              
701 13     13 1 65 sub separator { $_[0]->{+SEPARATOR} }
702 7     7 1 665 sub src_name { $_[0]->{+SRC_NAME} }
703              
704              
705             sub variables {
706 170   100 170 1 6982 my $vars = $_[0]->{+VARIABLES} // return undef;
707 169         471 return { map { $_ => {%{$vars->{$_}}} } keys(%$vars) };
  322         468  
  322         2464  
708             }
709              
710              
711             $_look_up = sub {
712             my ($self, $curr_sect, $variable) = @_;
713             my $matched = $variable =~ $self->{+VREF_RE};
714             my ($v_section, $v_basename) = $matched ? ($1, $2) : ($curr_sect, $variable);
715             my $v_value;
716             my $variables = $self->{+VARIABLES};
717             my $tocopy_section = $self->{+TOCOPY_SECTION};
718             if (!exists($variables->{$v_section})) {
719             $v_value = "";
720             }
721             elsif (exists($variables->{$v_section}{$v_basename})) {
722             $v_value = $variables->{$v_section}{$v_basename};
723             }
724             elsif ($v_basename !~ /\S/) {
725             $v_value = $v_basename;
726             }
727             elsif ($v_basename eq '=') {
728             $v_value = $v_section;
729             }
730             elsif ($v_basename =~ /^=(?:ENV|env):\s*(.*)$/) {
731             $v_value = $ENV{$1} // "";
732             }
733             elsif ($v_basename =~ /^=CONFIG:\s*(.*)$/) {
734             $v_value = $Config{$1} // "";
735             }
736             elsif (exists($self->{+GLOBAL_VARS}{$v_basename})) {
737             $v_value = $self->{+GLOBAL_VARS}{$v_basename};
738             }
739             elsif ($self->{+GLOBAL_MODE} && exists($variables->{$tocopy_section}{$v_basename})) {
740             if (!$matched
741             && $curr_sect ne $tocopy_section
742             && exists($self->{+NOT_TOCOPY}{$v_basename})
743             ) {
744             $v_value = "";
745             }
746             else {
747             $v_value = $variables->{$tocopy_section}{$v_basename};
748             }
749             }
750             else {
751             $v_value = "";
752             }
753             die("Internal error") if !defined($v_value);
754             return wantarray ? ($v_section, $v_basename, $v_value) : $v_value;
755             };
756              
757              
758             # extended var name
759             $_x_var_name = sub {
760             my ($self, $curr_sect, $variable) = @_;
761              
762             if ($variable =~ $self->{+VREF_RE}) {
763             return ($2, "[$1]$2");
764             }
765             else {
766             return ($variable, "[$curr_sect]$variable");
767             }
768             };
769              
770              
771             $_expand_vars = sub {
772             my ($self, $curr_sect, $variable, $value, $seen, $not_seen) = @_;
773             my $top = !$seen;
774             my @result = ("");
775             my @raw = ("");
776             my $level = 0;
777             my $x_variable_name;
778              
779             if (defined($variable)) {
780             ((my $var_basename), $x_variable_name) = $self->$_x_var_name($curr_sect, $variable);
781              
782             return $self->$_look_up($curr_sect, $variable)
783             if (exists($self->{+EXPANDED}{$x_variable_name}) || $var_basename =~ /^=(?:ENV|CONFIG):/);
784              
785             croak("recursive variable '$x_variable_name' references itself")
786             if exists($seen->{$x_variable_name});
787              
788             $seen->{$x_variable_name} = undef if !$not_seen;
789             }
790              
791             foreach my $token (split(/(\$\(|\))/, $value)) {
792             if ($token eq '$(') {
793             ++$level;
794             $raw[$level - 1] .= '$(' if $level > 1;
795             }
796             elsif ($token eq ')' && $level) {
797             my $raw_expr = $raw[$level];
798              
799             if ($result[$level] eq '==') {
800             $result[$level - 1] .= $variable;
801             }
802             elsif ($raw_expr =~ /^\s*=#\s*(.*)$/s) {
803             $result[$level - 1] .= $self->$_user_function_call($curr_sect, $1, $seen);
804             }
805             elsif ($raw_expr =~ /^\s*=&\s*(.*)$/s) {
806             $result[$level - 1] .= $self->$_dispatch_call($curr_sect, $1, $seen);
807             }
808             else {
809             $result[$level - 1] .= $self->$_expand_vars(
810             $self->$_look_up($curr_sect, $result[$level]),
811             $seen,
812             );
813             }
814             $raw[$level - 1] .= $raw_expr . ')';
815             pop(@result);
816             pop(@raw);
817             --$level;
818             }
819             else {
820             $result[$level] .= $token;
821             $raw[$level] .= $token;
822             }
823             }
824              
825             croak("'$x_variable_name': unterminated variable reference") if $level;
826             $value = $result[0];
827             if ($x_variable_name) {
828             $self->{+EXPANDED}{$x_variable_name} = undef if $top;
829             delete $seen->{$x_variable_name};
830             }
831             return $value;
832             };
833              
834              
835             #
836             # This is a function, not a method!
837             #
838             sub _check_args {
839 428     428   738 my ($args, $allowed_args) = @_;
840 428         1095 foreach my $key (keys(%$args)) {
841 572 100       1301 croak("'$key': unsupported argument") if !exists($allowed_args->{$key});
842             }
843 426         898 delete @{$args}{ grep { !defined($args->{$_}) } keys(%$args) };
  426         810  
  568         1266  
844             }
845              
846              
847             1; # End of Config::INI::RefVars
848              
849              
850             __END__