Hello,
Thanks so much for getting back to me Tom. I've attached a patch file. There are a few other things I included in the patch that I think would be cool to get in, if you wanted to review them and make a decision.
An overview:
I've attempted to provide options that allow the data structure to be more predictably organized for easier programmatic traversal:
-ForceBlockName
*Ensures that blocks are always stored with a left-hand and right-hand value. If
there is no right-hand value, the right-hand value key will simply be undefined.
Example:
Markup 1:
<Foo "bar">
Blah Blah
</Foo>
Would produce this data structure:
$VAR1= {
'Foo' => {
'Bar' => {
'Blah' => 'Blah',
},
},
};
Markup 2:
<Foo>
<Bar>
Blah Blah
</Bar>
</Foo>
Would produce this data structure:
$VAR1= {
'Foo' => {
undef => {
'Bar' => {
undef => {
'Blah' => 'Blah',
},
},
},
},
};
This way, you could create a map that leads directly the the option value using
the keys of the structure which would look like this:
my @map=('Foo', undef, 'Bar', undef', 'Blah');
This predictable structure allow us to maintain the association of the block
and any provided block name.
Provided the last key points to an option value, you can use all elements in the
map (minus the last one) to create a list of key/value pairs for every layer of
block context, where the key is the block, and the value is the block name:
use Tie::DxHash;
my $sections={};
tie %{$sections}, "Tie::DxHash";
%{$sections}=@map;
You could then use each key/value pair to programmatically evaluate the
meaning of every block.
-ForceValueArray
*Forces single VALUES to be stored in an array. I took out the syntax requirement
(enclosing the option value in square brackets in the config file), because I feel
the point of this option is to allow the programmer to always expect option values
to be stored in an array (rather than sometimes being an array and sometimes
being a hash) when programmatically traversing the data structure. I don't think
arbitrary requirements should be placed on the manager of the config file
(who might not be affiliated with the programmer) to facilitate this.
-ForceBlockArray
*Forces single BLOCKS to be stored in an array. This is the same idea as
ForceValueArray, but it applies specifically to the storage of blocks.
In the current release of Config::General, arrays will be generated in the hash
structure if there are duplicate elements, but arrays will not be generated if there
is only one element.
For example, two VirtualHost blocks for two distinct users on a shared IP address:
<VirtualHost 192.168.1.10:80>
ServerName example-a.com
DocumentRoot /home/a/public_html
</VirtualHost>
<VirtualHost 192.168.1.10:80>
ServerName example-b.com
DocumentRoot /home/b/public_html
</VirtualHost>
...Create a data structure that looks like this:
$VAR1 = {
'VirtualHost' => {
'192.168.1.10:80' => [ #Array of Hashes
{
'ServerName' => 'example-a.com',
'DocumentRoot' => '/home/a/public_html',
},
{
'ServerName' => 'example-b.com',
'DocumentRoot' => '/home/b/public_html',
},
],
},
};
Whereas a single VirtualHost block for a single user on a dedicated IP address...:
<VirtualHost 192.168.1.120:80>
ServerName example-c.com
DocumentRoot /home/c/public_html
</VirtualHost>
...Produces a data structure that looks like this:
$VAR1 = {
'VirtualHost' => {
'192.168.1.10:80' => { #No array
'ServerName' => 'example-c.com',
'DocumentRoot' => '/home/c/public_html',
},
},
};
This variation in the data's internal structure makes it difficult to
programmatically traverse the structure... ForceBlockArray would
ensure that the programmer can expect all elements contained in
a block will be found in the array that is directly following the block:
The dedicated IP example:
<VirtualHost 192.168.1.120:80>
ServerName example-c.com
DocumentRoot /home/c/public_html
</VirtualHost>
...Would then produce a structure that looks like this:
$VAR1 = {
'VirtualHost' => {
'192.168.1.10:80' => {
[ #For an array
'ServerName' => 'example-c.com',
'DocumentRoot' => '/home/c/public_html',
],
},
},
};
-NormalizeBlockName
*This is just another way to provide distinction between blocks and block names.
Example:
-NormalizeBlock => sub { my $x = shift; $x =~ s/^/BLOCK:/; $x; }
-NormalizeBlockName => sub { my $x = shift; $x =~ s/^/BLOCKNAME:/; $x; }
-NormalizeOption => sub { my $x = shift; $x =~ s/^/OPTION:/; $x; }
-NormalizeValue => sub { my $x = shift; $x =~ s/^/BLOCKVALUE:/; $x; }
I've attached the .patch file with all my changes.
On Tue Jul 24 08:50:24 2018, TLINDEN wrote:
Show quoted text> Hello Nolan,
>
> On Mon Jul 23 13:31:49 2018, himynameisnolan@gmail.com wrote:
> > I made a quick fix for this
>
> Can you please provide a patch file, you code snippet seems to be
> incomplete.
>
>
> Thanks,
> Tom
--- orig/General.pm 2018-07-25 10:06:25.147883737 -0600
+++ new/General.pm 2018-07-25 10:20:43.594506937 -0600
@@ -89,10 +89,13 @@
files => {}, # which files we have read, if any
UTF8 => 0,
SaveSorted => 0,
- ForceArray => 0, # force single value array if value enclosed in []
+ ForceValueArray => 0, # force values to be stored in an array
+ ForceBlockArray => 0, # force block contents to be stored in an array
+ ForceBlockName => 0, # force undefined block name if no block name is provided.
AllowSingleQuoteInterpolation => 0,
NoEscape => 0,
NormalizeBlock => 0,
+ NormalizeBlockName => 0,
NormalizeOption => 0,
NormalizeValue => 0,
Plug => {},
@@ -900,7 +903,7 @@
$option = $this->{NormalizeOption}($option);
}
- if ($value && $value =~ /^"/ && $value =~ /"$/) {
+ if (defined $value && $value =~ /^"/ && $value =~ /"$/) {
$value =~ s/^"//; # remove leading and trailing "
$value =~ s/"$//;
}
@@ -920,8 +923,10 @@
}
if($this->{NormalizeBlock}) {
$block = $this->{NormalizeBlock}($block);
+ }
+ if($this->{NormalizeBlockName}) {
if (defined $blockname) {
- $blockname = $this->{NormalizeBlock}($blockname);
+ $blockname = $this->{NormalizeBlockName}($blockname);
if($blockname eq "") {
# if, after normalization no blockname is left, remove it
$blockname = undef;
@@ -988,9 +993,12 @@
}
}
else {
- if($this->{ForceArray} && defined $value && $value =~ /^\[\s*(.+?)\s*\]$/) {
+ if($this->{ForceValueArray}) {
+ if(defined $value) {
+ ($value) = $value =~ /^(?>\[\s*+)?(.+?)(?>\s*+\])?$/;
+ }
# force single value array entry
- push @{$config->{$option}}, $this->_parse_value($config, $option, $1);
+ push @{$config->{$option}}, $this->_parse_value($config, $option, $value);
}
else {
# standard config option, insert key/value pair into node
@@ -1014,7 +1022,7 @@
push @newcontent, $_; # push onto new content stack
}
else { # calling myself recursively, end of $block reached, $block_level is 0
- if (defined $blockname) {
+ if (defined $blockname or $this->{ForceBlockName}) {
# a named block, make it a hashref inside a hash within the current node
if (! exists $config->{$block}) {
@@ -1070,7 +1078,12 @@
$tmphash->{__stack} = $this->_copy($config->{__stack});
}
- $config->{$block}->{$blockname} = $this->_parse($tmphash, \@newcontent);
+ if ($this->{ForceBlockArray}) {
+ push @{$config->{$block}->{$blockname}}, $this->_parse($tmphash, \@newcontent);
+ }
+ else {
+ $config->{$block}->{$blockname} = $this->_parse($tmphash, \@newcontent);
+ }
}
}
else {
@@ -1124,7 +1137,12 @@
$tmphash->{__stack} = $this->_copy($config->{__stack});
}
- $config->{$block} = $this->_parse($tmphash, \@newcontent);
+ if ($this->{ForceBlockArray}) {
+ push @{$config->{$block}}, $this->_parse($tmphash, \@newcontent);
+ }
+ else {
+ $config->{$block} = $this->_parse($tmphash, \@newcontent);
+ }
}
}
undef $blockname;
@@ -1346,18 +1364,12 @@
}
if (ref($config->{$entry}) eq 'ARRAY') {
- if( $this->{ForceArray} && scalar @{$config->{$entry}} == 1 && ! ref($config->{$entry}->[0]) ) {
- # a single value array forced to stay as array
- $config_string .= $this->_write_scalar($level, $entry, '[' . $config->{$entry}->[0] . ']');
- }
- else {
- foreach my $line ( $this->{SaveSorted} ? sort @{$config->{$entry}} : @{$config->{$entry}} ) {
- if (ref($line) eq 'HASH') {
- $config_string .= $this->_write_hash($level, $entry, $line);
- }
- else {
- $config_string .= $this->_write_scalar($level, $entry, $line);
- }
+ foreach my $line ( $this->{SaveSorted} ? sort @{$config->{$entry}} : @{$config->{$entry}} ) {
+ if (ref($line) eq 'HASH') {
+ $config_string .= $this->_write_hash($level, $entry, $line);
+ }
+ else {
+ $config_string .= $this->_write_scalar($level, $entry, $line);
}
}
}
@@ -2103,6 +2115,10 @@
This removes trailing whitespaces of block names.
+=item B<-NormalizeBlockName>
+
+Same as B<-NormalizeBlock> but applied on block names only.
+
=item B<-NormalizeOption>
Same as B<-NormalizeBlock> but applied on options only.
@@ -2472,13 +2488,24 @@
=head2 FORCE SINGLE VALUE ARRAYS
You may also force a single config line to get parsed into an array by
-turning on the option B<-ForceArray> and by surrounding the value of the
-config entry by []. Example:
+turning on the option B<-ForceValueArray>. Example:
- hostlist = [ foo.bar ]
+ hostlist = foo.bar
Will be a singlevalue array entry if the option is turned on. If you want
-it to remain to be an array you have to turn on B<-ForceArray> during save too.
+it to remain to be an array you have to turn on B<-ForceValueArray> during save too.
+
+=head2 FORCE SINGLE BLOCK ARRAYS
+
+You may also force a single config block to get parsed into an array by
+turning on the option B<-ForceBlockArray>. Example:
+
+ <VirtualHost 192.168.1.10:80>
+ ServerName localhost.com
+ DocumentRoot /usr/local/apache/htdocs
+</VirtualHost>
+
+Will be a singleblock array entry if the option is turned on.
=head1 LONG LINES