diff options
author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-10-04 18:24:24 +0000 |
---|---|---|
committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-10-04 18:24:24 +0000 |
commit | 28cee40689440388994a4768bd301ae32c8ecc05 (patch) | |
tree | c865ab44f4c9247052cf83de16ffc8ebe8b15e54 /lib | |
parent | e0e291332bd4676962a28c7b220ae5c5e9651c0a (diff) | |
download | puppet-28cee40689440388994a4768bd301ae32c8ecc05.tar.gz puppet-28cee40689440388994a4768bd301ae32c8ecc05.tar.xz puppet-28cee40689440388994a4768bd301ae32c8ecc05.zip |
Merging the changes from the override-refactor branch. This is a significant rewrite of the parser, but it has little affect on the rest of the code tree.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1726 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib')
47 files changed, 3979 insertions, 3783 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb index a802688e8..b0b3bbea9 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,5 +1,6 @@ # see the bottom of the file for further inclusions require 'singleton' +require 'facter' require 'puppet/error' require 'puppet/event-loop' require 'puppet/util' diff --git a/lib/puppet/element.rb b/lib/puppet/element.rb index cc682f2e4..934fafeb9 100644 --- a/lib/puppet/element.rb +++ b/lib/puppet/element.rb @@ -7,6 +7,7 @@ require 'puppet' class Puppet::Element include Puppet include Puppet::Util + include Puppet::Util::Errors attr_writer :noop class << self @@ -48,10 +49,10 @@ class Puppet::Element @path = [@parent.path, self.class.name.to_s + "=" + self.name] end else - # The top-level name is always puppet[top], so we don't bother with + # The top-level name is always main[top], so we don't bother with # that. And we don't add the hostname here, it gets added # in the log server thingy. - if self.name == "puppet[top]" + if self.name == "main[top]" @path = ["/"] else if self.is_a?(Puppet.type(:component)) diff --git a/lib/puppet/loadedfile.rb b/lib/puppet/loadedfile.rb index b0e408475..c14bcc195 100755 --- a/lib/puppet/loadedfile.rb +++ b/lib/puppet/loadedfile.rb @@ -44,6 +44,7 @@ module Puppet "Can not use a non-existent file for parsing" end @statted = 0 + @stamp = nil @tstamp = stamp() end diff --git a/lib/puppet/log.rb b/lib/puppet/log.rb index 0659042ce..c8d92e649 100644 --- a/lib/puppet/log.rb +++ b/lib/puppet/log.rb @@ -489,10 +489,8 @@ module Puppet # If they pass a source in to us, we make sure it is a string, and # we retrieve any tags we can. def source=(source) - # We can't store the actual source, we just store the path. This - # is a bit of a stupid hack, specifically testing for elements, but - # eh. - if source.is_a?(Puppet::Element) and source.respond_to?(:path) + # We can't store the actual source, we just store the path. + if source.respond_to?(:path) @objectsource = true @source = source.path else diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index ccde7928b..6f9c9492c 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -3,158 +3,126 @@ require 'puppet' require 'puppet/autoload' -module Puppet - module Parser - # The base class for all of the objects that make up the parse trees. - # Handles things like file name, line #, and also does the initialization - # for all of the parameters of all of the child objects. - class AST - # Do this so I don't have to type the full path in all of the subclasses - AST = Puppet::Parser::AST - - Puppet.setdefaults("ast", - :typecheck => [true, "Whether to validate types during parsing."], - :paramcheck => [true, "Whether to validate parameters during parsing."] - ) - attr_accessor :line, :file, :parent, :scope - - # Just used for 'tree', which is only used in debugging. - @@pink = "[0;31m" - @@green = "[0;32m" - @@yellow = "[0;33m" - @@slate = "[0;34m" - @@reset = "[0m" - - # Just used for 'tree', which is only used in debugging. - @@indent = " " * 4 - @@indline = @@pink + ("-" * 4) + @@reset - @@midline = @@slate + ("-" * 4) + @@reset - - @@settypes = {} - - # Just used for 'tree', which is only used in debugging. - def AST.indention - return @@indent * @@indention - end - - # Just used for 'tree', which is only used in debugging. - def AST.midline - return @@midline - end - - # Evaluate the current object. Basically just iterates across all - # of the contained children and evaluates them in turn, returning a - # list of all of the collected values, rejecting nil values - def evaluate(args) - #Puppet.debug("Evaluating ast %s" % @name) - value = self.collect { |obj| - obj.safeevaluate(args) - }.reject { |obj| - obj.nil? - } - end - - # The version of the evaluate method that should be called, because it - # correctly handles errors. It is critical to use this method because - # it can enable you to catch the error where it happens, rather than - # much higher up the stack. - def safeevaluate(*args) - begin - self.evaluate(*args) - rescue Puppet::DevError => except - except.line ||= @line - except.file ||= @file - raise - rescue Puppet::ParseError => except - except.line ||= @line - except.file ||= @file - raise - rescue => detail - error = Puppet::DevError.new( - "Child of type %s failed with error %s: %s" % - [self.class, detail.class, detail.to_s] - ) - error.line ||= @line - error.file ||= @file - error.set_backtrace detail.backtrace - raise error - end - end - - # Again, just used for printing out the parse tree. - def typewrap(string) - #return self.class.to_s.sub(/.+::/,'') + - #"(" + @@green + string.to_s + @@reset + ")" - return @@green + string.to_s + @@reset + - "(" + self.class.to_s.sub(/.+::/,'') + ")" - end - - # Initialize the object. Requires a hash as the argument, and - # takes each of the parameters of the hash and calls the settor - # method for them. This is probably pretty inefficient and should - # likely be changed at some point. - def initialize(args) - @file = nil - @line = nil - args.each { |param,value| - method = param.to_s + "=" - unless self.respond_to?(method) - error = Puppet::ParseError.new( - "Invalid parameter %s to object class %s" % - [param,self.class.to_s] - ) - error.line = self.line - error.file = self.file - raise error - end - - begin - #Puppet.debug("sending %s to %s" % [method, self.class]) - self.send(method,value) - rescue => detail - error = Puppet::DevError.new( - "Could not set parameter %s on class %s: %s" % - [method,self.class.to_s,detail] - ) - error.line ||= self.line - error.file ||= self.file - raise error - end - } - end - #--------------------------------------------------------------- - # Now autoload everything. - # XXX We can't do this, because it causes multiple loads of some - # things. - #@autoloader = Puppet::Autoload.new(self, - # "puppet/parser/ast" - #) - #@autoloader.loadall +# The base class for all of the objects that make up the parse trees. +# Handles things like file name, line #, and also does the initialization +# for all of the parameters of all of the child objects. +class Puppet::Parser::AST + # Do this so I don't have to type the full path in all of the subclasses + AST = Puppet::Parser::AST + + include Puppet::Util::Errors + include Puppet::Util::MethodHelper + + Puppet.setdefaults("ast", + :typecheck => [true, "Whether to validate types during parsing."], + :paramcheck => [true, "Whether to validate parameters during parsing."] + ) + attr_accessor :line, :file, :parent, :scope + + # Just used for 'tree', which is only used in debugging. + @@pink = "[0;31m" + @@green = "[0;32m" + @@yellow = "[0;33m" + @@slate = "[0;34m" + @@reset = "[0m" + + # Just used for 'tree', which is only used in debugging. + @@indent = " " * 4 + @@indline = @@pink + ("-" * 4) + @@reset + @@midline = @@slate + ("-" * 4) + @@reset + + @@settypes = {} + + # Just used for 'tree', which is only used in debugging. + def AST.indention + return @@indent * @@indention + end + + # Just used for 'tree', which is only used in debugging. + def AST.midline + return @@midline + end + + # Evaluate the current object. Basically just iterates across all + # of the contained children and evaluates them in turn, returning a + # list of all of the collected values, rejecting nil values + def evaluate(args) + #Puppet.debug("Evaluating ast %s" % @name) + value = self.collect { |obj| + obj.safeevaluate(args) + }.reject { |obj| + obj.nil? + } + end + + # Throw a parse error. + def parsefail(message) + self.fail(Puppet::ParseError, message) + end + + # Wrap a statemp in a reusable way so we always throw a parse error. + def parsewrap + exceptwrap :type => Puppet::ParseError do + yield + end + end + + # The version of the evaluate method that should be called, because it + # correctly handles errors. It is critical to use this method because + # it can enable you to catch the error where it happens, rather than + # much higher up the stack. + def safeevaluate(*args) + exceptwrap do + self.evaluate(*args) end end + + # Again, just used for printing out the parse tree. + def typewrap(string) + #return self.class.to_s.sub(/.+::/,'') + + #"(" + @@green + string.to_s + @@reset + ")" + return @@green + string.to_s + @@reset + + "(" + self.class.to_s.sub(/.+::/,'') + ")" + end + + # Initialize the object. Requires a hash as the argument, and + # takes each of the parameters of the hash and calls the settor + # method for them. This is probably pretty inefficient and should + # likely be changed at some point. + def initialize(args) + @file = nil + @line = nil + set_options(args) + end + #--------------------------------------------------------------- + # Now autoload everything. + # XXX We can't do this, because it causes multiple loads of some + # things. + @autoloader = Puppet::Autoload.new(self, + "puppet/parser/ast" + ) + @autoloader.loadall end -require 'puppet/parser/ast/astarray' -require 'puppet/parser/ast/branch' -require 'puppet/parser/ast/collection' -require 'puppet/parser/ast/caseopt' -require 'puppet/parser/ast/casestatement' -require 'puppet/parser/ast/classdef' -require 'puppet/parser/ast/compdef' -require 'puppet/parser/ast/component' -require 'puppet/parser/ast/else' -require 'puppet/parser/ast/hostclass' -require 'puppet/parser/ast/ifstatement' -require 'puppet/parser/ast/leaf' -require 'puppet/parser/ast/node' -require 'puppet/parser/ast/nodedef' -require 'puppet/parser/ast/objectdef' -require 'puppet/parser/ast/objectparam' -require 'puppet/parser/ast/objectref' -require 'puppet/parser/ast/selector' -require 'puppet/parser/ast/typedefaults' -require 'puppet/parser/ast/vardef' -require 'puppet/parser/ast/tag' -require 'puppet/parser/ast/function' +#require 'puppet/parser/ast/astarray' +#require 'puppet/parser/ast/branch' +#require 'puppet/parser/ast/collection' +#require 'puppet/parser/ast/caseopt' +#require 'puppet/parser/ast/casestatement' +#require 'puppet/parser/ast/component' +#require 'puppet/parser/ast/else' +#require 'puppet/parser/ast/hostclass' +#require 'puppet/parser/ast/ifstatement' +#require 'puppet/parser/ast/leaf' +#require 'puppet/parser/ast/node' +#require 'puppet/parser/ast/resourcedef' +#require 'puppet/parser/ast/resourceparam' +#require 'puppet/parser/ast/resourceref' +#require 'puppet/parser/ast/resourceoverride' +#require 'puppet/parser/ast/selector' +#require 'puppet/parser/ast/resourcedefaults' +#require 'puppet/parser/ast/vardef' +#require 'puppet/parser/ast/tag' +#require 'puppet/parser/ast/function' # $Id$ diff --git a/lib/puppet/parser/ast/astarray.rb b/lib/puppet/parser/ast/astarray.rb index c42f658fd..fb0a3f671 100644 --- a/lib/puppet/parser/ast/astarray.rb +++ b/lib/puppet/parser/ast/astarray.rb @@ -25,14 +25,10 @@ class Puppet::Parser::AST # This is such a stupid hack. I've no real idea how to make a # "real" declarative language, so I hack it so it looks like # one, yay. - definelist = [ - AST::CompDef, AST::NodeDef, AST::ClassDef - ] setlist = [ - AST::VarDef, AST::TypeDefaults, AST::Function + AST::VarDef, AST::ResourceDefaults, AST::Function ] - definers = [] settors = [] others = [] @@ -53,15 +49,13 @@ class Puppet::Parser::AST # Now sort them all according to the type of action items.each { |child| - if definelist.include?(child.class) - definers << child - elsif setlist.include?(child.class) + if setlist.include?(child.class) settors << child else others << child end } - rets = [definers, settors, others].flatten.collect { |child| + rets = [settors, others].flatten.collect { |child| child.safeevaluate(:scope => scope) } else @@ -107,37 +101,7 @@ class Puppet::Parser::AST # Used for abstracting the grammar declarations. Basically unnecessary # except that I kept finding bugs because I had too many arrays that # meant completely different things. - class ObjectInst < ASTArray; end - - # Another simple container class to make sure we can correctly arrayfy - # things. - class CompArgument < ASTArray - @@warnings = {} - def initialize(hash) - super - name = @children[0].value - - # If it's not a metaparamer, we're fine. - return unless Puppet::Type.metaparamclass(name) - - if @children[1] - if @children[1].value == false - raise Puppet::ParseError, - "%s is a metaparameter; please choose another name" % - name - else - unless @@warnings[name] - @@warnings[name] = true - Puppet.warning "%s is a metaparam; this value will inherit to all contained elements" % name - end - end - else - raise Puppet::ParseError, - "%s is a metaparameter; please choose another name" % - name - end - end - end + class ResourceInst < ASTArray; end end # $Id$ diff --git a/lib/puppet/parser/ast/classdef.rb b/lib/puppet/parser/ast/classdef.rb deleted file mode 100644 index e09db985f..000000000 --- a/lib/puppet/parser/ast/classdef.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'puppet/parser/ast/compdef' - -class Puppet::Parser::AST - # Define a new class. Syntactically similar to component definitions, - # but classes are always singletons -- only one can exist on a given - # host. - class ClassDef < AST::CompDef - @keyword = "class" - - def self.genclass - AST::HostClass - end - - def each - if @parentclass - #[@type,@args,@parentclass,@code].each { |child| yield child } - [@type,@parentclass,@code].each { |child| yield child } - else - #[@type,@args,@code].each { |child| yield child } - [@type,@code].each { |child| yield child } - end - end - - # Store our parse tree according to type. - def disabled_evaluate(hash) - scope = hash[:scope] - type = @type.safeevaluate(:scope => scope) - #args = @args.safeevaluate(:scope => scope) - - #:args => args, - arghash = { - :type => type, - :code => @code - } - - if @parentclass - arghash[:parentclass] = @parentclass.safeevaluate(:scope => scope) - end - - #Puppet.debug("defining hostclass '%s' with arguments [%s]" % - # [type,args]) - - begin - hclass = HostClass.new(arghash) - hclass.keyword = self.keyword - scope.settype(type, hclass) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - end - - def tree(indent = 0) - #@args.tree(indent + 1), - return [ - @type.tree(indent + 1), - ((@@indline * 4 * indent) + self.typewrap("class")), - @parentclass ? @parentclass.tree(indent + 1) : "", - @code.tree(indent + 1), - ].join("\n") - end - - def to_s - return "class %s(%s) inherits %s {\n%s }" % - [@type, @parentclass, @code] - #[@type, @args, @parentclass, @code] - end - end - -end diff --git a/lib/puppet/parser/ast/collection.rb b/lib/puppet/parser/ast/collection.rb index 8e9acce57..fa480b179 100644 --- a/lib/puppet/parser/ast/collection.rb +++ b/lib/puppet/parser/ast/collection.rb @@ -1,91 +1,30 @@ -require 'puppet/rails' +require 'puppet' +require 'puppet/parser/ast/branch' +require 'puppet/parser/collector' +# An object that collects stored objects from the central cache and returns +# them to the current host, yo. class Puppet::Parser::AST - # An object that collects stored objects from the central cache and returns - # them to the current host, yo. - class Collection < AST::Branch - attr_accessor :type +class Collection < AST::Branch + attr_accessor :type, :query, :form - # We cannot evaluate directly here; instead we need to store a - # CollectType object, which will do the collection. This is - # the only way to find certain exported types in the current - # configuration. - def evaluate(hash) - scope = hash[:scope] + # We return an object that does a late-binding evaluation. + def evaluate(hash) + scope = hash[:scope] - @convertedtype = @type.safeevaluate(:scope => scope) - - scope.newcollection(self) + if self.query + q = self.query.safeevaluate :scope => scope + else + q = nil end - # Now perform the actual collection, yo. - def perform(scope) - # First get everything from the export table. - - # FIXME This will only find objects that are before us in the tree, - # which is a problem. - objects = scope.exported(@convertedtype) - - # We want to return all of these objects, and then whichever objects - # we find in the db. - array = objects.values - - # Mark all of these objects as collected, so that they also get - # returned to the client. We don't store them in our scope - # or anything, which is a little odd, but eh. - array.each do |obj| - obj.collected = true - end - - count = array.length - - # Now we also have to see if there are any exported objects - # in our own scope. - scope.lookupexported(@convertedtype).each do |obj| - objects[obj.name] = obj - obj.collected = true - end - - bucket = Puppet::TransBucket.new - - Puppet::Rails::RailsObject.find_all_by_collectable(true).each do |obj| - # FIXME This should check that the source of the object is the - # host we're running on, else it's a bad conflict. - if objects.include?(obj.name) - scope.debug("%s[%s] is already exported" % [@convertedtype, obj.name]) - next - end - count += 1 - trans = obj.to_trans - bucket.push(trans) + newcoll = Puppet::Parser::Collector.new(scope, @type, q, self.form) - args = { - :name => trans.name, - :type => trans.type, - } + scope.newcollection(newcoll) - [:file, :line].each do |param| - if val = trans.send(param) - args[param] = val - end - end - - args[:arguments] = {} - trans.each do |p,v| args[:arguments][p] = v end - - - # XXX Because the scopes don't expect objects to return values, - # we have to manually add our objects to the scope. This is - # uber-lame. - scope.setobject(args) - end - - scope.debug("Collected %s objects of type %s" % - [count, @convertedtype]) - - return bucket - end + newcoll end end +end # $Id$ diff --git a/lib/puppet/parser/ast/collexpr.rb b/lib/puppet/parser/ast/collexpr.rb new file mode 100644 index 000000000..f69b2e92e --- /dev/null +++ b/lib/puppet/parser/ast/collexpr.rb @@ -0,0 +1,81 @@ +require 'puppet' +require 'puppet/parser/ast/branch' +require 'puppet/parser/collector' + +# An object that collects stored objects from the central cache and returns +# them to the current host, yo. +class Puppet::Parser::AST +class CollExpr < AST::Branch + attr_accessor :test1, :test2, :oper, :form, :type, :parens + + # We return an object that does a late-binding evaluation. + def evaluate(hash) + scope = hash[:scope] + + # Make sure our contained expressions have all the info they need. + [@test1, @test2].each do |t| + if t.is_a?(self.class) + t.form ||= self.form + t.type ||= self.type + end + end + + # The code is only used for virtual lookups + str1, code1 = @test1.safeevaluate :scope => scope + str2, code2 = @test2.safeevaluate :scope => scope + + # First build up the virtual code. + # If we're a conjunction operator, then we're calling code. I did + # some speed comparisons, and it's at least twice as fast doing these + # case statements as doing an eval here. + code = proc do |resource| + case @oper + when "and": code1.call(resource) and code2.call(resource) + when "or": code1.call(resource) or code2.call(resource) + when "==": resource[str1] == str2 + when "!=": resource[str1] != str2 + end + end + + # Now build up the rails conditions code + if self.parens and self.form == :exported + Puppet.warning "Parentheses are ignored in Rails searches" + end + + case @oper + when "and", "or": oper = @oper.upcase + when "==": oper = "=" + else + oper = @oper + end + + if oper == "=" or oper == "!=" + # Add the rails association info where necessary + if str1 == "title" + str = "title #{oper} '#{str2}'" + else + unless self.form == :virtual or str1 == "title" + parsefail "Collection from the database only supports " + + "title matching currently" + end + str = "rails_parameters.name = '#{str1}' and " + + "rails_parameters.value #{oper} '#{str2}'" + end + else + str = [str1, oper, str2].join(" ") + end + + return str, code + end + + def initialize(hash = {}) + super + + unless %w{== != and or}.include?(@oper) + raise ArgumentError, "Invalid operator %s" % @oper + end + end +end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/compdef.rb b/lib/puppet/parser/ast/compdef.rb deleted file mode 100644 index 17ee60181..000000000 --- a/lib/puppet/parser/ast/compdef.rb +++ /dev/null @@ -1,98 +0,0 @@ -class Puppet::Parser::AST - # Define a new component. This basically just stores the - # associated parse tree by name in our current scope. Note that - # there is currently a mismatch in how we look up components -- it - # usually uses scopes, but sometimes uses '@@settypes'. - # FIXME This class should verify that each of its direct children - # has an abstractable name -- i.e., if a file does not include a - # variable in its name, then the user is essentially guaranteed to - # encounter an error if the component is instantiated more than - # once. - class CompDef < AST::Branch - attr_accessor :type, :args, :code, :scope, :parentclass - attr_writer :keyword - - @keyword = "define" - - class << self - attr_reader :keyword - end - - - def self.genclass - AST::Component - end - - def each - [@type,@args,@code].each { |child| yield child } - end - - # Store the parse tree. - def evaluate(hash) - scope = hash[:scope] - arghash = {:code => @code} - arghash[:type] = @type.safeevaluate(:scope => scope) - - if @args - arghash[:args] = @args.safeevaluate(:scope => scope) - end - - if @parentclass - arghash[:parentclass] = @parentclass.safeevaluate(:scope => scope) - end - - - begin - comp = self.class.genclass.new(arghash) - comp.keyword = self.keyword - scope.settype(arghash[:type], comp) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - end - - def initialize(hash) - @parentclass = nil - @args = nil - super - - #if @parentclass - # Puppet.notice "Parent class of %s is %s" % - # [@type.value, @parentclass.value] - #end - - #Puppet.debug "Defining type %s" % @type.value - end - - def keyword - if defined? @keyword - @keyword - else - self.class.keyword - end - end - - def tree(indent = 0) - return [ - @type.tree(indent + 1), - ((@@indline * 4 * indent) + self.typewrap("define")), - @args.tree(indent + 1), - @code.tree(indent + 1), - ].join("\n") - end - - def to_s - return "define %s(%s) {\n%s }" % [@type, @args, @code] - end - end -end - -# $Id$ diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb index ebbf21959..312b11d99 100644 --- a/lib/puppet/parser/ast/component.rb +++ b/lib/puppet/parser/ast/component.rb @@ -1,80 +1,68 @@ +require 'puppet/parser/ast/branch' + class Puppet::Parser::AST # Evaluate the stored parse tree for a given component. This will # receive the arguments passed to the component and also the type and # name of the component. class Component < AST::Branch + include Puppet::Util + include Puppet::Util::Warnings + include Puppet::Util::MethodHelper class << self attr_accessor :name end # The class name - @name = :component + @name = :definition - attr_accessor :type, :args, :code, :scope, :keyword - attr_accessor :collectable, :parentclass + attr_accessor :type, :arguments, :code, :scope, :keyword + attr_accessor :exported, :namespace, :fqname, :interp # These are retrieved when looking up the superclass - attr_accessor :name, :arguments + attr_accessor :name def evaluate(hash) origscope = hash[:scope] objtype = hash[:type] - @name = hash[:name] - @arguments = hash[:arguments] || {} + name = hash[:name] + args = symbolize_options(hash[:arguments] || {}) - @collectable = hash[:collectable] + exported = hash[:exported] pscope = origscope - #pscope = if ! Puppet[:lexical] or hash[:asparent] == false - # origscope - #else - # @scope - #end - scope = pscope.newscope( - :type => @type, - :name => @name, - :keyword => self.keyword - ) - newcontext = hash[:newcontext] + scope = subscope(pscope, name) - if @collectable or origscope.collectable - scope.collectable = true + if exported or origscope.exported? + scope.exported = true end - - unless self.is_a? AST::HostClass and ! newcontext - #scope.warning "Setting context to %s" % self.object_id - scope.context = self.object_id - end - @scope = scope - + scope = scope # Additionally, add a tag for whatever kind of class # we are - scope.tag(@type) + if @type != "" + scope.tag(@type) + end - unless @name.nil? - scope.tag(@name) + unless name.nil? or name =~ /[^\w]/ + scope.tag(name) end # define all of the arguments in our local scope - if self.args + if self.arguments # Verify that all required arguments are either present or # have been provided with defaults. # FIXME This should probably also require each parent # class's arguments... - self.args.each { |arg, default| - unless @arguments.include?(arg) + self.arguments.each { |arg, default| + arg = symbolize(arg) + unless args.include?(arg) if defined? default and ! default.nil? - @arguments[arg] = default + default = default.safeevaluate :scope => scope + args[arg] = default #Puppet.debug "Got default %s for %s in %s" % # [default.inspect, arg.inspect, @name.inspect] else - error = Puppet::ParseError.new( - "Must pass %s to %s of type %s" % - [arg.inspect,@name,@type] - ) - error.line = self.line - error.file = self.file - raise error + parsefail "Must pass %s to %s of type %s" % + [arg,name,@type] end end } @@ -82,54 +70,116 @@ class Puppet::Parser::AST # Set each of the provided arguments as variables in the # component's scope. - @arguments.each { |arg,value| - begin - scope.setvar(arg,@arguments[arg]) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => except - error = Puppet::ParseError.new(except.message) - error.line = self.line - error.file = self.file - error.set_backtrace except.backtrace - raise error + args.each { |arg,value| + unless validattr?(arg) + parsefail "%s does not accept attribute %s" % [@type, arg] + end + + exceptwrap do + scope.setvar(arg.to_s,args[arg]) end } - unless @arguments.include? "name" - scope.setvar("name",@name) + unless args.include? "name" + scope.setvar("name",name) end - # Now just evaluate the code with our new bindings. - #scope.inside(self) do # part of definition inheritance - self.code.safeevaluate(:scope => scope) - #end + if self.code + return self.code.safeevaluate(:scope => scope) + else + return nil + end + end + + def initialize(hash = {}) + @arguments = nil + @parentclass = nil + super + + # Deal with metaparams in the argument list. + if @arguments + @arguments.each do |arg, defvalue| + next unless Puppet::Type.metaparamclass(arg) + if defvalue + warnonce "%s is a metaparam; this value will inherit to all contained elements" % arg + else + raise Puppet::ParseError, + "%s is a metaparameter; please choose another name" % + name + end + end + end + end + + def parentclass + parentobj do |name| + @interp.findclass(namespace, name) + end + end - # If we're being evaluated as a parent class, we want to return the - # scope, so it can be overridden and such, but if not, we want to - # return a TransBucket of our objects. - if hash.include?(:asparent) - return scope + # Set our parent class, with a little check to avoid some potential + # weirdness. + def parentclass=(name) + if name == @type + parsefail "Parent classes must have dissimilar names" + end + + @parentclass = name + end + + # Hunt down our class object. + def parentobj + if @parentclass + # Cache our result, since it should never change. + unless @parentclass.is_a?(AST::HostClass) + unless tmp = yield(@parentclass) + parsefail "Could not find %s %s" % [self.class.name, @parentclass] + end + + if tmp == self + parsefail "Parent classes must have dissimilar names" + end + + @parentclass = tmp + end + @parentclass else - return scope.to_trans + nil end end + # Create a new subscope in which to evaluate our code. + def subscope(scope, name = nil) + args = { + :type => @type, + :keyword => self.keyword, + :namespace => self.namespace + } + + args[:name] = name if name + args[:type] = self.type if self.type + scope = scope.newscope(args) + scope.source = self + + return scope + end + + def to_s + fqname + end + # Check whether a given argument is valid. Searches up through # any parent classes that might exist. - def validarg?(param) + def validattr?(param) + return true if Puppet::Type.metaparam?(param) + + param = param.to_s found = false - unless @args.is_a? Array - @args = [@args] + unless @arguments.is_a? Array + @arguments = [@arguments] end - found = @args.detect { |arg| + found = @arguments.detect { |arg| if arg.is_a? Array arg[0] == param else diff --git a/lib/puppet/parser/ast/function.rb b/lib/puppet/parser/ast/function.rb index 2c63c8b76..f67531ae3 100644 --- a/lib/puppet/parser/ast/function.rb +++ b/lib/puppet/parser/ast/function.rb @@ -11,7 +11,10 @@ class Puppet::Parser::AST args = @arguments.safeevaluate(:scope => scope) - return scope.send("function_" + @name, args) + #exceptwrap :message => "Failed to execute %s" % @name, + # :type => Puppet::ParseError do + return scope.send("function_" + @name, args) + #end end def initialize(hash) diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 44077983d..4a4664f0f 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -1,3 +1,5 @@ +require 'puppet/parser/ast/component' + class Puppet::Parser::AST # The code associated with a class. This is different from components # in that each class is a singleton -- only one will exist for a given @@ -5,152 +7,50 @@ class Puppet::Parser::AST class HostClass < AST::Component @name = :class + # Are we a child of the passed class? Do a recursive search up our + # parentage tree to figure it out. + def child_of?(klass) + return false unless self.parentclass + + if klass == self.parentclass + return true + else + return self.parentclass.child_of?(klass) + end + end + + # Evaluate the code associated with this class. def evaluate(hash) scope = hash[:scope] - objname = hash[:name] args = hash[:arguments] + # Verify that we haven't already been evaluated - # FIXME The second subclass won't evaluate the parent class - # code at all, and any overrides will throw an error. - if myscope = scope.lookupclass(self.object_id) + if scope.setclass?(self) Puppet.debug "%s class already evaluated" % @type - - # Not used, but will eventually be used to fix #140. - if myscope.is_a? Puppet::Parser::Scope - unless scope.object_id == myscope.object_id - #scope.parent = myscope - end - end return nil end - # Set the class before we do anything else, so that it's set - # during the evaluation and can be inspected. - scope.setclass(self.object_id, @type) - - origscope = scope - - # Default to creating a new context - newcontext = true - - # If we've got a parent, then we pass it the original scope we - # received. It will get passed all the way up to the top class, - # which will create a subscope and pass that subscope to its - # subclass. - if @parentscope = self.evalparent( - :scope => scope, :arguments => args, :name => objname - ) - if @parentscope.is_a? Puppet::TransBucket - raise Puppet::DevError, "Got a bucket instead of a scope" - end - - # Override our scope binding with the parent scope - # binding. - scope = @parentscope - - # But don't create a new context if our parent created one - newcontext = false - end - - # Just use the Component evaluate method, but change the type - # to our own type. - result = super( - :scope => scope, - :arguments => args, - :type => @type, - :name => objname, # might be nil - :newcontext => newcontext, - :asparent => hash[:asparent] || false # might be nil - ) - - # Now set the class again, this time using the scope. This way - # we can look up the parent scope of this class later, so we - # can hook the children together. - scope.setscope(self.object_id, result) - - # This is important but painfully difficult. If we're the top-level - # class, that is, we have no parent classes, then the transscope - # is our own scope, but if there are parent classes, then the topmost - # parent's scope is the transscope, since it contains its code and - # all of the subclass's code. - transscope ||= result - - if hash[:asparent] - # If we're a parent class, then return the scope object itself. - return result - else - transscope = nil - if @parentscope - transscope = @parentscope - until transscope.parent.object_id == origscope.object_id - transscope = transscope.parent - end + if @parentclass + if pklass = self.parentclass + pklass.safeevaluate :scope => scope else - transscope = result + parsefail "Could not find class %s" % @parentclass end - - # But if we're the final subclass, translate the whole scope tree - # into TransObjects and TransBuckets. - return transscope.to_trans end - end - # Evaluate our parent class. Parent classes are evaluated in the - # exact same scope as the children. This is maybe not a good idea - # but, eh. - #def evalparent(scope, args, name) - def evalparent(hash) - scope = hash[:scope] - args = hash[:arguments] - name = hash[:name] - if @parentclass - #scope.warning "parent class of %s is %s" % - # [@type, @parentclass.inspect] - parentobj = nil + # Set the class before we do anything else, so that it's set + # during the evaluation and can be inspected. + scope.setclass(self) - begin - parentobj = scope.lookuptype(@parentclass) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - raise error - end - unless parentobj - error = Puppet::ParseError.new( - "Could not find parent '%s' of '%s'" % - [@parentclass,@name]) - error.line = self.line - error.file = self.file - raise error - end + unless hash[:nosubscope] + scope = subscope(scope) + end - # Verify that the parent and child are of the same type - unless parentobj.class == self.class - error = Puppet::ParseError.new( - "Class %s has incompatible parent type, %s vs %s" % - [@type, parentobj.class, self.class] - ) - error.file = self.file - error.line = self.line - raise error - end - # We don't need to pass the type, because the parent will just - # use its own type. Specify that it's being evaluated as a parent, - # so that it returns the scope, not a transbucket. - return parentobj.safeevaluate( - :scope => scope, - :arguments => args, - :name => name, - :asparent => true, - :collectable => self.collectable - ) + # Now evaluate our code, yo. + if self.code + return self.code.evaluate(:scope => scope) else - return false + return nil end end @@ -158,7 +58,7 @@ class Puppet::Parser::AST @parentclass = nil super end - end - end + +# $Id$ diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb index 7b9a283d7..298c0168f 100644 --- a/lib/puppet/parser/ast/leaf.rb +++ b/lib/puppet/parser/ast/leaf.rb @@ -28,15 +28,11 @@ class Puppet::Parser::AST def initialize(hash) super - unless @value == 'true' or @value == 'false' + unless @value == true or @value == false raise Puppet::DevError, "'%s' is not a boolean" % @value end - if @value == 'true' - @value = true - else - @value = false - end + @value end end @@ -69,6 +65,9 @@ class Puppet::Parser::AST # Lower-case words. class Name < AST::Leaf; end + # double-colon separated class names + class ClassName < AST::Leaf; end + # Host names, either fully qualified or just the short name class HostName < AST::Leaf def initialize(hash) @@ -87,18 +86,8 @@ class Puppet::Parser::AST # Looks up the value of the object in the scope tree (does # not include syntactical constructs, like '$' and '{}'). def evaluate(hash) - begin + parsewrap do return hash[:scope].lookupvar(@value) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::DevError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error end end end diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb index e4e69bed9..3f508b2bf 100644 --- a/lib/puppet/parser/ast/node.rb +++ b/lib/puppet/parser/ast/node.rb @@ -1,113 +1,70 @@ +require 'puppet/parser/ast/hostclass' + class Puppet::Parser::AST # The specific code associated with a host. Nodes are annoyingly unlike # other objects. That's just the way it is, at least for now. class Node < AST::HostClass @name = :node - attr_accessor :type, :args, :code, :parentclass + attr_accessor :name #def evaluate(scope, facts = {}) def evaluate(hash) - origscope = hash[:scope] - facts = hash[:facts] || {} - - # nodes are never instantiated like a normal object, - # but we need the type to be the name users would use for - # instantiation, otherwise tags don't work out + scope = hash[:scope] - pscope = origscope #pscope = if ! Puppet[:lexical] or hash[:asparent] # @scope #else # origscope #end - scope = pscope.newscope( - :type => self.type, - :keyword => @keyword - ) - scope.context = self.object_id + Puppet.warning "%s => %s" % [scope.type, self.name] - # Now set all of the facts inside this scope - facts.each { |var, value| - scope.setvar(var, value) - } - - if tmp = self.evalparent(scope) - # Again, override our scope with the parent scope, if - # there is one. - scope = tmp + # We don't have to worry about the declarativeness of node parentage, + # because the entry point is always a single node definition. + if parent = self.parentclass + scope = parent.safeevaluate :scope => scope end + Puppet.notice "%s => %s" % [scope.type, self.name] - #scope.tag(@name) - - # We never pass the facts to the parent class, because they've - # already been defined at this top-level scope. - #super(scope, facts, @name, @name) + scope = scope.newscope( + :type => self.name, + :keyword => @keyword, + :source => self, + :namespace => "" # nodes are always in "" + ) + Puppet.info "%s => %s" % [scope.type, self.name] # Mark our node name as a class, too, but strip it of the domain # name. Make the mark before we evaluate the code, so that it is # marked within the code itself. - scope.setclass(self.object_id, @type.sub(/\..+/, '')) + scope.setclass(self) - # And then evaluate our code. - @code.safeevaluate(:scope => scope) + # And then evaluate our code if we have any + if self.code + @code.safeevaluate(:scope => scope) + end return scope end - # Evaluate our parent class. - def evalparent(scope) - if @parentclass - # This is pretty messed up. I don't know if this will - # work in the long term, but we need to evaluate the node - # in our own scope, even though our parent node has - # a scope associated with it, because otherwise we 1) won't - # get our facts defined, and 2) we won't actually get the - # objects returned, based on how nodes work. - - # We also can't just evaluate the node itself, because - # it would create a node scope within this scope, - # and that would cause mass havoc. - node = nil - - # The 'node' method just returns a hash of the node - # code and name. It's used here, and in 'evalnode'. - unless hash = scope.node(@parentclass) - raise Puppet::ParseError, - "Could not find parent node %s" % - @parentclass - end - - node = hash[:node] - type = nil - if type = node.type - scope.tag(node.type) - end - - begin - code = node.code - code.safeevaluate(:scope => scope, :asparent => true) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - raise error - end + def initialize(hash) + @parentclass = nil + super - if node.parentclass - node.evalparent(scope) - end + # Do some validation on the node name + if @name =~ /[^-\w.]/ + raise Puppet::ParseError, "Invalid node name %s" % @name end end - def initialize(hash) - @parentclass = nil - super + def parentclass + parentobj do |name| + @interp.nodesearch(name) + end + @parentclass end end end + +# $Id$ diff --git a/lib/puppet/parser/ast/nodedef.rb b/lib/puppet/parser/ast/nodedef.rb deleted file mode 100644 index 06b104828..000000000 --- a/lib/puppet/parser/ast/nodedef.rb +++ /dev/null @@ -1,73 +0,0 @@ -class Puppet::Parser::AST - # Define a node. The node definition stores a parse tree for each - # specified node, and this parse tree is only ever looked up when - # a client connects. - class NodeDef < AST::Branch - attr_accessor :names, :code, :parentclass, :keyword, :scope - - def each - [@names,@code].each { |child| yield child } - end - - # Do implicit iteration over each of the names passed. - def evaluate(hash) - scope = hash[:scope] - names = @names.safeevaluate(:scope => scope) - - unless names.is_a?(Array) - names = [names] - end - - names.each { |name| - #Puppet.debug("defining host '%s' in scope %s" % - # [name, scope.object_id]) - # We use 'type' here instead of name, because every component - # type supports both 'type' and 'name', and 'type' is a more - # appropriate description of the syntactic role that this term - # plays. - arghash = { - :type => name, - :code => @code - } - - if @parentclass - arghash[:parentclass] = @parentclass.safeevaluate(:scope => scope) - end - - begin - node = Node.new(arghash) - node.keyword = true - scope.setnode(name, node) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - raise error - end - } - end - - def initialize(hash) - @parentclass = nil - @keyword = "node" - super - end - - def tree(indent = 0) - return [ - @names.tree(indent + 1), - ((@@indline * 4 * indent) + self.typewrap("node")), - @code.tree(indent + 1), - ].join("\n") - end - - def to_s - return "node %s {\n%s }" % [@name, @code] - end - end - -end diff --git a/lib/puppet/parser/ast/objectdef.rb b/lib/puppet/parser/ast/objectdef.rb deleted file mode 100644 index ac5fe3070..000000000 --- a/lib/puppet/parser/ast/objectdef.rb +++ /dev/null @@ -1,353 +0,0 @@ -class Puppet::Parser::AST - # Any normal puppet object declaration. Can result in a class or a - # component, in addition to builtin types. - class ObjectDef < AST::Branch - attr_accessor :name, :type, :collectable - attr_reader :params - - # probably not used at all - def []=(index,obj) - @params[index] = obj - end - - # probably not used at all - def [](index) - return @params[index] - end - - # Iterate across all of our children. - def each - [@type,@name,@params].flatten.each { |param| - #Puppet.debug("yielding param %s" % param) - yield param - } - end - - # Does not actually return an object; instead sets an object - # in the current scope. - def evaluate(hash) - scope = hash[:scope] - @scope = scope - hash = {} - - # Get our type and name. - objtype = @type.safeevaluate(:scope => scope) - - # Disable definition inheritance, for now. 8/27/06, luke - #if objtype == "super" - # objtype = supertype() - # @subtype = true - #else - @subtype = false - #end - - # If the type was a variable, we wouldn't have typechecked yet. - # Do it now, if so. - unless @checked - self.typecheck(objtype) - end - - # See if our object type was defined. If not, we know it's - # builtin because we already typechecked. - begin - object = scope.lookuptype(objtype) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - - hash = {} - # Evaluate all of the specified params. - @params.each { |param| - ary = param.safeevaluate(:scope => scope) - hash[ary[0]] = ary[1] - } - - # Now collect info from our parent. - parentname = nil - if @subtype - parentname = supersetup(hash) - end - - objnames = [nil] - # Determine our name if we have one. - if self.name - objnames = @name.safeevaluate(:scope => scope) - # it's easier to always use an array, even for only one name - unless objnames.is_a?(Array) - objnames = [objnames] - end - else - if parentname - objnames = [parentname] - else - # See if they specified the name as a parameter instead of - # as a normal name (i.e., before the colon). - unless object # we're a builtin - if objclass = Puppet::Type.type(objtype) - namevar = objclass.namevar - - tmp = hash["name"] || hash[namevar.to_s] - - if tmp - objnames = [tmp] - end - else - # this should never happen, because we've already - # typechecked, but it's no real problem if it does - # happen. We just end up with an object with no - # name. - end - end - end - end - - # this is where our implicit iteration takes place; - # if someone passed an array as the name, then we act - # just like the called us many times - objnames.collect { |objname| - # If the object is a class, that means it's a builtin type, so - # we just store it in the scope - begin - #Puppet.debug( - # ("Setting object '%s' " + - # "in scope %s " + - # "with arguments %s") % - # [objname, scope.object_id, hash.inspect] - #) - obj = scope.setobject( - :type => objtype, - :name => objname, - :arguments => hash, - :file => @file, - :line => @line, - :collectable => self.collectable - ) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - }.reject { |obj| obj.nil? } - end - - # Create our ObjectDef. Handles type checking for us. - def initialize(hash) - @checked = false - super - - #self.typecheck(@type.value) - end - - # Verify that all passed parameters are valid - def paramcheck(builtin, objtype) - # This defaults to true - unless Puppet[:paramcheck] - return - end - - @params.each { |param| - if builtin - self.parambuiltincheck(builtin, param) - else - self.paramdefinedcheck(objtype, param) - end - } - - # Mark that we've made it all the way through. - @checked = true - end - - def parambuiltincheck(type, param) - unless param.is_a?(AST::ObjectParam) - raise Puppet::DevError, - "Got something other than param" - end - begin - pname = param.param.value - rescue => detail - raise Puppet::DevError, detail.to_s - end - - return if pname == "name" # always allow these - unless type.validattr?(pname) - error = Puppet::ParseError.new( - "Invalid parameter '%s' for type '%s'" % - [pname, type.name] - ) - error.line = self.line - error.file = self.file - raise error - end - end - - def paramdefinedcheck(objtype, param) - # FIXME We might need to do more here eventually. Metaparams - # behave strangely on containers. - if Puppet::Type.metaparam?(param.param.value.intern) - return - end - - begin - pname = param.param.value - rescue => detail - raise Puppet::DevError, detail.to_s - end - - unless objtype.validarg?(pname) - error = Puppet::ParseError.new( - "Invalid parameter '%s' for type '%s'" % - [pname,objtype.type] - ) - error.line = self.line - error.file = self.file - raise error - end - end - - # Set the parameters for our object. - def params=(params) - if params.is_a?(AST::ASTArray) - @params = params - else - @params = AST::ASTArray.new( - :line => params.line, - :file => params.file, - :children => [params] - ) - end - end - - def supercomp - unless defined? @supercomp - if @scope and comp = @scope.inside - @supercomp = comp - else - error = Puppet::ParseError.new( - "'super' is only valid within definitions" - ) - error.line = self.line - error.file = self.file - raise error - end - end - @supercomp - end - - # Take all of the arguments of our parent and add them into our own, - # without overriding anything. - def supersetup(hash) - comp = supercomp() - - # Now check each of the arguments from the parent. - comp.arguments.each do |name, value| - unless hash.has_key? name - hash[name] = value - end - end - - # Return the parent name, so it can be used if appropriate. - return comp.name - end - - # Retrieve our supertype. - def supertype - unless defined? @supertype - if parent = supercomp.parentclass - @supertype = parent - else - error = Puppet::ParseError.new( - "%s does not have a parent class" % comp.type - ) - error.line = self.line - error.file = self.file - raise error - end - end - @supertype - end - - # Print this object out. - def tree(indent = 0) - return [ - @type.tree(indent + 1), - @name.tree(indent + 1), - ((@@indline * indent) + self.typewrap(self.pin)), - @params.collect { |param| - begin - param.tree(indent + 1) - rescue NoMethodError => detail - Puppet.err @params.inspect - error = Puppet::DevError.new( - "failed to tree a %s" % self.class - ) - error.set_backtrace detail.backtrace - raise error - end - }.join("\n") - ].join("\n") - end - - # Verify that the type is valid. This throws an error if there's - # a problem, so the return value doesn't matter - def typecheck(objtype) - # This will basically always be on, but I wanted to make it at - # least simple to turn off if it came to that - unless Puppet[:typecheck] - return - end - - builtin = false - begin - builtin = Puppet::Type.type(objtype) - rescue TypeError - # nothing; we've already set builtin to false - end - - typeobj = nil - if builtin - self.paramcheck(builtin, objtype) - else - # If there's no set scope, then we're in initialize, not - # evaluate, so we can't test defined types. - return true unless defined? @scope and @scope - - # Unless we can look up the type, throw an error - unless typeobj = @scope.lookuptype(objtype) - error = Puppet::ParseError.new( - "Unknown type '%s'" % objtype - ) - error.line = self.line - error.file = self.file - raise error - end - - # Now that we have the type, verify all of the parameters. - # Note that we're now passing an AST Class object or whatever - # as the type, not a simple string. - self.paramcheck(builtin, typeobj) - end - end - - def to_s - return "%s => { %s }" % [@name, - @params.collect { |param| - param.to_s - }.join("\n") - ] - end - end -end diff --git a/lib/puppet/parser/ast/objectref.rb b/lib/puppet/parser/ast/objectref.rb deleted file mode 100644 index f9a63e222..000000000 --- a/lib/puppet/parser/ast/objectref.rb +++ /dev/null @@ -1,77 +0,0 @@ -class Puppet::Parser::AST - # A reference to an object. Only valid as an rvalue. - class ObjectRef < AST::Branch - attr_accessor :name, :type - - def each - [@type,@name].flatten.each { |param| - #Puppet.debug("yielding param %s" % param) - yield param - } - end - - # Evaluate our object, but just return a simple array of the type - # and name. - def evaluate(hash) - scope = hash[:scope] - objtype = @type.safeevaluate(:scope => scope) - objnames = @name.safeevaluate(:scope => scope) - - # it's easier to always use an array, even for only one name - unless objnames.is_a?(Array) - objnames = [objnames] - end - - # See if we can look the object up in our scope tree. - begin - object = scope.lookuptype(objtype) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - - # If the type isn't defined, verify that it's builtin - unless object or Puppet::Type.type(objtype) - error = Puppet::ParseError.new("Could not find type %s" % - objtype.inspect) - error.line = self.line - error.file = self.file - raise error - end - - # should we implicitly iterate here? - # yes, i believe that we essentially have to... - ret = objnames.collect { |objname| - if object.is_a?(AST::Component) - objname = "%s[%s]" % [objtype,objname] - objtype = "component" - end - [objtype,objname] - }.reject { |obj| obj.nil? } - - # Return a flattened array, since we know that we've created an - # array - return *ret - end - - def tree(indent = 0) - return [ - @type.tree(indent + 1), - @name.tree(indent + 1), - ((@@indline * indent) + self.typewrap(self.pin)) - ].join("\n") - end - - def to_s - return "%s[%s]" % [@name,@type] - end - end - -end diff --git a/lib/puppet/parser/ast/resourcedef.rb b/lib/puppet/parser/ast/resourcedef.rb new file mode 100644 index 000000000..1c9333030 --- /dev/null +++ b/lib/puppet/parser/ast/resourcedef.rb @@ -0,0 +1,215 @@ +require 'puppet/parser/ast/branch' + +# Any normal puppet object declaration. Can result in a class or a +# component, in addition to builtin types. +class Puppet::Parser::AST +class ResourceDef < AST::Branch + attr_accessor :title, :type, :exported, :virtual + attr_reader :params + + # probably not used at all + def []=(index,obj) + @params[index] = obj + end + + # probably not used at all + def [](index) + return @params[index] + end + + # Iterate across all of our children. + def each + [@type,@title,@params].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + # Does not actually return an object; instead sets an object + # in the current scope. + def evaluate(hash) + scope = hash[:scope] + @scope = scope + hash = {} + + # Get our type and name. + objtype = @type + + # Disable definition inheritance, for now. 8/27/06, luke + #if objtype == "super" + # objtype = supertype() + # @subtype = true + #else + @subtype = false + #end + + # Evaluate all of the specified params. + paramobjects = @params.collect { |param| + param.safeevaluate(:scope => scope) + } + + # Now collect info from our parent. + parentname = nil + if @subtype + parentname = supersetup(hash) + end + + objtitles = nil + # Determine our name if we have one. + if self.title + objtitles = @title.safeevaluate(:scope => scope) + # it's easier to always use an array, even for only one name + unless objtitles.is_a?(Array) + objtitles = [objtitles] + end + else + if parentname + objtitles = [parentname] + else + # See if they specified the name as a parameter instead of + # as a normal name (i.e., before the colon). + unless object # we're a builtin + if objclass = Puppet::Type.type(objtype) + namevar = objclass.namevar + + tmp = hash["name"] || hash[namevar.to_s] + + if tmp + objtitles = [tmp] + end + else + # This isn't grammatically legal. + raise Puppet::ParseError, "Got a resource with no title" + end + end + end + end + + # This is where our implicit iteration takes place; if someone + # passed an array as the name, then we act just like the called us + # many times. + objtitles.collect { |objtitle| + exceptwrap :type => Puppet::ParseError do + obj = Puppet::Parser::Resource.new( + :type => objtype, + :title => objtitle, + :params => paramobjects, + :file => @file, + :line => @line, + :exported => self.exported || scope.exported, + :virtual => self.virtual, + :source => scope.source, + :scope => scope + ) + + # And then store the resource in the scope. + # XXX At some point, we need to switch all of this to return + # objects instead of storing them like this. + scope.setresource(obj) + obj + end + }.reject { |obj| obj.nil? } + end + + # Create our ResourceDef. Handles type checking for us. + def initialize(hash) + @checked = false + super + + #self.typecheck(@type.value) + end + + # Set the parameters for our object. + def params=(params) + if params.is_a?(AST::ASTArray) + @params = params + else + @params = AST::ASTArray.new( + :line => params.line, + :file => params.file, + :children => [params] + ) + end + end + + def supercomp + unless defined? @supercomp + if @scope and comp = @scope.inside + @supercomp = comp + else + error = Puppet::ParseError.new( + "'super' is only valid within definitions" + ) + error.line = self.line + error.file = self.file + raise error + end + end + @supercomp + end + + # Take all of the arguments of our parent and add them into our own, + # without overriding anything. + def supersetup(hash) + comp = supercomp() + + # Now check each of the arguments from the parent. + comp.arguments.each do |name, value| + unless hash.has_key? name + hash[name] = value + end + end + + # Return the parent name, so it can be used if appropriate. + return comp.name + end + + # Retrieve our supertype. + def supertype + unless defined? @supertype + if parent = supercomp.parentclass + @supertype = parent + else + error = Puppet::ParseError.new( + "%s does not have a parent class" % comp.type + ) + error.line = self.line + error.file = self.file + raise error + end + end + @supertype + end + + # Print this object out. + def tree(indent = 0) + return [ + @type.tree(indent + 1), + @title.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @params.collect { |param| + begin + param.tree(indent + 1) + rescue NoMethodError => detail + Puppet.err @params.inspect + error = Puppet::DevError.new( + "failed to tree a %s" % self.class + ) + error.set_backtrace detail.backtrace + raise error + end + }.join("\n") + ].join("\n") + end + + def to_s + return "%s => { %s }" % [@title, + @params.collect { |param| + param.to_s + }.join("\n") + ] + end +end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/typedefaults.rb b/lib/puppet/parser/ast/resourcedefaults.rb index 2e11a1b94..44db4d465 100644 --- a/lib/puppet/parser/ast/typedefaults.rb +++ b/lib/puppet/parser/ast/resourcedefaults.rb @@ -1,32 +1,22 @@ class Puppet::Parser::AST - # A statement syntactically similar to an ObjectDef, but uses a + # A statement syntactically similar to an ResourceDef, but uses a # capitalized object type and cannot have a name. - class TypeDefaults < AST::Branch + class ResourceDefaults < AST::Branch attr_accessor :type, :params def each [@type,@params].each { |child| yield child } end - # As opposed to ObjectDef, this stores each default for the given + # As opposed to ResourceDef, this stores each default for the given # object type. def evaluate(hash) scope = hash[:scope] - type = @type.safeevaluate(:scope => scope) + type = @type.downcase params = @params.safeevaluate(:scope => scope) - begin - scope.setdefaults(type.downcase,params) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error + parsewrap do + scope.setdefaults(type, params) end end @@ -44,3 +34,5 @@ class Puppet::Parser::AST end end + +# $Id$ diff --git a/lib/puppet/parser/ast/resourceoverride.rb b/lib/puppet/parser/ast/resourceoverride.rb new file mode 100644 index 000000000..26d69ae97 --- /dev/null +++ b/lib/puppet/parser/ast/resourceoverride.rb @@ -0,0 +1,62 @@ +require 'puppet/parser/ast/resourcedef' + +class Puppet::Parser::AST + # Set a parameter on a resource specification created somewhere else in the + # configuration. The object is responsible for verifying that this is allowed. + class ResourceOverride < ResourceDef + attr_accessor :object + attr_reader :params + + # Iterate across all of our children. + def each + [@object,@params].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + # Does not actually return an object; instead sets an object + # in the current scope. + def evaluate(hash) + scope = hash[:scope] + + # Get our object reference. + object = @object.safeevaluate(:scope => scope) + + hash = {} + + # Evaluate all of the specified params. + params = @params.collect { |param| + param.safeevaluate(:scope => scope) + } + + # Now we just create a normal resource, but we call a very different + # method on the scope. + obj = Puppet::Parser::Resource.new( + :type => object.type, + :title => object.title, + :params => params, + :file => @file, + :line => @line, + :source => scope.source, + :scope => scope + ) + + # Now we tell the scope that it's an override, and it behaves as + # necessary. + scope.setoverride(obj) + + obj + end + + # Create our ResourceDef. Handles type checking for us. + def initialize(hash) + @checked = false + super + + #self.typecheck(@type.value) + end + end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/objectparam.rb b/lib/puppet/parser/ast/resourceparam.rb index 87c3e5e8e..248e91b32 100644 --- a/lib/puppet/parser/ast/objectparam.rb +++ b/lib/puppet/parser/ast/resourceparam.rb @@ -1,6 +1,8 @@ +require 'puppet/parser/ast/branch' + class Puppet::Parser::AST - # The AST object for the parameters inside ObjectDefs and Selectors. - class ObjectParam < AST::Branch + # The AST object for the parameters inside ResourceDefs and Selectors. + class ResourceParam < AST::Branch attr_accessor :value, :param def each @@ -10,9 +12,17 @@ class Puppet::Parser::AST # Return the parameter and the value. def evaluate(hash) scope = hash[:scope] - param = @param.safeevaluate(:scope => scope) + param = @param value = @value.safeevaluate(:scope => scope) - return [param, value] + + args = {:name => param, :value => value, :source => scope.source} + [:line, :file].each do |p| + if v = self.send(p) + args[p] = v + end + end + + return Puppet::Parser::Resource::Param.new(args) end def tree(indent = 0) @@ -27,5 +37,6 @@ class Puppet::Parser::AST return "%s => %s" % [@param,@value] end end - end + +# $Id$ diff --git a/lib/puppet/parser/ast/resourceref.rb b/lib/puppet/parser/ast/resourceref.rb new file mode 100644 index 000000000..b0fe5f6d7 --- /dev/null +++ b/lib/puppet/parser/ast/resourceref.rb @@ -0,0 +1,44 @@ +require 'puppet/parser/ast/branch' + +class Puppet::Parser::AST + # A reference to an object. Only valid as an rvalue. + class ResourceRef < AST::Branch + attr_accessor :title, :type + + def each + [@type,@title].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + # Evaluate our object, but just return a simple array of the type + # and name. + def evaluate(hash) + scope = hash[:scope] + + # We want a lower-case type. + objtype = @type.downcase + + title = @title.safeevaluate(:scope => scope) + + return Puppet::Parser::Resource::Reference.new( + :type => objtype, :title => title + ) + end + + def tree(indent = 0) + return [ + @type.tree(indent + 1), + @title.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)) + ].join("\n") + end + + def to_s + return "%s[%s]" % [@type,@title] + end + end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/selector.rb b/lib/puppet/parser/ast/selector.rb index 5d1c41494..fe3fe9eaf 100644 --- a/lib/puppet/parser/ast/selector.rb +++ b/lib/puppet/parser/ast/selector.rb @@ -42,12 +42,8 @@ class Puppet::Parser::AST if default retvalue = default.value.safeevaluate(:scope => scope) else - error = Puppet::ParseError.new( + self.fail Puppet::ParseError, "No value for selector param '%s'" % paramvalue - ) - error.line = self.line - error.file = self.file - raise error end end diff --git a/lib/puppet/parser/ast/vardef.rb b/lib/puppet/parser/ast/vardef.rb index 79129f31a..30eef204a 100644 --- a/lib/puppet/parser/ast/vardef.rb +++ b/lib/puppet/parser/ast/vardef.rb @@ -10,18 +10,8 @@ class Puppet::Parser::AST name = @name.safeevaluate(:scope => scope) value = @value.safeevaluate(:scope => scope) - begin + parsewrap do scope.setvar(name,value) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error end end @@ -43,3 +33,5 @@ class Puppet::Parser::AST end end + +# $Id$ diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb new file mode 100644 index 000000000..f3c7949de --- /dev/null +++ b/lib/puppet/parser/collector.rb @@ -0,0 +1,97 @@ +# An object that collects stored objects from the central cache and returns +# them to the current host, yo. +class Puppet::Parser::Collector + attr_accessor :type, :scope, :query, :form + + # Collect exported objects. + def collect_exported + require 'puppet/rails' + # First get everything from the export table. Just reuse our + # collect_virtual method but tell it to use 'exported? for the test. + resources = collect_virtual(true) + + count = resources.length + + # We're going to collect objects from rails, but we don't want any + # objects from this host. + host = Puppet::Rails::Host.find_by_name(@scope.host) + + args = {} + if host + args[:conditions] = "host_id != #{host.id}" + else + #Puppet.info "Host %s is uninitialized" % @scope.host + end + + # Now look them up in the rails db. When we support attribute comparison + # and such, we'll need to vary the conditions, but this works with no + # attributes, anyway. + Puppet::Util.benchmark(:debug, "Collected #{self.type} resources") do + Puppet::Rails::RailsResource.find_all_by_restype_and_exported(@type, true, + args + ).each do |obj| + count += 1 + resource = obj.to_resource(self.scope) + + # XXX Because the scopes don't expect objects to return values, + # we have to manually add our objects to the scope. This is + # uber-lame. + scope.setresource(resource) + + resources << resource + end + end + + scope.debug("Collected %s objects of type %s" % + [count, @convertedtype]) + + return resources + end + + # Collect just virtual objects, from our local configuration. + def collect_virtual(exported = false) + if exported + method = :exported? + else + method = :virtual? + end + scope.resources.find_all do |resource| + resource.type == @type and resource.send(method) and match?(resource) + end + end + + # Call the collection method, mark all of the returned objects as non-virtual, + # and then delete this object from the list of collections to evaluate. + def evaluate + method = "collect_#{@form.to_s}" + objects = send(method).each do |obj| + obj.virtual = false + end + + # And then remove us from the list of collections, since we've + # now been evaluated. + @scope.collections.delete(self) + + objects + end + + def initialize(scope, type, query, form) + @scope = scope + @type = type + @query = query + @form = form + @tests = [] + end + + # Does the resource match our tests? We don't yet support tests, + # so it's always true at the moment. + def match?(resource) + if self.query + return self.query.call(resource) + else + return true + end + end +end + +# $Id$ diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 9562441bc..96fccd7cd 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -76,29 +76,24 @@ module Functions # Include the specified classes newfunction(:include) do |vals| - vals.each do |val| - if objecttype = lookuptype(val) - # It's a defined type, so set it into the scope so it can - # be evaluated. - setobject( - :type => val, - :arguments => {} - ) - else - raise Puppet::ParseError, "Unknown class %s" % val - end + klasses = evalclasses(*vals) + + missing = vals.find_all do |klass| + ! klass.include?(klass) + end + + # Throw an error if we didn't evaluate all of the classes. + if missing.length == 1 + self.fail Puppet::ParseError, + "Could not find class %s" % missing + elsif missing.length > 1 + self.fail Puppet::ParseError, + "Could not find classes %s" % missing.join(", ") end end # Tag the current scope with each passed name newfunction(:tag) do |vals| - vals.each do |val| - # Some hackery, because the tags are stored by object id - # for singletonness. - self.setclass(val.object_id, val) - end - - # Also add them as tags self.tag(*vals) end @@ -120,16 +115,13 @@ module Functions # Test whether a given class or definition is defined newfunction(:defined, :rvalue) do |vals| - retval = true - - vals.each do |val| - unless builtintype?(val) or lookuptype(val) - retval = false - break - end + # For some reason, it doesn't want me to return from here. + if vals.detect do |val| Puppet::Type.type(val) or finddefine(val) end + true + else + false end - return retval end newfunction(:fail, :statement) do |vals| @@ -151,7 +143,7 @@ module Functions # Use a wrapper, so the template can't get access to the full # Scope object. debug "Retrieving template %s" % file - wrapper = Puppet::Parser::Scope::TemplateWrapper.new(self, file) + wrapper = Puppet::Parser::TemplateWrapper.new(self, file) begin wrapper.result() diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra index 3a1a78e50..5c91aabdc 100644 --- a/lib/puppet/parser/grammar.ra +++ b/lib/puppet/parser/grammar.ra @@ -5,38 +5,52 @@ class Puppet::Parser::Parser token LBRACK DQTEXT SQTEXT RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE -token FALSE EQUALS LESSEQUAL NOTEQUAL DOT COLON TYPE +token FALSE EQUALS LESSEQUAL NOTEQUAL DOT COLON TYPE LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN -token NAME SEMIC CASE DEFAULT AT LCOLLECT RCOLLECT +token NAME SEMIC CASE DEFAULT AT LCOLLECT RCOLLECT CLASSNAME CLASSREF +token NOT OR AND # We have 2 shift/reduce conflicts #expect 2 rule program: statements { - # Make sure we always return an array. - if val[0].is_a?(AST::ASTArray) - result = val[0] + if val[0] + # Make sure we always return an array. + if val[0].is_a?(AST::ASTArray) + if val[0].children.empty? + result = nil + else + result = val[0] + end + else + result = aryfy(val[0]) + end else - result = aryfy(val[0]) + result = nil end } - | nothing + | nil -statements: statement +statements: statement | statements statement { - if val[0].instance_of?(AST::ASTArray) - val[0].push(val[1]) - result = val[0] - else - result = ast AST::ASTArray, :children => [val[0],val[1]] + if val[0] and val[1] + if val[0].instance_of?(AST::ASTArray) + val[0].push(val[1]) + result = val[0] + else + result = ast AST::ASTArray, :children => [val[0],val[1]] + end + elsif obj = (val[0] || val[1]) + result = obj + else result = nil end } # The main list of valid statements -statement: object - | collectable +statement: resource + | virtualresource | collection | assignment | casestatement @@ -46,15 +60,16 @@ statement: object | definition | hostclass | nodedef + | resourceoverride -fstatement: NAME LPAREN classnames RPAREN { +fstatement: NAME LPAREN namestrings RPAREN { args = aryfy(val[2]) result = ast AST::Function, :name => val[0], :arguments => args, :ftype => :statement } - | NAME classnames { + | NAME namestrings { args = aryfy(val[1]) result = ast AST::Function, :name => val[0], @@ -62,108 +77,163 @@ fstatement: NAME LPAREN classnames RPAREN { :ftype => :statement } -# Includes are just syntactic sugar for classes with no names and -# no arguments. -#include: INCLUDE classnames { -# result = function_include(val[1]) -#} - -# Define a new tag. Both of these functions should really be done generically, -# but I'm not in a position to do that just yet. :/ -#tag: TAG classnames { -# result = function_tag(val[1]) -#} - -classnames: classname - | classnames COMMA classname { +namestrings: namestring + | namestrings COMMA namestring { result = aryfy(val[0], val[2]) result.line = @lexer.line result.file = @lexer.file } -classname: name +namestring: name | variable | quotedtext -#object: name LBRACE objectname COLON params endcomma RBRACE { -object: name LBRACE objectinstances endsemi RBRACE { +resource: NAME LBRACE resourceinstances endsemi RBRACE { if val[0].instance_of?(AST::ASTArray) - raise Puppet::ParseError, "Invalid name" + error "Invalid name" end array = val[2] - if array.instance_of?(AST::ObjectInst) + if array.instance_of?(AST::ResourceInst) array = [array] end result = ast AST::ASTArray - # this iterates across each specified objectinstance + # this iterates across each specified resourceinstance array.each { |instance| - unless instance.instance_of?(AST::ObjectInst) + unless instance.instance_of?(AST::ResourceInst) raise Puppet::Dev, "Got something that isn't an instance" end # now, i need to somehow differentiate between those things with # arrays in their names, and normal things - result.push ast(AST::ObjectDef, + result.push ast(AST::ResourceDef, :type => val[0], - :name => instance[0], + :title => instance[0], :params => instance[1]) } -} | name LBRACE params endcomma RBRACE { - if val[0].instance_of?(AST::ASTArray) - Puppet.notice "invalid name" - raise Puppet::ParseError, "Invalid name" - end - # an object but without a name - # this cannot be an instance of a library type - result = ast AST::ObjectDef, :type => val[0], :params => val[2] - -} | type LBRACE params endcomma RBRACE { +} | NAME LBRACE params endcomma RBRACE { + # This is a deprecated syntax. + error "All resource specifications require names" +} | TYPE LBRACE params endcomma RBRACE { # a template setting for a type if val[0].instance_of?(AST::ASTArray) - raise Puppet::ParseError, "Invalid type" + error "Invalid type" end - result = ast(AST::TypeDefaults, :type => val[0], :params => val[2]) + result = ast(AST::ResourceDefaults, :type => val[0], :params => val[2]) +} + +# Override a value set elsewhere in the configuration. +resourceoverride: resourceref LBRACE params RBRACE { + result = ast AST::ResourceOverride, :object => val[0], :params => val[2] } -# Collectable objects; these get stored in the database, instead of -# being passed to the client. -collectable: AT object { - unless Puppet[:storeconfigs] - raise Puppet::ParseError, "You cannot collect without storeconfigs being set" +# Exported and virtual resources; these don't get sent to the client +# unless they get collected elsewhere in the db. +virtualresource: at resource { + type = val[0] + + if type == :exported and ! Puppet[:storeconfigs] + error "You cannot collect without storeconfigs being set" end - if val[1].is_a? AST::TypeDefaults - raise Puppet::ParseError, "Defaults are not collectable" + if val[1].is_a? AST::ResourceDefaults + error "Defaults are not virtualizable" end - # Just mark our objects as collectable and pass them through. + method = type.to_s + "=" + + # Just mark our resources as exported and pass them through. if val[1].instance_of?(AST::ASTArray) val[1].each do |obj| - obj.collectable = true + obj.send(method, true) end else - val[1].collectable = true + val[1].send(method, true) end result = val[1] } +at: AT { result = :virtual } + | AT AT { result = :exported } + # A collection statement. Currently supports no arguments at all, but eventually # will, I assume. -collection: name LCOLLECT RCOLLECT { - unless Puppet[:storeconfigs] - raise Puppet::ParseError, "You cannot collect without storeconfigs being set" +collection: collectname collectrhand { + if val[0] =~ /^[a-z]/ + Puppet.warning addcontext("Collection names must now be capitalized") + end + type = val[0].downcase + args = {:type => type} + + if val[1].is_a?(AST::CollExpr) + args[:query] = val[1] + args[:query].type = type + args[:form] = args[:query].form + else + args[:form] = val[1] + end + if args[:form] == :exported and ! Puppet[:storeconfigs] + error "You cannot collect exported resources without storeconfigs being set" end - result = ast AST::Collection, :type => val[0] + result = ast AST::Collection, args } -objectinst: objectname COLON params endcomma { - result = ast AST::ObjectInst, :children => [val[0],val[2]] +collectname: TYPE | NAME + +collectrhand: LCOLLECT collstatements RCOLLECT { + if val[1] + result = val[1] + result.form = :virtual + else + result = :virtual + end } + | LLCOLLECT collstatements RRCOLLECT { + if val[1] + result = val[1] + result.form = :exported + else + result = :exported + end +} + +# A mini-language for handling collection comparisons. This is organized +# to avoid the need for precedence indications. +collstatements: nil + | collstatement + | collstatements colljoin collstatement { + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] +} + +collstatement: collexpr + | LPAREN collstatements RPAREN { + result = val[1] + result.parens = true +} + +colljoin: AND | OR -objectinstances: objectinst - | objectinstances SEMIC objectinst { - if val[0].instance_of?(AST::ObjectInst) +collexpr: colllval ISEQUAL simplervalue { + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + #result = ast AST::CollExpr + #result.push *val +} + | colllval NOTEQUAL simplervalue { + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + #result = ast AST::CollExpr + #result.push *val +} + +colllval: variable + | name + +resourceinst: resourcename COLON params endcomma { + result = ast AST::ResourceInst, :children => [val[0],val[2]] +} + +resourceinstances: resourceinst + | resourceinstances SEMIC resourceinst { + if val[0].instance_of?(AST::ResourceInst) result = ast AST::ASTArray, :children => [val[0],val[2]] else val[0].push val[2] @@ -182,7 +252,7 @@ type: TYPE { result = ast AST::Type, :value => val[0] } -objectname: quotedtext +resourcename: quotedtext | name | type | selector @@ -191,7 +261,7 @@ objectname: quotedtext assignment: VARIABLE EQUALS rvalue { # this is distinct from referencing a variable - variable = ast AST::Name, :value => val[0].sub(/^\$/,'') + variable = ast AST::Name, :value => val[0] result = ast AST::VarDef, :name => variable, :value => val[2] } @@ -210,8 +280,7 @@ params: # nothing } param: NAME FARROW rvalue { - leaf = ast AST::String, :value => val[0] - result = ast AST::ObjectParam, :param => leaf, :value => val[2] + result = ast AST::ResourceParam, :param => val[0], :value => val[2] } rvalues: rvalue @@ -223,6 +292,13 @@ rvalues: rvalue end } +simplervalue: quotedtext + | name + | type + | boolean + | selector + | variable + rvalue: quotedtext | name | type @@ -230,11 +306,11 @@ rvalue: quotedtext | selector | variable | array - | objectref + | resourceref | funcrvalue # We currently require arguments in these functions. -funcrvalue: NAME LPAREN classnames RPAREN { +funcrvalue: NAME LPAREN namestrings RPAREN { args = aryfy(val[2]) result = ast AST::Function, :name => val[0], @@ -252,8 +328,11 @@ boolean: BOOLEAN { result = ast AST::Boolean, :value => val[0] } -objectref: name LBRACK rvalue RBRACK { - result = ast AST::ObjectRef, :type => val[0], :name => val[2] +resourceref: NAME LBRACK rvalue RBRACK { + Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") + result = ast AST::ResourceRef, :type => val[0], :title => val[2] +} | TYPE LBRACK rvalue RBRACK { + result = ast AST::ResourceRef, :type => val[0], :title => val[2] } ifstatement: IF iftest LBRACE statements RBRACE else { @@ -333,7 +412,7 @@ sintvalues: selectval } selectval: selectlhand FARROW rvalue { - result = ast AST::ObjectParam, :param => val[0], :value => val[2] + result = ast AST::ResourceParam, :param => val[0], :value => val[2] } selectlhand: name @@ -378,7 +457,7 @@ import: IMPORT quotedtext { end files.each { |file| - parser = Puppet::Parser::Parser.new() + parser = Puppet::Parser::Parser.new(interp) parser.files = self.files Puppet.debug("importing '%s'" % file) @@ -396,8 +475,13 @@ import: IMPORT quotedtext { end # push the results into the main result array # We always return an array when we parse. - parser.parse.each do |child| - result.push child + ast = parser.parse + + # Things that just get added to the classtable or whatever return nil + if ast + ast.each do |child| + result.push child + end end } } @@ -405,165 +489,99 @@ import: IMPORT quotedtext { # Disable definition inheritance for now. 8/27/06, luke #definition: DEFINE NAME argumentlist parent LBRACE statements RBRACE { -definition: DEFINE NAME argumentlist LBRACE statements RBRACE { - args = { - :type => ast(AST::Name, :value => val[1]), - :args => val[2], - :code => val[4] # Switch to 5 for parents - } +definition: DEFINE fqname argumentlist LBRACE statements RBRACE { + interp.newdefine fqname(val[1]), :arguments => val[2], :code => val[4] + @lexer.indefine = false + result = nil - if val[3].instance_of?(AST::Name) - args[:parentclass] = val[3] - end - result = ast AST::CompDef, args #} | DEFINE NAME argumentlist parent LBRACE RBRACE { -} | DEFINE NAME argumentlist LBRACE RBRACE { - args = { - :type => ast(AST::Name, :value => val[1]), - :args => val[2], - :code => ast(AST::ASTArray) - } - - if val[3].instance_of?(AST::Name) - args[:parentclass] = val[3] - end - - result = ast AST::CompDef, args +} | DEFINE fqname argumentlist LBRACE RBRACE { + interp.newdefine fqname(val[1]), :arguments => val[2] + @lexer.indefine = false + result = nil } #hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { -hostclass: CLASS NAME parent LBRACE statements RBRACE { - #:args => val[2], - args = { - :type => ast(AST::Name, :value => val[1]), - :code => val[4] - } - # It'll be an ASTArray if we didn't get a parent - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::ClassDef, args -} | CLASS NAME parent LBRACE RBRACE { - args = { - :type => ast(AST::Name, :value => val[1]), - :code => ast(AST::ASTArray, :children => []) - } - # It'll be an ASTArray if we didn't get a parent - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::ClassDef, args +hostclass: CLASS fqname parent LBRACE statements RBRACE { + # Our class gets defined in the parent namespace, not our own. + @lexer.namepop + interp.newclass fqname(val[1]), :code => val[4], :parent => val[2] + result = nil +} | CLASS fqname parent LBRACE RBRACE { + # Our class gets defined in the parent namespace, not our own. + @lexer.namepop + interp.newclass fqname(val[1]), :parent => val[2] + result = nil } nodedef: NODE hostnames parent LBRACE statements RBRACE { - unless val[1].instance_of?(AST::ASTArray) - val[1] = ast AST::ASTArray, - :line => val[1].line, - :file => val[1].file, - :children => [val[1]] - end - args = { - :names => val[1], - :code => val[4] - } - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::NodeDef, args + interp.newnode val[1], :parent => val[2], :code => val[4] + result = nil } | NODE hostnames parent LBRACE RBRACE { - unless val[1].instance_of?(AST::ASTArray) - val[1] = ast AST::ASTArray, - :line => val[1].line, - :file => val[1].file, - :children => [val[1]] - end - args = { - :names => val[1], - :code => ast(AST::ASTArray, :children => []) - } - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::NodeDef,args + interp.newnode val[1], :parent => val[2] + result = nil } -# Multiple hostnames, as used for node names. +fqname: NAME + | CLASSNAME + +# Multiple hostnames, as used for node names. These are all literal +# strings, not AST objects. hostnames: hostname | hostnames COMMA hostname { - if val[0].instance_of?(AST::ASTArray) - result = val[0] - result.push val[2] - else - result = ast AST::ASTArray, :children => [val[0], val[2]] - end + result = val[0] + result = [result] unless result.is_a?(Array) + result << val[2] } -hostname: NAME { - result = ast AST::HostName, :value => val[0] -} | SQTEXT { - result = ast AST::HostName, :value => val[0] -} | DEFAULT { - result = ast AST::Default, :value => val[0] +hostname: NAME + | SQTEXT + | DEFAULT + +nil: { + result = nil } nothing: { result = ast AST::ASTArray, :children => [] } -argumentlist: nothing +argumentlist: nil | LPAREN nothing RPAREN { - result = val[1] + result = nil } | LPAREN arguments RPAREN { - if val[1].instance_of?(AST::ASTArray) - result = val[1] - else - result = ast AST::ASTArray, :children => [val[1]] - end + result = val[1] + result = [result] unless result[0].is_a?(Array) } arguments: argument | arguments COMMA argument { - if val[0].instance_of?(AST::ASTArray) - val[0].push(val[2]) - result = val[0] - else - result = ast AST::ASTArray, :children => [val[0],val[2]] - end + result = val[0] + result = [result] unless result[0].is_a?(Array) + result << val[2] } -argument: name EQUALS rvalue { - msg = "Deprecation notice: #{val[0].value} must now include '$' in prototype" - msg += " at line %s" % @lexer.line - msg += " in file %s" % @lexer.file if @lexer.file - Puppet.warning msg - result = ast AST::CompArgument, :children => [val[0],val[2]] +argument: NAME EQUALS rvalue { + Puppet.warning addcontext("Deprecation notice: #{val[0].value} must now include '$' in prototype") + result = [val[0], val[2]] } - | name { - msg = "Deprecation notice: #{val[0].value} must now include '$' in prototype" - msg += " at line %s" % @lexer.line - msg += " in file %s" % @lexer.file if @lexer.file - Puppet.warning msg - result = ast AST::CompArgument, :children => [val[0]] -} | lvariable EQUALS rvalue { - result = ast AST::CompArgument, :children => [val[0],val[2]] -} | lvariable { - result = ast AST::CompArgument, :children => [val[0]] + | NAME { + Puppet.warning addcontext("Deprecation notice: #{val[0].value} must now include '$' in prototype") + result = [val[0]] +} | VARIABLE EQUALS rvalue { + result = [val[0], val[2]] +} | VARIABLE { + result = [val[0]] } -parent: nothing +parent: nil | INHERITS NAME { - result = ast AST::Name, :value => val[1] + result = val[1] } variable: VARIABLE { - name = val[0].sub(/^\$/,'') - result = ast AST::Variable, :value => name -} - -# This is variables as lvalues; we're assigning them, not deferencing them. -lvariable: VARIABLE { - result = ast AST::Name, :value => val[0].sub(/^\$/,'') + result = ast AST::Variable, :value => val[0] } array: LBRACK rvalues RBRACK { @@ -602,9 +620,21 @@ Puppet[:paramcheck] = true ---- inner ---- require 'puppet/parser/functions' -attr_reader :file +attr_reader :file, :interp attr_accessor :files +# Add context to a message; useful for error messages and such. +def addcontext(message, obj = nil) + obj ||= @lexer + + message += " on line %s" % obj.line + if file = obj.file + message += " in file %s" % file + end + + return message +end + # Create an AST array out of all of the args def aryfy(*args) if args[0].instance_of?(AST::ASTArray) @@ -636,6 +666,17 @@ def ast(klass, hash = nil) return klass.new(hash) end +# Raise a Parse error. +def error(message) + except = Puppet::ParseError.new(message) + except.line = @lexer.line + if @lexer.file + except.file = @lexer.file + end + + raise except +end + def file=(file) unless FileTest.exists?(file) unless file =~ /\.pp$/ @@ -653,23 +694,20 @@ def file=(file) end end -def initialize +def initialize(interpreter) + @interp = interpreter + initvars() +end + +# Initialize or reset all of our variables. +def initvars @lexer = Puppet::Parser::Lexer.new() @files = [] - #if Puppet[:debug] - # @yydebug = true - #end end -# Add a new file to be checked when we're checking to see if we should be -# reparsed. -def newfile(*files) - files.each do |file| - unless file.is_a? Puppet::LoadedFile - file = Puppet::LoadedFile.new(file) - end - @files << file - end +# The fully qualifed name, with the full namespace. +def fqname(name) + [@lexer.namespace, name].join("::").sub(/^::/, '') end def on_error(token,value,stack) @@ -693,7 +731,7 @@ def parse(string = nil) self.string = string end begin - yyparse(@lexer,:scan) + main = yyparse(@lexer,:scan) rescue Racc::ParseError => except error = Puppet::ParseError.new(except) error.line = @lexer.line @@ -720,6 +758,13 @@ def parse(string = nil) error.set_backtrace except.backtrace raise error end + if main + # Store the results as the top-level class. + interp.newclass("", :code => main) + return main + end +ensure + @lexer.clear end # See if any of the files have changed. diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 26bf8104e..111fe2ed3 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -3,463 +3,774 @@ # and calls out to other objects. require 'puppet' +require 'timeout' require 'puppet/parser/parser' require 'puppet/parser/scope' +class Puppet::Parser::Interpreter + include Puppet::Util + + Puppet.setdefaults("ldap", + :ldapnodes => [false, + "Whether to search for node configurations in LDAP."], + :ldapssl => [false, + "Whether SSL should be used when searching for nodes. + Defaults to false because SSL usually requires certificates + to be set up on the client side."], + :ldaptls => [false, + "Whether TLS should be used when searching for nodes. + Defaults to false because TLS usually requires certificates + to be set up on the client side."], + :ldapserver => ["ldap", + "The LDAP server. Only used if ``ldapnodes`` is enabled."], + :ldapport => [389, + "The LDAP port. Only used if ``ldapnodes`` is enabled."], + :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", + "The search string used to find an LDAP node."], + :ldapattrs => ["puppetclass", + "The LDAP attributes to use to define Puppet classes. Values + should be comma-separated."], + :ldapparentattr => ["parentnode", + "The attribute to use to define the parent node."], + :ldapuser => ["", + "The user to use to connect to LDAP. Must be specified as a + full DN."], + :ldappassword => ["", + "The password to use to connect to LDAP."], + :ldapbase => ["", + "The search base for LDAP searches. It's impossible to provide + a meaningful default here, although the LDAP libraries might + have one already set. Generally, it should be the 'ou=Hosts' + branch under your main directory."] + ) + + Puppet.setdefaults(:puppetmaster, + :storeconfigs => [false, + "Whether to store each client's configuration. This + requires ActiveRecord from Ruby on Rails."] + ) + + attr_accessor :usenodes + + class << self + attr_writer :ldap + end + # just shorten the constant path a bit, using what amounts to an alias + AST = Puppet::Parser::AST + + include Puppet::Util::Errors + + # Create an ldap connection. This is a class method so others can call + # it and use the same variables and such. + def self.ldap + unless defined? @ldap and @ldap + if Puppet[:ldapssl] + @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) + elsif Puppet[:ldaptls] + @ldap = LDAP::SSLConn.new( + Puppet[:ldapserver], Puppet[:ldapport], true + ) + else + @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + end + @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) + end -module Puppet - module Parser - class Interpreter - include Puppet::Util - - Puppet.setdefaults("ldap", - :ldapnodes => [false, - "Whether to search for node configurations in LDAP."], - :ldapssl => [false, - "Whether SSL should be used when searching for nodes. - Defaults to false because SSL usually requires certificates - to be set up on the client side."], - :ldaptls => [false, - "Whether TLS should be used when searching for nodes. - Defaults to false because TLS usually requires certificates - to be set up on the client side."], - :ldapserver => ["ldap", - "The LDAP server. Only used if ``ldapnodes`` is enabled."], - :ldapport => [389, - "The LDAP port. Only used if ``ldapnodes`` is enabled."], - :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", - "The search string used to find an LDAP node."], - :ldapattrs => ["puppetclass", - "The LDAP attributes to use to define Puppet classes. Values - should be comma-separated."], - :ldapparentattr => ["parentnode", - "The attribute to use to define the parent node."], - :ldapuser => ["", - "The user to use to connect to LDAP. Must be specified as a - full DN."], - :ldappassword => ["", - "The password to use to connect to LDAP."], - :ldapbase => ["", - "The search base for LDAP searches. It's impossible to provide - a meaningful default here, although the LDAP libraries might - have one already set. Generally, it should be the 'ou=Hosts' - branch under your main directory."] - ) - - Puppet.setdefaults(:puppetmaster, - :storeconfigs => [false, - "Whether to store each client's configuration. This - requires ActiveRecord from Ruby on Rails."] - ) + return @ldap + end - attr_accessor :ast + def clear + initparsevars + end - class << self - attr_writer :ldap - end - # just shorten the constant path a bit, using what amounts to an alias - AST = Puppet::Parser::AST - - # Create an ldap connection. This is a class method so others can call - # it and use the same variables and such. - def self.ldap - unless defined? @ldap and @ldap - if Puppet[:ldapssl] - @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) - elsif Puppet[:ldaptls] - @ldap = LDAP::SSLConn.new( - Puppet[:ldapserver], Puppet[:ldapport], true - ) - else - @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + # Iteratively evaluate all of the objects. This finds all fo the + # objects that represent definitions and evaluates the definitions appropriately. + # It also adds defaults and overrides as appropriate. + def evaliterate(scope) + count = 0 + begin + timeout 300 do + while ary = scope.unevaluated + ary.each do |resource| + resource.evaluate end - @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) - @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) - @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) end - - return @ldap end + rescue Timeout::Error + raise Puppet::DevError, "Got a timeout trying to evaluate all definitions" + end + end - # create our interpreter - def initialize(hash) - if @code = hash[:Code] - @file = nil # to avoid warnings - elsif ! @file = hash[:Manifest] - raise Puppet::DevError, "You must provide code or a manifest" - end + # Evaluate a specific node. + def evalnode(client, scope, facts) + return unless self.usenodes - if hash.include?(:UseNodes) - @usenodes = hash[:UseNodes] - else - @usenodes = true - end + unless client + raise Puppet::Error, + "Cannot evaluate nodes with a nil client" + end + names = [client] + + # Make sure both the fqdn and the short name of the + # host can be used in the manifest + if client =~ /\./ + names << client.sub(/\..+/,'') + else + names << "#{client}.#{facts['domain']}" + end - # By default, we only search the parse tree. - @nodesources = [] + if names.empty? + raise Puppet::Error, + "Cannot evaluate nodes with a nil client" + end - if Puppet[:ldapnodes] - @nodesources << :ldap - end + # Look up our node object. + if nodeclass = nodesearch(*names) + nodeclass.safeevaluate :scope => scope + else + raise Puppet::Error, "Could not find %s with names %s" % + [client, names.join(", ")] + end + end - if hash[:NodeSources] - hash[:NodeSources].each do |src| - if respond_to? "nodesearch_#{src.to_s}" - @nodesources << src.to_s.intern - else - Puppet.warning "Node source '#{src}' not supported" - end - end - end + # Evaluate all of the code we can find that's related to our client. + def evaluate(client, facts) - @setup = false + scope = Puppet::Parser::Scope.new(:interp => self) # no parent scope + scope.name = "top" + scope.type = "main" - # Set it to either the value or nil. This is currently only used - # by the cfengine module. - @classes = hash[:Classes] || [] + scope.host = facts["hostname"] || Facter.value("hostname") - @local = hash[:Local] || false + classes = @classes.dup - if hash.include?(:ForkSave) - @forksave = hash[:ForkSave] - else - # This is just too dangerous right now. Sorry, it's going - # to have to be slow. - @forksave = false - end + # Okay, first things first. Set our facts. + scope.setfacts(facts) - if Puppet[:storeconfigs] - Puppet::Rails.init - end + # Everyone will always evaluate the top-level class, if there is one. + if klass = findclass("", "") + # Set the source, so objects can tell where they were defined. + scope.source = klass + klass.safeevaluate :scope => scope, :nosubscope => true + end - @files = [] + # Next evaluate the node + evalnode(client, scope, facts) - # Create our parser object - parsefiles + # If we were passed any classes, evaluate those. + if classes + classes.each do |klass| + if klassobj = findclass("", klass) + klassobj.safeevaluate :scope => scope + end end + end - # Search for our node in the various locations. This only searches - # locations external to the files; the scope is responsible for - # searching the parse tree. - def nodesearch(*nodes) - # At this point, stop at the first source that defines - # the node - @nodesources.each do |source| - method = "nodesearch_%s" % source - parent = nil - nodeclasses = nil - if self.respond_to? method - nodes.each do |node| - parent, nodeclasses = self.send(method, node) - - if parent or (nodeclasses and !nodeclasses.empty?) - Puppet.info "Found %s in %s" % [node, source] - return parent, nodeclasses - else - # Look for a default node. - parent, nodeclasses = self.send(method, "default") - if parent or (nodeclasses and !nodeclasses.empty?) - Puppet.info "Found default node for %s in %s" % - [node, source] - return parent, nodeclasses - end - end - end - end - end + # That was the first pass evaluation. Now iteratively evaluate + # until we've gotten rid of all of everything or thrown an error. + evaliterate(scope) + + # Now make sure we fail if there's anything left to do + failonleftovers(scope) - return nil, nil + # Now perform the collections + scope.collections.each do |coll| + coll.evaluate + end + + # Now finish everything. This recursively calls finish on the + # contained scopes and resources. + scope.finish + + # Store everything. We need to do this before translation, because + # it operates on resources, not transobjects. + if Puppet[:storeconfigs] + args = { + :resources => scope.resources, + :name => client, + :facts => facts + } + unless scope.classlist.empty? + args[:classes] = scope.classlist end - # Find the ldap node and extra the info, returning just - # the critical data. - def nodesearch_ldap(node) - unless defined? @ldap and @ldap - setup_ldap() - unless @ldap - Puppet.info "Skipping ldap source; no ldap connection" - return nil, [] - end - end + storeconfigs(args) + end - if node =~ /\./ - node = node.sub(/\..+/, '') - end + # Now, finally, convert our scope tree + resources into a tree of + # buckets and objects. + objects = scope.translate - filter = Puppet[:ldapstring] - attrs = Puppet[:ldapattrs].split("\s*,\s*") - sattrs = attrs.dup - pattr = nil - if pattr = Puppet[:ldapparentattr] - if pattr == "" - pattr = nil - else - sattrs << pattr - end - end + # Add the class list + unless scope.classlist.empty? + objects.classes = scope.classlist + end - if filter =~ /%s/ - filter = filter.gsub(/%s/, node) - end + return objects + end - parent = nil - classes = [] - - found = false - count = 0 - begin - # We're always doing a sub here; oh well. - @ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry| - found = true - if pattr - if values = entry.vals(pattr) - if values.length > 1 - raise Puppet::Error, - "Node %s has more than one parent: %s" % - [node, values.inspect] - end - unless values.empty? - parent = values.shift - end - end - end + # Fail if there any overrides left to perform. + def failonleftovers(scope) + overrides = scope.overrides + if overrides.empty? + return nil + else + fail Puppet::ParseError, + "Could not find object(s) %s" % overrides.collect { |o| + o.ref + }.join(", ") + end + end - attrs.each { |attr| - if values = entry.vals(attr) - values.each do |v| classes << v end - end - } - end - rescue => detail - if count == 0 - # Try reconnecting to ldap - @ldap = nil - setup_ldap() - retry - else - raise Puppet::Error, "LDAP Search failed: %s" % detail - end - end + # Find a class definition, relative to the current namespace. + def findclass(namespace, name) + fqfind namespace, name, @classtable + end - classes.flatten! + # Find a component definition, relative to the current namespace. + def finddefine(namespace, name) + fqfind namespace, name, @definetable + end - return parent, classes - end + # The recursive method used to actually look these objects up. + def fqfind(namespace, name, table) + if name =~ /^::/ or namespace == "" + return table[name.sub(/^::/, '')] + end + ary = namespace.split("::") - def parsedate - parsefiles() - @parsedate + while ary.length > 0 + newname = (ary + [name]).join("::").sub(/^::/, '') + if obj = table[newname] + return obj end - # Add a new file to check for updateness. - def newfile(file) - unless @files.find { |f| f.file == file } - @files << Puppet::LoadedFile.new(file) - end - end + # Delete the second to last object, which reduces our namespace by one. + ary.pop + end - # evaluate our whole tree - def run(client, facts) - # We have to leave this for after initialization because there - # seems to be a problem keeping ldap open after a fork. - unless @setup - @nodesources.each { |source| - method = "setup_%s" % source.to_s - if respond_to? method - begin - self.send(method) - rescue => detail - raise Puppet::Error, - "Could not set up node source %s" % source - end - end - } - end - parsefiles() + # If we've gotten to this point without finding it, see if the name + # exists at the top namespace + if obj = table[name] + return obj + end + + return nil + end + + # Create a new node, just from a list of names, classes, and an optional parent. + def gennode(name, hash) + facts = hash[:facts] + classes = hash[:classes] + parent = hash[:parentnode] + arghash = { + :name => name, + :code => AST::ASTArray.new(:pin => "[]"), + :interp => self, + :fqname => name + } + classes = [classes] unless classes.is_a?(Array) + + classcode = @parser.ast(AST::ASTArray, :children => classes.collect do |klass| + @parser.ast(AST::FlatString, :value => klass) + end) + + # Now generate a function call. + code = @parser.ast(AST::Function, + :name => "include", + :arguments => classcode, + :ftype => :statement + ) + + if parent + arghash[:parentclass] = parent + end + + # Create the node + return @parser.ast(AST::Node, arghash) + end + + # create our interpreter + def initialize(hash) + if @code = hash[:Code] + @file = nil # to avoid warnings + elsif ! @file = hash[:Manifest] + devfail "You must provide code or a manifest" + end + + if hash.include?(:UseNodes) + @usenodes = hash[:UseNodes] + else + @usenodes = true + end + + # By default, we only search for parsed nodes. + @nodesources = [:code] - # Really, we should stick multiple names in here - # but for now just make a simple array - names = [client] + if Puppet[:ldapnodes] + # Nodes in the file override nodes in ldap. + @nodesources << :ldap + end - # Make sure both the fqdn and the short name of the - # host can be used in the manifest - if client =~ /\./ - names << client.sub(/\..+/,'') + if hash[:NodeSources] + unless hash[:NodeSources].is_a?(Array) + hash[:NodeSources] = [hash[:NodeSources]] + end + hash[:NodeSources].each do |src| + if respond_to? "nodesearch_#{src.to_s}" + @nodesources << src.to_s.intern else - names << "#{client}.#{facts['domain']}" + Puppet.warning "Node source '#{src}' not supported" end + end + end - scope = Puppet::Parser::Scope.new() # no parent scope - scope.name = "top" - scope.type = "puppet" - scope.interp = self + @setup = false - classes = @classes.dup + initparsevars() - args = {:ast => @ast, :facts => facts, :classes => classes} + # Set it to either the value or nil. This is currently only used + # by the cfengine module. + @classes = hash[:Classes] || [] - if @usenodes - unless client - raise Puppet::Error, - "Cannot evaluate nodes with a nil client" - end + @local = hash[:Local] || false - args[:names] = names + if hash.include?(:ForkSave) + @forksave = hash[:ForkSave] + else + # This is just too dangerous right now. Sorry, it's going + # to have to be slow. + @forksave = false + end + + # The class won't always be defined during testing. + if Puppet[:storeconfigs] and defined? ActiveRecord::Base + Puppet::Rails.init + end + + @files = [] + + # Create our parser object + parsefiles + end + + # Initialize or reset the variables related to parsing. + def initparsevars + @classtable = {} + @namespace = "main" + + @nodetable = {} + + @definetable = {} + end + + # Find the ldap node and extra the info, returning just + # the critical data. + def ldapsearch(node) + unless defined? @ldap and @ldap + setup_ldap() + unless @ldap + Puppet.info "Skipping ldap source; no ldap connection" + return nil, [] + end + end - parent, nodeclasses = nodesearch(*names) + if node =~ /\./ + node = node.sub(/\..+/, '') + end - args[:classes] += nodeclasses if nodeclasses + filter = Puppet[:ldapstring] + attrs = Puppet[:ldapattrs].split("\s*,\s*") + sattrs = attrs.dup + pattr = nil + if pattr = Puppet[:ldapparentattr] + if pattr == "" + pattr = nil + else + sattrs << pattr + end + end - args[:parentnode] = parent if parent + if filter =~ /%s/ + filter = filter.gsub(/%s/, node) + end - if nodeclasses or parent - args[:searched] = true + parent = nil + classes = [] + + found = false + count = 0 + begin + # We're always doing a sub here; oh well. + @ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry| + found = true + if pattr + if values = entry.vals(pattr) + if values.length > 1 + raise Puppet::Error, + "Node %s has more than one parent: %s" % + [node, values.inspect] + end + unless values.empty? + parent = values.shift + end end end - begin - objects = scope.evaluate(args) - rescue Puppet::DevError, Puppet::Error, Puppet::ParseError => except - raise - rescue => except - error = Puppet::DevError.new("%s: %s" % - [except.class, except.message]) - error.set_backtrace except.backtrace - raise error - end + attrs.each { |attr| + if values = entry.vals(attr) + values.each do |v| classes << v end + end + } + end + rescue => detail + if count == 0 + # Try reconnecting to ldap + @ldap = nil + setup_ldap() + retry + else + raise Puppet::Error, "LDAP Search failed: %s" % detail + end + end - if Puppet[:storeconfigs] - storeconfigs( - :objects => objects, - :host => client, - :facts => facts - ) - end + classes.flatten! + + return parent, classes + end + + # Split an fq name into a namespace and name + def namesplit(fullname) + ary = fullname.split("::") + n = ary.pop || "" + ns = ary.join("::") + return ns, n + end - return objects + # Create a new class, or merge with an existing class. + def newclass(fqname, options = {}) + if @definetable.include?(fqname) + raise Puppet::ParseError, "Cannot redefine class %s as a definition" % + fqname + end + code = options[:code] + parent = options[:parent] + + # If the class is already defined, then add code to it. + if other = @classtable[fqname] + # Make sure the parents match + if parent and other.parentclass and (parent != other.parentclass) + @parser.error @parser.addcontext("Class %s is already defined" % fqname) + + " with parent %s" % [fqname, other.parentclass] end - # Connect to the LDAP Server - def setup_ldap - self.class.ldap = nil - begin - require 'ldap' - rescue LoadError - Puppet.notice( - "Could not set up LDAP Connection: Missing ruby/ldap libraries" - ) - @ldap = nil - return + # This might be dangerous... + if parent and ! other.parentclass + other.parentclass = parent + end + + # This might just be an empty, stub class. + if code + tmp = fqname + if tmp == "" + tmp = "main" end - begin - @ldap = self.class.ldap() - rescue => detail - raise Puppet::Error, "Could not connect to LDAP: %s" % detail + + Puppet.debug @parser.addcontext("Adding code to %s" % tmp) + # Else, add our code to it. + if other.code and code + other.code.children += code.children + else + other.code ||= code end end + else + # Define it anew. + ns, name = namesplit(fqname) + args = {:type => name, :namespace => ns, :fqname => fqname, :interp => self} + args[:code] = code if code + args[:parentclass] = parent if parent + @classtable[fqname] = @parser.ast AST::HostClass, args + end - def scope - return @scope - end + return @classtable[fqname] + end - private + # Create a new definition. + def newdefine(fqname, options = {}) + if @classtable.include?(fqname) + raise Puppet::ParseError, "Cannot redefine class %s as a definition" % + fqname + end + # Make sure our definition doesn't already exist + if other = @definetable[fqname] + @parser.error @parser.addcontext( + "%s is already defined at line %s" % [fqname, other.line], + other + ) + end - # Check whether any of our files have changed. - def checkfiles - if @files.find { |f| f.changed? } - @parsedate = Time.now.to_i - end + ns, name = namesplit(fqname) + args = { + :type => name, + :namespace => ns, + :arguments => options[:arguments], + :code => options[:code], + :fqname => fqname + } + + [:code, :arguments].each do |param| + args[param] = options[param] if options[param] + end + + @definetable[fqname] = @parser.ast AST::Component, args + end + + # Create a new node. Nodes are special, because they're stored in a global + # table, not according to namespaces. + def newnode(names, options = {}) + names = [names] unless names.instance_of?(Array) + names.collect do |name| + if other = @nodetable[name] + @parser.error @parser.addcontext("Node %s is already defined" % [other.name], other) + end + name = name.to_s if name.is_a?(Symbol) + args = { + :name => name, + } + if options[:code] + args[:code] = options[:code] + end + if options[:parent] + args[:parentclass] = options[:parent] end + @nodetable[name] = @parser.ast(AST::Node, args) + @nodetable[name].fqname = name + @nodetable[name] + @nodetable[name].interp = self + @nodetable[name] + end + end + + # Add a new file to be checked when we're checking to see if we should be + # reparsed. + def newfile(*files) + files.each do |file| + unless file.is_a? Puppet::LoadedFile + file = Puppet::LoadedFile.new(file) + end + @files << file + end + end - # Parse the files, generating our parse tree. This automatically - # reparses only if files are updated, so it's safe to call multiple - # times. - def parsefiles - # First check whether there are updates to any non-puppet files - # like templates. If we need to reparse, this will get quashed, - # but it needs to be done first in case there's no reparse - # but there are other file changes. - checkfiles() - - # Check if the parser should reparse. - if @file - if defined? @parser - if stamp = @parser.reparse? - Puppet.notice "Reloading files" - else - return false + # Search for our node in the various locations. + def nodesearch(*nodes) + # At this point, stop at the first source that defines + # the node + @nodesources.each do |source| + method = "nodesearch_%s" % source + if self.respond_to? method + # Do an inverse sort on the length, so the longest match always + # wins + nodes.sort { |a,b| b.length <=> a.length }.each do |node| + node = node.to_s if node.is_a?(Symbol) + if obj = self.send(method, node) + nsource = obj.file || source + Puppet.info "Found %s in %s" % [node, nsource] + return obj + else + # Look for a default node. + if defobj = self.send(method, "default") + Puppet.info "Found default node for %s in %s" % + [node, source] + return defobj end end + end + end + end - unless FileTest.exists?(@file) - if @ast - return - else - raise Puppet::Error, "Manifest %s must exist" % @file - end + return nil + end + + # See if our node was defined in the code. + def nodesearch_code(name) + @nodetable[name] + end + + # Look for our node in ldap. + def nodesearch_ldap(node) + parent, classes = ldapsearch(node) + if parent or classes + args = {} + args[:classes] = classes if classes and ! classes.empty? + args[:parentnode] = parent if parent + return gennode(node, args) + else + return nil + end + end + + def parsedate + parsefiles() + @parsedate + end + + # evaluate our whole tree + def run(client, facts) + # We have to leave this for after initialization because there + # seems to be a problem keeping ldap open after a fork. + unless @setup + @nodesources.each { |source| + method = "setup_%s" % source.to_s + if respond_to? method + exceptwrap :type => Puppet::Error, + :message => "Could not set up node source %s" % source do + self.send(method) end end + } + end + parsefiles() + + # Evaluate all of the appropriate code. + objects = evaluate(client, facts) + + # And return it all. + return objects + end + + # Connect to the LDAP Server + def setup_ldap + self.class.ldap = nil + begin + require 'ldap' + rescue LoadError + Puppet.notice( + "Could not set up LDAP Connection: Missing ruby/ldap libraries" + ) + @ldap = nil + return + end + begin + @ldap = self.class.ldap() + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end + + def scope + return @scope + end + + # Iteratively make sure that every object in the scope tree is translated. + def translate(scope) + end + + private - # should i be creating a new parser each time...? - @parser = Puppet::Parser::Parser.new() - if @code - @parser.string = @code + # Check whether any of our files have changed. + def checkfiles + if @files.find { |f| f.changed? } + @parsedate = Time.now.to_i + end + end + + # Parse the files, generating our parse tree. This automatically + # reparses only if files are updated, so it's safe to call multiple + # times. + def parsefiles + # First check whether there are updates to any non-puppet files + # like templates. If we need to reparse, this will get quashed, + # but it needs to be done first in case there's no reparse + # but there are other file changes. + checkfiles() + + # Check if the parser should reparse. + if @file + if defined? @parser + if stamp = @parser.reparse? + Puppet.notice "Reloading files" else - @parser.file = @file - # Mark when we parsed, so we can check freshness - @parsedate = File.stat(@file).ctime.to_i + return false end + end - if @local - @ast = @parser.parse + unless FileTest.exists?(@file) + # If we've already parsed, then we're ok. + if findclass("", "") + return else - benchmark(:info, "Parsed manifest") do - @ast = @parser.parse - end + raise Puppet::Error, "Manifest %s must exist" % @file end - @parsedate = Time.now.to_i end + end - # Store the configs into the database. - def storeconfigs(hash) - unless defined? ActiveRecord - require 'puppet/rails' - unless defined? ActiveRecord - raise LoadError, - "storeconfigs is enabled but rails is unavailable" - end - end + # Reset our parse tables. + clear() + + # Create a new parser, just to keep things fresh. + @parser = Puppet::Parser::Parser.new(self) + if @code + @parser.string = @code + else + @parser.file = @file + # Mark when we parsed, so we can check freshness + @parsedate = File.stat(@file).ctime.to_i + end - Puppet::Rails.init - - # Fork the storage, since we don't need the client waiting - # on that. How do I avoid this duplication? - if @forksave - fork { - # We store all of the objects, even the collectable ones - benchmark(:info, "Stored configuration for #{hash[:client]}") do - # Try to batch things a bit, by putting them into - # a transaction - Puppet::Rails::Host.transaction do - Puppet::Rails::Host.store(hash) - end - end - } - else - # We store all of the objects, even the collectable ones - benchmark(:info, "Stored configuration for #{hash[:client]}") do - Puppet::Rails::Host.transaction do - Puppet::Rails::Host.store(hash) - end + # Parsing stores all classes and defines and such in their + # various tables, so we don't worry about the return. + if @local + @parser.parse + else + benchmark(:info, "Parsed manifest") do + @parser.parse + end + end + @parsedate = Time.now.to_i + end + + # Store the configs into the database. + def storeconfigs(hash) + unless defined? ActiveRecord + require 'puppet/rails' + unless defined? ActiveRecord + raise LoadError, + "storeconfigs is enabled but rails is unavailable" + end + end + + Puppet::Rails.init + + # Fork the storage, since we don't need the client waiting + # on that. How do I avoid this duplication? + if @forksave + fork { + # We store all of the objects, even the collectable ones + benchmark(:info, "Stored configuration for #{hash[:name]}") do + # Try to batch things a bit, by putting them into + # a transaction + Puppet::Rails::Host.transaction do + Puppet::Rails::Host.store(hash) end end - - # Now that we've stored everything, we need to strip out - # the collectable objects so that they are not sent on - # to the host - hash[:objects].collectstrip! + } + else + # We store all of the objects, even the collectable ones + benchmark(:info, "Stored configuration for #{hash[:name]}") do + Puppet::Rails::Host.transaction do + Puppet::Rails::Host.store(hash) + end end end + + # Now that we've stored everything, we need to strip out + # the collectable objects so that they are not sent on + # to the host + #hash[:objects].collectstrip! end end diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb index 80a8715ba..39fef8668 100644 --- a/lib/puppet/parser/lexer.rb +++ b/lib/puppet/parser/lexer.rb @@ -12,6 +12,8 @@ module Puppet class Lexer attr_reader :line, :last, :file + attr_accessor :indefine + #%r{\w+} => :WORD, @@tokens = { %r{#.*} => :COMMENT, @@ -31,10 +33,13 @@ module Puppet %r{<} => :LESSTHAN, %r{<=} => :LESSEQUAL, %r{!=} => :NOTEQUAL, + %r{!} => :NOT, %r{,} => :COMMA, %r{\.} => :DOT, %r{:} => :COLON, %r{@} => :AT, + %r{<<\|} => :LLCOLLECT, + %r{\|>>} => :RRCOLLECT, %r{<\|} => :LCOLLECT, %r{\|>} => :RCOLLECT, %r{;} => :SEMIC, @@ -42,6 +47,8 @@ module Puppet %r{\\} => :BACKSLASH, %r{=>} => :FARROW, %r{[a-z][-\w]*} => :NAME, + %r{([a-z][-\w]*::)+[a-z][-\w]*} => :CLASSNAME, + %r{([A-Z][-\w]*::)+[A-Z][-\w]*} => :CLASSREF, %r{[A-Z][-\w]*} => :TYPE, %r{[0-9]+} => :NUMBER, %r{\$\w+} => :VARIABLE @@ -59,9 +66,15 @@ module Puppet "else" => :ELSE, "inherits" => :INHERITS, "node" => :NODE, - "true" => :BOOLEAN + "true" => :BOOLEAN, + "and" => :AND, + "or" => :OR } + def clear + initvars + end + # scan the whole file # basically just used for testing def fullscan @@ -90,14 +103,47 @@ module Puppet } end + def indefine? + if defined? @indefine + @indefine + else + false + end + end + def initialize + initvars() + end + + def initvars @line = 1 @last = "" + @lasttoken = nil @scanner = nil @file = nil # AAARRGGGG! okay, regexes in ruby are bloody annoying # no one else has "\n" =~ /\s/ @skip = %r{[ \t]+} + + @namestack = [] + @indefine = false + end + + # Go up one in the namespace. + def namepop + @namestack.pop + end + + # Collect the current namespace. + def namespace + @namestack.join("::") + end + + # This value might have :: in it, but we don't care -- it'll be + # handled normally when joining, and when popping we want to pop + # this full value, however long the namespace is. + def namestack(value) + @namestack << value end def rest @@ -164,6 +210,7 @@ module Puppet # if this gets much more complicated, it should # be moved up to where the tokens themselves are defined # which will get me about 75% of the way to a lexer generator + ptoken = stoken case stoken when :NAME then wtoken = stoken @@ -171,36 +218,51 @@ module Puppet if @@keywords.include?(value) wtoken = @@keywords[value] #Puppet.debug("token '%s'" % wtoken) + if wtoken == :BOOLEAN + value = eval(value) + end end - yield [wtoken,value] - @last = value + ptoken = wtoken when :NUMBER then - yield [:NAME,value] - # just throw comments away + ptoken = :NAME when :COMMENT then # just throw comments away + next when :RETURN then @line += 1 @scanner.skip(@skip) + next when :SQUOTE then #Puppet.debug("searching '%s' after '%s'" % [self.rest,value]) value = self.slurpstring(value) - yield [:SQTEXT,value] - @last = value - #stoken = :DQTEXT + ptoken = :SQTEXT #Puppet.debug("got string '%s' => '%s'" % [:DQTEXT,value]) when :DQUOTE then - #Puppet.debug("searching '%s' after '%s'" % [self.rest,value]) value = self.slurpstring(value) - yield [:DQTEXT,value] - @last = value - #stoken = :DQTEXT - #Puppet.debug("got string '%s' => '%s'" % [:DQTEXT,value]) - else - #Puppet.debug("got token '%s' => '%s'" % [stoken,value]) - yield [stoken,value] - @last = value + ptoken = :DQTEXT + when :VARIABLE then + value = value.sub(/^\$/, '') + end + + yield [ptoken, value] + + if @lasttoken == :CLASS + namestack(value) end + + if @lasttoken == :DEFINE + if indefine? + self.indefine = false + raise Puppet::ParseError, + "Definitions cannot nest" + end + + @indefine = true + end + + @last = value + @lasttoken = ptoken + @scanner.skip(@skip) end @scanner = nil diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb index 3cda43ee0..047dace3a 100644 --- a/lib/puppet/parser/parser.rb +++ b/lib/puppet/parser/parser.rb @@ -29,12 +29,24 @@ module Puppet class Parser < Racc::Parser -module_eval <<'..end grammar.ra modeval..idc4b6d943e3', 'grammar.ra', 603 +module_eval <<'..end grammar.ra modeval..ide506d3a623', 'grammar.ra', 621 require 'puppet/parser/functions' -attr_reader :file +attr_reader :file, :interp attr_accessor :files +# Add context to a message; useful for error messages and such. +def addcontext(message, obj = nil) + obj ||= @lexer + + message += " on line %s" % obj.line + if file = obj.file + message += " in file %s" % file + end + + return message +end + # Create an AST array out of all of the args def aryfy(*args) if args[0].instance_of?(AST::ASTArray) @@ -66,6 +78,17 @@ def ast(klass, hash = nil) return klass.new(hash) end +# Raise a Parse error. +def error(message) + except = Puppet::ParseError.new(message) + except.line = @lexer.line + if @lexer.file + except.file = @lexer.file + end + + raise except +end + def file=(file) unless FileTest.exists?(file) unless file =~ /\.pp$/ @@ -83,23 +106,20 @@ def file=(file) end end -def initialize +def initialize(interpreter) + @interp = interpreter + initvars() +end + +# Initialize or reset all of our variables. +def initvars @lexer = Puppet::Parser::Lexer.new() @files = [] - #if Puppet[:debug] - # @yydebug = true - #end end -# Add a new file to be checked when we're checking to see if we should be -# reparsed. -def newfile(*files) - files.each do |file| - unless file.is_a? Puppet::LoadedFile - file = Puppet::LoadedFile.new(file) - end - @files << file - end +# The fully qualifed name, with the full namespace. +def fqname(name) + [@lexer.namespace, name].join("::").sub(/^::/, '') end def on_error(token,value,stack) @@ -123,7 +143,7 @@ def parse(string = nil) self.string = string end begin - yyparse(@lexer,:scan) + main = yyparse(@lexer,:scan) rescue Racc::ParseError => except error = Puppet::ParseError.new(except) error.line = @lexer.line @@ -150,6 +170,13 @@ def parse(string = nil) error.set_backtrace except.backtrace raise error end + if main + # Store the results as the top-level class. + interp.newclass("", :code => main) + return main + end +ensure + @lexer.clear end # See if any of the files have changed. @@ -172,363 +199,413 @@ end # $Id$ -..end grammar.ra modeval..idc4b6d943e3 +..end grammar.ra modeval..ide506d3a623 ##### racc 1.4.5 generates ### racc_reduce_table = [ 0, 0, :racc_error, - 1, 44, :_reduce_1, - 1, 44, :_reduce_none, - 1, 45, :_reduce_none, - 2, 45, :_reduce_4, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 4, 55, :_reduce_16, - 2, 55, :_reduce_17, - 1, 59, :_reduce_none, - 3, 59, :_reduce_19, - 1, 60, :_reduce_none, - 1, 60, :_reduce_none, - 1, 60, :_reduce_none, - 5, 48, :_reduce_23, - 5, 48, :_reduce_24, - 5, 48, :_reduce_25, - 2, 49, :_reduce_26, - 3, 50, :_reduce_27, - 4, 69, :_reduce_28, - 1, 64, :_reduce_none, - 3, 64, :_reduce_30, - 0, 65, :_reduce_none, - 1, 65, :_reduce_none, - 1, 61, :_reduce_33, - 1, 68, :_reduce_34, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 3, 51, :_reduce_41, - 0, 66, :_reduce_42, - 1, 66, :_reduce_43, - 3, 66, :_reduce_44, - 3, 74, :_reduce_45, - 1, 75, :_reduce_none, - 3, 75, :_reduce_47, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 4, 79, :_reduce_57, - 1, 63, :_reduce_58, - 1, 63, :_reduce_59, - 1, 77, :_reduce_60, - 4, 78, :_reduce_61, - 6, 53, :_reduce_62, - 0, 81, :_reduce_none, - 4, 81, :_reduce_64, + 1, 51, :_reduce_1, + 1, 51, :_reduce_none, + 1, 52, :_reduce_none, + 2, 52, :_reduce_4, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 4, 62, :_reduce_17, + 2, 62, :_reduce_18, + 1, 67, :_reduce_none, + 3, 67, :_reduce_20, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 5, 55, :_reduce_24, + 5, 55, :_reduce_25, + 5, 55, :_reduce_26, + 4, 66, :_reduce_27, + 2, 56, :_reduce_28, + 1, 77, :_reduce_29, + 2, 77, :_reduce_30, + 2, 57, :_reduce_31, + 1, 78, :_reduce_none, + 1, 78, :_reduce_none, + 3, 79, :_reduce_34, + 3, 79, :_reduce_35, + 1, 80, :_reduce_none, 1, 80, :_reduce_none, - 5, 52, :_reduce_66, + 3, 80, :_reduce_38, + 1, 81, :_reduce_none, + 3, 81, :_reduce_40, 1, 82, :_reduce_none, - 2, 82, :_reduce_68, - 5, 83, :_reduce_69, - 4, 83, :_reduce_70, + 1, 82, :_reduce_none, + 3, 83, :_reduce_43, + 3, 83, :_reduce_44, + 1, 84, :_reduce_none, 1, 84, :_reduce_none, - 3, 84, :_reduce_72, - 3, 71, :_reduce_73, - 1, 86, :_reduce_none, - 3, 86, :_reduce_75, - 1, 88, :_reduce_none, - 3, 88, :_reduce_77, - 3, 87, :_reduce_78, + 4, 86, :_reduce_47, + 1, 72, :_reduce_none, + 3, 72, :_reduce_49, + 0, 73, :_reduce_none, + 1, 73, :_reduce_none, + 1, 69, :_reduce_52, + 1, 88, :_reduce_53, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 3, 58, :_reduce_60, + 0, 74, :_reduce_61, + 1, 74, :_reduce_62, + 3, 74, :_reduce_63, + 3, 92, :_reduce_64, + 1, 93, :_reduce_none, + 3, 93, :_reduce_66, 1, 85, :_reduce_none, 1, 85, :_reduce_none, 1, 85, :_reduce_none, 1, 85, :_reduce_none, 1, 85, :_reduce_none, 1, 85, :_reduce_none, - 1, 85, :_reduce_85, - 2, 54, :_reduce_86, - 6, 56, :_reduce_87, - 5, 56, :_reduce_88, - 6, 57, :_reduce_89, - 5, 57, :_reduce_90, - 6, 58, :_reduce_91, - 5, 58, :_reduce_92, 1, 91, :_reduce_none, - 3, 91, :_reduce_94, - 1, 92, :_reduce_95, - 1, 92, :_reduce_96, - 1, 92, :_reduce_97, - 0, 46, :_reduce_98, - 1, 89, :_reduce_none, - 3, 89, :_reduce_100, - 3, 89, :_reduce_101, - 1, 93, :_reduce_none, - 3, 93, :_reduce_103, - 3, 94, :_reduce_104, - 1, 94, :_reduce_105, - 3, 94, :_reduce_106, - 1, 94, :_reduce_107, - 1, 90, :_reduce_none, - 2, 90, :_reduce_109, - 1, 62, :_reduce_110, - 1, 95, :_reduce_111, - 3, 72, :_reduce_112, - 2, 72, :_reduce_113, - 1, 76, :_reduce_none, - 1, 76, :_reduce_none, - 0, 67, :_reduce_none, - 1, 67, :_reduce_117 ] - -racc_reduce_n = 118 - -racc_shift_n = 197 + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 4, 96, :_reduce_82, + 1, 71, :_reduce_83, + 1, 71, :_reduce_84, + 1, 95, :_reduce_85, + 4, 76, :_reduce_86, + 4, 76, :_reduce_87, + 6, 60, :_reduce_88, + 0, 98, :_reduce_none, + 4, 98, :_reduce_90, + 1, 97, :_reduce_none, + 5, 59, :_reduce_92, + 1, 99, :_reduce_none, + 2, 99, :_reduce_94, + 5, 100, :_reduce_95, + 4, 100, :_reduce_96, + 1, 101, :_reduce_none, + 3, 101, :_reduce_98, + 3, 89, :_reduce_99, + 1, 103, :_reduce_none, + 3, 103, :_reduce_101, + 1, 105, :_reduce_none, + 3, 105, :_reduce_103, + 3, 104, :_reduce_104, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_111, + 2, 61, :_reduce_112, + 6, 63, :_reduce_113, + 5, 63, :_reduce_114, + 6, 64, :_reduce_115, + 5, 64, :_reduce_116, + 6, 65, :_reduce_117, + 5, 65, :_reduce_118, + 1, 106, :_reduce_none, + 1, 106, :_reduce_none, + 1, 109, :_reduce_none, + 3, 109, :_reduce_122, + 1, 110, :_reduce_none, + 1, 110, :_reduce_none, + 1, 110, :_reduce_none, + 0, 53, :_reduce_126, + 0, 111, :_reduce_127, + 1, 107, :_reduce_none, + 3, 107, :_reduce_129, + 3, 107, :_reduce_130, + 1, 112, :_reduce_none, + 3, 112, :_reduce_132, + 3, 113, :_reduce_133, + 1, 113, :_reduce_134, + 3, 113, :_reduce_135, + 1, 113, :_reduce_136, + 1, 108, :_reduce_none, + 2, 108, :_reduce_138, + 1, 70, :_reduce_139, + 3, 90, :_reduce_140, + 2, 90, :_reduce_141, + 1, 94, :_reduce_none, + 1, 94, :_reduce_none, + 0, 75, :_reduce_none, + 1, 75, :_reduce_145 ] + +racc_reduce_n = 146 + +racc_shift_n = 240 racc_action_table = [ - 48, 36, 38, 81, 162, 20, 48, 36, 38, 64, - 79, 161, 48, 36, 38, 86, 20, 36, 38, 75, - 36, 38, 20, 37, -79, 145, 36, 38, 20, 44, - 37, -82, -79, 49, 53, 44, 31, 55, 31, 49, - 53, 44, 72, 55, 65, 49, 53, -81, 44, 55, - 48, 36, 38, 37, 44, 145, 48, 36, 38, 37, - 37, 137, 48, 36, 38, 113, 20, 139, 29, 79, - 29, 33, 20, 33, -80, 79, 85, 170, 20, 44, - 154, 146, 102, 49, 53, 44, 115, 55, 171, 49, - 53, 44, 149, 55, 77, 49, 53, 36, 38, 55, - 48, 36, 38, 36, 38, 116, 48, 36, 38, 117, - 118, 88, 48, 36, 38, 179, 20, 117, 118, -81, - 45, 87, 20, 112, 155, 44, -82, 158, 20, 44, - 37, 44, 105, 49, 53, 44, 37, 55, 64, 49, - 53, 44, 163, 55, 77, 49, 53, 85, 84, 55, - 48, 36, 38, -80, 169, 72, 48, 36, 38, 172, - 173, -83, 48, 36, 38, -84, 20, 178, 108, 136, - 182, 77, 20, 36, 38, 112, 71, 160, 20, 44, - 109, 70, 69, 49, 53, 44, 113, 55, 20, 49, - 53, 44, 190, 55, 66, 49, 93, 36, 38, 55, - 112, 44, 35, 28, 166, 49, 53, 36, 38, 55, - 125, nil, 20, 36, 38, nil, nil, nil, nil, 36, - 38, nil, 20, nil, nil, 44, nil, nil, 20, 49, - 53, nil, nil, 55, 20, 44, nil, nil, nil, 49, - 53, 44, nil, 55, nil, 49, 53, 44, nil, 55, - nil, 49, 53, 36, 38, 55, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 20, 176, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 20, 44, nil, nil, 168, 49, 53, nil, 12, 55, - 16, 19, nil, 24, 26, 20, 3, nil, 9, 175, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, 196, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, 153, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, 185, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, 189, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, 195, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, 148, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, 193, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, nil, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, nil, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, nil, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, nil, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, nil, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - nil, 3, nil, 9, nil, 14, nil, 21 ] + 59, 50, 52, 50, 52, 97, 59, 50, 52, 33, + 146, 75, 59, 50, 52, 76, 69, -105, 59, 50, + 52, 171, 111, 120, 59, 50, 52, 117, 69, 32, + 84, 45, 38, 45, 69, 64, 67, 45, 54, 70, + 69, 64, 108, 45, 146, 70, 137, 64, 67, 45, + 150, 70, 121, 64, 67, 45, 39, 70, 178, 64, + 67, 50, 52, 70, 59, 50, 52, 50, 52, 122, + 59, 50, 52, 48, 134, 135, 59, 50, 52, 75, + 69, 99, 59, 50, 52, -110, 69, 124, 59, 50, + 52, 45, 69, 120, 143, 45, 54, 45, 69, 64, + 67, 45, 54, 70, 69, 64, 67, 45, 126, 70, + -107, 64, 67, 45, -108, 70, 139, 64, 67, 45, + 183, 70, 87, 64, 67, 138, 99, 70, 59, 50, + 52, -105, 173, 45, 59, 50, 52, 177, 54, 205, + 59, 50, 52, 119, 69, 134, 135, 50, 52, 197, + 69, 208, 173, 50, 52, 179, 111, 177, 180, 45, + 41, 215, 111, 64, 67, 45, 198, 70, 111, 64, + 67, 45, 41, 70, 214, 64, 163, 45, 124, 70, + 124, 64, 163, 45, -106, 70, 210, 64, 163, 50, + 52, 70, 35, 209, 42, 50, 52, 43, 202, 34, + 35, 120, 50, 52, 111, 164, 42, 34, 99, 43, + 111, 50, 52, 50, 52, 50, 52, 111, 150, 45, + 50, 52, 147, 64, 163, 45, 111, 70, 111, 64, + 163, -107, 45, 70, 152, 111, 64, 163, 154, 155, + 70, 45, -108, 45, 211, 64, 163, 64, 163, 70, + 45, 70, 212, 87, 64, 163, 87, 87, 70, 48, + 50, 52, 213, 56, 45, 219, 140, 45, 45, 54, + 134, 135, 54, 54, -105, 224, 17, 154, 155, 46, + 216, 84, 206, 124, 76, -106, 14, -109, 20, 23, + 45, 1, 5, 17, 9, 54, 12, -108, 16, 217, + 24, -107, -110, 14, 56, 20, 23, 150, 1, 5, + 17, 9, 82, 12, -106, 16, 220, 24, 225, 227, + 14, 81, 20, 23, 145, 1, 5, 17, 9, 127, + 12, 124, 16, 232, 24, 77, 141, 14, 234, 20, + 23, 131, 1, 5, 17, 9, 132, 12, 30, 16, + 235, 24, 151, nil, 14, nil, 20, 23, nil, 1, + 5, 17, 9, nil, 12, nil, 16, 193, 24, nil, + nil, 14, nil, 20, 23, nil, 1, 5, 17, 9, + nil, 12, nil, 16, 238, 24, nil, nil, 14, nil, + 20, 23, nil, 1, 5, 17, 9, nil, 12, nil, + 16, 181, 24, nil, nil, 14, nil, 20, 23, nil, + 1, 5, 17, 9, nil, 12, nil, 16, 239, 24, + nil, nil, 14, nil, 20, 23, nil, 1, 5, 17, + 9, nil, 12, nil, 16, nil, 24, nil, nil, 14, + nil, 20, 23, nil, 1, 5, 17, 9, nil, 12, + nil, 16, nil, 24, nil, nil, 14, nil, 20, 23, + nil, 1, 5, 17, 9, nil, 12, nil, 16, nil, + 24, nil, nil, 14, nil, 20, 23, nil, 1, 5, + 17, 9, nil, 12, nil, 16, nil, 24, nil, nil, + 14, nil, 20, 23, nil, 1, 5, 17, 9, nil, + 12, nil, 16, nil, 24, nil, nil, 14, nil, 20, + 23, nil, 1, 5, nil, 9, nil, 12, nil, 16, + nil, 24 ] racc_action_check = [ - 48, 48, 48, 48, 133, 21, 86, 86, 86, 17, - 39, 133, 137, 137, 137, 57, 48, 16, 16, 30, - 79, 79, 86, 21, 98, 170, 45, 45, 137, 48, - 170, 99, 57, 48, 48, 86, 3, 48, 75, 86, - 86, 137, 30, 86, 17, 137, 137, 100, 79, 137, - 69, 69, 69, 79, 45, 105, 119, 119, 119, 45, - 105, 101, 172, 172, 172, 93, 69, 103, 3, 121, - 75, 3, 119, 75, 92, 80, 93, 141, 172, 69, - 121, 107, 65, 69, 69, 119, 80, 69, 141, 119, - 119, 172, 111, 119, 112, 172, 172, 85, 85, 172, - 12, 12, 12, 9, 9, 83, 14, 14, 14, 83, - 83, 62, 113, 113, 113, 156, 12, 156, 156, 61, - 9, 60, 14, 90, 122, 85, 59, 128, 113, 12, - 85, 9, 66, 12, 12, 14, 9, 12, 68, 14, - 14, 113, 135, 14, 136, 113, 113, 53, 52, 113, - 173, 173, 173, 51, 140, 70, 158, 158, 158, 142, - 144, 50, 64, 64, 64, 46, 173, 153, 72, 95, - 161, 35, 158, 131, 131, 164, 28, 131, 64, 173, - 74, 26, 24, 173, 173, 158, 77, 173, 131, 158, - 158, 64, 178, 158, 19, 64, 64, 88, 88, 64, - 76, 131, 6, 2, 138, 131, 131, 87, 87, 131, - 87, nil, 88, 125, 125, nil, nil, nil, nil, 162, - 162, nil, 87, nil, nil, 88, nil, nil, 125, 88, - 88, nil, nil, 88, 162, 87, nil, nil, nil, 87, - 87, 125, nil, 87, nil, 125, 125, 162, nil, 125, - nil, 162, 162, 180, 180, 162, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 180, 147, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 147, 180, nil, nil, 139, 180, 180, nil, 147, 180, - 147, 147, nil, 147, 147, 139, 147, nil, 147, 146, - 147, nil, 147, 139, nil, 139, 139, nil, 139, 139, - 146, 139, nil, 139, 194, 139, nil, 139, 146, nil, - 146, 146, nil, 146, 146, 194, 146, nil, 146, 120, - 146, nil, 146, 194, nil, 194, 194, nil, 194, 194, - 120, 194, nil, 194, 167, 194, nil, 194, 120, nil, - 120, 120, nil, 120, 120, 167, 120, nil, 120, 174, - 120, nil, 120, 167, nil, 167, 167, nil, 167, 167, - 174, 167, nil, 167, 192, 167, nil, 167, 174, nil, - 174, 174, nil, 174, 174, 192, 174, nil, 174, 109, - 174, nil, 174, 192, nil, 192, 192, nil, 192, 192, - 109, 192, nil, 192, 182, 192, nil, 192, 109, nil, - 109, 109, nil, 109, 109, 182, 109, nil, 109, nil, - 109, nil, 109, 182, nil, 182, 182, nil, 182, 182, - 84, 182, nil, 182, nil, 182, nil, 182, 84, nil, - 84, 84, nil, 84, 84, 190, 84, nil, 84, nil, - 84, nil, 84, 190, nil, 190, 190, nil, 190, 190, - 5, 190, nil, 190, nil, 190, nil, 190, 5, nil, - 5, 5, nil, 5, 5, 0, 5, nil, 5, nil, - 5, nil, 5, 0, nil, 0, 0, nil, 0, 0, - nil, 0, nil, 0, nil, 0, nil, 0 ] + 75, 75, 75, 46, 46, 44, 56, 56, 56, 2, + 108, 17, 153, 153, 153, 17, 75, 187, 146, 146, + 146, 123, 56, 108, 59, 59, 59, 59, 153, 2, + 44, 75, 6, 46, 146, 75, 75, 56, 46, 75, + 59, 56, 56, 153, 124, 56, 89, 153, 153, 146, + 125, 153, 72, 146, 146, 59, 6, 146, 128, 59, + 59, 99, 99, 59, 30, 30, 30, 120, 120, 74, + 48, 48, 48, 67, 89, 89, 14, 14, 14, 69, + 30, 98, 16, 16, 16, 68, 48, 76, 202, 202, + 202, 99, 14, 67, 98, 30, 99, 120, 16, 30, + 30, 48, 120, 30, 202, 48, 48, 14, 77, 48, + 66, 14, 14, 16, 65, 14, 93, 16, 16, 202, + 133, 16, 136, 202, 202, 93, 165, 202, 212, 212, + 212, 63, 215, 136, 216, 216, 216, 215, 136, 165, + 147, 147, 147, 61, 212, 133, 133, 167, 167, 148, + 216, 167, 127, 210, 210, 130, 147, 127, 130, 212, + 97, 175, 167, 212, 212, 216, 149, 212, 210, 216, + 216, 147, 9, 216, 175, 147, 147, 167, 150, 147, + 151, 167, 167, 210, 57, 167, 169, 210, 210, 223, + 223, 210, 23, 169, 97, 122, 122, 97, 158, 23, + 5, 163, 119, 119, 223, 119, 9, 5, 47, 9, + 122, 139, 139, 138, 138, 20, 20, 119, 113, 223, + 164, 164, 109, 223, 223, 122, 139, 223, 138, 122, + 122, 107, 119, 122, 118, 164, 119, 119, 118, 118, + 119, 139, 106, 138, 172, 139, 139, 138, 138, 139, + 164, 138, 173, 87, 164, 164, 39, 38, 164, 12, + 12, 12, 174, 12, 87, 182, 95, 39, 38, 87, + 95, 95, 39, 38, 104, 204, 182, 204, 204, 12, + 177, 36, 166, 180, 33, 185, 182, 71, 182, 182, + 12, 182, 182, 166, 182, 12, 182, 188, 182, 178, + 182, 189, 190, 166, 32, 166, 166, 200, 166, 166, + 178, 166, 27, 166, 101, 166, 194, 166, 206, 209, + 178, 24, 178, 178, 100, 178, 178, 194, 178, 80, + 178, 82, 178, 218, 178, 19, 96, 194, 225, 194, + 194, 84, 194, 194, 218, 194, 86, 194, 1, 194, + 227, 194, 115, nil, 218, nil, 218, 218, nil, 218, + 218, 227, 218, nil, 218, nil, 218, 141, 218, nil, + nil, 227, nil, 227, 227, nil, 227, 227, 141, 227, + nil, 227, nil, 227, 236, 227, nil, nil, 141, nil, + 141, 141, nil, 141, 141, 236, 141, nil, 141, nil, + 141, 132, 141, nil, nil, 236, nil, 236, 236, nil, + 236, 236, 132, 236, nil, 236, nil, 236, 237, 236, + nil, nil, 132, nil, 132, 132, nil, 132, 132, 237, + 132, nil, 132, nil, 132, nil, 132, nil, nil, 237, + nil, 237, 237, nil, 237, 237, 22, 237, nil, 237, + nil, 237, nil, 237, nil, nil, 22, nil, 22, 22, + nil, 22, 22, 234, 22, nil, 22, nil, 22, nil, + 22, nil, nil, 234, nil, 234, 234, nil, 234, 234, + 121, 234, nil, 234, nil, 234, nil, 234, nil, nil, + 121, nil, 121, 121, nil, 121, 121, 0, 121, nil, + 121, nil, 121, nil, 121, nil, nil, 0, nil, 0, + 0, nil, 0, 0, nil, 0, nil, 0, nil, 0, + nil, 0 ] racc_action_pointer = [ - 457, nil, 203, 32, nil, 442, 196, nil, nil, 100, - nil, nil, 98, nil, 104, nil, 14, 3, nil, 158, - nil, -13, nil, nil, 169, nil, 145, nil, 176, nil, - 9, nil, nil, nil, nil, 135, nil, nil, nil, 0, - nil, nil, nil, nil, nil, 23, 146, nil, -2, nil, - 142, 134, 142, 127, nil, nil, nil, 13, nil, 107, - 102, 100, 105, nil, 160, 40, 112, nil, 132, 48, - 122, nil, 132, nil, 174, 34, 190, 177, nil, 17, - 65, nil, nil, 100, 412, 94, 4, 204, 194, nil, - 113, nil, 55, 56, nil, 152, nil, nil, 5, 12, - 28, 24, nil, 61, nil, 24, nil, 75, nil, 382, - nil, 85, 58, 110, nil, nil, nil, nil, nil, 54, - 322, 59, 119, nil, nil, 210, nil, nil, 118, nil, - nil, 170, nil, -6, nil, 135, 108, 10, 197, 277, - 133, 67, 146, nil, 147, nil, 292, 262, nil, nil, - nil, nil, nil, 140, nil, nil, 108, nil, 154, nil, - nil, 164, 216, nil, 165, nil, nil, 337, nil, nil, - -6, nil, 60, 148, 352, nil, nil, nil, 186, nil, - 250, nil, 397, nil, nil, nil, nil, nil, nil, nil, - 427, nil, 367, nil, 307, nil, nil ] + 479, 335, -9, nil, nil, 162, 13, nil, nil, 168, + nil, nil, 257, nil, 74, nil, 80, 9, nil, 335, + 212, nil, 428, 154, 279, nil, nil, 306, nil, nil, + 62, nil, 298, 278, nil, nil, 246, nil, 235, 234, + nil, nil, nil, nil, -5, nil, 0, 198, 68, nil, + nil, nil, nil, nil, nil, nil, 4, 163, nil, 22, + nil, 122, nil, 110, nil, 93, 89, 71, 64, 77, + nil, 266, 46, nil, 63, -2, 49, 108, nil, nil, + 307, nil, 293, nil, 303, nil, 340, 231, nil, 26, + nil, nil, nil, 101, nil, 222, 330, 156, 71, 58, + 319, 293, nil, nil, 253, nil, 221, 210, 1, 183, + nil, nil, nil, 208, nil, 335, nil, nil, 229, 199, + 64, 462, 192, 16, 35, 40, nil, 119, 52, nil, + 148, nil, 394, 97, nil, nil, 100, nil, 210, 208, + nil, 360, nil, nil, nil, nil, 16, 138, 142, 159, + 140, 142, nil, 10, nil, nil, nil, nil, 189, nil, + nil, nil, nil, 179, 217, 116, 275, 144, nil, 176, + nil, nil, 237, 239, 239, 151, nil, 267, 292, nil, + 245, nil, 258, nil, nil, 264, nil, -4, 276, 280, + 281, nil, nil, nil, 309, nil, nil, nil, nil, nil, + 297, nil, 86, nil, 268, nil, 289, nil, nil, 313, + 150, nil, 126, nil, nil, 99, 132, nil, 326, nil, + nil, nil, nil, 186, nil, 332, nil, 343, nil, nil, + nil, nil, nil, nil, 445, nil, 377, 411, nil, nil ] racc_action_default = [ - -98, -12, -118, -118, -13, -1, -118, -14, -2, -33, - -15, -3, -118, -5, -118, -6, -118, -118, -7, -118, - -34, -118, -8, -9, -118, -10, -118, -11, -118, -95, - -98, -96, -93, -97, -4, -42, -58, -33, -59, -17, - -18, -20, -21, -22, -110, -118, -51, -55, -118, -60, - -56, -50, -118, -33, -52, -85, -54, -49, -65, -53, - -118, -48, -118, -86, -42, -118, -98, -26, -118, -118, - -98, 197, -118, -108, -118, -118, -116, -118, -43, -118, - -118, -113, -46, -118, -118, -118, -118, -118, -118, -84, - -116, -83, -37, -33, -29, -118, -38, -40, -36, -39, - -35, -31, -27, -118, -99, -98, -41, -118, -109, -118, - -94, -118, -117, -118, -19, -16, -112, -114, -115, -118, - -118, -118, -118, -80, -79, -118, -82, -81, -118, -73, - -74, -118, -67, -118, -71, -118, -42, -32, -118, -118, - -118, -118, -105, -102, -107, -111, -118, -118, -92, -25, - -44, -45, -47, -63, -57, -61, -118, -76, -118, -68, - -66, -118, -118, -24, -116, -30, -23, -118, -88, -100, - -118, -101, -118, -118, -118, -90, -91, -62, -118, -75, - -118, -78, -118, -72, -28, -87, -103, -104, -106, -89, - -118, -77, -118, -70, -118, -69, -64 ] + -126, -146, -146, -16, -5, -146, -146, -6, -7, -146, + -8, -9, -33, -10, -146, -11, -146, -32, -12, -146, + -146, -13, -1, -146, -29, -14, -2, -146, -15, -3, + -146, -28, -146, -146, -120, -119, -126, -31, -126, -126, + -121, -124, -123, -125, -126, -139, -146, -18, -146, -19, + -83, -21, -84, -22, -52, -23, -61, -75, -77, -146, + -79, -146, -91, -74, -85, -78, -73, -52, -76, -53, + -111, -81, -146, -80, -146, -146, -61, -146, -112, -4, + -126, -30, -61, -60, -146, -137, -146, -126, -46, -146, + -45, -37, -39, -146, -36, -146, -146, -146, -146, -146, + -146, -56, -57, -59, -55, -62, -58, -54, -52, -50, + -110, -53, -109, -144, -48, -146, -65, -141, -146, -146, + -146, -146, -146, -146, -146, -144, 240, -127, -146, -128, + -146, -138, -146, -146, -42, -41, -146, -35, -146, -146, + -34, -146, -122, -17, -20, -86, -146, -51, -146, -146, + -145, -61, -140, -146, -142, -143, -106, -105, -146, -108, + -99, -107, -100, -52, -146, -146, -146, -146, -93, -146, + -97, -87, -146, -136, -146, -146, -131, -134, -146, -27, + -146, -116, -146, -40, -38, -69, -71, -68, -72, -67, + -70, -43, -44, -118, -146, -64, -49, -24, -25, -63, + -144, -66, -146, -102, -146, -82, -89, -94, -92, -146, + -146, -26, -146, -129, -130, -146, -146, -114, -146, -115, + -117, -47, -104, -146, -101, -146, -88, -146, -98, -135, + -132, -133, -113, -103, -146, -96, -146, -146, -95, -90 ] racc_goto_table = [ - 5, 76, 34, 43, 32, 8, 58, 42, 62, 111, - 63, 132, 39, 143, 150, 51, 52, 51, 130, 94, - 74, 128, 134, 135, 177, 96, 89, 119, 97, 131, - 90, 83, 138, 129, 101, 156, 103, 114, 30, 43, - 67, 141, 82, 42, 2, nil, nil, nil, 80, 89, - 89, 51, nil, nil, 159, nil, 157, nil, 100, 128, - 107, nil, 99, 106, nil, 134, nil, 92, nil, nil, - nil, 104, 51, 43, nil, nil, 110, 42, 186, 43, - 122, 127, 127, 42, 120, 126, 126, 89, 121, 51, - 123, 123, 165, 89, nil, nil, 183, 184, 96, 89, - 180, 97, 164, nil, nil, nil, nil, 151, nil, 147, - 140, 191, nil, 152, 128, nil, 51, 34, nil, 127, - nil, nil, 51, 126, 89, 127, nil, nil, 123, 126, - nil, 100, nil, 41, 123, 99, 57, nil, 57, 167, - 92, nil, 89, nil, 34, 68, 174, nil, nil, nil, - nil, nil, 181, nil, nil, nil, 127, nil, nil, nil, - 126, 51, nil, nil, 34, 123, 187, 188, nil, 41, - nil, 34, 57, 91, 127, 51, 51, nil, 126, nil, - nil, nil, 192, 123, nil, nil, nil, nil, 98, 34, - 194, 34, nil, 57, nil, nil, 91, 91, nil, nil, - nil, nil, nil, 41, nil, nil, nil, nil, nil, 41, - 57, 124, 124, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 142, - nil, nil, nil, nil, 91, nil, nil, 57, nil, nil, - 91, nil, nil, 57, nil, nil, 91, nil, nil, 124, - nil, nil, nil, nil, nil, 124, nil, nil, nil, nil, - nil, 98, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 91, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 57, nil, nil, nil, 124, nil, nil, 91, - nil, nil, nil, nil, 142, nil, 57, 57, nil, nil, - nil, nil, nil, nil, 124 ] + 22, 79, 51, 162, 102, 47, 113, 55, 62, 40, + 74, 114, 103, 153, 158, 78, 176, 170, 109, 149, + 168, 89, 95, 72, 83, 199, 125, 36, 88, 88, + 86, 172, 130, 191, 192, 226, 51, 167, 96, 98, + 26, 55, 100, 148, 144, 80, 104, 73, 203, 73, + 160, 107, 31, 116, 204, 199, 53, 118, 128, 158, + 37, 44, 170, 73, 184, 207, 174, 175, 19, 123, + 133, nil, nil, nil, nil, nil, 85, 88, nil, nil, + nil, 73, 90, 90, 85, nil, 186, 186, nil, 51, + 53, nil, 73, nil, 55, 102, nil, 142, nil, 223, + 106, 200, 196, 103, 230, 228, 221, 233, 73, 157, + 51, 112, 157, 165, 161, 55, nil, 161, 158, nil, + 129, 166, nil, nil, nil, nil, 88, nil, 187, 187, + nil, 90, 182, 189, 189, nil, nil, 104, nil, nil, + 195, 194, 107, 53, nil, 79, nil, 201, nil, nil, + nil, nil, nil, nil, 157, nil, nil, 157, nil, 161, + 110, 79, 161, 159, 53, 101, 159, nil, nil, nil, + nil, nil, nil, 79, 112, nil, nil, 112, 218, 73, + 90, nil, 188, 188, nil, nil, 73, nil, nil, nil, + nil, 106, nil, 112, 112, nil, 222, 79, nil, nil, + 157, nil, 112, nil, nil, 161, 229, nil, 159, nil, + 231, 159, nil, 157, nil, 79, 79, nil, 161, 112, + nil, nil, 112, 110, nil, nil, 110, 236, 156, nil, + nil, 156, nil, nil, 237, 73, nil, nil, nil, nil, + nil, nil, 190, 190, nil, 73, nil, 185, 185, 73, + nil, 110, nil, nil, 159, nil, 101, nil, nil, nil, + nil, nil, nil, nil, nil, 112, nil, 159, 110, nil, + nil, 110, nil, 156, nil, nil, 156, nil, 112, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 110, nil, nil, nil, nil, 156, + nil, nil, nil, nil, nil, nil, nil, 110, nil, nil, + nil, nil, 156 ] racc_goto_check = [ - 2, 23, 4, 20, 49, 3, 30, 19, 30, 24, - 20, 40, 16, 51, 31, 25, 37, 25, 44, 26, - 47, 42, 42, 24, 38, 28, 34, 33, 29, 39, - 23, 32, 22, 43, 21, 45, 46, 17, 48, 20, - 5, 50, 30, 19, 1, nil, nil, nil, 16, 34, - 34, 25, nil, nil, 40, nil, 44, nil, 20, 42, - 47, nil, 19, 30, nil, 42, nil, 25, nil, nil, - nil, 3, 25, 20, nil, nil, 49, 19, 51, 20, - 30, 20, 20, 19, 2, 19, 19, 34, 16, 25, - 25, 25, 26, 34, nil, nil, 42, 24, 28, 34, - 33, 29, 23, nil, nil, nil, nil, 30, nil, 2, - 3, 44, nil, 30, 42, nil, 25, 4, nil, 20, - nil, nil, 25, 19, 34, 20, nil, nil, 25, 19, - nil, 20, nil, 18, 25, 19, 18, nil, 18, 2, - 25, nil, 34, nil, 4, 18, 2, nil, nil, nil, - nil, nil, 30, nil, nil, nil, 20, nil, nil, nil, - 19, 25, nil, nil, 4, 25, 30, 30, nil, 18, - nil, 4, 18, 36, 20, 25, 25, nil, 19, nil, - nil, nil, 2, 25, nil, nil, nil, nil, 18, 4, - 2, 4, nil, 18, nil, nil, 36, 36, nil, nil, - nil, nil, nil, 18, nil, nil, nil, nil, nil, 18, - 18, 18, 18, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 18, - nil, nil, nil, nil, 36, nil, nil, 18, nil, nil, - 36, nil, nil, 18, nil, nil, 36, nil, nil, 18, - nil, nil, nil, nil, nil, 18, nil, nil, nil, nil, - nil, 18, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 36, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 18, nil, nil, nil, 18, nil, nil, 36, - nil, nil, nil, nil, 18, nil, 18, 18, nil, nil, - nil, nil, nil, nil, 18 ] + 2, 4, 19, 54, 39, 17, 24, 21, 41, 60, + 41, 36, 40, 44, 52, 21, 63, 52, 22, 25, + 50, 30, 30, 47, 41, 42, 24, 56, 19, 19, + 58, 25, 24, 35, 35, 48, 19, 49, 58, 17, + 3, 21, 41, 23, 18, 56, 19, 26, 54, 26, + 53, 21, 5, 41, 55, 42, 20, 43, 57, 52, + 29, 59, 52, 26, 31, 50, 61, 62, 1, 41, + 30, nil, nil, nil, nil, nil, 3, 19, nil, nil, + nil, 26, 20, 20, 3, nil, 39, 39, nil, 19, + 20, nil, 26, nil, 21, 39, nil, 60, nil, 44, + 20, 24, 36, 40, 63, 52, 25, 54, 26, 19, + 19, 46, 19, 17, 21, 21, nil, 21, 52, nil, + 3, 2, nil, nil, nil, nil, 19, nil, 19, 19, + nil, 20, 2, 21, 21, nil, nil, 19, nil, nil, + 41, 2, 21, 20, nil, 4, nil, 41, nil, nil, + nil, nil, nil, nil, 19, nil, nil, 19, nil, 21, + 45, 4, 21, 20, 20, 38, 20, nil, nil, nil, + nil, nil, nil, 4, 46, nil, nil, 46, 2, 26, + 20, nil, 20, 20, nil, nil, 26, nil, nil, nil, + nil, 20, nil, 46, 46, nil, 41, 4, nil, nil, + 19, nil, 46, nil, nil, 21, 41, nil, 20, nil, + 41, 20, nil, 19, nil, 4, 4, nil, 21, 46, + nil, nil, 46, 45, nil, nil, 45, 2, 38, nil, + nil, 38, nil, nil, 2, 26, nil, nil, nil, nil, + nil, nil, 45, 45, nil, 26, nil, 38, 38, 26, + nil, 45, nil, nil, 20, nil, 38, nil, nil, nil, + nil, nil, nil, nil, nil, 46, nil, 20, 45, nil, + nil, 45, nil, 38, nil, nil, 38, nil, 46, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 45, nil, nil, nil, nil, 38, + nil, nil, nil, nil, nil, nil, nil, 45, nil, nil, + nil, nil, 38 ] racc_goto_pointer = [ - nil, 44, 0, 5, -3, 19, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 3, -42, 124, -2, - -6, -30, -69, -34, -67, 3, -45, nil, -39, -36, - -6, -98, -17, -56, -38, nil, 109, 4, -129, -59, - -77, nil, -66, -54, -69, -90, -30, -10, 35, 1, - -64, -92, nil ] + nil, 68, 0, 40, -21, 50, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, -7, -55, -10, + 44, -5, -38, -66, -50, -94, 33, nil, nil, 54, + -17, -72, nil, nil, nil, -105, -45, nil, 109, -52, + -44, -6, -125, -2, -105, 104, 55, 9, -171, -85, + -102, nil, -105, -69, -116, -110, 22, -22, -6, 52, + 0, -61, -60, -111 ] racc_goto_default = [ - nil, nil, nil, 73, 11, 13, 15, 18, 22, 23, - 25, 27, 1, 4, 7, 10, nil, 40, 17, 59, - 61, nil, nil, nil, nil, 6, nil, 95, 54, 56, - nil, 78, nil, nil, 46, 47, 50, nil, nil, nil, - nil, 133, 60, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 144 ] + nil, nil, nil, 94, 29, 4, 7, 8, 10, 11, + 13, 15, 18, 21, 25, 28, 3, nil, 49, 63, + 65, 66, nil, nil, nil, nil, 27, 2, 6, nil, + nil, 91, 136, 92, 93, nil, nil, 115, 57, 58, + 60, nil, 105, nil, nil, 68, 71, nil, nil, nil, + nil, 169, 61, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil ] racc_token_table = { false => 0, @@ -550,34 +627,41 @@ racc_token_table = { :DOT => 16, :COLON => 17, :TYPE => 18, - :QMARK => 19, - :LPAREN => 20, - :RPAREN => 21, - :ISEQUAL => 22, - :GREATEREQUAL => 23, - :GREATERTHAN => 24, - :LESSTHAN => 25, - :IF => 26, - :ELSE => 27, - :IMPORT => 28, - :DEFINE => 29, - :ELSIF => 30, - :VARIABLE => 31, - :CLASS => 32, - :INHERITS => 33, - :NODE => 34, - :BOOLEAN => 35, - :NAME => 36, - :SEMIC => 37, - :CASE => 38, - :DEFAULT => 39, - :AT => 40, - :LCOLLECT => 41, - :RCOLLECT => 42 } + :LLCOLLECT => 19, + :RRCOLLECT => 20, + :QMARK => 21, + :LPAREN => 22, + :RPAREN => 23, + :ISEQUAL => 24, + :GREATEREQUAL => 25, + :GREATERTHAN => 26, + :LESSTHAN => 27, + :IF => 28, + :ELSE => 29, + :IMPORT => 30, + :DEFINE => 31, + :ELSIF => 32, + :VARIABLE => 33, + :CLASS => 34, + :INHERITS => 35, + :NODE => 36, + :BOOLEAN => 37, + :NAME => 38, + :SEMIC => 39, + :CASE => 40, + :DEFAULT => 41, + :AT => 42, + :LCOLLECT => 43, + :RCOLLECT => 44, + :CLASSNAME => 45, + :CLASSREF => 46, + :NOT => 47, + :OR => 48, + :AND => 49 } racc_use_result_var = true -racc_nt_base = 43 +racc_nt_base = 50 Racc_arg = [ racc_action_table, @@ -615,6 +699,8 @@ Racc_token_to_s_table = [ 'DOT', 'COLON', 'TYPE', +'LLCOLLECT', +'RRCOLLECT', 'QMARK', 'LPAREN', 'RPAREN', @@ -639,13 +725,18 @@ Racc_token_to_s_table = [ 'AT', 'LCOLLECT', 'RCOLLECT', +'CLASSNAME', +'CLASSREF', +'NOT', +'OR', +'AND', '$start', 'program', 'statements', -'nothing', +'nil', 'statement', -'object', -'collectable', +'resource', +'virtualresource', 'collection', 'assignment', 'casestatement', @@ -655,18 +746,29 @@ Racc_token_to_s_table = [ 'definition', 'hostclass', 'nodedef', -'classnames', -'classname', +'resourceoverride', +'namestrings', +'namestring', 'name', 'variable', 'quotedtext', -'objectinstances', +'resourceinstances', 'endsemi', 'params', 'endcomma', +'resourceref', +'at', +'collectname', +'collectrhand', +'collstatements', +'collstatement', +'colljoin', +'collexpr', +'colllval', +'simplervalue', +'resourceinst', +'resourcename', 'type', -'objectinst', -'objectname', 'selector', 'array', 'rvalue', @@ -674,7 +776,6 @@ Racc_token_to_s_table = [ 'rvalues', 'comma', 'boolean', -'objectref', 'funcrvalue', 'iftest', 'else', @@ -685,13 +786,14 @@ Racc_token_to_s_table = [ 'svalues', 'selectval', 'sintvalues', +'fqname', 'argumentlist', 'parent', 'hostnames', 'hostname', +'nothing', 'arguments', -'argument', -'lvariable'] +'argument'] Racc_debug_parser = false @@ -699,13 +801,21 @@ Racc_debug_parser = false # reduce 0 omitted -module_eval <<'.,.,', 'grammar.ra', 24 +module_eval <<'.,.,', 'grammar.ra', 33 def _reduce_1( val, _values, result ) - # Make sure we always return an array. - if val[0].is_a?(AST::ASTArray) - result = val[0] + if val[0] + # Make sure we always return an array. + if val[0].is_a?(AST::ASTArray) + if val[0].children.empty? + result = nil + else + result = val[0] + end + else + result = aryfy(val[0]) + end else - result = aryfy(val[0]) + result = nil end result end @@ -715,13 +825,18 @@ module_eval <<'.,.,', 'grammar.ra', 24 # reduce 3 omitted -module_eval <<'.,.,', 'grammar.ra', 35 +module_eval <<'.,.,', 'grammar.ra', 49 def _reduce_4( val, _values, result ) - if val[0].instance_of?(AST::ASTArray) - val[0].push(val[1]) - result = val[0] - else - result = ast AST::ASTArray, :children => [val[0],val[1]] + if val[0] and val[1] + if val[0].instance_of?(AST::ASTArray) + val[0].push(val[1]) + result = val[0] + else + result = ast AST::ASTArray, :children => [val[0],val[1]] + end + elsif obj = (val[0] || val[1]) + result = obj + else result = nil end result end @@ -749,8 +864,10 @@ module_eval <<'.,.,', 'grammar.ra', 35 # reduce 15 omitted -module_eval <<'.,.,', 'grammar.ra', 56 - def _reduce_16( val, _values, result ) + # reduce 16 omitted + +module_eval <<'.,.,', 'grammar.ra', 71 + def _reduce_17( val, _values, result ) args = aryfy(val[2]) result = ast AST::Function, :name => val[0], @@ -760,8 +877,8 @@ module_eval <<'.,.,', 'grammar.ra', 56 end .,., -module_eval <<'.,.,', 'grammar.ra', 63 - def _reduce_17( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 78 + def _reduce_18( val, _values, result ) args = aryfy(val[1]) result = ast AST::Function, :name => val[0], @@ -771,10 +888,10 @@ module_eval <<'.,.,', 'grammar.ra', 63 end .,., - # reduce 18 omitted + # reduce 19 omitted -module_eval <<'.,.,', 'grammar.ra', 82 - def _reduce_19( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 85 + def _reduce_20( val, _values, result ) result = aryfy(val[0], val[2]) result.line = @lexer.line result.file = @lexer.file @@ -782,80 +899,86 @@ module_eval <<'.,.,', 'grammar.ra', 82 end .,., - # reduce 20 omitted - # reduce 21 omitted # reduce 22 omitted -module_eval <<'.,.,', 'grammar.ra', 111 - def _reduce_23( val, _values, result ) + # reduce 23 omitted + +module_eval <<'.,.,', 'grammar.ra', 113 + def _reduce_24( val, _values, result ) if val[0].instance_of?(AST::ASTArray) - raise Puppet::ParseError, "Invalid name" + error "Invalid name" end array = val[2] - if array.instance_of?(AST::ObjectInst) + if array.instance_of?(AST::ResourceInst) array = [array] end result = ast AST::ASTArray - # this iterates across each specified objectinstance + # this iterates across each specified resourceinstance array.each { |instance| - unless instance.instance_of?(AST::ObjectInst) + unless instance.instance_of?(AST::ResourceInst) raise Puppet::Dev, "Got something that isn't an instance" end # now, i need to somehow differentiate between those things with # arrays in their names, and normal things - result.push ast(AST::ObjectDef, + result.push ast(AST::ResourceDef, :type => val[0], - :name => instance[0], + :title => instance[0], :params => instance[1]) } result end .,., -module_eval <<'.,.,', 'grammar.ra', 120 - def _reduce_24( val, _values, result ) - if val[0].instance_of?(AST::ASTArray) - Puppet.notice "invalid name" - raise Puppet::ParseError, "Invalid name" - end - # an object but without a name - # this cannot be an instance of a library type - result = ast AST::ObjectDef, :type => val[0], :params => val[2] +module_eval <<'.,.,', 'grammar.ra', 116 + def _reduce_25( val, _values, result ) + # This is a deprecated syntax. + error "All resource specifications require names" result end .,., -module_eval <<'.,.,', 'grammar.ra', 126 - def _reduce_25( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 122 + def _reduce_26( val, _values, result ) # a template setting for a type if val[0].instance_of?(AST::ASTArray) - raise Puppet::ParseError, "Invalid type" + error "Invalid type" end - result = ast(AST::TypeDefaults, :type => val[0], :params => val[2]) + result = ast(AST::ResourceDefaults, :type => val[0], :params => val[2]) result end .,., -module_eval <<'.,.,', 'grammar.ra', 149 - def _reduce_26( val, _values, result ) - unless Puppet[:storeconfigs] - raise Puppet::ParseError, "You cannot collect without storeconfigs being set" +module_eval <<'.,.,', 'grammar.ra', 127 + def _reduce_27( val, _values, result ) + result = ast AST::ResourceOverride, :object => val[0], :params => val[2] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 154 + def _reduce_28( val, _values, result ) + type = val[0] + + if type == :exported and ! Puppet[:storeconfigs] + error "You cannot collect without storeconfigs being set" end - if val[1].is_a? AST::TypeDefaults - raise Puppet::ParseError, "Defaults are not collectable" + if val[1].is_a? AST::ResourceDefaults + error "Defaults are not virtualizable" end - # Just mark our objects as collectable and pass them through. + method = type.to_s + "=" + + # Just mark our resources as exported and pass them through. if val[1].instance_of?(AST::ASTArray) val[1].each do |obj| - obj.collectable = true + obj.send(method, true) end else - val[1].collectable = true + val[1].send(method, true) end result = val[1] @@ -863,28 +986,130 @@ module_eval <<'.,.,', 'grammar.ra', 149 end .,., -module_eval <<'.,.,', 'grammar.ra', 158 - def _reduce_27( val, _values, result ) - unless Puppet[:storeconfigs] - raise Puppet::ParseError, "You cannot collect without storeconfigs being set" +module_eval <<'.,.,', 'grammar.ra', 155 + def _reduce_29( val, _values, result ) + result = :virtual + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 156 + def _reduce_30( val, _values, result ) + result = :exported + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 179 + def _reduce_31( val, _values, result ) + if val[0] =~ /^[a-z]/ + Puppet.warning addcontext("Collection names must now be capitalized") + end + type = val[0].downcase + args = {:type => type} + + if val[1].is_a?(AST::CollExpr) + args[:query] = val[1] + args[:query].type = type + args[:form] = args[:query].form + else + args[:form] = val[1] + end + if args[:form] == :exported and ! Puppet[:storeconfigs] + error "You cannot collect exported resources without storeconfigs being set" end - result = ast AST::Collection, :type => val[0] + result = ast AST::Collection, args result end .,., -module_eval <<'.,.,', 'grammar.ra', 162 - def _reduce_28( val, _values, result ) - result = ast AST::ObjectInst, :children => [val[0],val[2]] + # reduce 32 omitted + + # reduce 33 omitted + +module_eval <<'.,.,', 'grammar.ra', 190 + def _reduce_34( val, _values, result ) + if val[1] + result = val[1] + result.form = :virtual + else + result = :virtual + end result end .,., - # reduce 29 omitted +module_eval <<'.,.,', 'grammar.ra', 198 + def _reduce_35( val, _values, result ) + if val[1] + result = val[1] + result.form = :exported + else + result = :exported + end + result + end +.,., -module_eval <<'.,.,', 'grammar.ra', 172 - def _reduce_30( val, _values, result ) - if val[0].instance_of?(AST::ObjectInst) + # reduce 36 omitted + + # reduce 37 omitted + +module_eval <<'.,.,', 'grammar.ra', 206 + def _reduce_38( val, _values, result ) + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + result + end +.,., + + # reduce 39 omitted + +module_eval <<'.,.,', 'grammar.ra', 212 + def _reduce_40( val, _values, result ) + result = val[1] + result.parens = true + result + end +.,., + + # reduce 41 omitted + + # reduce 42 omitted + +module_eval <<'.,.,', 'grammar.ra', 220 + def _reduce_43( val, _values, result ) + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + #result = ast AST::CollExpr + #result.push *val + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 225 + def _reduce_44( val, _values, result ) + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + #result = ast AST::CollExpr + #result.push *val + result + end +.,., + + # reduce 45 omitted + + # reduce 46 omitted + +module_eval <<'.,.,', 'grammar.ra', 232 + def _reduce_47( val, _values, result ) + result = ast AST::ResourceInst, :children => [val[0],val[2]] + result + end +.,., + + # reduce 48 omitted + +module_eval <<'.,.,', 'grammar.ra', 242 + def _reduce_49( val, _values, result ) + if val[0].instance_of?(AST::ResourceInst) result = ast AST::ASTArray, :children => [val[0],val[2]] else val[0].push val[2] @@ -894,61 +1119,61 @@ module_eval <<'.,.,', 'grammar.ra', 172 end .,., - # reduce 31 omitted + # reduce 50 omitted - # reduce 32 omitted + # reduce 51 omitted -module_eval <<'.,.,', 'grammar.ra', 179 - def _reduce_33( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 249 + def _reduce_52( val, _values, result ) result = ast AST::Name, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 183 - def _reduce_34( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 253 + def _reduce_53( val, _values, result ) result = ast AST::Type, :value => val[0] result end .,., - # reduce 35 omitted + # reduce 54 omitted - # reduce 36 omitted + # reduce 55 omitted - # reduce 37 omitted + # reduce 56 omitted - # reduce 38 omitted + # reduce 57 omitted - # reduce 39 omitted + # reduce 58 omitted - # reduce 40 omitted + # reduce 59 omitted -module_eval <<'.,.,', 'grammar.ra', 196 - def _reduce_41( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 266 + def _reduce_60( val, _values, result ) # this is distinct from referencing a variable - variable = ast AST::Name, :value => val[0].sub(/^\$/,'') + variable = ast AST::Name, :value => val[0] result = ast AST::VarDef, :name => variable, :value => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 201 - def _reduce_42( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 271 + def _reduce_61( val, _values, result ) result = ast AST::ASTArray result end .,., -module_eval <<'.,.,', 'grammar.ra', 201 - def _reduce_43( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 271 + def _reduce_62( val, _values, result ) result = val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 210 - def _reduce_44( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 280 + def _reduce_63( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] @@ -959,18 +1184,17 @@ module_eval <<'.,.,', 'grammar.ra', 210 end .,., -module_eval <<'.,.,', 'grammar.ra', 215 - def _reduce_45( val, _values, result ) - leaf = ast AST::String, :value => val[0] - result = ast AST::ObjectParam, :param => leaf, :value => val[2] +module_eval <<'.,.,', 'grammar.ra', 284 + def _reduce_64( val, _values, result ) + result = ast AST::ResourceParam, :param => val[0], :value => val[2] result end .,., - # reduce 46 omitted + # reduce 65 omitted -module_eval <<'.,.,', 'grammar.ra', 224 - def _reduce_47( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 293 + def _reduce_66( val, _values, result ) if val[0].instance_of?(AST::ASTArray) result = val[0].push(val[2]) else @@ -980,26 +1204,38 @@ module_eval <<'.,.,', 'grammar.ra', 224 end .,., - # reduce 48 omitted + # reduce 67 omitted - # reduce 49 omitted + # reduce 68 omitted - # reduce 50 omitted + # reduce 69 omitted - # reduce 51 omitted + # reduce 70 omitted - # reduce 52 omitted + # reduce 71 omitted - # reduce 53 omitted + # reduce 72 omitted - # reduce 54 omitted + # reduce 73 omitted - # reduce 55 omitted + # reduce 74 omitted - # reduce 56 omitted + # reduce 75 omitted + + # reduce 76 omitted + + # reduce 77 omitted + + # reduce 78 omitted + + # reduce 79 omitted -module_eval <<'.,.,', 'grammar.ra', 243 - def _reduce_57( val, _values, result ) + # reduce 80 omitted + + # reduce 81 omitted + +module_eval <<'.,.,', 'grammar.ra', 319 + def _reduce_82( val, _values, result ) args = aryfy(val[2]) result = ast AST::Function, :name => val[0], @@ -1009,36 +1245,44 @@ module_eval <<'.,.,', 'grammar.ra', 243 end .,., -module_eval <<'.,.,', 'grammar.ra', 247 - def _reduce_58( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 323 + def _reduce_83( val, _values, result ) result = ast AST::String, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 249 - def _reduce_59( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 325 + def _reduce_84( val, _values, result ) result = ast AST::FlatString, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 253 - def _reduce_60( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 329 + def _reduce_85( val, _values, result ) result = ast AST::Boolean, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 257 - def _reduce_61( val, _values, result ) - result = ast AST::ObjectRef, :type => val[0], :name => val[2] +module_eval <<'.,.,', 'grammar.ra', 334 + def _reduce_86( val, _values, result ) + Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") + result = ast AST::ResourceRef, :type => val[0], :title => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 270 - def _reduce_62( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 336 + def _reduce_87( val, _values, result ) + result = ast AST::ResourceRef, :type => val[0], :title => val[2] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 349 + def _reduce_88( val, _values, result ) args = { :test => val[1], :statements => val[3] @@ -1053,19 +1297,19 @@ module_eval <<'.,.,', 'grammar.ra', 270 end .,., - # reduce 63 omitted + # reduce 89 omitted -module_eval <<'.,.,', 'grammar.ra', 275 - def _reduce_64( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 354 + def _reduce_90( val, _values, result ) result = ast AST::Else, :statements => val[2] result end .,., - # reduce 65 omitted + # reduce 91 omitted -module_eval <<'.,.,', 'grammar.ra', 287 - def _reduce_66( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 366 + def _reduce_92( val, _values, result ) options = val[3] unless options.instance_of?(AST::ASTArray) options = ast AST::ASTArray, :children => [val[3]] @@ -1075,10 +1319,10 @@ module_eval <<'.,.,', 'grammar.ra', 287 end .,., - # reduce 67 omitted + # reduce 93 omitted -module_eval <<'.,.,', 'grammar.ra', 297 - def _reduce_68( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 376 + def _reduce_94( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push val[1] result = val[0] @@ -1089,15 +1333,15 @@ module_eval <<'.,.,', 'grammar.ra', 297 end .,., -module_eval <<'.,.,', 'grammar.ra', 301 - def _reduce_69( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 380 + def _reduce_95( val, _values, result ) result = ast AST::CaseOpt, :value => val[0], :statements => val[3] result end .,., -module_eval <<'.,.,', 'grammar.ra', 306 - def _reduce_70( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 385 + def _reduce_96( val, _values, result ) result = ast(AST::CaseOpt, :value => val[0], :statements => ast(AST::ASTArray) @@ -1106,10 +1350,10 @@ module_eval <<'.,.,', 'grammar.ra', 306 end .,., - # reduce 71 omitted + # reduce 97 omitted -module_eval <<'.,.,', 'grammar.ra', 316 - def _reduce_72( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 395 + def _reduce_98( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] @@ -1120,26 +1364,26 @@ module_eval <<'.,.,', 'grammar.ra', 316 end .,., -module_eval <<'.,.,', 'grammar.ra', 320 - def _reduce_73( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 399 + def _reduce_99( val, _values, result ) result = ast AST::Selector, :param => val[0], :values => val[2] result end .,., - # reduce 74 omitted + # reduce 100 omitted -module_eval <<'.,.,', 'grammar.ra', 322 - def _reduce_75( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 401 + def _reduce_101( val, _values, result ) result = val[1] result end .,., - # reduce 76 omitted + # reduce 102 omitted -module_eval <<'.,.,', 'grammar.ra', 333 - def _reduce_77( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 412 + def _reduce_103( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] @@ -1150,34 +1394,34 @@ module_eval <<'.,.,', 'grammar.ra', 333 end .,., -module_eval <<'.,.,', 'grammar.ra', 337 - def _reduce_78( val, _values, result ) - result = ast AST::ObjectParam, :param => val[0], :value => val[2] +module_eval <<'.,.,', 'grammar.ra', 416 + def _reduce_104( val, _values, result ) + result = ast AST::ResourceParam, :param => val[0], :value => val[2] result end .,., - # reduce 79 omitted + # reduce 105 omitted - # reduce 80 omitted + # reduce 106 omitted - # reduce 81 omitted + # reduce 107 omitted - # reduce 82 omitted + # reduce 108 omitted - # reduce 83 omitted + # reduce 109 omitted - # reduce 84 omitted + # reduce 110 omitted -module_eval <<'.,.,', 'grammar.ra', 347 - def _reduce_85( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 426 + def _reduce_111( val, _values, result ) result = ast AST::Default, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 404 - def _reduce_86( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 488 + def _reduce_112( val, _values, result ) # importing files # yuk, i hate keywords # we'll probably have to have some kind of search path eventually @@ -1209,7 +1453,7 @@ module_eval <<'.,.,', 'grammar.ra', 404 end files.each { |file| - parser = Puppet::Parser::Parser.new() + parser = Puppet::Parser::Parser.new(interp) parser.files = self.files Puppet.debug("importing '%s'" % file) @@ -1227,8 +1471,13 @@ module_eval <<'.,.,', 'grammar.ra', 404 end # push the results into the main result array # We always return an array when we parse. - parser.parse.each do |child| - result.push child + ast = parser.parse + + # Things that just get added to the classtable or whatever return nil + if ast + ast.each do |child| + result.push child + end end } } @@ -1236,249 +1485,173 @@ module_eval <<'.,.,', 'grammar.ra', 404 end .,., -module_eval <<'.,.,', 'grammar.ra', 420 - def _reduce_87( val, _values, result ) - args = { - :type => ast(AST::Name, :value => val[1]), - :args => val[2], - :code => val[4] # Switch to 5 for parents - } +module_eval <<'.,.,', 'grammar.ra', 498 + def _reduce_113( val, _values, result ) + interp.newdefine fqname(val[1]), :arguments => val[2], :code => val[4] + @lexer.indefine = false + result = nil - if val[3].instance_of?(AST::Name) - args[:parentclass] = val[3] - end - result = ast AST::CompDef, args #} | DEFINE NAME argumentlist parent LBRACE RBRACE { result end .,., -module_eval <<'.,.,', 'grammar.ra', 432 - def _reduce_88( val, _values, result ) - args = { - :type => ast(AST::Name, :value => val[1]), - :args => val[2], - :code => ast(AST::ASTArray) - } - - if val[3].instance_of?(AST::Name) - args[:parentclass] = val[3] - end - - result = ast AST::CompDef, args +module_eval <<'.,.,', 'grammar.ra', 502 + def _reduce_114( val, _values, result ) + interp.newdefine fqname(val[1]), :arguments => val[2] + @lexer.indefine = false + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 446 - def _reduce_89( val, _values, result ) - #:args => val[2], - args = { - :type => ast(AST::Name, :value => val[1]), - :code => val[4] - } - # It'll be an ASTArray if we didn't get a parent - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::ClassDef, args +module_eval <<'.,.,', 'grammar.ra', 510 + def _reduce_115( val, _values, result ) + # Our class gets defined in the parent namespace, not our own. + @lexer.namepop + interp.newclass fqname(val[1]), :code => val[4], :parent => val[2] + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 456 - def _reduce_90( val, _values, result ) - args = { - :type => ast(AST::Name, :value => val[1]), - :code => ast(AST::ASTArray, :children => []) - } - # It'll be an ASTArray if we didn't get a parent - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::ClassDef, args +module_eval <<'.,.,', 'grammar.ra', 515 + def _reduce_116( val, _values, result ) + # Our class gets defined in the parent namespace, not our own. + @lexer.namepop + interp.newclass fqname(val[1]), :parent => val[2] + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 473 - def _reduce_91( val, _values, result ) - unless val[1].instance_of?(AST::ASTArray) - val[1] = ast AST::ASTArray, - :line => val[1].line, - :file => val[1].file, - :children => [val[1]] - end - args = { - :names => val[1], - :code => val[4] - } - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::NodeDef, args +module_eval <<'.,.,', 'grammar.ra', 520 + def _reduce_117( val, _values, result ) + interp.newnode val[1], :parent => val[2], :code => val[4] + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 488 - def _reduce_92( val, _values, result ) - unless val[1].instance_of?(AST::ASTArray) - val[1] = ast AST::ASTArray, - :line => val[1].line, - :file => val[1].file, - :children => [val[1]] - end - args = { - :names => val[1], - :code => ast(AST::ASTArray, :children => []) - } - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::NodeDef,args +module_eval <<'.,.,', 'grammar.ra', 523 + def _reduce_118( val, _values, result ) + interp.newnode val[1], :parent => val[2] + result = nil result end .,., - # reduce 93 omitted + # reduce 119 omitted -module_eval <<'.,.,', 'grammar.ra', 499 - def _reduce_94( val, _values, result ) - if val[0].instance_of?(AST::ASTArray) - result = val[0] - result.push val[2] - else - result = ast AST::ASTArray, :children => [val[0], val[2]] - end - result - end -.,., + # reduce 120 omitted -module_eval <<'.,.,', 'grammar.ra', 503 - def _reduce_95( val, _values, result ) - result = ast AST::HostName, :value => val[0] - result - end -.,., + # reduce 121 omitted -module_eval <<'.,.,', 'grammar.ra', 505 - def _reduce_96( val, _values, result ) - result = ast AST::HostName, :value => val[0] +module_eval <<'.,.,', 'grammar.ra', 535 + def _reduce_122( val, _values, result ) + result = val[0] + result = [result] unless result.is_a?(Array) + result << val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 507 - def _reduce_97( val, _values, result ) - result = ast AST::Default, :value => val[0] + # reduce 123 omitted + + # reduce 124 omitted + + # reduce 125 omitted + +module_eval <<'.,.,', 'grammar.ra', 543 + def _reduce_126( val, _values, result ) + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 511 - def _reduce_98( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 547 + def _reduce_127( val, _values, result ) result = ast AST::ASTArray, :children => [] result end .,., - # reduce 99 omitted + # reduce 128 omitted -module_eval <<'.,.,', 'grammar.ra', 516 - def _reduce_100( val, _values, result ) - result = val[1] +module_eval <<'.,.,', 'grammar.ra', 552 + def _reduce_129( val, _values, result ) + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 523 - def _reduce_101( val, _values, result ) - if val[1].instance_of?(AST::ASTArray) - result = val[1] - else - result = ast AST::ASTArray, :children => [val[1]] - end +module_eval <<'.,.,', 'grammar.ra', 556 + def _reduce_130( val, _values, result ) + result = val[1] + result = [result] unless result[0].is_a?(Array) result end .,., - # reduce 102 omitted + # reduce 131 omitted -module_eval <<'.,.,', 'grammar.ra', 533 - def _reduce_103( val, _values, result ) - if val[0].instance_of?(AST::ASTArray) - val[0].push(val[2]) - result = val[0] - else - result = ast AST::ASTArray, :children => [val[0],val[2]] - end +module_eval <<'.,.,', 'grammar.ra', 563 + def _reduce_132( val, _values, result ) + result = val[0] + result = [result] unless result[0].is_a?(Array) + result << val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 541 - def _reduce_104( val, _values, result ) - msg = "Deprecation notice: #{val[0].value} must now include '$' in prototype" - msg += " at line %s" % @lexer.line - msg += " in file %s" % @lexer.file if @lexer.file - Puppet.warning msg - result = ast AST::CompArgument, :children => [val[0],val[2]] +module_eval <<'.,.,', 'grammar.ra', 568 + def _reduce_133( val, _values, result ) + Puppet.warning addcontext("Deprecation notice: #{val[0].value} must now include '$' in prototype") + result = [val[0], val[2]] result end .,., -module_eval <<'.,.,', 'grammar.ra', 548 - def _reduce_105( val, _values, result ) - msg = "Deprecation notice: #{val[0].value} must now include '$' in prototype" - msg += " at line %s" % @lexer.line - msg += " in file %s" % @lexer.file if @lexer.file - Puppet.warning msg - result = ast AST::CompArgument, :children => [val[0]] +module_eval <<'.,.,', 'grammar.ra', 572 + def _reduce_134( val, _values, result ) + Puppet.warning addcontext("Deprecation notice: #{val[0].value} must now include '$' in prototype") + result = [val[0]] result end .,., -module_eval <<'.,.,', 'grammar.ra', 550 - def _reduce_106( val, _values, result ) - result = ast AST::CompArgument, :children => [val[0],val[2]] +module_eval <<'.,.,', 'grammar.ra', 574 + def _reduce_135( val, _values, result ) + result = [val[0], val[2]] result end .,., -module_eval <<'.,.,', 'grammar.ra', 552 - def _reduce_107( val, _values, result ) - result = ast AST::CompArgument, :children => [val[0]] +module_eval <<'.,.,', 'grammar.ra', 576 + def _reduce_136( val, _values, result ) + result = [val[0]] result end .,., - # reduce 108 omitted + # reduce 137 omitted -module_eval <<'.,.,', 'grammar.ra', 557 - def _reduce_109( val, _values, result ) - result = ast AST::Name, :value => val[1] +module_eval <<'.,.,', 'grammar.ra', 581 + def _reduce_138( val, _values, result ) + result = val[1] result end .,., -module_eval <<'.,.,', 'grammar.ra', 562 - def _reduce_110( val, _values, result ) - name = val[0].sub(/^\$/,'') - result = ast AST::Variable, :value => name +module_eval <<'.,.,', 'grammar.ra', 585 + def _reduce_139( val, _values, result ) + result = ast AST::Variable, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 567 - def _reduce_111( val, _values, result ) - result = ast AST::Name, :value => val[0].sub(/^\$/,'') - result - end -.,., - -module_eval <<'.,.,', 'grammar.ra', 575 - def _reduce_112( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 593 + def _reduce_140( val, _values, result ) if val[1].instance_of?(AST::ASTArray) result = val[1] else @@ -1488,21 +1661,21 @@ module_eval <<'.,.,', 'grammar.ra', 575 end .,., -module_eval <<'.,.,', 'grammar.ra', 577 - def _reduce_113( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 595 + def _reduce_141( val, _values, result ) result = ast AST::ASTArray result end .,., - # reduce 114 omitted + # reduce 142 omitted - # reduce 115 omitted + # reduce 143 omitted - # reduce 116 omitted + # reduce 144 omitted -module_eval <<'.,.,', 'grammar.ra', 582 - def _reduce_117( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 600 + def _reduce_145( val, _values, result ) result = nil result end diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb new file mode 100644 index 000000000..5d09eaa9b --- /dev/null +++ b/lib/puppet/parser/resource.rb @@ -0,0 +1,324 @@ +# A resource that we're managing. This handles making sure that only subclasses +# can set parameters. +class Puppet::Parser::Resource + require 'puppet/parser/resource/param' + require 'puppet/parser/resource/reference' + ResParam = Struct.new :name, :value, :source, :line, :file + include Puppet::Util + include Puppet::Util::MethodHelper + include Puppet::Util::Errors + include Puppet::Util::Logging + + attr_accessor :source, :line, :file, :scope + attr_accessor :virtual, :override, :params, :translated + + attr_reader :exported + + attr_writer :tags + + # Proxy a few methods to our @ref object. + [:builtin?, :type, :title].each do |method| + define_method(method) do + @ref.send(method) + end + end + + # Set up some boolean test methods + [:exported, :translated, :override].each do |method| + newmeth = (method.to_s + "?").intern + define_method(newmeth) do + self.send(method) + end + end + + def [](param) + param = symbolize(param) + if param == :title + return self.title + end + if @params.has_key?(param) + @params[param].value + else + nil + end + end + + # Add default values from our definition. + def adddefaults + defaults = scope.lookupdefaults(self.type) + + defaults.each do |name, param| + unless @params.include?(param.name) + self.debug "Adding default for %s" % param.name + + @params[param.name] = param + end + end + end + + # Add any metaparams defined in our scope. This actually adds any metaparams + # from any parent scope, and there's currently no way to turn that off. + def addmetaparams + Puppet::Type.eachmetaparam do |name| + if val = scope.lookupvar(name.to_s, false) + unless val == :undefined + set Param.new(:name => name, :value => val, :source => scope.source) + end + end + end + end + + # Add any overrides for this object. + def addoverrides + overrides = scope.lookupoverrides(self) + + overrides.each do |over| + self.merge(over) + end + + overrides.clear + end + + def builtin=(bool) + @ref.builtin = bool + end + + # Retrieve the associated definition and evaluate it. + def evaluate + if builtin? + devfail "Cannot evaluate a builtin type" + end + + unless klass = scope.finddefine(self.type) + self.fail "Cannot find definition %s" % self.type + end + + finish() + + scope.deleteresource(self) + + return klass.evaluate(:scope => scope, + :type => self.type, + :name => self.title, + :arguments => self.to_hash, + :scope => self.scope, + :exported => self.exported + ) + ensure + @evaluated = true + end + + def exported=(value) + if value + @virtual = true + @exported = value + else + @exported = value + end + end + + def evaluated? + if defined? @evaluated and @evaluated + true + else + false + end + end + + # Do any finishing work on this object, called before evaluation or + # before storage/translation. + def finish + addoverrides() + adddefaults() + addmetaparams() + end + + def initialize(options) + options = symbolize_options(options) + + # Collect the options necessary to make the reference. + refopts = [:type, :title].inject({}) do |hash, param| + hash[param] = options[param] || + devfail("%s must be passed to Resources" % param) + options.delete(param) + hash + end + + @params = {} + tmpparams = nil + if tmpparams = options[:params] + options.delete(:params) + end + + # Now set the rest of the options. + set_options(options) + + @ref = Reference.new(refopts) + + requiredopts(:scope, :source) + + @ref.scope = self.scope + + if tmpparams + tmpparams.each do |param| + # We use the method here, because it does type-checking. + set(param) + end + end + end + + # Merge an override resource in. + def merge(resource) + # Some of these might fail, but they'll fail in the way we want. + resource.params.each do |name, param| + set(param) + end + end + + # Verify that all passed parameters are valid. This throws an error if there's + # a problem, so we don't have to worry about the return value. + def paramcheck(param) + # This defaults to true + unless Puppet[:paramcheck] + return + end + + return if param == "name" or param == "title" # always allow these + + # FIXME We might need to do more here eventually. Metaparams + # behave strangely on containers. + return if Puppet::Type.metaparam?(param) + + # Now make sure it's a valid argument to our class. + unless @ref.typeclass.validattr?(param) + self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % + [param.inspect, @ref.type] + end + end + + # A temporary occasion, until I get paths in the scopes figured out. + def path + to_s + end + + # Return the short version of our name. + def ref + @ref.to_s + end + + # You have to pass a Resource::Param to this. + def set(param) + # Because definitions are now parse-time, I can paramcheck immediately. + paramcheck(param.name) + + if current = @params[param.name] + # XXX Should we ignore any settings that have the same values? + if param.source.child_of?(current.source) + # Replace it, keeping all of its info. + @params[param.name] = param + else + fail Puppet::ParseError, "Parameter %s is already set on %s by %s" % + [param.name, self.to_s, param.source] + end + else + if self.source == param.source or param.source.child_of?(self.source) + @params[param.name] = param + else + fail Puppet::ParseError, "Only subclasses can set parameters" + end + end + end + + # Store our object as a Rails object. We need the host object we're storing it + # with. + def store(host) + args = {} + %w{type title tags file line exported}.each do |param| + if value = self.send(param) + args[param] = value + end + end + + # 'type' isn't a valid column name, so we have to use something else. + args = symbolize_options(args) + args[:restype] = args[:type] + args.delete(:type) + + # Let's see if the object exists + #if obj = host.rails_resources.find_by_type_and_title(self.type, self.title) + if obj = host.rails_resources.find_by_restype_and_title(self.type, self.title) + # We exist + args.each do |param, value| + obj[param] = value + end + else + # Else create it anew + obj = host.rails_resources.build(args) + end + + # Either way, now add our parameters + @params.each do |name, param| + param.store(obj) + end + + return obj + end + + def tags + unless defined? @tags + @tags = scope.tags + @tags << self.type + end + @tags + end + + def to_hash + @params.inject({}) do |hash, ary| + param = ary[1] + hash[param.name] = param.value + hash + end + end + + def to_s + self.ref + end + + # Translate our object to a transportable object. + def to_trans + unless builtin? + devfail "Tried to translate a non-builtin resource" + end + + return nil if virtual? + + # Now convert to a transobject + obj = Puppet::TransObject.new(@ref.title, @ref.type) + to_hash.each do |p, v| + if v.is_a?(Reference) + v = v.to_ref + elsif v.is_a?(Array) + v = v.collect { |av| + if av.is_a?(Reference) + av = av.to_ref + end + av + } + end + obj[p.to_s] = v + end + + obj.file = self.file + obj.line = self.line + + obj.tags = self.tags + + return obj + end + + def virtual? + self.virtual + end +end + +# $Id$ diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb new file mode 100644 index 000000000..259e935d1 --- /dev/null +++ b/lib/puppet/parser/resource/param.rb @@ -0,0 +1,44 @@ +# The parameters we stick in Resources. +class Puppet::Parser::Resource::Param + attr_accessor :name, :value, :source, :line, :file + include Puppet::Util::Errors + include Puppet::Util::MethodHelper + + def initialize(hash) + set_options(hash) + requiredopts(:name, :value, :source) + @name = @name.intern if @name.is_a?(String) + end + + def inspect + "#<#{self.class} @name => #{self.name}, @value => #{self.value}, @source => #{self.source.type}>" + end + + # Store this parameter in a Rails db. + def store(resource) + args = {} + [:name, :value, :line, :file].each do |var| + if val = self.send(var) + args[var] = val + end + end + args[:name] = args[:name].to_s + if obj = resource.rails_parameters.find_by_name(self.name) + # We exist + args.each do |p, v| + obj[p] = v + end + else + # Else create it anew + obj = resource.rails_parameters.build(args) + end + + return obj + end + + def to_s + "%s => %s" % [self.name, self.value] + end +end + +# $Id$ diff --git a/lib/puppet/parser/resource/reference.rb b/lib/puppet/parser/resource/reference.rb new file mode 100644 index 000000000..2210b71c2 --- /dev/null +++ b/lib/puppet/parser/resource/reference.rb @@ -0,0 +1,68 @@ +# A reference to a resource. Mostly just the type and title. +class Puppet::Parser::Resource::Reference + include Puppet::Util::MethodHelper + include Puppet::Util::Errors + + attr_accessor :type, :title, :builtin, :file, :line, :scope + + # Are we a builtin type? + def builtin? + unless defined? @builtin + if builtintype() + @builtin = true + else + @builtin = false + end + end + + self.builtin + end + + def builtintype + if t = Puppet::Type.type(self.type) and t.name != :component + t + else + nil + end + end + + # Return the defined type for our obj. + def definedtype + unless defined? @definedtype + if tmp = @scope.finddefine(self.type) + @definedtype = tmp + else + fail Puppet::ParseError, "Could not find definition %s" % self.type + end + end + + @definedtype + end + + def initialize(hash) + set_options(hash) + requiredopts(:type, :title) + end + + def to_ref + return [type.to_s,title.to_s] + end + + def to_s + "%s[%s]" % [type, title] + end + + def typeclass + unless defined? @typeclass + if tmp = builtintype || definedtype + @typeclass = tmp + else + fail Puppet::ParseError, "Could not find type %s" % self.type + end + end + + @typeclass + end +end + +# $Id$ diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index a26ec938d..9b59ebd64 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -2,1191 +2,645 @@ # such. require 'puppet/parser/parser' +require 'puppet/parser/templatewrapper' require 'puppet/transportable' -module Puppet::Parser - class Scope - class ScopeObj < Hash - attr_accessor :file, :line, :type, :name - end - - # A simple wrapper for templates, so they don't have full access to - # the scope objects. - class TemplateWrapper - attr_accessor :scope, :file - include Puppet::Util - Puppet::Util.logmethods(self) - - def initialize(scope, file) - @scope = scope - if file =~ /^#{File::SEPARATOR}/ - @file = file - else - @file = File.join(Puppet[:templatedir], file) - end +class Puppet::Parser::Scope + require 'puppet/parser/resource' - unless FileTest.exists?(@file) - raise Puppet::ParseError, - "Could not find template %s" % file - end + AST = Puppet::Parser::AST - # We'll only ever not have an interpreter in testing, but, eh. - if @scope.interp - @scope.interp.newfile(@file) - end - end + # This doesn't actually work right now. + Puppet.config.setdefaults(:puppet, + :lexical => [false, "Whether to use lexical scoping (vs. dynamic)."], + :templatedir => ["$vardir/templates", + "Where Puppet looks for template files." + ] + ) - # Ruby treats variables like methods, so we can cheat here and - # trap missing vars like they were missing methods. - def method_missing(name, *args) - # We have to tell lookupvar to return :undefined to us when - # appropriate; otherwise it converts to "". - value = @scope.lookupvar(name.to_s, false) - if value != :undefined - return value - else - # Just throw an error immediately, instead of searching for - # other missingmethod things or whatever. - raise Puppet::ParseError, - "Could not find value for '%s'" % name - end - end - - def result - result = nil - benchmark(:debug, "Interpolated template #{@file}") do - template = ERB.new(File.read(@file), 0, "-") - result = template.result(binding) - end - - result - end - - def to_s - "template[%s]" % @file - end - end + Puppet::Util.logmethods(self) - # This doesn't actually work right now. - Puppet.config.setdefaults(:puppet, - :lexical => [false, "Whether to use lexical scoping (vs. dynamic)."], - :templatedir => ["$vardir/templates", - "Where Puppet looks for template files." - ] - ) + include Enumerable + include Puppet::Util::Errors + attr_accessor :parent, :level, :interp, :source, :host + attr_accessor :name, :type, :topscope, :base, :keyword, :namespace + attr_accessor :top, :context, :translated, :exported - Puppet::Util.logmethods(self) + # Whether we behave declaratively. Note that it's a class variable, + # so all scopes behave the same. + @@declarative = true - include Enumerable - attr_accessor :parent, :level, :interp - attr_accessor :name, :type, :topscope, :base, :keyword + # Retrieve and set the declarative setting. + def self.declarative + return @@declarative + end - attr_accessor :top, :context, :translated, :collectable + def self.declarative=(val) + @@declarative = val + end - # This is probably not all that good of an idea, but... - # This way a parent can share its tables with all of its children. - attr_writer :nodetable, :classtable, :definedtable, :exportable + # This handles the shared tables that all scopes have. They're effectively + # global tables, except that they're only global for a single scope tree, + # which is why I can't use class variables for them. + def self.sharedtable(*names) + attr_accessor(*names) + @@sharedtables ||= [] + @@sharedtables += names + end - # Whether we behave declaratively. Note that it's a class variable, - # so all scopes behave the same. - @@declarative = true + # This is probably not all that good of an idea, but... + # This way a parent can share its tables with all of its children. + sharedtable :classtable, :definedtable, :exportable, :overridetable, :collecttable - # Retrieve and set the declarative setting. - def self.declarative - return @@declarative + # Is the value true? This allows us to control the definition of truth + # in one place. + def self.true?(value) + if value == false or value == "" + return false + else + return true end + end - def self.declarative=(val) - @@declarative = val + # Is the type a builtin type? + def builtintype?(type) + if typeklass = Puppet::Type.type(type) + return typeklass + else + return false end + end - # Is the value true? This allows us to control the definition of truth - # in one place. - def self.true?(value) - if value == false or value == "" - return false - else - return true - end + # Create a new child scope. + def child=(scope) + @children.push(scope) + + # Copy all of the shared tables over to the child. + @@sharedtables.each do |name| + scope.send(name.to_s + "=", self.send(name)) end + end - # Add all of the defaults for a given object to that object. - def adddefaults(obj) - defaults = lookupdefaults(obj.type) + # Verify that the given object isn't defined elsewhere. + def chkobjectclosure(obj) + if @definedtable.include?(obj.ref) + typeklass = Puppet::Type.type(obj.type) + if typeklass and ! typeklass.isomorphic? + Puppet.info "Allowing duplicate %s" % type + else + exobj = @definedtable[obj.ref] - defaults.each do |var, value| - unless obj[var] - self.debug "Adding default %s for %s" % - [var, obj.type] + # Either it's a defined type, which are never + # isomorphic, or it's a non-isomorphic type. + msg = "Duplicate definition: %s is already defined" % obj.ref - obj[var] = value + if exobj.file and exobj.line + msg << " in file %s at line %s" % + [exobj.file, exobj.line] end - end - end - # Add a single object's tags to the global list of tags for - # that object. - def addtags(obj) - unless defined? @tagtable - raise Puppet::DevError, "Told to add tags, but no tag table" - end - list = @tagtable[obj.type][obj.name] - - obj.tags.each { |tag| - unless list.include?(tag) - if tag.nil? or tag == "" - Puppet.debug "Got tag %s from %s(%s)" % - [tag.inspect, obj.type, obj.name] - else - list << tag - end + if obj.line or obj.file + msg << "; cannot redefine" end - } - end - # Is the type a builtin type? - def builtintype?(type) - if typeklass = Puppet::Type.type(type) - return typeklass - else - return false + raise Puppet::ParseError.new(msg) end end - # Verify that the given object isn't defined elsewhere. - def chkobjectclosure(hash) - type = hash[:type] - name = hash[:name] - unless name - return true - end - if @definedtable[type].include?(name) - typeklass = Puppet::Type.type(type) - if typeklass and ! typeklass.isomorphic? - Puppet.info "Allowing duplicate %s" % type - else - exobj = @definedtable[type][name] - - # Either it's a defined type, which are never - # isomorphic, or it's a non-isomorphic type. - msg = "Duplicate definition: %s[%s] is already defined" % - [type, name] - - if exobj.file and exobj.line - msg << " in file %s at line %s" % - [exobj.file, exobj.line] - end - - if hash[:line] or hash[:file] - msg << "; cannot redefine" - end - - error = Puppet::ParseError.new(msg) - raise error - end - end - - return true - end - - def declarative=(val) - self.class.declarative = val - end - - def declarative - self.class.declarative - end - - # Log the existing tags. At some point this should be in a better - # place, but eh. - def logtags - @tagtable.sort { |a, b| - a[0] <=> b[0] - }.each { |type, names| - names.sort { |a, b| - a[0] <=> b[0] - }.each { |name, tags| - Puppet.info "%s(%s): '%s'" % [type, name, tags.join("' '")] - } - } - end + return true + end - # Create a new child scope. - def child=(scope) - @children.push(scope) + # Return the list of collections. + def collections + @collecttable + end - if defined? @nodetable - scope.nodetable = @nodetable - else - raise Puppet::DevError, "No nodetable has been defined" - end + def declarative=(val) + self.class.declarative = val + end - if defined? @classtable - scope.classtable = @classtable - else - raise Puppet::DevError, "No classtable has been defined" - end + def declarative + self.class.declarative + end - if defined? @exportable - scope.exportable = @exportable - else - raise Puppet::DevError, "No exportable has been defined" - end + # Test whether a given scope is declarative. Even though it's + # a global value, the calling objects don't need to know that. + def declarative? + @@declarative + end - if defined? @definedtable - scope.definedtable = @definedtable - else - raise Puppet::DevError, "No definedtable has been defined" - end - end + # Remove a specific child. + def delete(child) + @children.delete(child) + end - # Test whether a given scope is declarative. Even though it's - # a global value, the calling objects don't need to know that. - def declarative? - @@declarative + # Remove a resource from the various tables. This is only used when + # a resource maps to a definition and gets evaluated. + def deleteresource(resource) + if @definedtable[resource.ref] + @definedtable.delete(resource.ref) end - # Remove a specific child. - def delete(child) - @children.delete(child) + if @children.include?(resource) + @children.delete(resource) end + end - # Are we the top scope? - def topscope? - @level == 1 - end + # Are we the top scope? + def topscope? + @level == 1 + end - # Return a list of all of the defined classes. - def classlist - unless defined? @classtable - raise Puppet::DevError, "Scope did not receive class table" - end - return @classtable.collect { |id, klass| - # The class table can contain scopes or strings as its values - # so support them accordingly. - if klass.is_a? Scope - klass.type - else - klass - end - } + # Return a list of all of the defined classes. + def classlist + unless defined? @classtable + raise Puppet::DevError, "Scope did not receive class table" end + return @classtable.keys.reject { |k| k == "" } + end - # Yield each child scope in turn - def each - @children.each { |child| - yield child - } - end + # Yield each child scope in turn + def each + @children.each { |child| + yield child + } + end - # Evaluate a list of classes. - def evalclasses(classes) - return unless classes - classes.each do |klass| - if code = lookuptype(klass) - # Just reuse the 'include' function, since that's the equivalent - # of what we're doing here. - function_include(klass) - end + # Evaluate a list of classes. + def evalclasses(*classes) + retval = [] + classes.each do |klass| + if obj = findclass(klass) + obj.safeevaluate :scope => self + retval << klass end end + end - # Evaluate a specific node's code. This method will normally be called - # on the top-level scope, but it actually evaluates the node at the - # appropriate scope. - def evalnode(hash) - objects = hash[:ast] - names = hash[:names] or - raise Puppet::DevError, "Node names must be provided to evalnode" - facts = hash[:facts] - classes = hash[:classes] - parent = hash[:parent] - - # Always add "default" to our name list, so we're always searching - # for a default node. - names << "default" - - scope = code = nil - # Find a node that matches one of our names - names.each { |node| - if nodehash = @nodetable[node] - code = nodehash[:node] - scope = nodehash[:scope] - - if node == "default" - Puppet.info "Using default node" - end - break - end - } - - # And fail if we don't find one. - unless scope and code - raise Puppet::Error, "Could not find configuration for %s" % - names.join(" or ") - end + def exported? + self.exported + end - # We need to do a little skullduggery here. We want a - # temporary scope, because we don't want this scope to - # show up permanently in the scope tree -- otherwise we could - # not evaluate the node multiple times. We could conceivably - # cache the results, but it's not worth it at this stage. + def findclass(name) + interp.findclass(namespace, name) + end - # Note that we evaluate the node code with its containing - # scope, not with the top scope. We also retrieve the created - # scope so that we can get any classes set within it - nodescope = code.safeevaluate(:scope => scope, :facts => facts) + def finddefine(name) + interp.finddefine(namespace, name) + end - scope.evalclasses(classes) + def findresource(string, name = nil) + if name + string = "%s[%s]" % [string, name] end - # The top-level evaluate, used to evaluate a whole AST tree. This is - # a strange method, in that it turns around and calls evaluate() on its - # :ast argument. - def evaluate(hash) - objects = hash[:ast] - facts = hash[:facts] || {} - - @@done = [] - - unless objects - raise Puppet::DevError, "Evaluation requires an AST tree" - end - - # Set all of our facts in the top-level scope. - facts.each { |var, value| - self.setvar(var, value) - } - - # Evaluate all of our configuration. This does not evaluate any - # node definitions. - result = objects.safeevaluate(:scope => self) - - # If they've provided a name or a parent, we assume they're looking - # for nodes. - if hash[:searched] - # Specifying a parent node takes precedence, because it is assumed - # that this node was found in a remote repository like ldap. - gennode(hash) - elsif hash.include? :names # else, look for it in the config - evalnode(hash) - else - # Else we're not using nodes at all, so just evaluate any passed-in - # classes. - classes = hash[:classes] || [] - evalclasses(classes) - - # These classes would be passed in manually, via something like - # a cfengine module - end - - bucket = self.to_trans - - # Add our class list - unless self.classlist.empty? - bucket.classes = self.classlist - end - - # Now clean up after ourselves - [@@done].each do |table| - table.clear - end + @definedtable[string] + end - return bucket + # Recursively complete the whole tree, in preparation for + # translation or storage. + def finish + self.each do |obj| + obj.finish end + end - # Return the hash of objects that we specifically exported. We return - # a hash to make it easy for the caller to deduplicate based on name. - def exported(type) - if @exportable.include?(type) - return @exportable[type].dup + # Initialize our new scope. Defaults to having no parent and to + # being declarative. + def initialize(hash = {}) + @parent = nil + @type = nil + @name = nil + @finished = false + hash.each { |name, val| + method = name.to_s + "=" + if self.respond_to? method + self.send(method, val) else - return {} - end - end - - # Store our object in the central export table. - def exportobject(obj) - if @exportable.include?(obj.type) and - @exportable[obj.type].include?(obj.name) - raise Puppet::ParseError, "Object %s[%s] is already exported" % - [obj.type, obj.name] + raise Puppet::DevError, "Invalid scope argument %s" % name end + } - debug "Exporting %s[%s]" % [obj.type, obj.name] + @tags = [] - @exportable[obj.type][obj.name] = obj + if @parent.nil? + unless hash.include?(:declarative) + hash[:declarative] = true + end + self.istop(hash[:declarative]) + @inside = nil + else + # This is here, rather than in newchild(), so that all + # of the later variable initialization works. + @parent.child = self - return obj + @level = @parent.level + 1 + @interp = @parent.interp + @source = hash[:source] || @parent.source + @topscope = @parent.topscope + @context = @parent.context + @inside = @parent.inside + @host = @parent.host + @type ||= @parent.type end - # Pull in all of the appropriate classes and evaluate them. It'd - # be nice if this didn't know quite so much about how AST::Node - # operated internally. This is used when a list of classes is passed in, - # instead of a node definition, such as from the cfengine module. - def gennode(hash) - names = hash[:names] or - raise Puppet::DevError, "Node names must be provided to gennode" - facts = hash[:facts] - classes = hash[:classes] - parent = hash[:parentnode] - name = names.shift - arghash = { - :type => name, - :code => AST::ASTArray.new(:pin => "[]") - } + # Our child scopes and objects + @children = [] - #Puppet.notice "hash is %s" % - # hash.inspect - #Puppet.notice "Classes are %s, parent is %s" % - # [classes.inspect, parent.inspect] + # The symbol table for this scope + @symtable = Hash.new(nil) - if parent - arghash[:parentclass] = parent - end + # All of the defaults set for types. It's a hash of hashes, + # with the first key being the type, then the second key being + # the parameter. + @defaultstable = Hash.new { |dhash,type| + dhash[type] = Hash.new(nil) + } - # Create the node - node = AST::Node.new(arghash) - node.keyword = "node" + # Map the names to the tables. + @map = { + "variable" => @symtable, + "defaults" => @defaultstable + } - # Now evaluate it, which evaluates the parent and nothing else - # but does return the nodescope. - scope = node.safeevaluate(:scope => self) - - # Finally evaluate our list of classes in this new scope. - scope.evalclasses(classes) + unless @interp + raise Puppet::DevError, "Scopes require an interpreter" end + end - # Initialize our new scope. Defaults to having no parent and to - # being declarative. - def initialize(hash = {}) - @parent = nil - @type = nil - @name = nil - @finished = false - hash.each { |name, val| - method = name.to_s + "=" - if self.respond_to? method - self.send(method, val) - else - raise Puppet::DevError, "Invalid scope argument %s" % name - end - } - - @tags = [] - - if @parent.nil? - unless hash.include?(:declarative) - hash[:declarative] = true - end - self.istop(hash[:declarative]) - @inside = nil - else - # This is here, rather than in newchild(), so that all - # of the later variable initialization works. - @parent.child = self - - @level = @parent.level + 1 - @interp = @parent.interp - @topscope = @parent.topscope - @context = @parent.context - @inside = @parent.inside - end - - # Our child scopes and objects - @children = [] - - # The symbol table for this scope - @symtable = Hash.new(nil) + # Associate the object directly with the scope, so that contained objects + # can look up what container they're running within. + def inside(arg = nil) + return @inside unless arg + + old = @inside + @inside = arg + yield + ensure + #Puppet.warning "exiting %s" % @inside.name + @inside = old + end - # The type table for this scope - @typetable = Hash.new(nil) + # Mark that we're the top scope, and set some hard-coded info. + def istop(declarative = true) + # the level is mostly used for debugging + @level = 1 - # All of the defaults set for types. It's a hash of hashes, - # with the first key being the type, then the second key being - # the parameter. - @defaultstable = Hash.new { |dhash,type| - dhash[type] = Hash.new(nil) - } + # The table for storing class singletons. This will only actually + # be used by top scopes and node scopes. + @classtable = Hash.new(nil) - # The object table is similar, but it is actually a hash of hashes - # where the innermost objects are TransObject instances. - @objectable = Hash.new { |typehash,typekey| - # See #newobject for how to create the actual objects - typehash[typekey] = Hash.new(nil) - } + self.class.declarative = declarative - # This is just for collecting statements locally, so we can - # verify that there is no overlap within this specific scope - @localobjectable = Hash.new { |typehash,typekey| - typehash[typekey] = Hash.new(nil) - } + # The table for all defined objects. + @definedtable = {} - # Map the names to the tables. - @map = { - "variable" => @symtable, - "type" => @typetable, - "node" => @nodetable, - "object" => @objectable, - "defaults" => @defaultstable - } - end + # The list of objects that will available for export. + @exportable = {} - # Associate the object directly with the scope, so that contained objects - # can look up what container they're running within. - def inside(arg = nil) - return @inside unless arg - - old = @inside - @inside = arg - yield - ensure - #Puppet.warning "exiting %s" % @inside.name - @inside = old + # The list of overrides. This is used to cache overrides on objects + # that don't exist yet. We store an array of each override. + @overridetable = Hash.new do |overs, ref| + overs[ref] = [] end - # Mark that we're the top scope, and set some hard-coded info. - def istop(declarative = true) - # the level is mostly used for debugging - @level = 1 - - # The table for storing class singletons. This will only actually - # be used by top scopes and node scopes. - @classtable = Hash.new(nil) + # Eventually, if we support sites, this will allow definitions + # of nodes with the same name in different sites. For now + # the top-level scope is always the only site scope. + @sitescope = true - self.class.declarative = declarative + @namespace = "" - # The table for all defined objects. - @definedtable = Hash.new { |types, type| - types[type] = {} - } - - # A table for storing nodes. - @nodetable = Hash.new(nil) + # The list of collections that have been created. This is a global list, + # but they each refer back to the scope that created them. + @collecttable = [] - # The list of objects that will available for export. - @exportable = Hash.new { |types, type| - types[type] = {} - } + @context = nil + @topscope = self + @type = "puppet" + @name = "top" + end - # Eventually, if we support sites, this will allow definitions - # of nodes with the same name in different sites. For now - # the top-level scope is always the only site scope. - @sitescope = true - - # And create a tag table, so we can collect all of the tags - # associated with any objects created in this scope tree - @tagtable = Hash.new { |types, type| - types[type] = Hash.new { |names, name| - names[name] = [] - } + # Collect all of the defaults set at any higher scopes. + # This is a different type of lookup because it's additive -- + # it collects all of the defaults, with defaults in closer scopes + # overriding those in later scopes. + def lookupdefaults(type) + values = {} + + # first collect the values from the parents + unless @parent.nil? + @parent.lookupdefaults(type).each { |var,value| + values[var] = value } - - @context = nil - @topscope = self - @type = "puppet" - @name = "top" - end - - # Look up a given class. This enables us to make sure classes are - # singletons - def lookupclass(klassid) - unless defined? @classtable - raise Puppet::DevError, "Scope did not receive class table" - end - return @classtable[klassid] end - # Collect all of the defaults set at any higher scopes. - # This is a different type of lookup because it's additive -- - # it collects all of the defaults, with defaults in closer scopes - # overriding those in later scopes. - def lookupdefaults(type) - values = {} - - # first collect the values from the parents - unless @parent.nil? - @parent.lookupdefaults(type).each { |var,value| - values[var] = value - } - end - - # then override them with any current values - # this should probably be done differently - if @defaultstable.include?(type) - @defaultstable[type].each { |var,value| - values[var] = value - } - end - #Puppet.debug "Got defaults for %s: %s" % - # [type,values.inspect] - return values - end - - # Look up all of the exported objects of a given type. Just like - # lookupobject, this only searches up through parent classes, not - # the whole scope tree. - def lookupexported(type) - found = [] - sub = proc { |table| - # We always return nil so that it will search all the way - # up the scope tree. - if table.has_key?(type) - table[type].each do |name, obj| - found << obj - end - nil - else - info table.keys.inspect - nil - end + # then override them with any current values + # this should probably be done differently + if @defaultstable.include?(type) + @defaultstable[type].each { |var,value| + values[var] = value } - - value = lookup("object",sub, false) - - return found end - # Look up a node by name - def lookupnode(name) - #Puppet.debug "Looking up type %s" % name - value = lookup("type",name) - if value == :undefined - return nil - else - #Puppet.debug "Found node %s" % name - return value - end - end + #Puppet.debug "Got defaults for %s: %s" % + # [type,values.inspect] + return values + end - # Look up a defined type. - def lookuptype(name) - #Puppet.debug "Looking up type %s" % name - value = lookup("type",name) - if value == :undefined - return nil - else - #Puppet.debug "Found type %s" % name - return value - end + # Look up all of the exported objects of a given type. Just like + # lookupobject, this only searches up through parent classes, not + # the whole scope tree. + def lookupexported(type) + @definedtable.find_all do |name, r| + r.type == type and r.exported? end + end - # Look up an object by name and type. This should only look up objects - # within a class structure, not within the entire scope structure. - def lookupobject(hash) - type = hash[:type] - name = hash[:name] - #Puppet.debug "Looking up object %s of type %s in level %s" % - # [name, type, @level] - sub = proc { |table| - if table.include?(type) - if table[type].include?(name) - table[type][name] - end - else - nil - end - } - value = lookup("object",sub, true) - if value == :undefined - return nil - else - return value - end - end + def lookupoverrides(obj) + @overridetable[obj.ref] + end - # Look up a variable. The simplest value search we do. Default to returning - # an empty string for missing values, but support returning a constant. - def lookupvar(name, usestring = true) - value = lookup("variable", name) - if value == :undefined - if usestring - return "" - else - return :undefined - end + # Look up a defined type. + def lookuptype(name) + finddefine(name) || findclass(name) + end + + # Look up a variable. The simplest value search we do. Default to returning + # an empty string for missing values, but support returning a constant. + def lookupvar(name, usestring = true) + value = lookup("variable", name) + if value == :undefined + if usestring + return "" else - return value + return :undefined end + else + return value end + end - def newcollection(coll) - @children << coll - end - - # Add a new object to our object table. - def newobject(hash) - if @objectable[hash[:type]].include?(hash[:name]) - raise Puppet::DevError, "Object %s[%s] is already defined" % - [hash[:type], hash[:name]] - end - - self.chkobjectclosure(hash) - - obj = nil - - obj = Puppet::TransObject.new(hash[:name], hash[:type]) - - @children << obj + # Add a collection to the global list. + def newcollection(coll) + @collecttable << coll + end - @objectable[hash[:type]][hash[:name]] = obj + # Create a new scope. + def newscope(hash = {}) + hash[:parent] = self + #debug "Creating new scope, level %s" % [self.level + 1] + return Puppet::Parser::Scope.new(hash) + end - @definedtable[hash[:type]][hash[:name]] = obj + # Return the list of remaining overrides. + def overrides + @overridetable.collect { |name, overs| overs }.flatten + end - return obj - end + def resources + @definedtable.values + end - # Create a new scope. - def newscope(hash = {}) - hash[:parent] = self - #debug "Creating new scope, level %s" % [self.level + 1] - return Puppet::Parser::Scope.new(hash) + def setclass?(obj) + if obj.respond_to?(:fqname) + @classtable.has_key?(obj.fqname) + else + @classtable[obj] end + end - # Retrieve a specific node. This is used in ast.rb to find a - # parent node and in findnode to retrieve and evaluate a node. - def node(name) - @nodetable[name] + # Store the fact that we've evaluated a given class. We use a hash + # that gets inherited from the top scope down, rather than a global + # hash. We store the object ID, not class name, so that we + # can support multiple unrelated classes with the same name. + def setclass(obj) + if obj.is_a?(AST::HostClass) + unless obj.fqname + raise Puppet::DevError, "Got a %s with no fully qualified name" % + obj.class + end + @classtable[obj.fqname] = obj + else + raise Puppet::DevError, "Invalid class %s" % obj.inspect end + end - # Store the fact that we've evaluated a given class. We use a hash - # that gets inherited from the top scope down, rather than a global - # hash. We store the object ID, not class name, so that we - # can support multiple unrelated classes with the same name. - def setclass(id, name) - unless name =~ /^[a-z][\w-]*$/ - raise Puppet::ParseError, "Invalid class name '%s'" % name - end + # Set all of our facts in the top-level scope. + def setfacts(facts) + facts.each { |var, value| + self.setvar(var, value) + } + end - @classtable[id] = name - end + # Add a new object to our object table and the global list, and do any necessary + # checks. + def setresource(obj) + self.chkobjectclosure(obj) - # Store the scope for each class, so that other subclasses can look - # them up. - def setscope(id, scope) - @classtable[id] = scope - end + @children << obj - # Set defaults for a type. The typename should already be downcased, - # so that the syntax is isolated. - def setdefaults(type,params) - table = @defaultstable[type] + # The global table + @definedtable[obj.ref] = obj - # if we got a single param, it'll be in its own array - unless params[0].is_a?(Array) - params = [params] - end + return obj + end - params.each { |ary| - #Puppet.debug "Default for %s is %s => %s" % - # [type,ary[0].inspect,ary[1].inspect] - if @@declarative - if table.include?(ary[0]) - error = Puppet::ParseError.new( - "Default already defined for %s { %s }" % - [type,ary[0]] - ) - raise error - end - else - if table.include?(ary[0]) - # we should maybe allow this warning to be turned off... - Puppet.warning "Replacing default for %s { %s }" % - [type,ary[0]] - end - end - table[ary[0]] = ary[1] - } + # Override a parameter in an existing object. If the object does not yet + # exist, then cache the override in a global table, so it can be flushed + # at the end. + def setoverride(resource) + resource.override = true + if obj = @definedtable[resource.ref] + obj.merge(resource) + else + @overridetable[resource.ref] << resource end + end - # Store a host in the site node table. - def setnode(name,code) - unless defined? @nodetable - raise Puppet::DevError, "No node table defined" - end - if @nodetable.include?(name) - raise Puppet::ParseError, "Host %s is already defined" % name + # Set defaults for a type. The typename should already be downcased, + # so that the syntax is isolated. We don't do any kind of type-checking + # here; instead we let the resource do it when the defaults are used. + def setdefaults(type, params) + table = @defaultstable[type] + + # if we got a single param, it'll be in its own array + params = [params] unless params.is_a?(Array) + + params.each { |param| + #Puppet.debug "Default for %s is %s => %s" % + # [type,ary[0].inspect,ary[1].inspect] + if @@declarative + if table.include?(param.name) + self.fail "Default already defined for %s { %s }" % + [type,param.name] + end else - #Puppet.warning "Setting node %s at level %s" % [name, @level] - - # We have to store both the scope that's setting the node and - # the node itself, so that the node gets evaluated in the correct - # scope. - code.scope = self - @nodetable[name] = { - :scope => self, - :node => code - } + if table.include?(param.name) + # we should maybe allow this warning to be turned off... + Puppet.warning "Replacing default for %s { %s }" % + [type,param.name] + end end - end + table[param.name] = param + } + end - # Define our type. - def settype(name,ltype) - unless name - raise Puppet::DevError, "Got told to set type with a nil type" - end - unless ltype - raise Puppet::DevError, "Got told to set type with a nil object" - end - # Don't let them redefine the class in this scope. - if @typetable.include?(name) - raise Puppet::ParseError, - "%s is already defined" % name - else - ltype.scope = self - @typetable[name] = ltype + # Set a variable in the current scope. This will override settings + # in scopes above, but will not allow variables in the current scope + # to be reassigned if we're declarative (which is the default). + def setvar(name,value) + #Puppet.debug "Setting %s to '%s' at level %s" % + # [name.inspect,value,self.level] + if @@declarative and @symtable.include?(name) + raise Puppet::ParseError, "Cannot reassign variable %s" % name + else + if @symtable.include?(name) + Puppet.warning "Reassigning %s to %s" % [name,value] end + @symtable[name] = value end + end - # This method will fail if the named object is already defined anywhere - # in the scope tree, which is what provides some minimal closure-like - # behaviour. - def setobject(hash) - # FIXME This objectlookup stuff should be looking up using both - # the name and the namevar. - - # First see if we can look the object up using normal scope - # rules, i.e., one of our parent classes has defined the - # object or something - - name = hash[:name] - type = hash[:type] - params = hash[:arguments] - file = hash[:file] - line = hash[:line] - - collectable = hash[:collectable] || self.collectable - - # Verify that we're not overriding any already-set parameters. - if localobj = @localobjectable[type][name] - params.each { |var, value| - if localobj.include?(var) - msg = "Cannot reassign attribute %s on %s[%s]" % - [var, type, name] - - error = Puppet::ParseError.new(msg) - error.line = line - error.file = file - raise error - end - } + # Return an interpolated string. + def strinterp(string) + newstring = string.gsub(/\\\$|\$\{(\w+)\}|\$(\w+)/) do |value| + # If it matches the backslash, then just retun the dollar sign. + if value == '\\$' + '$' + else # look the variable up + var = $1 || $2 + lookupvar($1 || $2) end + end - # First look for it in a parent scope - obj = lookupobject(:name => name, :type => type) - - if obj - unless collectable == obj.collectable - msg = nil - if collectable - msg = "Exported %s[%s] cannot override local objects" - [type, name] - else - msg = "Local %s[%s] cannot override exported objects" - [type, name] - end - - error = Puppet::ParseError.new(msg) - error.line = line - error.file = file - raise error - end - end + return newstring.gsub(/\\t/, "\t").gsub(/\\n/, "\n").gsub(/\\s/, "\s") + end - unless obj and obj != :undefined - unless obj = @objectable[type][name] - obj = self.newobject( - :type => type, - :name => name, - :line => line, - :file => file - ) - - obj.collectable = collectable - - # only set these if we've created the object, - # which is the most common case - # FIXME we eventually need to store the file - # and line with each param, not the object - # itself. - obj.file = file - obj.line = line - end + # Add a tag to our current list. These tags will be added to all + # of the objects contained in this scope. + def tag(*ary) + ary.each { |tag| + unless tag =~ /^\w[-\w]+$/ + fail Puppet::ParseError, "Invalid tag %s" % tag end - - # Now add our parameters. This has the function of overriding - # existing values, which might have been defined in a higher - # scope. - params.each { |var,value| - # Add it to our found object - obj[var] = value - } - - # This is only used for override verification -- the local object - # table does not have transobjects or whatever in it, it just has - # simple hashes. This is necessary because setobject can modify - # our object table or a parent class's object table, and we - # still need to make sure param settings cannot be duplicated - # within our scope. - @localobjectable[type][name] ||= {} - params.each { |var,value| - # And add it to the local table; mmm, hack - @localobjectable[type][name][var] = value - } - - return obj - end - - # Set a variable in the current scope. This will override settings - # in scopes above, but will not allow variables in the current scope - # to be reassigned if we're declarative (which is the default). - def setvar(name,value) - #Puppet.debug "Setting %s to '%s' at level %s" % - # [name.inspect,value,self.level] - if @@declarative and @symtable.include?(name) - raise Puppet::ParseError, "Cannot reassign variable %s" % name - else - if @symtable.include?(name) - Puppet.warning "Reassigning %s to %s" % [name,value] - end - @symtable[name] = value + if tag.nil? or tag == "" + puts caller + Puppet.debug "got told to tag with %s" % tag.inspect + next end - end - - # Return an interpolated string. - def strinterp(string) - newstring = string.gsub(/\\\$|\$\{(\w+)\}|\$(\w+)/) do |value| - # If it matches the backslash, then just retun the dollar sign. - if value == '\\$' - '$' - else # look the variable up - var = $1 || $2 - lookupvar($1 || $2) - end + tag = tag.to_s + unless @tags.include?(tag) + #Puppet.info "Tagging scope %s with %s" % [self.object_id, tag] + @tags << tag end + } + end - return newstring.gsub(/\\t/, "\t").gsub(/\\n/, "\n").gsub(/\\s/, "\s") - end - - # Add a tag to our current list. These tags will be added to all - # of the objects contained in this scope. - def tag(*ary) - ary.each { |tag| + # Return the tags associated with this scope. It's basically + # just our parents' tags, plus our type. We don't cache this value + # because our parent tags might change between calls. + def tags + tmp = [] + @tags + unless ! defined? @type or @type.nil? or @type == "" + tmp << @type.to_s + end + if @parent + #info "Looking for tags in %s" % @parent.type + @parent.tags.each { |tag| if tag.nil? or tag == "" - Puppet.debug "got told to tag with %s" % tag.inspect + Puppet.debug "parent returned tag %s" % tag.inspect next end - unless @tags.include?(tag) - #Puppet.info "Tagging scope %s with %s" % [self.object_id, tag] - @tags << tag.to_s + unless tmp.include?(tag) + tmp << tag end } end + return tmp.sort.uniq + end - # Return the tags associated with this scope. It's basically - # just our parents' tags, plus our type. - def tags - tmp = [] + @tags - unless ! defined? @type or @type.nil? or @type == "" - tmp << @type.to_s - end - if @parent - #info "Looking for tags in %s" % @parent.type - @parent.tags.each { |tag| - if tag.nil? or tag == "" - Puppet.debug "parent returned tag %s" % tag.inspect - next - end - unless tmp.include?(tag) - tmp << tag - end - } - end - return tmp + # Used mainly for logging + def to_s + if self.name + return "%s[%s]" % [@type, @name] + else + return self.type.to_s end + end - # Used mainly for logging - def to_s - if @name - return "%s[%s]" % [@type, @name] + # Convert all of our objects as necessary. + def translate + ret = @children.collect do |child| + case child + when self.class + child.translate + when Puppet::Parser::Resource + child.to_trans else - return @type.to_s + devfail "Got %s for translation" % child.class end + end.reject { |o| o.nil? } + bucket = Puppet::TransBucket.new ret + unless self.type + devfail "A Scope with no type" end + if @type == "" + bucket.type = "main" + else + bucket.type = @type + end + if self.name + bucket.name = self.name + end + return bucket + end - # Convert our scope to a TransBucket. Everything in our @localobjecttable - # gets converted to either an evaluated definition, or a TransObject - def to_trans - results = [] - - # Set this on entry, just in case someone tries to get all weird - @translated = true - - @children.dup.each do |child| - if @@done.include?(child) - raise Puppet::DevError, "Already translated %s" % - child.object_id - else - @@done << child - end - #warning "Working on %s of type %s with id %s" % - # [child.type, child.class, child.object_id] - - # If it's a scope, then it can only be a subclass's scope, so - # convert it to a transbucket and store it in our results list - result = nil - case child - when Scope - result = child.to_trans - when Puppet::TransObject - # These objects can map to defined types or builtin types. - # Builtin types should be passed out as they are, but defined - # types need to be evaluated. We have to wait until this - # point so that subclass overrides can happen. - - # Wait until the last minute to set tags, although this - # probably should not matter - child.tags = self.tags - - # Add any defaults. - self.adddefaults(child) - - # Then make sure this child's tags are stored in the - # central table. This should maybe be in the evaluate - # methods, but, eh. - @topscope.addtags(child) - - # Now that all that is done, check to see what kind of object - # it is. - if objecttype = lookuptype(child.type) - # It's a defined type, so evaluate it. Retain whether - # the object is collectable. If the object is collectable, - # then it will store all of its contents into the - # @exportable table, rather than returning them. - result = objecttype.safeevaluate( - :name => child.name, - :type => child.type, - :arguments => child.to_hash, - :scope => self, - :collectable => child.collectable - ) - else - # If it's collectable, then store it. It will be - # stripped out in the interpreter using the collectstrip - # method. If we don't do this, then these objects - # don't get stored in the DB. - if child.collectable - exportobject(child) - end - result = child - end - # This is pretty hackish, but the collection has to actually - # be performed after all of the classes and definitions are - # evaluated, otherwise we won't catch objects that are exported - # in them. I think this will still be pretty limited in some - # cases, especially those where you are both exporting and - # collecting, but it's the best I can do for now. - when Puppet::Parser::AST::Collection - child.perform(self).each do |obj| - results << obj - end - else - raise Puppet::DevError, - "Puppet::Parse::Scope cannot handle objects of type %s" % - child.class - end - - # Skip nil objects or empty transbuckets - if result - unless result.is_a? Puppet::TransBucket and result.empty? - results << result - end - end - end - - # Get rid of any nil objects. - results.reject! { |child| - child.nil? - } + # Undefine a variable; only used for testing. + def unsetvar(var) + if @symtable.include?(var) + @symtable.delete(var) + end + end - # If we have a name and type, then make a TransBucket, which - # becomes a component. - # Else, just stack all of the objects into the current bucket. - if @type - bucket = Puppet::TransBucket.new + # Return an array of all of the unevaluated objects + def unevaluated + ary = @definedtable.find_all do |name, object| + ! object.builtin? and ! object.evaluated? + end.collect { |name, object| object } - if defined? @name and @name - bucket.name = @name - end - # it'd be nice not to have to do this... - results.each { |result| - #Puppet.warning "Result type is %s" % result.class - bucket.push(result) - } - bucket.type = @type - - if defined? @keyword - bucket.keyword = @keyword - end - #Puppet.debug( - # "TransBucket with name %s and type %s in scope %s" % - # [@name,@type,self.object_id] - #) - - # now find metaparams - @symtable.each { |var,value| - if Puppet::Type.metaparam?(var.intern) - #Puppet.debug("Adding metaparam %s" % var) - bucket.param(var,value) - else - #Puppet.debug("%s is not a metaparam" % var) - end - } - #Puppet.debug "Returning bucket %s from scope %s" % - # [bucket.name,self.object_id] - return bucket - else - Puppet.debug "typeless scope; just returning a list" - return results - end + if ary.empty? + return nil + else + return ary end + end - # Undefine a variable; only used for testing. - def unsetvar(var) - if @symtable.include?(var) - @symtable.delete(var) - end - end + protected - protected - - # This method abstracts recursive searching. It accepts the type - # of search being done and then either a literal key to search for or - # a Proc instance to do the searching. - def lookup(type,sub, usecontext = false) - table = @map[type] - if table.nil? - error = Puppet::ParseError.new( - "Could not retrieve %s table at level %s" % - [type,self.level] - ) - raise error - end + # This method abstracts recursive searching. It accepts the type + # of search being done and then either a literal key to search for or + # a Proc instance to do the searching. + def lookup(type,sub, usecontext = false) + table = @map[type] + if table.nil? + self.fail "Could not retrieve %s table at level %s" % + [type,self.level] + end - if sub.is_a?(Proc) and obj = sub.call(table) - return obj - elsif table.include?(sub) - return table[sub] - elsif ! @parent.nil? - # Context is used for retricting overrides. - if usecontext and self.context != @parent.context - return :undefined - else - return @parent.lookup(type,sub, usecontext) - end - else + if sub.is_a?(Proc) and obj = sub.call(table) + return obj + elsif table.include?(sub) + return table[sub] + elsif ! @parent.nil? + # Context is used for retricting overrides. + if usecontext and self.context != @parent.context return :undefined + else + return @parent.lookup(type,sub, usecontext) end + else + return :undefined end end end diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb new file mode 100644 index 000000000..62b45852b --- /dev/null +++ b/lib/puppet/parser/templatewrapper.rb @@ -0,0 +1,58 @@ +# A simple wrapper for templates, so they don't have full access to +# the scope objects. +class Puppet::Parser::TemplateWrapper + attr_accessor :scope, :file + include Puppet::Util + Puppet::Util.logmethods(self) + + def initialize(scope, file) + @scope = scope + if file =~ /^#{File::SEPARATOR}/ + @file = file + else + @file = File.join(Puppet[:templatedir], file) + end + + unless FileTest.exists?(@file) + raise Puppet::ParseError, + "Could not find template %s" % file + end + + # We'll only ever not have an interpreter in testing, but, eh. + if @scope.interp + @scope.interp.newfile(@file) + end + end + + # Ruby treats variables like methods, so we can cheat here and + # trap missing vars like they were missing methods. + def method_missing(name, *args) + # We have to tell lookupvar to return :undefined to us when + # appropriate; otherwise it converts to "". + value = @scope.lookupvar(name.to_s, false) + if value != :undefined + return value + else + # Just throw an error immediately, instead of searching for + # other missingmethod things or whatever. + raise Puppet::ParseError, + "Could not find value for '%s'" % name + end + end + + def result + result = nil + benchmark(:debug, "Interpolated template #{@file}") do + template = ERB.new(File.read(@file), 0, "-") + result = template.result(binding) + end + + result + end + + def to_s + "template[%s]" % @file + end +end + +# $Id$ diff --git a/lib/puppet/rails/database.rb b/lib/puppet/rails/database.rb index caf87cbb8..8be05bd88 100644 --- a/lib/puppet/rails/database.rb +++ b/lib/puppet/rails/database.rb @@ -6,20 +6,23 @@ class Puppet::Rails::Database < ActiveRecord::Migration ActiveRecord::Migration.verbose = false end - create_table :rails_objects do |table| - table.column :name, :string, :null => false - table.column :ptype, :string, :null => false + # 'type' cannot be a column name, apparently + create_table :rails_resources do |table| + table.column :title, :string, :null => false + table.column :restype, :string, :null => false table.column :tags, :string table.column :file, :string table.column :line, :integer table.column :host_id, :integer - table.column :collectable, :boolean + table.column :exported, :boolean end create_table :rails_parameters do |table| table.column :name, :string, :null => false table.column :value, :string, :null => false - table.column :rails_object_id, :integer + table.column :file, :string + table.column :line, :integer + table.column :rails_resource_id, :integer end create_table :hosts do |table| @@ -33,8 +36,10 @@ class Puppet::Rails::Database < ActiveRecord::Migration end def self.down - drop_table :rails_objects + drop_table :rails_resources drop_table :rails_parameters drop_table :hosts end end + +# $Id$ diff --git a/lib/puppet/rails/host.rb b/lib/puppet/rails/host.rb index 77123c871..ccda1af64 100644 --- a/lib/puppet/rails/host.rb +++ b/lib/puppet/rails/host.rb @@ -1,11 +1,12 @@ -require 'puppet/rails/rails_object' +require 'puppet/rails/rails_resource' #RailsObject = Puppet::Rails::RailsObject class Puppet::Rails::Host < ActiveRecord::Base serialize :facts, Hash serialize :classes, Array - has_many :rails_objects, :dependent => :delete_all + has_many :rails_resources, :dependent => :delete_all, + :include => :rails_parameters # If the host already exists, get rid of its objects def self.clean(host) @@ -19,40 +20,43 @@ class Puppet::Rails::Host < ActiveRecord::Base # Store our host in the database. def self.store(hash) - name = hash[:host] || "localhost" - ip = hash[:ip] || "127.0.0.1" - facts = hash[:facts] || {} - objects = hash[:objects] + unless hash[:name] + raise ArgumentError, "You must specify the hostname for storage" + end - unless objects - raise ArgumentError, "You must pass objects" + args = {} + [:name, :facts, :classes].each do |param| + if hash[param] + args[param] = hash[param] + end end - hostargs = { - :name => name, - :ip => ip, - :facts => facts, - :classes => objects.classes - } + if hash[:facts].include?("ipaddress") + args[:ip] = hash[:facts]["ipaddress"] + end - objects = objects.flatten + unless hash[:resources] + raise ArgumentError, "You must pass resources" + end - host = nil - if host = clean(name) - [:name, :facts, :classes].each do |param| - unless host[param] == hostargs[param] - host[param] = hostargs[param] + if host = self.find_by_name(hash[:name]) + args.each do |param, value| + unless host[param] == args[param] + host[param] = args[param] end end - host.addobjects(objects) else - host = self.new(hostargs) do |hostobj| - hostobj.addobjects(objects) - end + # Create it anew + host = self.new(args) end + hash[:resources].each do |res| + res.store(host) + end - host.save + Puppet::Util.benchmark(:info, "Saved host to database") do + host.save + end return host end diff --git a/lib/puppet/rails/rails_object.rb b/lib/puppet/rails/rails_object.rb deleted file mode 100644 index d1e58e453..000000000 --- a/lib/puppet/rails/rails_object.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'puppet' -require 'puppet/rails/rails_parameter' - -#RailsParameter = Puppet::Rails::RailsParameter -class Puppet::Rails::RailsObject < ActiveRecord::Base - has_many :rails_parameters, :dependent => :delete_all - serialize :tags, Array - - belongs_to :host - - # Add a set of parameters. - def addparams(params) - params.each do |pname, pvalue| - rails_parameters.build( - :name => pname, - :value => pvalue - ) - - #self.rails_parameters << pobj - end - end - - # Convert our object to a trans_object. Do not retain whether the object - # is collectable, though, since that would cause it to get stripped - # from the configuration. - def to_trans - obj = Puppet::TransObject.new(name(), ptype()) - - [:file, :line, :tags].each do |method| - if val = send(method) - obj.send(method.to_s + "=", val) - end - end - rails_parameters.each do |param| - obj[param.name] = param.value - end - - return obj - end -end - -# $Id$ diff --git a/lib/puppet/rails/rails_parameter.rb b/lib/puppet/rails/rails_parameter.rb index a1966e3dc..295662146 100644 --- a/lib/puppet/rails/rails_parameter.rb +++ b/lib/puppet/rails/rails_parameter.rb @@ -1,5 +1,13 @@ class Puppet::Rails::RailsParameter < ActiveRecord::Base - belongs_to :rails_objects + belongs_to :rails_resources + + def to_resourceparam(source) + hash = self.attributes + hash[:source] = source + hash.delete("rails_resource_id") + hash.delete("id") + Puppet::Parser::Resource::Param.new hash + end end # $Id$ diff --git a/lib/puppet/rails/rails_resource.rb b/lib/puppet/rails/rails_resource.rb new file mode 100644 index 000000000..caad1b460 --- /dev/null +++ b/lib/puppet/rails/rails_resource.rb @@ -0,0 +1,34 @@ +require 'puppet' +require 'puppet/rails/rails_parameter' + +#RailsParameter = Puppet::Rails::RailsParameter +class Puppet::Rails::RailsResource < ActiveRecord::Base + has_many :rails_parameters, :dependent => :delete_all + serialize :tags, Array + + belongs_to :host + + # Convert our object to a resource. Do not retain whether the object + # is collectable, though, since that would cause it to get stripped + # from the configuration. + def to_resource(scope) + hash = self.attributes + hash["type"] = hash["restype"] + hash.delete("restype") + hash.delete("host_id") + hash.delete("id") + hash.each do |p, v| + hash.delete(p) if v.nil? + end + hash[:scope] = scope + hash[:source] = scope.source + obj = Puppet::Parser::Resource.new(hash) + rails_parameters.each do |param| + obj.set(param.to_resourceparam(scope.source)) + end + + return obj + end +end + +# $Id$ diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index 90bc70fc2..4b43b9825 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -141,8 +141,8 @@ module Puppet end.flatten end - def initialize - @children = [] + def initialize(children = []) + @children = children end def push(*args) @@ -219,7 +219,7 @@ module Puppet else #Puppet.debug "%s[%s] has no parameters" % [@type, @name] end - container = Puppet.type(:component).create(trans) + container = Puppet::Type::Component.create(trans) else hash = { :name => self.name, @@ -238,7 +238,7 @@ module Puppet #if parent # hash[:parent] = parent #end - container = Puppet.type(:component).create(hash) + container = Puppet::Type::Component.create(hash) end #Puppet.info container.inspect @@ -267,8 +267,10 @@ module Puppet begin child.to_type(container) rescue => detail - # We don't do anything on failures, since we assume it's - # been logged elsewhere. + if Puppet[:trace] and ! detail.is_a?(Puppet::Error) + puts detail.backtrace + end + Puppet.err detail.to_s end } diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 320aa83a0..55421eb4c 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -856,9 +856,7 @@ class Type < Puppet::Element end def self.validattr?(name) - if name.is_a?(String) - name = name.intern - end + name = symbolize(name) if self.validstate?(name) or self.validparameter?(name) or self.metaparam?(name) return true else @@ -1017,32 +1015,6 @@ class Type < Puppet::Element } end - def devfail(msg) - self.fail(Puppet::DevError, msg) - end - - # Throw an error, defaulting to a Puppet::Error - def fail(*args) - type = nil - if args[0].is_a?(Class) - type = args.shift - else - type = Puppet::Error - end - - error = type.new(args.join(" ")) - - if defined? @line and @line - error.line = @line - end - - if defined? @file and @file - error.file = @file - end - - raise error - end - # retrieve the 'is' value for a specified state def is(state) if @states.include?(state) @@ -2141,6 +2113,7 @@ class Type < Puppet::Element # Is the parameter in question a meta-parameter? def self.metaparam?(param) + param = symbolize(param) @@metaparamhash.include?(param) end diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index d8b8c812f..755c254d2 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -456,11 +456,13 @@ module Util end end +require 'puppet/util/errors' require 'puppet/util/methodhelper' require 'puppet/util/metaid' require 'puppet/util/classgen' require 'puppet/util/docs' require 'puppet/util/execution' +require 'puppet/util/logging' require 'puppet/util/package' require 'puppet/util/warnings' diff --git a/lib/puppet/util/errors.rb b/lib/puppet/util/errors.rb new file mode 100644 index 000000000..ad129ee34 --- /dev/null +++ b/lib/puppet/util/errors.rb @@ -0,0 +1,55 @@ +# Some helper methods for throwing errors. +module Puppet::Util::Errors + # Throw a dev error. + def devfail(msg) + self.fail(Puppet::DevError, msg) + end + + # Add line and file info if available and appropriate. + def adderrorcontext(error, other = nil) + error.line ||= self.line if self.respond_to?(:line) and self.line + error.file ||= self.file if self.respond_to?(:file) and self.file + + if other and other.respond_to?(:backtrace) + error.set_backtrace other.backtrace + end + + return error + end + + # Wrap a call in such a way that we always throw the right exception and keep + # as much context as possible. + def exceptwrap(options = {}) + options[:type] ||= Puppet::DevError + begin + retval = yield + rescue Puppet::Error => detail + raise adderrorcontext(detail) + rescue => detail + message = options[:message] || "%s failed with error %s: %s" % + [self.class, detail.class, detail.to_s] + + error = options[:type].new(message) + # We can't use self.fail here because it always expects strings, + # not exceptions. + raise adderrorcontext(error, detail) + end + + return retval + end + + # Throw an error, defaulting to a Puppet::Error. + def fail(*args) + if args[0].is_a?(Class) + type = args.shift + else + type = Puppet::Error + end + + error = adderrorcontext(type.new(args.join(" "))) + + raise error + end +end + +# $Id$ diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb new file mode 100644 index 000000000..1245e24de --- /dev/null +++ b/lib/puppet/util/logging.rb @@ -0,0 +1,20 @@ +# A module to make logging a bit easier. +require 'puppet/log' + +module Puppet::Util::Logging + # Create a method for each log level. + Puppet::Log.eachlevel do |level| + define_method(level) do |args| + if args.is_a?(Array) + args = args.join(" ") + end + Puppet::Log.create( + :level => level, + :source => self, + :message => args + ) + end + end +end + +# $Id$ diff --git a/lib/puppet/util/methodhelper.rb b/lib/puppet/util/methodhelper.rb index 5643ac245..c229e8efb 100644 --- a/lib/puppet/util/methodhelper.rb +++ b/lib/puppet/util/methodhelper.rb @@ -1,5 +1,26 @@ # Where we store helper methods related to, um, methods. module Puppet::Util::MethodHelper + def requiredopts(*names) + names.each do |name| + if self.send(name).nil? + devfail("%s is a required option for %s" % [name, self.class]) + end + end + end + + # Iterate over a hash, treating each member as an attribute. + def set_options(options) + options.dup.each do |param,value| + method = param.to_s + "=" + unless self.respond_to?(method) + self.fail "Invalid parameter %s to object class %s" % + [param,self.class.to_s] + end + + self.send(method,value) + end + end + # Take a hash and convert all of the keys to symbols if possible. def symbolize_options(options) options.inject({}) do |hash, opts| |