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   2939412 use 5.010;
  31         115  
3 31     31   141 use strict;
  31         76  
  31         1002  
4 31     31   169 use warnings;
  31         89  
  31         1839  
5 31     31   180 use feature ":5.10";
  31         103  
  31         5095  
6              
7 31     31   197 use Carp;
  31         41  
  31         2132  
8 31     31   167 use Config;
  31         64  
  31         1239  
9 31     31   160 use Cwd qw(abs_path);
  31         51  
  31         1714  
10 31     31   12009 use File::Spec::Functions qw(catdir catfile file_name_is_absolute splitpath);
  31         21463  
  31         2393  
11 31     31   14563 use Config::INI::RefVars::Builtins;
  31         110  
  31         1544  
12              
13             our $VERSION = '1.03';
14              
15 31     31   194 use constant DFLT_TOCOPY_SECTION => "__TOCOPY__";
  31         45  
  31         3205  
16 31     31   150 use constant FLD_KEY_PREFIX => __PACKAGE__ . ' __ ';
  31         47  
  31         3522  
17              
18             use constant {
19 31         253004 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   138 };
  31         47  
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 5329909 my ($class, %args) = @_;
244              
245 189         368 state $allowed_keys = { map { $_ => undef } qw(builtins
  232         618  
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         696 _check_args(\%args, $allowed_keys);
256              
257 188   100     966 my $builtins = delete($args{builtins}) // {};
258 188 50       615 croak("builtins must be a hash reference") if ref($builtins) ne 'HASH';
259 188         694 my $dispatch = Config::INI::RefVars::Builtins::default_dispatch_table();
260 188         580 foreach my $name (keys(%$builtins)) {
261 4 50       9 croak("builtin '$name' is not a CODE reference") if ref($builtins->{$name}) ne 'CODE';
262 4         7 $dispatch->{$name} = $builtins->{$name};
263             }
264 188         434 my $self = {
265             +DISPATCH_TABLE() => $dispatch,
266             };
267              
268 188 100       585 croak("'tocopy_section': must not be a reference") if ref($args{tocopy_section});
269              
270 187 100       482 if (exists($args{separator})) {
271 7         18 state $allowed_sep_chars = "#!%&',./:~\\";
272 7         17 my $sep = $args{separator};
273 7 100       34 croak("'separator': unexpected ref type, must be a scalar") if ref($sep);
274 6 100       347 croak("'separator': invalid value. Allowed chars: $allowed_sep_chars")
275             if $sep !~ m{^[\Q$allowed_sep_chars\E]+$};
276 5         18 $self->{+SEPARATOR} = $sep;
277 5         138 $self->{+VREF_RE} = qr/^(.*?)(?:\Q$sep\E)(.*)$/;
278             }
279             else {
280 180         727 $self->{+VREF_RE} = qr/^\[\s*(.*?)\s*\](.*)$/;
281             }
282 185         368 $self->{+CMNT_VL} = $args{cmnt_vl};
283 185   100     748 $self->{+TOCOPY_SECTION} = $args{tocopy_section} // DFLT_TOCOPY_SECTION;
284 185 100       402 $self->$_check_tocopy_vars($args{tocopy_vars}, 1) if exists($args{tocopy_vars});
285 180 100       413 $self->$_check_not_tocopy($args{not_tocopy}, 1) if exists($args{not_tocopy});
286              
287 177         419 $self->{+GLOBAL_MODE} = !!$args{global_mode};
288              
289 177 100       361 if (exists($args{varname_chk_re})) {
290             croak("'varname_chk_re': must be a compiled regex")
291 1 50       5 if ref($args{varname_chk_re}) ne 'Regexp';
292 1         3 $self->{+VARNAME_CHK_RE} = $args{varname_chk_re};
293             }
294 177         909 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 194756 my $self = shift;
529 239         891 my %args = (cleanup => 1, @_);
530              
531             state $allowed_keys = {
532 239         418 map { $_ => undef } qw(cleanup src src_name tocopy_section tocopy_vars not_tocopy)
  174         400  
533             };
534 239         362 state $dflt_src_name = "INI data";
535              
536 239         760 _check_args(\%args, $allowed_keys);
537              
538 238         493 foreach my $scalar_arg (qw(tocopy_section src_name)) {
539 475 100       1123 croak("'$scalar_arg': must not be a reference") if ref($args{$scalar_arg});
540             }
541              
542 236 100       820 delete $self->{+SRC_NAME} if exists($self->{+SRC_NAME});
543 236 100       596 $self->{+SRC_NAME} = $args{src_name} if exists($args{src_name});
544              
545             my ($cleanup, $src, $tocopy_section, $tocopy_vars, $not_tocopy)
546 236         753 = @args{qw(cleanup src tocopy_section tocopy_vars not_tocopy)};
547              
548 236 100       513 croak("'src': missing mandatory argument") if !defined($src);
549              
550 235   100     862 my $backup = $self->{+BACKUP} //= {};
551              
552 235 100       544 if (defined($tocopy_section)) {
553 8         23 $backup->{tocopy_section} = $self->{+TOCOPY_SECTION};
554 8         15 $self->{+TOCOPY_SECTION} = $tocopy_section;
555             }
556             else {
557 227         410 $tocopy_section = $self->{+TOCOPY_SECTION};
558             }
559              
560 235         501 $self->{+CURR_TOCP_SECTION} = $tocopy_section;
561 235         465 $Globals{'=TO_CP_SEC'} = $tocopy_section;
562              
563 235 100       491 if ($tocopy_vars) {
564 16         41 $backup->{tocopy_vars} = $self->{+TOCOPY_VARS};
565 16         120 $self->$_check_tocopy_vars($tocopy_vars, 1);
566             }
567              
568 230 100       395 if ($not_tocopy) {
569 10         29 $backup->{not_tocopy} = $self->{+NOT_TOCOPY};
570 10         31 $self->$_check_not_tocopy($not_tocopy, 1);
571             }
572              
573 227         542 $self->{+SECTIONS} = [];
574 227         507 $self->{+SECTIONS_H} = {};
575 227         552 $self->{+EXPANDED} = {};
576             $self->{+VARIABLES} = {
577 227 100       968 $tocopy_section => ($self->{+TOCOPY_VARS} ? {%{$self->{+TOCOPY_VARS}}} : {})
  24         193  
578             };
579 227         519 $self->{+FUNCTIONS} = {};
580              
581 227         1789 my $global_vars = $self->{+GLOBAL_VARS} = {%Globals};
582 227         464 my $variables = $self->{+VARIABLES};
583 227         370 my $tocopy_sec_vars = $variables->{$tocopy_section};
584              
585 227 100       618 if (my $ref_src = ref($src)) {
586 88 100       297 $self->{+SRC_NAME} = $dflt_src_name if !exists($self->{+SRC_NAME});
587              
588 88 100       219 if ($ref_src eq 'ARRAY') {
589 87         204 $src = [@$src];
590 87         192 foreach my $entry (@$src) {
591 525 100       775 croak("'src': unexpected ref type in array") if ref($entry);
592 524 50       814 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         15 croak("'src': $ref_src: ref type not allowed");
600             }
601             }
602             else {
603 139 100       342 if (index($src, "\n") < 0) {
604 14         26 my $path = $src;
605 14 50       652 my $abs_path = abs_path($path) or croak("'$path': cannot resolve file name");
606 14         54 $src = $_read_ini_file->($abs_path);
607 14 50       62 $self->{+SRC_NAME} = $path if !exists($self->{+SRC_NAME});
608              
609 14         90 my ($vol, $dirs, $file) = splitpath($abs_path);
610 14 50 50     406 @{$global_vars}{'=INIfile', '=INIdir'} = ($file,
  14         54  
611             catdir(length($vol // "") ? $vol : (), $dirs),
612             );
613             }
614             else {
615 125         644 $src = [split(/\n/, $src)];
616 125 50       500 $self->{+SRC_NAME} = $dflt_src_name if !exists($self->{+SRC_NAME});
617             }
618             }
619              
620 225         475 $global_vars->{'=srcname'} = $self->{+SRC_NAME};
621              
622 225         479 my $src_dir = '.';
623 225         500 my $include_stack = {};
624              
625 225 100 100     916 if (!ref($args{src}) && index($args{src}, "\n") < 0) {
626             my $abs_path = abs_path($args{src})
627 14 50       428 or croak("'$args{src}': cannot resolve file name");
628 14         45 $include_stack->{$abs_path} = undef;
629 14         50 my ($vol, $dirs) = splitpath($abs_path);
630 14 50 50     212 $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         761 $self->{+SRC_NAME},
638             );
639              
640             my @sections = ((exists($self->{+SECTIONS_H}{$tocopy_section}) ? () : $tocopy_section),
641 200 100       602 @{$self->{+SECTIONS}},
  200         576  
642             );
643              
644 200         382 foreach my $section (@sections) {
645 487         758 my $sec_vars = $variables->{$section};
646              
647 487         983 foreach my $variable (keys(%$sec_vars)) {
648 923         1351 my $value = $sec_vars->{$variable};
649 923         1511 $sec_vars->{$variable} = $self->$_expand_vars($section, $variable, $value);
650             }
651             }
652              
653 172 100       410 if ($cleanup) {
654 167         402 foreach my $section (keys(%$variables)) {
655 414         524 my $sec_vars = $variables->{$section};
656 414         691 foreach my $var (keys(%$sec_vars)) {
657 810 100       1460 delete $sec_vars->{$var} if index($var, '=') >= 0;
658             }
659             }
660              
661 167 100 100     787 delete $variables->{$self->{+TOCOPY_SECTION}} if (!$tocopy_sec_declared && !%$tocopy_sec_vars);
662             }
663             else {
664 5 100       16 if ($self->{+GLOBAL_MODE}) {
665 2         4 foreach my $section (keys(%$variables)) {
666 6         7 my $sec_vars = $variables->{$section};
667 6         10 $sec_vars->{'='} = $section;
668             }
669 2         6 @{$tocopy_sec_vars}{keys(%$global_vars)} = values(%$global_vars);
  2         9  
670             }
671             else {
672 3         11 foreach my $section (keys(%$variables)) {
673 9         18 my $sec_vars = $variables->{$section};
674 9         19 $sec_vars->{'='} = $section;
675 9         27 @{$sec_vars}{keys(%$global_vars)} = values(%$global_vars);
  9         60  
676             }
677             }
678             }
679              
680 172 100       453 $self->{+TOCOPY_SECTION} = $backup->{tocopy_section} if exists($backup->{tocopy_section});
681 172 100       395 $self->{+TOCOPY_VARS} = $backup->{tocopy_vars} if exists($backup->{tocopy_vars});
682 172 100       371 $self->{+NOT_TOCOPY} = $backup->{not_tocopy} if exists($backup->{not_tocopy});
683 172         288 $backup = {};
684              
685 172         1083 return $self;
686             }
687              
688              
689 11     11 1 64 sub current_tocopy_section { $_[0]->{+CURR_TOCP_SECTION} }
690 22     22 1 136 sub tocopy_section { $_[0]->{+TOCOPY_SECTION} }
691 20     20 1 2514 sub global_mode { $_[0]->{+GLOBAL_MODE} }
692              
693             sub sections {
694 23 100   23 1 1076 return defined($_[0]->{+SECTIONS}) ? [@{$_[0]->{+SECTIONS}}] : undef;
  22         118  
695             }
696              
697             sub sections_h {
698 22 100   22 1 4325 return defined($_[0]->{+SECTIONS_H}) ? +{ %{$_[0]->{+SECTIONS_H}} } : undef;
  21         151  
699             }
700              
701 13     13 1 53 sub separator { $_[0]->{+SEPARATOR} }
702 7     7 1 457 sub src_name { $_[0]->{+SRC_NAME} }
703              
704              
705             sub variables {
706 170   100 170 1 6636 my $vars = $_[0]->{+VARIABLES} // return undef;
707 169         417 return { map { $_ => {%{$vars->{$_}}} } keys(%$vars) };
  322         393  
  322         2370  
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   720 my ($args, $allowed_args) = @_;
840 428         1081 foreach my $key (keys(%$args)) {
841 571 100       1359 croak("'$key': unsupported argument") if !exists($allowed_args->{$key});
842             }
843 426         926 delete @{$args}{ grep { !defined($args->{$_}) } keys(%$args) };
  426         813  
  568         1450  
844             }
845              
846              
847             1; # End of Config::INI::RefVars
848              
849              
850             __END__