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