diff options
Diffstat (limited to 'lib/puppet/parser/scope.rb')
-rw-r--r-- | lib/puppet/parser/scope.rb | 898 |
1 files changed, 449 insertions, 449 deletions
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 9b49ab680..ae0f9ea4a 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -9,475 +9,475 @@ require 'strscan' require 'puppet/resource/type_collection_helper' class Puppet::Parser::Scope - include Puppet::Resource::TypeCollectionHelper - require 'puppet/parser/resource' - - AST = Puppet::Parser::AST - - Puppet::Util.logmethods(self) - - include Enumerable - include Puppet::Util::Errors - attr_accessor :level, :source, :resource - attr_accessor :base, :keyword - attr_accessor :top, :translated, :compiler - attr_accessor :parent - attr_reader :namespaces - - # thin wrapper around an ephemeral - # symbol table. - # when a symbol - class Ephemeral - def initialize(parent=nil) - @symbols = {} - @parent = parent + include Puppet::Resource::TypeCollectionHelper + require 'puppet/parser/resource' + + AST = Puppet::Parser::AST + + Puppet::Util.logmethods(self) + + include Enumerable + include Puppet::Util::Errors + attr_accessor :level, :source, :resource + attr_accessor :base, :keyword + attr_accessor :top, :translated, :compiler + attr_accessor :parent + attr_reader :namespaces + + # thin wrapper around an ephemeral + # symbol table. + # when a symbol + class Ephemeral + def initialize(parent=nil) + @symbols = {} + @parent = parent + end + + [:include?, :delete, :[]=].each do |m| + define_method(m) do |*args| + @symbols.send(m, *args) + end + end + + def [](name) + unless @symbols.include?(name) or @parent.nil? + @parent[name] + else + @symbols[name] + end + end + end + + # A demeterific shortcut to the catalog. + def catalog + compiler.catalog + end + + def environment + compiler.environment + end + + # Proxy accessors + def host + @compiler.node.name + end + + # Is the value true? This allows us to control the definition of truth + # in one place. + def self.true?(value) + (value != false and value != "" and value != :undef) + end + + # Is the value a number?, return the correct object or nil if not a number + def self.number?(value) + return nil unless value.is_a?(Fixnum) or value.is_a?(Bignum) or value.is_a?(Float) or value.is_a?(String) + + if value.is_a?(String) + if value =~ /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ + return value.to_f + elsif value =~ /^0x[0-9a-f]+$/i + return value.to_i(16) + elsif value =~ /^0[0-7]+$/ + return value.to_i(8) + elsif value =~ /^-?\d+$/ + return value.to_i + else + return nil + end + end + # it is one of Fixnum,Bignum or Float + value + end + + # Add to our list of namespaces. + def add_namespace(ns) + return false if @namespaces.include?(ns) + if @namespaces == [""] + @namespaces = [ns] + else + @namespaces << ns + end + end + + # Remove this when rebasing + def environment + compiler.environment + end + + # Are we the top scope? + def topscope? + @level == 1 + end + + def find_hostclass(name) + known_resource_types.find_hostclass(namespaces, name) + end + + def find_definition(name) + known_resource_types.find_definition(namespaces, name) + end + + def findresource(string, name = nil) + compiler.findresource(string, name) + end + + # Initialize our new scope. Defaults to having no parent. + def initialize(hash = {}) + if hash.include?(:namespace) + if n = hash[:namespace] + @namespaces = [n] + end + hash.delete(:namespace) + else + @namespaces = [""] + end + hash.each { |name, val| + method = name.to_s + "=" + if self.respond_to? method + self.send(method, val) + else + raise Puppet::DevError, "Invalid scope argument #{name}" + end + } + + extend_with_functions_module + + @tags = [] + + # The symbol table for this scope. This is where we store variables. + @symtable = {} + + # the ephemeral symbol tables + # those should not persist long, and are used for the moment only + # for $0..$xy capture variables of regexes + # this is actually implemented as a stack, with each ephemeral scope + # shadowing the previous one + @ephemeral = [ Ephemeral.new ] + + # 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. + @defaults = Hash.new { |dhash,type| + dhash[type] = {} + } + + # The table for storing class singletons. This will only actually + # be used by top scopes and node scopes. + @class_scopes = {} + end + + # Store the fact that we've evaluated a class, and store a reference to + # the scope in which it was evaluated, so that we can look it up later. + def class_set(name, scope) + return parent.class_set(name,scope) if parent + @class_scopes[name] = scope + end + + # Return the scope associated with a class. This is just here so + # that subclasses can set their parent scopes to be the scope of + # their parent class, and it's also used when looking up qualified + # variables. + def class_scope(klass) + # They might pass in either the class or class name + k = klass.respond_to?(:name) ? klass.name : klass + @class_scopes[k] || (parent && parent.class_scope(k)) + 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 @defaults.include?(type) + @defaults[type].each { |var,value| + values[var] = value + } + end + + #Puppet.debug "Got defaults for %s: %s" % + # [type,values.inspect] + values + end + + # Look up a defined type. + def lookuptype(name) + find_definition(name) || find_hostclass(name) + end + + def lookup_qualified_var(name, usestring) + parts = name.split(/::/) + shortname = parts.pop + klassname = parts.join("::") + klass = find_hostclass(klassname) + unless klass + warning "Could not look up qualified variable '#{name}'; class #{klassname} could not be found" + return usestring ? "" : :undefined + end + unless kscope = class_scope(klass) + warning "Could not look up qualified variable '#{name}'; class #{klassname} has not been evaluated" + return usestring ? "" : :undefined + end + kscope.lookupvar(shortname, usestring) + end + + private :lookup_qualified_var + + # 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) + table = ephemeral?(name) ? @ephemeral.last : @symtable + # If the variable is qualified, then find the specified scope and look the variable up there instead. + if name =~ /::/ + return lookup_qualified_var(name, usestring) + end + # We can't use "if table[name]" here because the value might be false + if ephemeral_include?(name) or table.include?(name) + if usestring and table[name] == :undef + return "" + else + return table[name] + end + elsif self.parent + return parent.lookupvar(name, usestring) + elsif usestring + return "" + else + return :undefined + end + end + + # Return a hash containing our variables and their values, optionally (and + # by default) including the values defined in our parent. Local values + # shadow parent values. + def to_hash(recursive = true) + target = parent.to_hash(recursive) if recursive and parent + target ||= Hash.new + @symtable.keys.each { |name| + value = @symtable[name] + if value == :undef + target.delete(name) + else + target[name] = value + end + } + target + end + + def namespaces + @namespaces.dup + end + + # Create a new scope and set these options. + def newscope(options = {}) + compiler.newscope(self, options) + end + + def parent_module_name + return nil unless @parent + return nil unless @parent.source + @parent.source.module_name + end + + # Return the list of scopes up to the top scope, ordered with our own first. + # This is used for looking up variables and defaults. + def scope_path + if parent + [self, parent.scope_path].flatten.compact + else + [self] + end + end + + # 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 = @defaults[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 table.include?(param.name) + raise Puppet::ParseError.new("Default already defined for #{type} { #{param.name} }; cannot redefine", param.line, param.file) + end + table[param.name] = param + } + 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. + def setvar(name,value, options = {}) + table = options[:ephemeral] ? @ephemeral.last : @symtable + #Puppet.debug "Setting %s to '%s' at level %s mode append %s" % + # [name.inspect,value,self.level, append] + if table.include?(name) + unless options[:append] + error = Puppet::ParseError.new("Cannot reassign variable #{name}") + else + error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") + end + error.file = options[:file] if options[:file] + error.line = options[:line] if options[:line] + raise error + end + + unless options[:append] + table[name] = value + else # append case + # lookup the value in the scope if it exists and insert the var + table[name] = lookupvar(name) + # concatenate if string, append if array, nothing for other types + case value + when Array + table[name] += value + when Hash + raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" unless value.is_a?(Hash) + table[name].merge!(value) + else + table[name] << value + end + end + end + + # Return an interpolated string. + def strinterp(string, file = nil, line = nil) + # Most strings won't have variables in them. + ss = StringScanner.new(string) + out = "" + while not ss.eos? + if ss.scan(/^\$\{((\w*::)*\w+|[0-9]+)\}|^\$([0-9])|^\$((\w*::)*\w+)/) + # If it matches the backslash, then just retun the dollar sign. + if ss.matched == '\\$' + out << '$' + else # look the variable up + # make sure $0-$9 are lookupable only if ephemeral + var = ss[1] || ss[3] || ss[4] + if var and var =~ /^[0-9]+$/ and not ephemeral_include?(var) + next + end + out << lookupvar(var).to_s || "" end - - [:include?, :delete, :[]=].each do |m| - define_method(m) do |*args| - @symbols.send(m, *args) - end - end - - def [](name) - unless @symbols.include?(name) or @parent.nil? - @parent[name] - else - @symbols[name] - end - end - end - - # A demeterific shortcut to the catalog. - def catalog - compiler.catalog - end - - def environment - compiler.environment - end - - # Proxy accessors - def host - @compiler.node.name - end - - # Is the value true? This allows us to control the definition of truth - # in one place. - def self.true?(value) - (value != false and value != "" and value != :undef) - end - - # Is the value a number?, return the correct object or nil if not a number - def self.number?(value) - return nil unless value.is_a?(Fixnum) or value.is_a?(Bignum) or value.is_a?(Float) or value.is_a?(String) - - if value.is_a?(String) - if value =~ /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ - return value.to_f - elsif value =~ /^0x[0-9a-f]+$/i - return value.to_i(16) - elsif value =~ /^0[0-7]+$/ - return value.to_i(8) - elsif value =~ /^-?\d+$/ - return value.to_i - else - return nil - end - end - # it is one of Fixnum,Bignum or Float - value - end - - # Add to our list of namespaces. - def add_namespace(ns) - return false if @namespaces.include?(ns) - if @namespaces == [""] - @namespaces = [ns] + elsif ss.scan(/^\\(.)/) + # Puppet.debug("Got escape: pos:%d; m:%s" % [ss.pos, ss.matched]) + case ss[1] + when 'n' + out << "\n" + when 't' + out << "\t" + when 's' + out << " " + when '\\' + out << '\\' + when '$' + out << '$' else - @namespaces << ns + str = "Unrecognised escape sequence '#{ss.matched}'" + str += " in file #{file}" if file + str += " at line #{line}" if line + Puppet.warning str + out << ss.matched end - end - - # Remove this when rebasing - def environment - compiler.environment - end - - # Are we the top scope? - def topscope? - @level == 1 - end - - def find_hostclass(name) - known_resource_types.find_hostclass(namespaces, name) - end - - def find_definition(name) - known_resource_types.find_definition(namespaces, name) - end - - def findresource(string, name = nil) - compiler.findresource(string, name) - end - - # Initialize our new scope. Defaults to having no parent. - def initialize(hash = {}) - if hash.include?(:namespace) - if n = hash[:namespace] - @namespaces = [n] - end - hash.delete(:namespace) - else - @namespaces = [""] + elsif ss.scan(/^\$/) + out << '$' + elsif ss.scan(/^\\\n/) # an escaped carriage return + next + else + tmp = ss.scan(/[^\\$]+/) + # Puppet.debug("Got other: pos:%d; m:%s" % [ss.pos, tmp]) + unless tmp + error = Puppet::ParseError.new("Could not parse string #{string.inspect}") + {:file= => file, :line= => line}.each do |m,v| + error.send(m, v) if v + end + raise error end - hash.each { |name, val| - method = name.to_s + "=" - if self.respond_to? method - self.send(method, val) - else - raise Puppet::DevError, "Invalid scope argument #{name}" - end - } - - extend_with_functions_module - - @tags = [] - - # The symbol table for this scope. This is where we store variables. - @symtable = {} - - # the ephemeral symbol tables - # those should not persist long, and are used for the moment only - # for $0..$xy capture variables of regexes - # this is actually implemented as a stack, with each ephemeral scope - # shadowing the previous one - @ephemeral = [ Ephemeral.new ] - - # 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. - @defaults = Hash.new { |dhash,type| - dhash[type] = {} - } - - # The table for storing class singletons. This will only actually - # be used by top scopes and node scopes. - @class_scopes = {} + out << tmp + end end - # Store the fact that we've evaluated a class, and store a reference to - # the scope in which it was evaluated, so that we can look it up later. - def class_set(name, scope) - return parent.class_set(name,scope) if parent - @class_scopes[name] = scope - end + out + end - # Return the scope associated with a class. This is just here so - # that subclasses can set their parent scopes to be the scope of - # their parent class, and it's also used when looking up qualified - # variables. - def class_scope(klass) - # They might pass in either the class or class name - k = klass.respond_to?(:name) ? klass.name : klass - @class_scopes[k] || (parent && parent.class_scope(k)) - end + # 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 + resource.tags + 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 + # Used mainly for logging + def to_s + "Scope(#{@resource})" + end - # then override them with any current values - # this should probably be done differently - if @defaults.include?(type) - @defaults[type].each { |var,value| - values[var] = value - } - end + # Undefine a variable; only used for testing. + def unsetvar(var) + table = ephemeral?(var) ? @ephemeral.last : @symtable + table.delete(var) if table.include?(var) + end - #Puppet.debug "Got defaults for %s: %s" % - # [type,values.inspect] - values + # remove ephemeral scope up to level + def unset_ephemeral_var(level=:all) + if level == :all + @ephemeral = [ Ephemeral.new ] + else + (@ephemeral.size - level).times do + @ephemeral.pop + end end + end - # Look up a defined type. - def lookuptype(name) - find_definition(name) || find_hostclass(name) + # check if name exists in one of the ephemeral scope. + def ephemeral_include?(name) + @ephemeral.reverse.each do |eph| + return true if eph.include?(name) end + false + end - def lookup_qualified_var(name, usestring) - parts = name.split(/::/) - shortname = parts.pop - klassname = parts.join("::") - klass = find_hostclass(klassname) - unless klass - warning "Could not look up qualified variable '#{name}'; class #{klassname} could not be found" - return usestring ? "" : :undefined - end - unless kscope = class_scope(klass) - warning "Could not look up qualified variable '#{name}'; class #{klassname} has not been evaluated" - return usestring ? "" : :undefined - end - kscope.lookupvar(shortname, usestring) - end + # is name an ephemeral variable? + def ephemeral?(name) + name =~ /^\d+$/ + end - private :lookup_qualified_var + def ephemeral_level + @ephemeral.size + 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) - table = ephemeral?(name) ? @ephemeral.last : @symtable - # If the variable is qualified, then find the specified scope and look the variable up there instead. - if name =~ /::/ - return lookup_qualified_var(name, usestring) - end - # We can't use "if table[name]" here because the value might be false - if ephemeral_include?(name) or table.include?(name) - if usestring and table[name] == :undef - return "" - else - return table[name] - end - elsif self.parent - return parent.lookupvar(name, usestring) - elsif usestring - return "" - else - return :undefined - end - end + def new_ephemeral + @ephemeral.push(Ephemeral.new(@ephemeral.last)) + end - # Return a hash containing our variables and their values, optionally (and - # by default) including the values defined in our parent. Local values - # shadow parent values. - def to_hash(recursive = true) - target = parent.to_hash(recursive) if recursive and parent - target ||= Hash.new - @symtable.keys.each { |name| - value = @symtable[name] - if value == :undef - target.delete(name) - else - target[name] = value - end - } - target - end + def ephemeral_from(match, file = nil, line = nil) + raise(ArgumentError,"Invalid regex match data") unless match.is_a?(MatchData) - def namespaces - @namespaces.dup - end + new_ephemeral - # Create a new scope and set these options. - def newscope(options = {}) - compiler.newscope(self, options) + setvar("0", match[0], :file => file, :line => line, :ephemeral => true) + match.captures.each_with_index do |m,i| + setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) end + end - def parent_module_name - return nil unless @parent - return nil unless @parent.source - @parent.source.module_name - end - - # Return the list of scopes up to the top scope, ordered with our own first. - # This is used for looking up variables and defaults. - def scope_path - if parent - [self, parent.scope_path].flatten.compact - else - [self] - end - end - - # 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 = @defaults[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 table.include?(param.name) - raise Puppet::ParseError.new("Default already defined for #{type} { #{param.name} }; cannot redefine", param.line, param.file) - end - table[param.name] = param - } - 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. - def setvar(name,value, options = {}) - table = options[:ephemeral] ? @ephemeral.last : @symtable - #Puppet.debug "Setting %s to '%s' at level %s mode append %s" % - # [name.inspect,value,self.level, append] - if table.include?(name) - unless options[:append] - error = Puppet::ParseError.new("Cannot reassign variable #{name}") - else - error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") - end - error.file = options[:file] if options[:file] - error.line = options[:line] if options[:line] - raise error - end - - unless options[:append] - table[name] = value - else # append case - # lookup the value in the scope if it exists and insert the var - table[name] = lookupvar(name) - # concatenate if string, append if array, nothing for other types - case value - when Array - table[name] += value - when Hash - raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" unless value.is_a?(Hash) - table[name].merge!(value) - else - table[name] << value - end - end - end - - # Return an interpolated string. - def strinterp(string, file = nil, line = nil) - # Most strings won't have variables in them. - ss = StringScanner.new(string) - out = "" - while not ss.eos? - if ss.scan(/^\$\{((\w*::)*\w+|[0-9]+)\}|^\$([0-9])|^\$((\w*::)*\w+)/) - # If it matches the backslash, then just retun the dollar sign. - if ss.matched == '\\$' - out << '$' - else # look the variable up - # make sure $0-$9 are lookupable only if ephemeral - var = ss[1] || ss[3] || ss[4] - if var and var =~ /^[0-9]+$/ and not ephemeral_include?(var) - next - end - out << lookupvar(var).to_s || "" - end - elsif ss.scan(/^\\(.)/) - # Puppet.debug("Got escape: pos:%d; m:%s" % [ss.pos, ss.matched]) - case ss[1] - when 'n' - out << "\n" - when 't' - out << "\t" - when 's' - out << " " - when '\\' - out << '\\' - when '$' - out << '$' - else - str = "Unrecognised escape sequence '#{ss.matched}'" - str += " in file #{file}" if file - str += " at line #{line}" if line - Puppet.warning str - out << ss.matched - end - elsif ss.scan(/^\$/) - out << '$' - elsif ss.scan(/^\\\n/) # an escaped carriage return - next - else - tmp = ss.scan(/[^\\$]+/) - # Puppet.debug("Got other: pos:%d; m:%s" % [ss.pos, tmp]) - unless tmp - error = Puppet::ParseError.new("Could not parse string #{string.inspect}") - {:file= => file, :line= => line}.each do |m,v| - error.send(m, v) if v - end - raise error - end - out << tmp - end - end - - out - end - - # 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 - resource.tags - end - - # Used mainly for logging - def to_s - "Scope(#{@resource})" - end - - # Undefine a variable; only used for testing. - def unsetvar(var) - table = ephemeral?(var) ? @ephemeral.last : @symtable - table.delete(var) if table.include?(var) - end - - # remove ephemeral scope up to level - def unset_ephemeral_var(level=:all) - if level == :all - @ephemeral = [ Ephemeral.new ] - else - (@ephemeral.size - level).times do - @ephemeral.pop - end - end - end + private - # check if name exists in one of the ephemeral scope. - def ephemeral_include?(name) - @ephemeral.reverse.each do |eph| - return true if eph.include?(name) - end - false - end - - # is name an ephemeral variable? - def ephemeral?(name) - name =~ /^\d+$/ - end - - def ephemeral_level - @ephemeral.size - end - - def new_ephemeral - @ephemeral.push(Ephemeral.new(@ephemeral.last)) - end - - def ephemeral_from(match, file = nil, line = nil) - raise(ArgumentError,"Invalid regex match data") unless match.is_a?(MatchData) - - new_ephemeral - - setvar("0", match[0], :file => file, :line => line, :ephemeral => true) - match.captures.each_with_index do |m,i| - setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) - end - end - - private - - def extend_with_functions_module - extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root) - extend Puppet::Parser::Functions.environment_module(compiler ? environment : nil) - end + def extend_with_functions_module + extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root) + extend Puppet::Parser::Functions.environment_module(compiler ? environment : nil) + end end |