Class: Puppet::Parser::Scope

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Resource::TypeCollectionHelper, Util::Errors
Defined in:
lib/puppet/parser/scope.rb

Defined Under Namespace

Classes: Ephemeral

Constant Summary collapse

AST =
Puppet::Parser::AST

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Util::Errors

#adderrorcontext, #devfail, #error_context, #exceptwrap, #fail

Methods included from Resource::TypeCollectionHelper

#known_resource_types

Constructor Details

#initialize(hash = {}) ⇒ Scope

Initialize our new scope. Defaults to having no parent.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/puppet/parser/scope.rb', line 125

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

Instance Attribute Details

#baseObject

Returns the value of attribute base.



22
23
24
# File 'lib/puppet/parser/scope.rb', line 22

def base
  @base
end

#compilerObject

Returns the value of attribute compiler.



23
24
25
# File 'lib/puppet/parser/scope.rb', line 23

def compiler
  @compiler
end

#keywordObject

Returns the value of attribute keyword.



22
23
24
# File 'lib/puppet/parser/scope.rb', line 22

def keyword
  @keyword
end

#levelObject

Returns the value of attribute level.



21
22
23
# File 'lib/puppet/parser/scope.rb', line 21

def level
  @level
end

#namespacesObject (readonly)

Returns the value of attribute namespaces.



25
26
27
# File 'lib/puppet/parser/scope.rb', line 25

def namespaces
  @namespaces
end

#parentObject

Returns the value of attribute parent.



24
25
26
# File 'lib/puppet/parser/scope.rb', line 24

def parent
  @parent
end

#resourceObject

Returns the value of attribute resource.



21
22
23
# File 'lib/puppet/parser/scope.rb', line 21

def resource
  @resource
end

#sourceObject

Returns the value of attribute source.



21
22
23
# File 'lib/puppet/parser/scope.rb', line 21

def source
  @source
end

#topObject

Returns the value of attribute top.



23
24
25
# File 'lib/puppet/parser/scope.rb', line 23

def top
  @top
end

#translatedObject

Returns the value of attribute translated.



23
24
25
# File 'lib/puppet/parser/scope.rb', line 23

def translated
  @translated
end

Class Method Details

.number?(value) ⇒ Boolean

Is the value a number?, return the correct object or nil if not a number

Returns:

  • (Boolean)


72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/puppet/parser/scope.rb', line 72

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

.true?(value) ⇒ Boolean

Is the value true? This allows us to control the definition of truth in one place.

Returns:

  • (Boolean)


67
68
69
# File 'lib/puppet/parser/scope.rb', line 67

def self.true?(value)
  (value != false and value != "" and value != :undef)
end

Instance Method Details

#add_namespace(ns) ⇒ Object

Add to our list of namespaces.



93
94
95
96
97
98
99
100
# File 'lib/puppet/parser/scope.rb', line 93

def add_namespace(ns)
  return false if @namespaces.include?(ns)
  if @namespaces == [""]
    @namespaces = [ns]
  else
    @namespaces << ns
  end
end

#catalogObject

A demeterific shortcut to the catalog.



52
53
54
# File 'lib/puppet/parser/scope.rb', line 52

def catalog
  compiler.catalog
end

#class_scope(klass) ⇒ Object

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.



180
181
182
183
184
# File 'lib/puppet/parser/scope.rb', line 180

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) ⇒ Object

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.



171
172
173
174
# File 'lib/puppet/parser/scope.rb', line 171

def class_set(name, scope)
  return parent.class_set(name,scope) if parent
  @class_scopes[name] = scope
end

#environmentObject

Remove this when rebasing



103
104
105
# File 'lib/puppet/parser/scope.rb', line 103

def environment
  compiler.environment
end

#ephemeral?(name) ⇒ Boolean

is name an ephemeral variable?

Returns:

  • (Boolean)


454
455
456
# File 'lib/puppet/parser/scope.rb', line 454

def ephemeral?(name)
  name =~ /^\d+$/
end

#ephemeral_from(match, file = nil, line = nil) ⇒ Object

Raises:

  • (ArgumentError)


466
467
468
469
470
471
472
473
474
475
# File 'lib/puppet/parser/scope.rb', line 466

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

#ephemeral_include?(name) ⇒ Boolean

check if name exists in one of the ephemeral scope.

Returns:

  • (Boolean)


446
447
448
449
450
451
# File 'lib/puppet/parser/scope.rb', line 446

def ephemeral_include?(name)
  @ephemeral.reverse.each do |eph|
    return true if eph.include?(name)
  end
  false
end

#ephemeral_levelObject



458
459
460
# File 'lib/puppet/parser/scope.rb', line 458

def ephemeral_level
  @ephemeral.size
end

#find_builtin_resource_type(type) ⇒ Object



483
484
485
# File 'lib/puppet/parser/scope.rb', line 483

def find_builtin_resource_type(type)
  Puppet::Type.type(type.to_s.downcase.to_sym)
end

#find_defined_resource_type(type) ⇒ Object



487
488
489
# File 'lib/puppet/parser/scope.rb', line 487

def find_defined_resource_type(type)
  environment.known_resource_types.find_definition(namespaces, type.to_s.downcase)
end

#find_definition(name) ⇒ Object



116
117
118
# File 'lib/puppet/parser/scope.rb', line 116

def find_definition(name)
  known_resource_types.find_definition(namespaces, name)
end

#find_hostclass(name) ⇒ Object



112
113
114
# File 'lib/puppet/parser/scope.rb', line 112

def find_hostclass(name)
  known_resource_types.find_hostclass(namespaces, name)
end

#find_resource_type(type) ⇒ Object



477
478
479
480
481
# File 'lib/puppet/parser/scope.rb', line 477

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

#findresource(string, name = nil) ⇒ Object



120
121
122
# File 'lib/puppet/parser/scope.rb', line 120

def findresource(string, name = nil)
  compiler.findresource(string, name)
end

#hostObject

Proxy accessors



61
62
63
# File 'lib/puppet/parser/scope.rb', line 61

def host
  @compiler.node.name
end

#lookupdefaults(type) ⇒ Object

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.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/puppet/parser/scope.rb', line 190

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

#lookuptype(name) ⇒ Object

Look up a defined type.



214
215
216
# File 'lib/puppet/parser/scope.rb', line 214

def lookuptype(name)
  find_definition(name) || find_hostclass(name)
end

#lookupvar(name, usestring = true) ⇒ Object

Look up a variable. The simplest value search we do. Default to returning an empty string for missing values, but support returning a constant.



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/puppet/parser/scope.rb', line 238

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

#new_ephemeralObject



462
463
464
# File 'lib/puppet/parser/scope.rb', line 462

def new_ephemeral
  @ephemeral.push(Ephemeral.new(@ephemeral.last))
end

#newscope(options = {}) ⇒ Object

Create a new scope and set these options.



282
283
284
# File 'lib/puppet/parser/scope.rb', line 282

def newscope(options = {})
  compiler.newscope(self, options)
end

#parent_module_nameObject



286
287
288
289
290
# File 'lib/puppet/parser/scope.rb', line 286

def parent_module_name
  return nil unless @parent
  return nil unless @parent.source
  @parent.source.module_name
end

#resolve_type_and_titles(type, titles) ⇒ Object

Raises:

  • (ArgumentError)


491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/puppet/parser/scope.rb', line 491

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

#scope_pathObject

Return the list of scopes up to the top scope, ordered with our own first. This is used for looking up variables and defaults.



294
295
296
297
298
299
300
# File 'lib/puppet/parser/scope.rb', line 294

def scope_path
  if parent
    [self, parent.scope_path].flatten.compact
  else
    [self]
  end
end

#setdefaults(type, params) ⇒ Object

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.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/puppet/parser/scope.rb', line 305

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

#setvar(name, value, options = {}) ⇒ Object

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.



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/puppet/parser/scope.rb', line 324

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

#strinterp(string, file = nil, line = nil) ⇒ Object

Return an interpolated string.



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/puppet/parser/scope.rb', line 358

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

#tagsObject

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.



419
420
421
# File 'lib/puppet/parser/scope.rb', line 419

def tags
  resource.tags
end

#to_hash(recursive = true) ⇒ Object

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.



263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/puppet/parser/scope.rb', line 263

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

#to_sObject

Used mainly for logging



424
425
426
# File 'lib/puppet/parser/scope.rb', line 424

def to_s
  "Scope(#{@resource})"
end

#topscope?Boolean

Are we the top scope?

Returns:

  • (Boolean)


108
109
110
# File 'lib/puppet/parser/scope.rb', line 108

def topscope?
  @level == 1
end

#unset_ephemeral_var(level = :all) ⇒ Object

remove ephemeral scope up to level



435
436
437
438
439
440
441
442
443
# File 'lib/puppet/parser/scope.rb', line 435

def unset_ephemeral_var(level=:all)
  if level == :all
    @ephemeral = [ Ephemeral.new ]
  else
    (@ephemeral.size - level).times do
      @ephemeral.pop
    end
  end
end

#unsetvar(var) ⇒ Object

Undefine a variable; only used for testing.



429
430
431
432
# File 'lib/puppet/parser/scope.rb', line 429

def unsetvar(var)
  table = ephemeral?(var) ? @ephemeral.last : @symtable
  table.delete(var) if table.include?(var)
end