Skip to content

Commit

Permalink
Item14237: New method expandStr() to replace expandValue()
Browse files Browse the repository at this point in the history
expandStr() brings in support for ${Key.SubKey} macro format as a part
of movement to get rid of %Foswiki::cfg global. Another form of the
macro is ${Key}{SubKey}.

expandStr() uses parametrized calling convention and supports expanding
both free-form strings and keys defined by their names. Handling of
undefs is similar to that in expandValue: i.e. undef could be replaced
with a string (though expansStr can replace it with any user-defined
value), or undef would be returned for the expansion, or an exception
could be thrown.
  • Loading branch information
vrurg committed Apr 11, 2017
1 parent 1a572d7 commit bf5630c
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 5 deletions.
110 changes: 110 additions & 0 deletions UnitTestContrib/test/unit/ConfigTests.pm
Original file line number Diff line number Diff line change
Expand Up @@ -588,4 +588,114 @@ sub test_enhanceNonExisting {
);
}

sub test_expand {
my $this = shift;

my $cfg = $this->app->cfg;
my $cfgData = $cfg->data;

$cfgData->{ExpTest} = {
Key1 => {
SubKey => "{subkey}",
TestKeys => {
Test1 => "{test1}",
Test2 => '{test2 and ${ExpTest.Key1.TestKeys.Test1}}',
},
UndefKey => undef,
},
Key2 => {
Key2_1 => '{expanding ${ExpTest.Key1.SubKey}}',
Key2_2 => '{ref to undef ${ExpTest.Key1.UndefKey}}',
},

Exps => {
Exp1 => 'Expanding ExpTest.Key2.Key2_1: ${ExpTest.Key2.Key2_1}',
NoExp1 => 'Must not expand \${ExpTest.Key2.Key2_1}',
ExpUndef => 'Undef: "${ExpTest.Key2.Key2_2}"',
},
};

my $estr = $cfg->expandStr( str => 'str:${ExpTest.Exps.Exp1}', );
$this->assert_equals(
"str:Expanding ExpTest.Key2.Key2_1: {expanding {subkey}}", $estr );

my @estrs = $cfg->expandStr(
str => [ 'str1:${ExpTest.Exps.Exp1}', 'str2:${ExpTest.Exps.NoExp1}', ],
);
$this->assert_deep_equals(
[
"str1:Expanding ExpTest.Key2.Key2_1: {expanding {subkey}}",
'str2:Must not expand ${ExpTest.Key2.Key2_1}'
],
\@estrs,
"Multi-valued str key"
);

$estr = $cfg->expandStr(
str => [ 'str1:${ExpTest.Exps.Exp1}', 'str2:${ExpTest.Exps.NoExp1}', ],
);
$this->assert_deep_equals(
[
"str1:Expanding ExpTest.Key2.Key2_1: {expanding {subkey}}",
'str2:Must not expand ${ExpTest.Key2.Key2_1}'
],
$estr,
"Multi-valued str key in scalar context"
);

@estrs = $cfg->expandStr(
str => 'str1:${ExpTest.Exps.Exp1}',
key => 'ExpTest.Exps.NoExp1',
);
$this->assert_deep_equals(
[
"str1:Expanding ExpTest.Key2.Key2_1: {expanding {subkey}}",
'Must not expand ${ExpTest.Key2.Key2_1}'
],
\@estrs,
"Single-valued str and key"
);

@estrs =
$cfg->expandStr( key => [ 'ExpTest.Exps.Exp1', 'ExpTest.Exps.NoExp1', ],
);
$this->assert_deep_equals(
[
"Expanding ExpTest.Key2.Key2_1: {expanding {subkey}}",
'Must not expand ${ExpTest.Key2.Key2_1}'
],
\@estrs,
"Multi-valued key"
);

$estr = $cfg->expandStr( key => 'ExpTest.Exps.Exp1' );
$this->assert_equals( "Expanding ExpTest.Key2.Key2_1: {expanding {subkey}}",
$estr );

$estr = $cfg->expandStr( key => 'ExpTest.Exps.NoExp1' );
$this->assert_equals( 'Must not expand ${ExpTest.Key2.Key2_1}', $estr );

$estr =
$cfg->expandStr( key => 'ExpTest.Exps.ExpUndef', undef => '*undef*', );
$this->assert_equals( 'Undef: "{ref to undef *undef*}"', $estr );

$estr = $cfg->expandStr( key => 'ExpTest.Exps.ExpUndef', undef => undef, );
$this->assert_equals( undef, $estr );

try {
$estr =
$cfg->expandStr( key => 'ExpTest.Exps.ExpUndef', undefFail => 1, );
Foswiki::Exception::Fatal->throw(
text => "Expansion of an undef value must have failed" );
}
catch {
my $e = Foswiki::Exception::Fatal->transmute( $_, 0 );

$this->assert_matches(
qr/Failed to expand string '.*?': key ExpTest.Key1.UndefKey value is undefined/,
$e->text
);
};
}

1;
159 changes: 155 additions & 4 deletions core/lib/Foswiki/Config.pm
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# See bottom of file for license and copyright information

package Foswiki::Exception::_expandStr::UndefVal {
use Foswiki::Class;
extends qw(Foswiki::Exception::Harmless);
}

package Foswiki::Config;

=begin TML
Expand Down Expand Up @@ -33,7 +38,9 @@ use constant TRAUTO => 1;
# This should be the one place in Foswiki that knows the syntax of valid
# configuration item keys. Only simple scalar hash keys are supported.
#
our $ITEMREGEX = qr/(?:\{(?:'(?:\\.|[^'])+'|"(?:\\.|[^"])+"|[A-Za-z0-9_]+)\})+/;
our $ITEMREGEX =
qr/(?:\{(?:'(?:\\.|[^'])+'|"(?:\\.|[^"])+"|[A-Za-z0-9_\.]+)\})+/;
our $KEYMACROREGEX = qr/\$(?:(?<key>(?:\{\w+\})+)|\{(?<key>[\w\.]+)\})/;

# Generic booleans, used in some older LSC's
our $TRUE = 1;
Expand Down Expand Up @@ -656,6 +663,150 @@ sub _handleExpand {
return '';
}

sub _doExpandStr {
my $this = shift;
my $str = shift;
my %params = @_;

local $params{__expLevel} = $params{__expLevel} + 1;

# Reset pos
$str =~ /^/gs;

my $expStr = "";

try {
while ($str =~ /(?<txt>.*?)\\(?<chr>.)/gsc
|| $str =~ /(?<txt>.*?)$KEYMACROREGEX/gsc )
{
$expStr .= $+{txt};
if ( defined $+{chr} ) {

# Expand \ escaping
$expStr .= $+{chr};
}
else {
my $key = $+{key};
my $keyVal = $this->get($key);
if ( defined $keyVal ) {
$expStr .= $this->_expandStr( $keyVal, %params );
}
else {
if ( $params{undefFail} ) {
Foswiki::Exception::Fatal->throw(
text => "Failed to expand string '"
. $str
. "': key "
. $key
. " value is undefined" );
}
Foswiki::Exception::_expandStr::UndefVal->throw(
text => $key )
unless defined $params{undef};
$expStr .= $params{undef};
}
}
}

$str =~ /\G(?<txt>.*)$/;
$expStr .= $+{txt};
}
catch {
my $e = Foswiki::Exception::Fatal->transmute( $_, 0 );

#if ( $e->isa('Foswiki::Exception::_expandStr::UndefVal') ) {
# $expStr = undef;
#}
#else {
$e->rethrow;

#}
};

return $expStr;
}

sub _expandStr {
my $this = shift;
my $str = shift;
my %params = @_;
my $expStr;

$params{__expLevel} //= 0;

try {
$expStr = $this->_doExpandStr( $str, %params );
}
catch {
my $e = Foswiki::Exception::Fatal->transmute( $_, 0 );
if ( !$params{__expLevel}
&& $e->isa('Foswiki::Exception::_expandStr::UndefVal') )
{
$expStr = undef;
}
else {
$e->rethrow;
}
};
return $expStr;
}

# Expand a string possibly containing config value macro ${Key}
# Profile keys:
# str – a string to expand or an arrayref of strings.
# key – a config key to expand it's value or an array ref of keys.
# undef - what to replace undef value with
# undefFail - true if undef must cause an exception.
#
# Returns a scalar only if has been called in a scalar context and only one
# value was expanded. Note that if both str and key keys are passed it means at
# least two strings are expanded.
# If more than one string have been expanded then returns an array ref in scalar
# context or a list.
pluggable expandStr => sub {
my $this = shift;
my %params = @_;

my ( @strs, @estrs );

# $isList is true if a list is requested; i.e. any of str or key are passed
# in with an array ref.
my $isList;

if ( $params{str} ) {
if ( my $rt = ref( $params{str} ) ) {
Foswiki::Exception::Fatal->throw(
"expandStr method's str parameter cannot be " . $rt . " ref" )
unless $rt eq 'ARRAY';
push @strs, @{ $params{str} };
$isList = 1;
}
else {
push @strs, $params{str};
}
delete $params{str};
}

if ( $params{key} ) {
if ( my $rt = ref( $params{key} ) ) {
Foswiki::Exception::Fatal->throw(
"expandStr method's key parameter cannot be " . $rt . " ref" )
unless $rt eq 'ARRAY';
push @strs, $this->get($_) foreach @{ $params{key} };
$isList = 1;
}
else {
push @strs, $this->get( $params{key} );
}
delete $params{key};
}

push @estrs, $this->_expandStr( $_, %params ) foreach @strs;

return (
wantarray ? @estrs : ( @estrs > 1 || $isList ? [@estrs] : $estrs[0] ) );
};

=begin TML
---++ ObjectMethod bootstrapSystemSettings()
Expand Down Expand Up @@ -1233,8 +1384,8 @@ sub parseKeys {
) unless ref( $path[0] ) eq 'ARRAY';
@keys = $this->parseKeys( @{ $path[0] } );
}
elsif ( $path[0] =~ /^(?:{[^{}]+})+$/ ) {
@keys = $path[0] =~ /{([^{}]+)}/g;
elsif ( $path[0] =~ /^(?:\{[^\{\}]+\})+$/ ) {
@keys = $path[0] =~ /\{([^\{\}]+)\}/g;
}
else {
@keys = split /\./, $path[0];
Expand Down Expand Up @@ -2781,7 +2932,7 @@ sub _specCfgKey {
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Copyright (C) 2016 Foswiki Contributors. Foswiki Contributors
Copyright (C) 2016-2017 Foswiki Contributors. Foswiki Contributors
are listed in the AUTHORS file in the root of this distribution.
NOTE: Please extend that file, not this notice.
Expand Down
2 changes: 1 addition & 1 deletion core/lib/Foswiki/Object.pm
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ sub _traceMsg {
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Copyright (C) 2016 Foswiki Contributors. Foswiki Contributors
Copyright (C) 2016-2017 Foswiki Contributors. Foswiki Contributors
are listed in the AUTHORS file in the root of this distribution.
NOTE: Please extend that file, not this notice.
Expand Down

0 comments on commit bf5630c

Please sign in to comment.