Skip to content

Commit

Permalink
Item13594: Merge feature: concatenate attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
www-data authored and Jlevens committed Nov 21, 2015
2 parents 5bccd9a + 9545209 commit b4ea64a
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 24 deletions.
104 changes: 104 additions & 0 deletions UnitTestContrib/test/unit/AttrsTests.pm
Expand Up @@ -313,4 +313,108 @@ sub test_endsWithEscapedQuote {
$this->assert( $attrs->isEmpty() );
}

sub test_concat_friendly {
my $this = shift;

my $attrs = Foswiki::Attrs->new(
"var1=\"val1\" var2=\"val2\" var3 = \"3\" c=\"One\" +\" and \" d=\"interloper\" c+=two c+=\" and three\" ",
1
);

$this->assert_str_equals( "val1", $attrs->remove("var1") );
$this->assert_str_equals( "val2", $attrs->remove("var2") );
$this->assert_str_equals( "3", $attrs->remove("var3") );
$this->assert_str_equals( "One and two and three", $attrs->remove("c") );
$this->assert_str_equals( "interloper", $attrs->remove("d") );
$this->assert( $attrs->isEmpty() );
}

# Many _unfriendly test cases added here
# VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV

sub test_isEmpty_unfriendly {
my $this = shift;

my $attrs = Foswiki::Attrs->new(undef);
$this->assert( $attrs->isEmpty() );
$attrs = Foswiki::Attrs->new("");
$this->assert( $attrs->isEmpty() );
$attrs = Foswiki::Attrs->new(" \t \n\t");
$this->assert( $attrs->isEmpty() );
}

sub test_1st_is_default_unfriendly {
my $this = shift;

my $attrs = Foswiki::Attrs->new("\"wibble\"");
$this->assert( !$attrs->isEmpty() );
$this->assert_str_equals( "wibble", $attrs->remove("_DEFAULT") );
$this->assert_null( $attrs->{"_DEFAULT"} );
$this->assert( $attrs->isEmpty() );

# Note difference with the _friendly version of this
$attrs = Foswiki::Attrs->new("\"wibble\" \"fleegle\"");
$this->assert_str_equals( "wibble\" \"fleegle",
$attrs->remove("_DEFAULT") );
$this->assert( $attrs->isEmpty() );
}

sub test_1st_is_default_assign_follows_unfriendly {
my $this = shift;

my $attrs = Foswiki::Attrs->new("\"wibble\" a=\"fleegle\"");
$this->assert_str_equals( "wibble", $attrs->remove("_DEFAULT") );
$this->assert_str_equals( "fleegle", $attrs->remove("a") );
$this->assert( $attrs->isEmpty() );
}

sub test_1st_is_default_concat_follows_unfriendly {
my $this = shift;

my $attrs = Foswiki::Attrs->new("\"wibble\" a+=\"fleegle\"");
$this->assert_str_equals( "wibble", $attrs->remove("_DEFAULT") );
$this->assert_str_equals( "fleegle", $attrs->remove("a") );
$this->assert( $attrs->isEmpty() );
}

sub test_concat_unfriendly {
my $this = shift;

my $attrs =
Foswiki::Attrs->new(
"var1=\"val1\" var2=\"val2\" var3 = \"3\" c=\"One\" +\" and \" d=\"interloper\" c+=\"two\" c+=\" and three\" "
);

$this->assert_str_equals( "val1", $attrs->remove("var1") );
$this->assert_str_equals( "val2", $attrs->remove("var2") );
$this->assert_str_equals( "3", $attrs->remove("var3") );
$this->assert_str_equals( "One and two and three", $attrs->remove("c") );
$this->assert_str_equals( "interloper", $attrs->remove("d") );
$this->assert( $attrs->isEmpty() );
}

sub test_doubleQuoted_unfriendly {
my $this = shift;

my $attrs = Foswiki::Attrs->new(
"var1 =\"val 1\" var2= \"val 2\" \" default \" var3 = \" val 3 \"");
$this->assert_str_equals( "val 1", $attrs->remove("var1") );
$this->assert_str_equals( "val 2", $attrs->remove("var2") );
$this->assert_str_equals( " val 3 ", $attrs->remove("var3") );
$this->assert_str_equals( " default ", $attrs->remove("_DEFAULT") );
$this->assert( $attrs->isEmpty() );
}

sub test_toString_unfriendly {
my $this = shift;

my $attrs = Foswiki::Attrs->new("a =\"\\\"\" b=\"'\" \"'\" ");
my $s = $attrs->stringify();
$attrs = Foswiki::Attrs->new( $attrs->stringify() );
$this->assert_str_equals( "\"", $attrs->remove("a") );
$this->assert_str_equals( "'", $attrs->remove("b") );
$this->assert_str_equals( "'", $attrs->remove("_DEFAULT") );
$this->assert( $attrs->isEmpty() );
}

1;
127 changes: 103 additions & 24 deletions core/lib/Foswiki/Attrs.pm
Expand Up @@ -97,26 +97,84 @@ sub new {
return $this;
}

sub _assign { ${ $_[0] } = $_[1]; }
sub _append { ${ $_[0] } .= $_[1]; }

# Kept as potential new FeatureProposal:
# sub _prepend { ${$_[0]} = $_[1] . ${$_[0]}; }

# Perl warned about undefined $_[1] (and the sub was buggy)
# hence 'my ($sr, $v) = @_;' which fixed it
sub _multi { my ( $sr, $v ) = @_; ${$sr} x= $v if $v =~ /[0-9]+/; }

my %Ops = (

# Op => [ WithName , Without Name ]
'=' => [ \&_assign, undef ],

'+=' => [ \&_append, undef ],
'+' => [ undef, \&_append ],

# Kept as potential FeatureProposal
# The community will also need to agree operators to use
# '-=' => [ \&_prepend , undef ] ,
# '-' => [ undef , \&_prepend ] ,

);

sub OpRegex {
my ($opType) = @_;
return join(
'|', map { quotemeta($_); }
grep { $Ops{$_}->[$opType]; }

# We need to reverse sort to ensure that in cases when we have two
# synonymous ops (e.g. '+=' & '=') then we always handle += first
# (remember random hash order otherwise)
# This ensures that in the _friendly case parm=56 +=78 always give
# parm as '5678' and not '56=78'
reverse sort { length($a) <=> length($b) }
keys %Ops
);
}

my $nameOp = OpRegex(0);
my $nonmOp = OpRegex(1);

sub _unfriendly {
my ( $this, $string ) = @_;

my $key = '_DEFAULT';
my $first = 1;

if ( $string =~ s/^\s*\"(.*?)\"\s*(?=[a-z0-9_]+\s*=\s*\"|$)//si ) {
if ( $string =~
s/^\s*\"(.*?)\"\s*(?=([a-z0-9_]+\s*(?:$nameOp)|[a-z0-9_]*\s*(?:$nonmOp))\s*\"|$)//is
)
{
$this->{_DEFAULT} = $1;
}

while ( $string =~ m/\S/s ) {

# name="value" pairs
if ( $string =~ s/^\s*([a-z0-9_]+)\s*=\s*\"(.*?)\"//is ) {
$this->{$1} = $2;
# name $op "value" pairs
if ( $string =~ s/^\s*([a-z0-9_]+)\s*($nameOp)\s*\"(.*?)\"//is ) {
$key = $1;
$Ops{$2}[0]( \$this->{$key}, $3 );
$first = 0;
}

# $op "value" (the name (or key) is the most recent one seen)
elsif ( $string =~ s/^\s*($nonmOp)\s*\"(.*?)\"//is ) {
$Ops{$1}[1]( \$this->{$key}, $2 );
$first = 0;
}

# simple double-quoted value with no name, sets the default
elsif ( $string =~ s/^\s*\"(.*?)\"//s ) {
$this->{_DEFAULT} = $1
unless defined( $this->{_DEFAULT} );
unless ( defined( $this->{_DEFAULT} ) ) {
$key = '_DEFAULT';
$this->{_DEFAULT} = $1;
}
$first = 0;
}

Expand All @@ -132,43 +190,64 @@ sub _unfriendly {
sub _friendly {
my ( $this, $string ) = @_;

my $first = 1;
my $key = "_DEFAULT";

while ( $string =~ m/\S/s ) {

# name="value" pairs
if ( $string =~ s/^[\s,]*([a-z0-9_]+)\s*=\s*\"(.*?)\"//is ) {
$this->{$1} = $2;
$first = 0;
# name $op "value" pairs
if ( $string =~ s/^[\s,]*([a-z0-9_]+)\s*($nameOp)\s*\"(.*?)\"//is ) {
$key = $1;
$Ops{$2}[0]( \$this->{$key}, $3 );
}

# simple double-quoted value with no name, sets the default
elsif ( $string =~ s/^[\s,]*\"(.*?)\"//s ) {
$this->{_DEFAULT} = $1
unless defined( $this->{_DEFAULT} );
$first = 0;
unless ( defined( $this->{_DEFAULT} ) ) {
$key = '_DEFAULT';
$this->{_DEFAULT} = $1;
}
}

# name $op 'value' pairs
elsif ( $string =~ s/^[\s,]*([a-z0-9_]+)\s*($nameOp)\s*'(.*?)'//is ) {
$key = $1;
$Ops{$2}[0]( \$this->{$key}, $3 );
}

# name $op value pairs
elsif (
$string =~ s/^[\s,]*([a-z0-9_]+)\s*($nameOp)\s*([^\s,\}\'\"]*)//is )
{
$key = $1;
$Ops{$2}[0]( \$this->{$key}, $3 );
}

# name='value' pairs
elsif ( $string =~ s/^[\s,]*([a-z0-9_]+)\s*=\s*'(.*?)'//is ) {
$this->{$1} = $2;
# $op "value"
elsif ( $string =~ s/^[\s,]*($nonmOp)\s*\"(.*?)\"//is ) {
$Ops{$1}[1]( \$this->{$key}, $2 );
}

# name=value pairs
elsif ( $string =~ s/^[\s,]*([a-z0-9_]+)\s*=\s*([^\s,\}\'\"]*)//is ) {
$this->{$1} = $2;
# $op 'value'
elsif ( $string =~ s/^[\s,]*($nonmOp)\s*'(.*?)'//is ) {
$Ops{$1}[1]( \$this->{$key}, $2 );
}

# $op value
elsif ( $string =~ s/^[\s,]*($nonmOp)\s*([^\s,\}\'\"]*)//is ) {
$Ops{$1}[1]( \$this->{$key}, $2 );
}

# simple single-quoted value with no name, sets the default
elsif ( $string =~ s/^[\s,]*'(.*?)'//s ) {
$this->{_DEFAULT} = $1
unless defined( $this->{_DEFAULT} );
unless ( defined( $this->{_DEFAULT} ) ) {
$key = '_DEFAULT';
$this->{_DEFAULT} = $1;
}
}

# simple name with no value (boolean, or _DEFAULT)
elsif ( $string =~ s/^[\s,]*([a-z][a-z0-9_]*)\b//is ) {
my $key = $1;
$this->{$key} = 1;
$this->{$1} = 1;
}

# otherwise the whole string - sans padding - is the default
Expand Down

0 comments on commit b4ea64a

Please sign in to comment.