This class is part of the internal parser/evaluator/compiler functionality of Puppet. It is passed between the various classes that participate in evaluation. None of its methods are API except those that are clearly marked as such.
@api public
Variables that always exist with nil value even if not set
Hash of hashes of default values per type name
Retrieves the variable value assigned to the name given as an argument. The name must be a String, and namespace can be qualified with ‘::’. The value is looked up in this scope, its parent scopes, or in a specific visible named scope.
@param varname [String] the name of the variable (may be a qualified name using `(ns’::‘)*varname` @param options [Hash] Additional options, not part of api. @return [Object] the value assigned to the given varname @see #[]= @api public
# File lib/puppet/parser/scope.rb, line 452 def [](varname, options={}) lookupvar(varname, options) end
Sets the variable value of the name given as an argument to the given value. The value is set in the current scope and may shadow a variable with the same name in a visible outer scope. It is illegal to re-assign a variable in the same scope. It is illegal to set a variable in some other scope/namespace than the scope passed to a method.
@param varname [String] The variable name to which the value is assigned. Must not contain `::` @param value [String] The value to assign to the given variable name. @param options [Hash] Additional options, not part of api.
@api public
# File lib/puppet/parser/scope.rb, line 756 def []=(varname, value, options = {}) setvar(varname, value, options = {}) end
Add to our list of namespaces.
# File lib/puppet/parser/scope.rb, line 251 def add_namespace(ns) return false if @namespaces.include?(ns) if @namespaces == [""] @namespaces = [ns] else @namespaces << ns end end
Returns true if the given name is bound in the current (most nested) scope for assignments.
# File lib/puppet/parser/scope.rb, line 214 def bound?(name) # Do not look in ephemeral (match scope), the semantics is to answer if an assignable variable is bound effective_symtable(false).bound?(name) end
Calls a 3.x or 4.x function by name with arguments given in an array using the 4.x calling convention and returns the result. Note that it is the caller’s responsibility to rescue the given ArgumentError and provide location information to aid the user find the problem. The problem is otherwise reported against the source location that invoked the function that ultimately called this method.
@return [Object] the result of the called function @raise ArgumentError if the function does not exist
# File lib/puppet/parser/scope.rb, line 936 def call_function(func_name, args, &block) if !Puppet.future_parser?(compiler.environment) return self.send("function_#{func_name}", args, &block) else Puppet::Pops::Parser::EvaluatingParser.new.evaluator.external_call_function(func_name, args, self, &block) end 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.
# File lib/puppet/parser/scope.rb, line 340 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
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.
# File lib/puppet/parser/scope.rb, line 328 def class_set(name, scope) if parent parent.class_set(name, scope) else @class_scopes[name] = scope 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.
# File lib/puppet/parser/scope.rb, line 637 def define_settings(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| 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
Return the effective “table” for setting variables. This method returns the first ephemeral “table” that acts as a local scope, or this scope’s symtable. If the parameter `use_ephemeral` is true, the “top most” ephemeral “table” will be returned (irrespective of it being a match scope or a local scope).
@param use_ephemeral [Boolean] whether the top most ephemeral (of any kind) should be used or not
# File lib/puppet/parser/scope.rb, line 733 def effective_symtable(use_ephemeral) s = @ephemeral.last if use_ephemeral return s || @symtable else while s && !s.is_local_scope?() s = s.parent end s || @symtable end end
The enclosing scope (topscope or nodescope) of this scope. The enclosing scopes are produced when a class or define is included at some point. The parent scope of the included class or define becomes the scope in which it was included. The chain of parent scopes is followed until a node scope or the topscope is found
@return [Puppet::Parser::Scope] The scope or nil if there is no enclosing scope
# File lib/puppet/parser/scope.rb, line 475 def enclosing_scope if has_enclosing_scope? if parent.is_topscope? or parent.is_nodescope? parent else parent.enclosing_scope end else nil end end
# File lib/puppet/parser/scope.rb, line 820 def ephemeral_from(match, file = nil, line = nil) case match when Hash # Create local scope ephemeral and set all values from hash new_ephemeral(true) match.each {|k,v| setvar(k, v, :file => file, :line => line, :ephemeral => true) } # Must always have an inner match data scope (that starts out as transparent) # In 3x slightly wasteful, since a new nested scope is created for a match # (TODO: Fix that problem) new_ephemeral(false) else raise(ArgumentError,"Invalid regex match data. Got a #{match.class}") unless match.is_a?(MatchData) # Create a match ephemeral and set values from match data new_match_scope(match) end end
# File lib/puppet/parser/scope.rb, line 796 def ephemeral_level @ephemeral.size end
Returns true if the variable of the given name is set to any value (including nil)
@return [Boolean] if variable exists or not
# File lib/puppet/parser/scope.rb, line 189 def exist?(name) # Note !! ensure the answer is boolean !! if name =~ /^(.*)::(.+)$/ class_name = $1 variable_name = $2 return true if class_name == '' && BUILT_IN_VARS.include?(variable_name) # lookup class, but do not care if it is not evaluated since that will result # in it not existing anyway. (Tests may run with just scopes and no evaluated classes which # will result in class_scope for "" not returning topscope). klass = find_hostclass(class_name) other_scope = klass.nil? ? nil : class_scope(klass) if other_scope.nil? class_name == '' ? compiler.topscope.exist?(variable_name) : false else other_scope.exist?(variable_name) end else next_scope = inherited_scope || enclosing_scope effective_symtable(true).include?(name) || next_scope && next_scope.exist?(name) || BUILT_IN_VARS.include?(name) end end
# File lib/puppet/parser/scope.rb, line 843 def find_builtin_resource_type(type) Puppet::Type.type(type.to_s.downcase.to_sym) end
# File lib/puppet/parser/scope.rb, line 847 def find_defined_resource_type(type) known_resource_types.find_definition(namespaces, type.to_s.downcase) end
# File lib/puppet/parser/scope.rb, line 264 def find_definition(name) known_resource_types.find_definition(namespaces, name) end
# File lib/puppet/parser/scope.rb, line 268 def find_global_scope() # walk upwards until first found node_scope or top_scope if is_nodescope? || is_topscope? self else next_scope = inherited_scope || enclosing_scope if next_scope.nil? # this happens when testing, and there is only a single test scope and no link to any # other scopes self else next_scope.find_global_scope() end end end
# File lib/puppet/parser/scope.rb, line 260 def find_hostclass(name, options = {}) known_resource_types.find_hostclass(namespaces, name, options) end
# File lib/puppet/parser/scope.rb, line 837 def find_resource_type(type) # It still works fine without the type == 'class' short-cut, but it is a lot slower. return nil if ["class", "node"].include? type.to_s.downcase find_builtin_resource_type(type) || find_defined_resource_type(type) end
# File lib/puppet/parser/scope.rb, line 519 def handle_not_found(class_name, variable_name, position, reason = nil) unless Puppet[:strict_variables] # Do not issue warning if strict variables are on, as an error will be raised by variable_not_found location = if position[:lineproc] " at #{position[:lineproc].call}" elsif position[:file] && position[:line] " at #{position[:file]}:#{position[:line]}" else "" end warning "Could not look up qualified variable '#{class_name}::#{variable_name}'; #{reason}#{location}" end variable_not_found("#{class_name}::#{variable_name}", reason) end
Returns true if the variable of the given name has a non nil value. TODO: This has vague semantics - does the variable exist or not?
use ['name'] to get nil or value, and if nil check with exist?('name')
this include? is only useful because of checking against the boolean value false.
# File lib/puppet/parser/scope.rb, line 181 def include?(name) ! self[name].nil? end
The scope of the inherited thing of this scope’s resource. This could either be a node that was inherited or the class.
@return [Puppet::Parser::Scope] The scope or nil if there is not an inherited scope
# File lib/puppet/parser/scope.rb, line 460 def inherited_scope if has_inherited_class? qualified_scope(resource.resource_type.parent) else nil end end
# File lib/puppet/parser/scope.rb, line 487 def is_classscope? resource and resource.type == "Class" end
# File lib/puppet/parser/scope.rb, line 491 def is_nodescope? resource and resource.type == "Node" end
# File lib/puppet/parser/scope.rb, line 495 def is_topscope? compiler and self == compiler.topscope end
Handles the special case of looking up fully qualified variable in not yet evaluated top scope This is ok if the lookup request originated in topscope (this happens when evaluating bindings; using the top scope to provide the values for facts. @param class_name [String] the classname part of a variable name, may be special “” @param variable_name [String] the variable name without the absolute leading ‘::’ @return [Boolean] true if the given variable name should be looked up directly in this scope
# File lib/puppet/parser/scope.rb, line 541 def lookup_as_local_name?(class_name, variable_name) # not a local if name has more than one segment return nil if variable_name =~ /::/ # partial only if the class for "" cannot be found return nil unless class_name == "" && klass = find_hostclass(class_name) && class_scope(klass).nil? is_topscope? end
# File lib/puppet/parser/scope.rb, line 499 def lookup_qualified_variable(class_name, variable_name, position) begin if lookup_as_local_name?(class_name, variable_name) if is_topscope? # This is the case where $::x is looked up from within the topscope itself, or from a local scope # parented at the top scope. In this case, the lookup must ignore local and ephemeral scopes. # handle_not_found(class_name, variable_name, position) unless @symtable.include?(variable_name) @symtable[variable_name] else self[variable_name] end else qualified_scope(class_name).lookupvar(variable_name, position) end rescue RuntimeError => e handle_not_found(class_name, variable_name, position, e.message) end 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.
The lookupdefaults searches in the the order:
* inherited * contained (recursive) * self
# File lib/puppet/parser/scope.rb, line 357 def lookupdefaults(type) values = {} # first collect the values from the parents if parent 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 values end
Look up a defined type.
# File lib/puppet/parser/scope.rb, line 379 def lookuptype(name) find_definition(name) || find_hostclass(name) end
Lookup a variable within this scope using the Puppet language’s scoping rules. Variables can be qualified using just as in a manifest.
@param [String] name the variable name to lookup
@return Object the value of the variable, or nil if it’s not found
@api public
# File lib/puppet/parser/scope.rb, line 400 def lookupvar(name, options = {}) unless name.is_a? String raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end table = @ephemeral.last if name =~ /^(.*)::(.+)$/ class_name = $1 variable_name = $2 lookup_qualified_variable(class_name, variable_name, options) # TODO: optimize with an assoc instead, this searches through scopes twice for a hit elsif table.include?(name) table[name] else next_scope = inherited_scope || enclosing_scope if next_scope next_scope.lookupvar(name, options) else variable_not_found(name) end end end
# File lib/puppet/parser/scope.rb, line 852 def method_missing(method, *args, &block) method.to_s =~ /^function_(.*)$/ name = $1 super unless name super unless Puppet::Parser::Functions.function(name) # In odd circumstances, this might not end up defined by the previous # method, so we might as well be certain. if respond_to? method send(method, *args) else raise Puppet::DevError, "Function #{name} not defined despite being loaded!" end end
TODO: Who calls this?
# File lib/puppet/parser/scope.rb, line 801 def new_ephemeral(local_scope = false) if local_scope @ephemeral.push(LocalScope.new(@ephemeral.last)) else @ephemeral.push(MatchScope.new(@ephemeral.last, nil)) end end
Nests a match data scope
# File lib/puppet/parser/scope.rb, line 816 def new_match_scope(match_data) @ephemeral.push(MatchScope.new(@ephemeral.last, match_data)) end
Create a new scope and set these options.
# File lib/puppet/parser/scope.rb, line 624 def newscope(options = {}) compiler.newscope(self, options) end
# File lib/puppet/parser/scope.rb, line 628 def parent_module_name return nil unless @parent return nil unless @parent.source @parent.source.module_name end
# File lib/puppet/parser/scope.rb, line 866 def resolve_type_and_titles(type, titles) raise ArgumentError, "titles must be an array" unless titles.is_a?(Array) case type.downcase when "class" # resolve the titles titles = titles.collect do |a_title| hostclass = find_hostclass(a_title) hostclass ? hostclass.name : a_title end when "node" # no-op else # resolve the type resource_type = find_resource_type(type) type = resource_type.name if resource_type end return [type, titles] end
# File lib/puppet/parser/scope.rb, line 696 def set_facts(hash) # Remove _timestamp (it has an illegal datatype). It is not allowed to mutate the given hash # since it contains the facts. hash = hash.dup hash.delete('_timestamp') setvar('facts', deep_freeze(hash), :privileged => true) end
Sets match data in the most nested scope (which always is a MatchScope), it clobbers match data already set there
# File lib/puppet/parser/scope.rb, line 811 def set_match_data(match_data) @ephemeral.last.match_data = match_data end
# File lib/puppet/parser/scope.rb, line 692 def set_trusted(hash) setvar('trusted', deep_freeze(hash), :privileged => true) 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.
It's preferred that you use self[]= instead of this; only use this
when you need to set options.
# File lib/puppet/parser/scope.rb, line 658 def setvar(name, value, options = {}) if name =~ /^[0-9]+$/ raise Puppet::ParseError.new("Cannot assign to a numeric match result variable '$#{name}'") # unless options[:ephemeral] end unless name.is_a? String raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end # Check for reserved variable names if @enable_immutable_data && !options[:privileged] && RESERVED_VARIABLE_NAMES.include?(name) raise Puppet::ParseError, "Attempt to assign to a reserved variable name: '#{name}'" end table = effective_symtable(options[:ephemeral]) if table.bound?(name) if options[:append] error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") else error = Puppet::ParseError.new("Cannot reassign variable #{name}") end error.file = options[:file] if options[:file] error.line = options[:line] if options[:line] raise error end if options[:append] # produced result (value) is the resulting appended value, note: the table[]= does not return the value table[name] = (value = append_value(undef_as('', self[name]), value)) else table[name] = value end value end
Returns a Hash containing all variables and their values, optionally (and by default) including the values defined in parent. Local values shadow parent values. Ephemeral scopes for match results ($0 - $n) are not included.
This is currently a wrapper for #to_hash_legacy or to_hash_future.
@see #to_hash_future
@see #to_hash_legacy
# File lib/puppet/parser/scope.rb, line 575 def to_hash(recursive = true) @future_parser ||= Puppet.future_parser? if @future_parser to_hash_future(recursive) else to_hash_legacy(recursive) end end
Fixed version of #to_hash that implements scoping correctly (i.e., with dynamic scoping disabled #28200 / PUP-1220
@see #to_hash
# File lib/puppet/parser/scope.rb, line 588 def to_hash_future(recursive) if recursive and has_enclosing_scope? target = enclosing_scope.to_hash_future(recursive) if !(inherited = inherited_scope).nil? target.merge!(inherited.to_hash_future(recursive)) end else target = Hash.new end # add all local scopes @ephemeral.last.add_entries_to(target) target end
The old broken implementation of #to_hash that retains the dynamic scoping semantics
@see #to_hash
# File lib/puppet/parser/scope.rb, line 607 def to_hash_legacy(recursive = true) if recursive and parent target = parent.to_hash_legacy(recursive) else target = Hash.new end # add all local scopes @ephemeral.last.add_entries_to(target) target end
Used mainly for logging
# File lib/puppet/parser/scope.rb, line 779 def to_s "Scope(#{@resource})" end
Transforms references to classes to the form suitable for lookup in the compiler.
Makes names passed in the names array absolute if they are relative Names are now made absolute if Puppet.future_parser? is true, this will be the default behavior in Puppet 4.0
Transforms Class[] and Resource[] type referenes to class name or raises an error if a Class[] is unspecific, if a Resource is not a ‘class’ resource, or if unspecific (no title).
TODO: Change this for 4.0 to always make names absolute
@param names [Array<String>] names to (optionally) make absolute @return [Array<String>] names after transformation
# File lib/puppet/parser/scope.rb, line 903 def transform_and_assert_classnames(names) if Puppet.future_parser? names.map do |name| case name when String name.sub(/^([^:]{1,2})/, '::\1') when Puppet::Resource assert_class_and_title(name.type, name.title) name.title.sub(/^([^:]{1,2})/, '::\1') when Puppet::Pops::Types::PHostClassType raise ArgumentError, "Cannot use an unspecific Class[] Type" unless name.class_name name.class_name.sub(/^([^:]{1,2})/, '::\1') when Puppet::Pops::Types::PResourceType assert_class_and_title(name.type_name, name.title) name.title.sub(/^([^:]{1,2})/, '::\1') end end else names end end
# File lib/puppet/parser/scope.rb, line 383 def undef_as(x,v) if v.nil? or v == :undef x else v end end
remove ephemeral scope up to level TODO: Who uses :all ? Remove ??
# File lib/puppet/parser/scope.rb, line 788 def unset_ephemeral_var(level=:all) if level == :all @ephemeral = [ MatchScope.new(@symtable, nil)] else @ephemeral.pop(@ephemeral.size - level) end end
# File lib/puppet/parser/scope.rb, line 425 def variable_not_found(name, reason=nil) # Built in variables always exist if BUILT_IN_VARS.include?(name) return nil end if Puppet[:strict_variables] if Puppet.future_parser? throw :undefined_variable else reason_msg = reason.nil? ? '' : "; #{reason}" raise Puppet::ParseError, "Undefined variable #{name.inspect}#{reason_msg}" end else nil end end
Initialize our new scope. Defaults to having no parent.
# File lib/puppet/parser/scope.rb, line 288 def initialize(compiler, options = {}) if compiler.is_a? Puppet::Parser::Compiler self.compiler = compiler else raise Puppet::DevError, "you must pass a compiler instance to a new scope object" end if n = options.delete(:namespace) @namespaces = [n] else @namespaces = [""] end raise Puppet::DevError, "compiler passed in options" if options.include? :compiler set_options(options) extend_with_functions_module # The symbol table for this scope. This is where we store variables. # @symtable = Ephemeral.new(nil, true) @symtable = LocalScope.new(nil) @ephemeral = [ MatchScope.new(@symtable, nil) ] # 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 = {} @enable_immutable_data = Puppet[:immutable_node_data] end
Coerce value to a number, or return `nil` if it isn’t one.
# File lib/puppet/parser/scope.rb, line 233 def self.number?(value) case value when Numeric value when /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ value.to_f when /^0x[0-9a-f]+$/ value.to_i(16) when /^0[0-7]+$/ value.to_i(8) when /^-?\d+$/ value.to_i else nil end end
Is the value true? This allows us to control the definition of truth in one place.
# File lib/puppet/parser/scope.rb, line 221 def self.true?(value) case value when '' false when :undef false else !!value end end