class Puppet::Parser::Scope

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

Constants

AST
BUILT_IN_VARS

Variables that always exist with nil value even if not set

RESERVED_VARIABLE_NAMES

Attributes

compiler[RW]
defaults[R]

Hash of hashes of default values per type name

namespaces[R]
parent[RW]
resource[RW]
source[RW]

Public Instance Methods

[](varname, options={}) click to toggle source

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
[]=(varname, value, options = {}) click to toggle source

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_namespace(ns) click to toggle source

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
bound?(name) click to toggle source

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
call_function(func_name, args, &block) click to toggle source

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
class_scope(klass) click to toggle source

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
class_set(name, scope) click to toggle source

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
define_settings(type, params) click to toggle source

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
effective_symtable(use_ephemeral) click to toggle source

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
enclosing_scope() click to toggle source

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
ephemeral_from(match, file = nil, line = nil) click to toggle source
# 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
ephemeral_level() click to toggle source
# File lib/puppet/parser/scope.rb, line 796
def ephemeral_level
  @ephemeral.size
end
exist?(name) click to toggle source

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
find_builtin_resource_type(type) click to toggle source
# File lib/puppet/parser/scope.rb, line 843
def find_builtin_resource_type(type)
  Puppet::Type.type(type.to_s.downcase.to_sym)
end
find_defined_resource_type(type) click to toggle source
# 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
find_definition(name) click to toggle source
# File lib/puppet/parser/scope.rb, line 264
def find_definition(name)
  known_resource_types.find_definition(namespaces, name)
end
find_global_scope() click to toggle source
# 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
find_hostclass(name, options = {}) click to toggle source
# File lib/puppet/parser/scope.rb, line 260
def find_hostclass(name, options = {})
  known_resource_types.find_hostclass(namespaces, name, options)
end
find_resource_type(type) click to toggle source
# 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
handle_not_found(class_name, variable_name, position, reason = nil) click to toggle source
# 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
include?(name) click to toggle source

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
inherited_scope() click to toggle source

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
inspect() click to toggle source
Alias for: to_s
is_classscope?() click to toggle source
# File lib/puppet/parser/scope.rb, line 487
def is_classscope?
  resource and resource.type == "Class"
end
is_nodescope?() click to toggle source
# File lib/puppet/parser/scope.rb, line 491
def is_nodescope?
  resource and resource.type == "Node"
end
is_topscope?() click to toggle source
# File lib/puppet/parser/scope.rb, line 495
def is_topscope?
  compiler and self == compiler.topscope
end
lookup_as_local_name?(class_name, variable_name) click to toggle source

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
lookup_qualified_variable(class_name, variable_name, position) click to toggle source
# 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
lookupdefaults(type) click to toggle source

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
lookuptype(name) click to toggle source

Look up a defined type.

# File lib/puppet/parser/scope.rb, line 379
def lookuptype(name)
  find_definition(name) || find_hostclass(name)
end
lookupvar(name, options = {}) click to toggle source

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
method_missing(method, *args, &block) click to toggle source
# 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
new_ephemeral(local_scope = false) click to toggle source

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
new_match_scope(match_data) click to toggle source

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
newscope(options = {}) click to toggle source

Create a new scope and set these options.

# File lib/puppet/parser/scope.rb, line 624
def newscope(options = {})
  compiler.newscope(self, options)
end
parent_module_name() click to toggle source
# 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
resolve_type_and_titles(type, titles) click to toggle source
# 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
set_facts(hash) click to toggle source
# 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
set_match_data(match_data) click to toggle source

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
set_trusted(hash) click to toggle source
# File lib/puppet/parser/scope.rb, line 692
def set_trusted(hash)
  setvar('trusted', deep_freeze(hash), :privileged => true)
end
setvar(name, value, options = {}) click to toggle source

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
to_hash(recursive = true) click to toggle source

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
to_hash_future(recursive) click to toggle source

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
to_hash_legacy(recursive = true) click to toggle source

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
to_s() click to toggle source

Used mainly for logging

# File lib/puppet/parser/scope.rb, line 779
def to_s
  "Scope(#{@resource})"
end
Also aliased as: inspect
transform_and_assert_classnames(names) click to toggle source

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
undef_as(x,v) click to toggle source
# File lib/puppet/parser/scope.rb, line 383
def undef_as(x,v)
  if v.nil? or v == :undef
    x
  else
    v
  end
end
unset_ephemeral_var(level=:all) click to toggle source

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
variable_not_found(name, reason=nil) click to toggle source
# 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

Public Class Methods

new(compiler, options = {}) click to toggle source

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
number?(value) click to toggle source

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
true?(value) click to toggle source

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