I had a couple of spare hours and decided to hack on this topic.
I don't think the work is completely finished (especially it lacks
hash access string interpolation which is much more complex than it
appears, advice needed here).
I'm looking for reviews, comments and testing as usual.
Thanks,
Brice
Original commit msg:
This bring a new container syntax to the Puppet DSL: hashes.
Hashes are defined like Ruby Hash:
{ key1 => val1, ... }
Hash keys are strings, but hash values can be any possible right
values admitted in Puppet DSL (ie function call, variables access...)
Currently it is possible:
1) to assign hashes to variable
$myhash = { key1 => "myval", key2 => $b }
2) to access hash members (recursively) from a variable containing
a hash (works for arrays and mix of arrays and hashes too):
$myhash = { key => { subkey => "b" }}
notice($myhash[key][subjey]]
3) to use hash member access as resource title
4) to use hash in default definition parameter or resource parameter if
the type supports it (known for the moment).
It is not possible to string interpolate an hash access. If it proves
to be an issue it can be added or work-arounded with a string concatenation
operator easily.
It is not possible to use an hash as a resource title. This might be
possible once we support compound resource title.
Unlike the proposed syntax in the ticket it is not possible to assign
individual hash member (mostly to respect write once nature of variable
in puppet).
Signed-off-by: Brice Figureau <brice-...@daysofwonder.com>
---
lib/puppet/parser/ast.rb | 1 +
lib/puppet/parser/ast/asthash.rb | 34 +
lib/puppet/parser/ast/leaf.rb | 21 +
lib/puppet/parser/grammar.ra | 48 +
lib/puppet/parser/parser.rb | 2001 +++++++++++++++++++++-----------------
lib/puppet/parser/scope.rb | 5 +-
spec/unit/parser/ast/asthash.rb | 64 ++
spec/unit/parser/ast/leaf.rb | 83 ++
spec/unit/parser/scope.rb | 10 +
test/data/snippets/hash.pp | 33 +
test/language/snippets.rb | 7 +
11 files changed, 1389 insertions(+), 918 deletions(-)
create mode 100644 lib/puppet/parser/ast/asthash.rb
create mode 100644 spec/unit/parser/ast/asthash.rb
create mode 100644 test/data/snippets/hash.pp
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index ad8af74..dab1091 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -92,6 +92,7 @@ end
# And include all of the AST subclasses.
require 'puppet/parser/ast/arithmetic_operator'
require 'puppet/parser/ast/astarray'
+require 'puppet/parser/ast/asthash'
require 'puppet/parser/ast/branch'
require 'puppet/parser/ast/boolean_operator'
require 'puppet/parser/ast/caseopt'
diff --git a/lib/puppet/parser/ast/asthash.rb b/lib/puppet/parser/ast/asthash.rb
new file mode 100644
index 0000000..2860657
--- /dev/null
+++ b/lib/puppet/parser/ast/asthash.rb
@@ -0,0 +1,34 @@
+require 'puppet/parser/ast/leaf'
+
+class Puppet::Parser::AST
+ class ASTHash < Leaf
+ include Enumerable
+
+ def [](index)
+ end
+
+ # Evaluate our children.
+ def evaluate(scope)
+ items = {}
+
+ @value.each_pair do |k,v|
+ items.merge!({ k => v.safeevaluate(scope) })
+ end
+
+ return items
+ end
+
+ def merge(hash)
+ case hash
+ when ASTHash
+ @value = @value.merge(hash.value)
+ when Hash
+ @value = @value.merge(hash)
+ end
+ end
+
+ def to_s
+ "{" + @value.collect { |v| v.collect { |a| a.to_s }.join(' => ') }.join(', ') + "}"
+ end
+ end
+end
diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb
index 153120a..9b3496f 100644
--- a/lib/puppet/parser/ast/leaf.rb
+++ b/lib/puppet/parser/ast/leaf.rb
@@ -152,6 +152,27 @@ class Puppet::Parser::AST
end
end
+ class HashOrArrayAccess < AST::Leaf
+ attr_accessor :variable, :key
+
+ def evaluate(scope)
+ container = variable.respond_to?(:evaluate) ? variable.safeevaluate(scope) : variable
+ object = (container.is_a?(Hash) or container.is_a?(Array)) ? container : scope.lookupvar(container)
+
+ accesskey = key.respond_to?(:evaluate) ? key.safeevaluate(scope) : key
+
+ unless object.is_a?(Hash) or object.is_a?(Array)
+ raise Puppet::ParseError, "#{variable} is not an hash or array when accessing it with #{accesskey}"
+ end
+
+ return object[accesskey]
+ end
+
+ def to_s
+ "\$#{variable.to_s}[#{key.to_s}]"
+ end
+ end
+
class Regex < AST::Leaf
def initialize(hash)
super
diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra
index 4c74211..6b7b31b 100644
--- a/lib/puppet/parser/grammar.ra
+++ b/lib/puppet/parser/grammar.ra
@@ -131,6 +131,7 @@ namestring: name
| funcrvalue
| selector
| quotedtext
+ | hasharrayaccesses
| CLASSNAME {
result = ast AST::Name, :value => val[0][:value]
}
@@ -325,6 +326,7 @@ resourcename: quotedtext
| selector
| variable
| array
+ | hasharrayaccesses
assignment: VARIABLE EQUALS expression {
if val[0][:value] =~ /::/
@@ -403,6 +405,8 @@ rvalue: quotedtext
| selector
| variable
| array
+ | hash
+ | hasharrayaccesses
| resourceref
| funcrvalue
| undef
@@ -781,6 +785,50 @@ regex: REGEX {
result = ast AST::Regex, :value => val[0][:value]
}
+hash: LBRACE hashpairs RBRACE {
+ if val[1].instance_of?(AST::ASTHash)
+ result = val[1]
+ else
+ result = ast AST::ASTHash, { :value => val[1] }
+ end
+}
+ | LBRACE hashpairs COMMA RBRACE {
+ if val[1].instance_of?(AST::ASTHash)
+ result = val[1]
+ else
+ result = ast AST::ASTHash, { :value => val[1] }
+ end
+} | LBRACE RBRACE {
+ result = ast AST::ASTHash
+}
+
+hashpairs: hashpair
+ | hashpairs COMMA hashpair {
+ if val[0].instance_of?(AST::ASTHash)
+ result = val[0].merge(val[2])
+ else
+ result = ast AST::ASTHash, :value => val[0]
+ result.merge(val[2])
+ end
+}
+
+hashpair: key FARROW rvalue {
+ result = ast AST::ASTHash, { :value => { val[0] => val[2] } }
+}
+
+key: NAME { result = val[0][:value] }
+ | SQTEXT { result = val[0][:value] }
+ | DQTEXT { result = val[0][:value] }
+
+hasharrayaccess: VARIABLE LBRACK rvalue RBRACK {
+ result = ast AST::HashOrArrayAccess, :variable => val[0][:value], :key => val[2]
+}
+
+hasharrayaccesses: hasharrayaccess
+ | hasharrayaccess LBRACK rvalue RBRACK {
+ result = ast AST::HashOrArrayAccess, :variable => val[0], :key => val[2]
+}
+
end
---- header ----
require 'puppet'
diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb
index 376b818..826b58c 100644
--- a/lib/puppet/parser/parser.rb
+++ b/lib/puppet/parser/parser.rb
[patch elided]
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb
index d6d6630..e27640f 100644
--- a/lib/puppet/parser/scope.rb
+++ b/lib/puppet/parser/scope.rb
@@ -317,8 +317,11 @@ class Puppet::Parser::Scope
# lookup the value in the scope if it exists and insert the var
table[name] = lookupvar(name)
# concatenate if string, append if array, nothing for other types
- if value.is_a?(Array)
+ case value
+ when Array
table[name] += value
+ when Hash
+ table[name].merge!(value)
else
table[name] << value
end
diff --git a/spec/unit/parser/ast/asthash.rb b/spec/unit/parser/ast/asthash.rb
new file mode 100644
index 0000000..4c700fe
--- /dev/null
+++ b/spec/unit/parser/ast/asthash.rb
@@ -0,0 +1,64 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe Puppet::Parser::AST::ASTHash do
+ before :each do
+ @scope = Puppet::Parser::Scope.new()
+ end
+
+ it "should have a [] accessor" do
+ hash = Puppet::Parser::AST::ASTHash.new(:value => {})
+ hash.should respond_to(:[])
+ end
+
+ it "should have a merge functionality" do
+ hash = Puppet::Parser::AST::ASTHash.new(:value => {})
+ hash.should respond_to(:merge)
+ end
+
+ it "should be able to merge 2 AST hashes" do
+ hash = Puppet::Parser::AST::ASTHash.new(:value => { "a" => "b" })
+
+ hash.merge(Puppet::Parser::AST::ASTHash.new(:value => {"c" => "d"}))
+
+ hash.value.should == { "a" => "b", "c" => "d" }
+ end
+
+ it "should be able to merge with a ruby Hash" do
+ hash = Puppet::Parser::AST::ASTHash.new(:value => { "a" => "b" })
+
+ hash.merge({"c" => "d"})
+
+ hash.value.should == { "a" => "b", "c" => "d" }
+ end
+
+ it "should evaluate each hash value" do
+ key1 = stub "key1"
+ value1 = stub "value1"
+ key2 = stub "key2"
+ value2 = stub "value2"
+
+ value1.expects(:safeevaluate).with(@scope).returns("b")
+ value2.expects(:safeevaluate).with(@scope).returns("d")
+
+ operator = Puppet::Parser::AST::ASTHash.new(:value => { key1 => value1, key2 => value2})
+ operator.evaluate(@scope)
+ end
+
+ it "should return an evaluated hash" do
+ key1 = stub "key1"
+ value1 = stub "value1", :safeevaluate => "b"
+ key2 = stub "key2"
+ value2 = stub "value2", :safeevaluate => "d"
+
+ operator = Puppet::Parser::AST::ASTHash.new(:value => { key1 => value1, key2 => value2})
+ operator.evaluate(@scope).should == { key1 => "b", key2 => "d" }
+ end
+
+ it "should return a valid string with to_s" do
+ hash = Puppet::Parser::AST::ASTHash.new(:value => { "a" => "b", "c" => "d" })
+
+ hash.to_s.should == '{a => b, c => d}'
+ end
+end
diff --git a/spec/unit/parser/ast/leaf.rb b/spec/unit/parser/ast/leaf.rb
index e968150..48e0e1f 100755
--- a/spec/unit/parser/ast/leaf.rb
+++ b/spec/unit/parser/ast/leaf.rb
@@ -72,6 +72,89 @@ describe Puppet::Parser::AST::String do
end
end
+describe Puppet::Parser::AST::HashOrArrayAccess do
+ before :each do
+ @scope = stub 'scope'
+ end
+
+ it "should evaluate the variable part if necessary" do
+ @scope.stubs(:lookupvar).with("a").returns(["b"])
+
+ variable = stub 'variable', :evaluate => "a"
+ access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => variable, :key => 0 )
+
+ variable.expects(:safeevaluate).with(@scope).returns("a")
+
+ access.evaluate(@scope).should == "b"
+ end
+
+ it "should evaluate the access key part if necessary" do
+ @scope.stubs(:lookupvar).with("a").returns(["b"])
+
+ index = stub 'index', :evaluate => 0
+ access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => index )
+
+ index.expects(:safeevaluate).with(@scope).returns(0)
+
+ access.evaluate(@scope).should == "b"
+ end
+
+ it "should be able to return an array member" do
+ @scope.stubs(:lookupvar).with("a").returns(["val1", "val2", "val3"])
+
+ access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => 1 )
+
+ access.evaluate(@scope).should == "val2"
+ end
+
+ it "should be able to return an hash value" do
+ @scope.stubs(:lookupvar).with("a").returns({ "key1" => "val1", "key2" => "val2", "key3" => "val3" })
+
+ access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" )
+
+ access.evaluate(@scope).should == "val2"
+ end
+
+ it "should raise an error if the variable lookup didn't return an hash or an array" do
+ @scope.stubs(:lookupvar).with("a").returns("I'm a string")
+
+ access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" )
+
+ lambda { access.evaluate(@scope) }.should raise_error
+ end
+
+ it "should raise an error if the variable wasn't in the scope" do
+ @scope.stubs(:lookupvar).with("a").returns(nil)
+
+ access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" )
+
+ lambda { access.evaluate(@scope) }.should raise_error
+ end
+
+ it "should return a correct string representation" do
+ access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" )
+ access.to_s.should == '$a[key2]'
+ end
+
+ it "should work with recursive hash access" do
+ @scope.stubs(:lookupvar).with("a").returns({ "key" => { "subkey" => "b" }})
+
+ access1 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key")
+ access2 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => access1, :key => "subkey")
+
+ access2.evaluate(@scope).should == 'b'
+ end
+
+ it "should work with interleaved array and hash access" do
+ @scope.stubs(:lookupvar).with("a").returns({ "key" => [ "a" , "b" ]})
+
+ access1 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key")
+ access2 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => access1, :key => 1)
+
+ access2.evaluate(@scope).should == 'b'
+ end
+end
+
describe Puppet::Parser::AST::Regex do
before :each do
@scope = stub 'scope'
diff --git a/spec/unit/parser/scope.rb b/spec/unit/parser/scope.rb
index 0859ead..66222c9 100755
--- a/spec/unit/parser/scope.rb
+++ b/spec/unit/parser/scope.rb
@@ -29,6 +29,11 @@ describe Puppet::Parser::Scope do
@scope.lookupvar("var").should == "yep"
end
+ it "should be able to look up hashes" do
+ @scope.setvar("var", {"a" => "b"})
+ @scope.lookupvar("var").should == {"a" => "b"}
+ end
+
it "should be able to look up variables in parent scopes" do
@topscope.setvar("var", "parentval")
@scope.lookupvar("var").should == "parentval"
@@ -132,6 +137,11 @@ describe Puppet::Parser::Scope do
@scope.lookupvar("var").should == [4,2]
end
+ it "it should store the merged hash {a => b, c => d}" do
+ @topscope.setvar("var",{"a" => "b"}, :append => false)
+ @scope.setvar("var",{"c" => "d"}, :append => true)
+ @scope.lookupvar("var").should == {"a" => "b", "c" => "d"}
+ end
end
describe "when calling number?" do
diff --git a/test/data/snippets/hash.pp b/test/data/snippets/hash.pp
new file mode 100644
index 0000000..d332498
--- /dev/null
+++ b/test/data/snippets/hash.pp
@@ -0,0 +1,33 @@
+
+$hash = { "file" => "/tmp/myhashfile1" }
+
+file {
+ $hash["file"]:
+ ensure => file, content => "content";
+}
+
+$hash2 = { "a" => { key => "/tmp/myhashfile2" }}
+
+file {
+ $hash2["a"][key]:
+ ensure => file, content => "content";
+}
+
+define test($a = { "b" => "c" }) {
+ file {
+ $a["b"]:
+ ensure => file, content => "content"
+ }
+}
+
+test {
+ "test":
+ a => { "b" => "/tmp/myhashfile3" }
+}
+
+$hash3 = { mykey => "/tmp/myhashfile4" }
+$key = "mykey"
+
+file {
+ $hash3[$key]: ensure => file, content => "content"
+}
diff --git a/test/language/snippets.rb b/test/language/snippets.rb
index 5c7805c..bfd0e53 100755
--- a/test/language/snippets.rb
+++ b/test/language/snippets.rb
@@ -486,6 +486,13 @@ class TestSnippets < Test::Unit::TestCase
assert_file("/tmp/testiftest","if test");
end
+ def snippet_hash
+ assert_file("/tmp/myhashfile1","hash test 1");
+ assert_file("/tmp/myhashfile2","hash test 2");
+ assert_file("/tmp/myhashfile3","hash test 3");
+ assert_file("/tmp/myhashfile4","hash test 4");
+ end
+
# Iterate across each of the snippets and create a test.
Dir.entries(snippetdir).sort.each { |file|
next if file =~ /^\./
--
1.6.5.2
I had a couple of spare hours and decided to hack on this topic.
I don't think the work is completely finished (especially it lacks
hash access string interpolation which is much more complex than it
appears, advice needed here).
individual hash member (mostly to respect write once nature of variable
in puppet).
# lookup the value in the scope if it exists and insert the var
table[name] = lookupvar(name)
# concatenate if string, append if array, nothing for other types
- if value.is_a?(Array)
+ case value
+ when Array
table[name] += value
+ when Hash
+ table[name].merge!(value)
else
table[name] << value
end
Sure. Maybe we can prevent those to occur. Do you have any examples?
> I had a couple of spare hours and decided to hack on this topic.
>> I don't think the work is completely finished (especially it lacks
>> hash access string interpolation which is much more complex than it
>> appears, advice needed here).
>>
>
> Can you elaborate?
I meant this:
notice("this is a hash access: ${hash[$mykey]}")
or even:
notice("this is a hash access: ${hash[key][subkey][subsubkey][1]}")
I see 2 possibilities:
* have a string concatenation operator (+ or .) to be able to write:
"this is a a hash access:" + $hash[$key] + " and this is cool"
* let string interpolation know about hash access.
>> Unlike the proposed syntax in the ticket it is not possible to assign
>
>> individual hash member (mostly to respect write once nature of variable
>> in puppet).
>>
>
> Hmmm. I can see the reasoning, but I'm not sure if restricting it to
> assigning values to previously unused keys wouldn't be a better analog.
The thing is that currently the hash is backed by a ruby Hash. If we
want to prevent modifying already present values, we need to wrap this
in one of our own object.
I didn't think about that in the first instance so I decided to first
release the patch as is to gather comments, and then if we need this
feature then it'll be time to refactor this.
> @@ -317,8 +317,11 @@ class Puppet::Parser::Scope
>
>> # lookup the value in the scope if it exists and insert the var
>> table[name] = lookupvar(name)
>> # concatenate if string, append if array, nothing for other
>> types
>> - if value.is_a?(Array)
>> + case value
>> + when Array
>> table[name] += value
>> + when Hash
>> + table[name].merge!(value)
>> else
>> table[name]<< value
>> end
>>
>
> What will this do with something like:
>
> $my_hash += "foo"
>
> Is it an error or does it add a { :value => "foo"} to $my_hash?
It should be an error. BTW, merge("foo") will produce an Argument Error.
So maybe I should check the operand first.
> I'll download and play with it in a bit,& return with more thoughts.
The code is in tickets/master/2389 in my github repository.
Thanks for your time,
--
Brice Figureau
My Blog: http://www.masterzen.fr/
> the case with language features powerful enough to be interesting/useful.Sure. Maybe we can prevent those to occur. Do you have any examples?
>> it lacks hash access string interpolation
>> which is much more complex than it
>> appears, advice needed here).
> Can you elaborate?I meant this:
notice("this is a hash access: ${hash[$mykey]}")
or even:
notice("this is a hash access: ${hash[key][subkey][subsubkey][1]}")
I see 2 possibilities:
* have a string concatenation operator (+ or .) to be able to write:
"this is a a hash access:" + $hash[$key] + " and this is cool"
* let string interpolation know about hash access.
>> Unlike the proposed syntax in the ticket it is not possible to assignThe thing is that currently the hash is backed by a ruby Hash. If we
>
>> individual hash member (mostly to respect write once nature of variable
>> in puppet).
>>
>
> Hmmm. I can see the reasoning, but I'm not sure if restricting it to
> assigning values to previously unused keys wouldn't be a better analog.
want to prevent modifying already present values, we need to wrap this
in one of our own object.
>> @@ -317,8 +317,11 @@ class Puppet::Parser::Scope
>> # lookup the value in the scope if it exists and insert the var
>> table[name] = lookupvar(name)
>> # concatenate if string, append if array, nothing for other
>> types
>> - if value.is_a?(Array)
>> + case value
>> + when Array
>> table[name] += value
>> + when Hash
>> + table[name].merge!(value)
>> else
>> table[name]<< value
>> end
>>
>
> What will this do with something like:
>
> $my_hash += "foo"
>
> Is it an error or does it add a { :value => "foo"} to $my_hash?
It should be an error. BTW, merge("foo") will produce an Argument Error.
So maybe I should check the operand first.
> I'll download and play with it in a bit,& return with more thoughts.The code is in tickets/master/2389 in my github repository.
Thanks for your time,
> Hashes are defined like Ruby Hash:
> { key1 => val1, ... }
>
> Hash keys are strings, but hash values can be any possible right
> values admitted in Puppet DSL (ie function call, variables access...)
>
> Currently it is possible:
>
> 1) to assign hashes to variable
> $myhash = { key1 => "myval", key2 => $b }
>
> 2) to access hash members (recursively) from a variable containing
> a hash (works for arrays and mix of arrays and hashes too):
>
> $myhash = { key => { subkey => "b" }}
> notice($myhash[key][subjey]]
>
> 3) to use hash member access as resource title
>
> 4) to use hash in default definition parameter or resource parameter
> if
> the type supports it (known for the moment).
>
> It is not possible to string interpolate an hash access. If it proves
> to be an issue it can be added or work-arounded with a string
> concatenation
> operator easily.
>
> It is not possible to use an hash as a resource title. This might be
> possible once we support compound resource title.
If we're going to add hash functionality, I think that at the same
time we should add the ability to treat resources like hashes. That
is, the following syntax should work:
file { "/foo": owner => luke }
$owner = File["/foo"][owner]
This treats the resource types like a hash, with the resource title as
the indexer, which makes sense and helps tie together this otherwise
somewhat confusing syntax with the new hash syntax.
If we don't add this, then I think the resource reference syntax and
the hash access syntax will be fantastically confusing.
Does that seem reasonable.
--
The great aim of education is not knowledge but action.
-- Herbert Spencer
---------------------------------------------------------------------
Luke Kanies | http://reductivelabs.com | http://madstop.com
If we're going to add hash functionality, I think that at the same
time we should add the ability to treat resources like hashes. That
is, the following syntax should work:
file { "/foo": owner => luke }
$owner = File["/foo"][owner]
This treats the resource types like a hash, with the resource title as
the indexer, which makes sense and helps tie together this otherwise
somewhat confusing syntax with the new hash syntax.
If we don't add this, then I think the resource reference syntax and
the hash access syntax will be fantastically confusing.
Does that seem reasonable.
I kinda missed this. What do you mean here?
--
A classic is something that everybody wants to have read and nobody
wants to read. -- Mark Twain
I kinda missed this. What do you mean here?> interpolation? I'm leaning towards making the parser interpolation
> aware rather than hacking a micro-parser into the string
> interpolation routine (or rather, extending the one that's there)
> but I'm not in love with any of the suggestions.
--
The great aim of education is not knowledge but action.
-- Herbert Spencer
---------------------------------------------------------------------
Luke Kanies | http://reductivelabs.com | http://madstop.com
Ah, right.
I tried to do the string interpolation at parse time but I couldn't
figure out how to do it and couldn't find a good example. If you can
make it work, I'm all for it.
--
The difference between scientists and engineers is that when
engineers screw up, people die. -- Professor Orthlieb
If we're not going to add the functionality, please say it now :-)
I know you're usually reluctant to transform the puppet DSL into a
poor-man's ruby, and I understand why well.
If you think we're going too far, let me know.
> I think that at the same
> time we should add the ability to treat resources like hashes. That
> is, the following syntax should work:
>
> file { "/foo": owner => luke }
>
> $owner = File["/foo"][owner]
>
> This treats the resource types like a hash, with the resource title as
> the indexer, which makes sense and helps tie together this otherwise
> somewhat confusing syntax with the new hash syntax.
Is Your concern the comparable syntax of resource references and hash
access?
Maybe we can write hash access differently, ala $hash{key} (kind of
perlish) or even instead of providing a syntax use a parser function?
> If we don't add this, then I think the resource reference syntax and
> the hash access syntax will be fantastically confusing.
>
> Does that seem reasonable.
Yes, it does. And I think this should be moderatly easy to add.
And of course this is only a view of the resource, there's no "write"
access to the resource parameter, correct?
--
Brice Figureau
Follow the latest Puppet Community evolutions on www.planetpuppet.org!
I'm comfortable adding it, although I think people are going to want
supporting functionality in the language -- e.g., something to easily
turn a hash into a resource. This also provides the functionality we
need to add richer data structures to Facter, which is good.
>> I think that at the same
>> time we should add the ability to treat resources like hashes. That
>> is, the following syntax should work:
>>
>> file { "/foo": owner => luke }
>>
>> $owner = File["/foo"][owner]
>>
>> This treats the resource types like a hash, with the resource title
>> as
>> the indexer, which makes sense and helps tie together this otherwise
>> somewhat confusing syntax with the new hash syntax.
>
> Is Your concern the comparable syntax of resource references and hash
> access?
> Maybe we can write hash access differently, ala $hash{key} (kind of
> perlish) or even instead of providing a syntax use a parser function?
We're definitely not using a different indexing bracket. :)
>> If we don't add this, then I think the resource reference syntax and
>> the hash access syntax will be fantastically confusing.
>>
>> Does that seem reasonable.
>
> Yes, it does. And I think this should be moderatly easy to add.
> And of course this is only a view of the resource, there's no "write"
> access to the resource parameter, correct?
I don't see why not, really, as long as it goes through the standard
parameter setting methods - then the overriding rules will still be
followed, so it's just syntactic sugar for an override.
--
Most people are born and years later die without really having lived
at all. They play it safe and tiptoe through life with no aspiration
other than to arrive at death safely. -- Tony Campolo, "Carpe Diem"
I started working on this, but I'm encountering an issue.
I started with the read version, ie:
$owner = File["/foo"][owner]
What I did is to create a new AST leaf which from a resource reference
aims to extract one parameter.
The issue is that at evaluation time the catalog might not know yet the
resource. So calling scope.compiler.find_resource( resource_ref ) will
certainly fail.
Basically this is the same issue as the defined parser function.
So the question is: should I care?
And if yes, what would be the best way to handle this?
Note: the write version doesn't have the issue since it will use the
override system which has the property to be deferred until we know all
the resources.
>>> time we should add the ability to treat resources like hashes. That
>>> is, the following syntax should work:
>>>
>>> file { "/foo": owner => luke }
>>>
>>> $owner = File["/foo"][owner]
>>>
>>> This treats the resource types like a hash, with the resource title
>>> as
>>> the indexer, which makes sense and helps tie together this otherwise
>>> somewhat confusing syntax with the new hash syntax.
>> Yes, it does. And I think this should be moderatly easy to add.
>> And of course this is only a view of the resource, there's no "write"
>> access to the resource parameter, correct?
>
> I don't see why not, really, as long as it goes through the standard
> parameter setting methods - then the overriding rules will still be
> followed, so it's just syntactic sugar for an override.
I started working on this, but I'm encountering an issue.
I started with the read version, ie:
$owner = File["/foo"][owner]
What I did is to create a new AST leaf which from a resource reference
aims to extract one parameter.
The issue is that at evaluation time the catalog might not know yet the
resource. So calling scope.compiler.find_resource( resource_ref ) will
certainly fail.
Given the way the system works now, the only real choice is to fail -
someone's got an implicit dependency that they haven't declared.
It's just like someone trying to refer to the contents of a hash that
hasn't been set.
--
The time to repair the roof is when the sun is shining.
-- John F. Kennedy
I have a harder question for you:
Given these these two classes:
class base { file { "/etc": mode => 0777 } }
class base::fixed inherits base { File["/etc"] { mode => 0755 } }
What is the $value in the following case:
include base
$value = File['/etc'][mode]
include base::fixed
The puppet dsl is designed to allow parse-time evaluation of assignments
by disallowing modification of already assigned variables. This makes
the language easier to understand, write and evaluate. Being able to
reference mutating values breaks many valuable assumptions. See also the
confusion/strangeness surrounding other parse-order dependent features
like defined() and tagged().
It'd be great if you could abolish the parse-order dependency
altogether. As long as all variable/value reference chains form a
directed acylcic graph, they could be evaluated after finishing the
parse, regardless of order.
I am aware though, that this would be *WAY* beyond the scope of simply
adding hashes to the dsl.
Regards, DavidS
> I have a harder question for you:
>
> Given these these two classes:
>
> class base { file { "/etc": mode => 0777 } }
> class base::fixed inherits base { File["/etc"] { mode => 0755 } }
>
> What is the $value in the following case:
>
> include base
> $value = File['/etc'][mode]
> include base::fixed
>
>
> The puppet dsl is designed to allow parse-time evaluation of
> assignments
> by disallowing modification of already assigned variables. This makes
> the language easier to understand, write and evaluate. Being able to
> reference mutating values breaks many valuable assumptions. See also
> the
> confusion/strangeness surrounding other parse-order dependent features
> like defined() and tagged().
>
> It'd be great if you could abolish the parse-order dependency
> altogether. As long as all variable/value reference chains form a
> directed acylcic graph, they could be evaluated after finishing the
> parse, regardless of order.
Could you elaborate on this?
> I am aware though, that this would be *WAY* beyond the scope of simply
> adding hashes to the dsl.
This isn't so much beyond the scope of adding hashes (which it is) as
it is beyond my ability to know how the heck to do it.
I'm all for the this functionality, I've just no real idea how to do
it. The best I can think of is to do something like store a reference
to a variable and a scope during compilation, and then only deref them
when we're converting the catalog at the last minute.
I expect we'll be trying to hack our way to a solution to this at some
point in the next few months, but until someone has some kind of mad
inspiration, it's mostly going to be wandering around in the dark.
--
Hegel was right when he said that we learn from history that man can
never learn anything from history. -- George Bernard Shaw
This seems right.
> * You could (and I just might be inclined to) argue either that
> reference to the properties of a resource was not allowed without an
> explicit dependency on that resource (even if the ordering by chance
> worked out) or that it was itself an implicit declaration of
> dependency and should be fully treated as one were.
In this case it's just a dependency during compilation, which isn't
the same as a run-time dependency, and isn't as useful - if you can
find the resource, you don't need to declare the dependency, and if
you can't, you don't know what class you need to evaluate to get it.
You're in a bit of a chicken/egg situation.
> * Are there any issues we're ignoring we respect to default values,
> overrides, and plussignment (e.g. ways that, depending on what we
> decide here, the results could be mysteriously order dependent)?
Probably. :/
> * What exactly are the use cases? Everything I've managed to think
> up so far would be simpler to express with a variable or hash,
> rather than routing the value through another resource--though I can
> vaguely see how t might reflect the semantics a little better to
> refer directly to a value you wanted to synchronize with rather than
> referring to the value that was used to set it.
I think the main use case is that it intuitively should exist, and it
unifies the syntax that we're talking about adding. It would be
weirder if it didn't exist.
As to how people will (ab)use it, I'm not really sure, but I'm quite
confident that people will quickly use it everywhere.
> * Would it make sense to allow this mechanism to refer to unmanaged
> properties (such as the mode or owner of a file)?
Yes, but how is this different from your first point?
--
Tradition is what you resort to when you don't have the time or the
money to do it right. -- Kurt Herbert Alder
> altogether. As long as all variable/value reference chains form aCould you elaborate on this?
> directed acylcic graph, they could be evaluated after finishing the
> parse, regardless of order.
Yes. You make it sound so easy :-) I'm worried because this also affects
function evaluation including include() and template(), both of which
might alter the manifest significantly, including autoloading new stuff.
Not saying it can't be done, just that it is really a sweeping change
through many parts of the language.
Regards, DavidS
Not saying it can't be done, just that it is really a sweeping change
through many parts of the language.
Regards, DavidS
Is this something you could reasonably implement without a lot of work?> (aka promises) mentioned earlier in this thread. Instead of
> stashing a (variable,scope) reference you create a placeholder
> object which either holds a value or the tree to evaluate to get
> that value.
>
> After parsing is done (and the full forest is known) you fulfill all
> futures that contain values, than iteratively fulfill all futures
> which only depend on fulfilled values. If it's a DAG, you're set.
> If there are loops, you'll be left with unfulfillable nodes and it's
> an error.