-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
access_operator.rb
648 lines (585 loc) · 23 KB
/
access_operator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
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
356
357
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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
module Puppet::Pops
module Evaluator
# AccessOperator handles operator []
# This operator is part of evaluation.
#
class AccessOperator
# Provides access to the Puppet 3.x runtime (scope, etc.)
# This separation has been made to make it easier to later migrate the evaluator to an improved runtime.
#
include Runtime3Support
EMPTY_STRING = ''.freeze
attr_reader :semantic
# Initialize with AccessExpression to enable reporting issues
# @param access_expression [Model::AccessExpression] the semantic object being evaluated
# @return [void]
#
def initialize(access_expression)
@@access_visitor ||= Visitor.new(self, "access", 2, nil)
@semantic = access_expression
end
def access (o, scope, *keys)
@@access_visitor.visit_this_2(self, o, scope, keys)
end
protected
def access_Object(o, scope, keys)
fail(Issues::OPERATOR_NOT_APPLICABLE, @semantic.left_expr, :operator=>'[]', :left_value => o)
end
def access_String(o, scope, keys)
keys.flatten!
result = case keys.size
when 0
fail(Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
when 1
# Note that Ruby 1.8.7 requires a length of 1 to produce a String
k1 = coerce_numeric(keys[0], @semantic.keys[0], scope)
bad_access_key_type(o, 0, k1, Integer) unless k1.is_a?(Integer)
k2 = 1
k1 = k1 < 0 ? o.length + k1 : k1 # abs pos
# if k1 is outside, a length of 1 always produces an empty string
if k1 < 0
EMPTY_STRING
else
o[ k1, k2 ]
end
when 2
k1 = coerce_numeric(keys[0], @semantic.keys[0], scope)
k2 = coerce_numeric(keys[1], @semantic.keys[1], scope)
[k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) }
k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end)
k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count)
# if k1 is outside, adjust to first position, and adjust length
if k1 < 0
k2 = k2 + k1
k1 = 0
end
o[ k1, k2 ]
else
fail(Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
end
# Specified as: an index outside of range, or empty result == empty string
(result.nil? || result.empty?) ? EMPTY_STRING : result
end
# Parameterizes a PRegexp Type with a pattern string or r ruby egexp
#
def access_PRegexpType(o, scope, keys)
keys.flatten!
unless keys.size == 1
blamed = keys.size == 0 ? @semantic : @semantic.keys[1]
fail(Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o, :min=>1, :actual => keys.size)
end
assert_keys(keys, o, 1, 1, String, Regexp)
Types::TypeFactory.regexp(*keys)
end
# Evaluates <ary>[] with 1 or 2 arguments. One argument is an index lookup, two arguments is a slice from/to.
#
def access_Array(o, scope, keys)
keys.flatten!
case keys.size
when 0
fail(Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
when 1
key = coerce_numeric(keys[0], @semantic.keys[0], scope)
unless key.is_a?(Integer)
bad_access_key_type(o, 0, key, Integer)
end
o[key]
when 2
# A slice [from, to] with support for -1 to mean start, or end respectively.
k1 = coerce_numeric(keys[0], @semantic.keys[0], scope)
k2 = coerce_numeric(keys[1], @semantic.keys[1], scope)
[k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) }
# Help confused Ruby do the right thing (it truncates to the right, but negative index + length can never overlap
# the available range.
k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end)
k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count)
# if k1 is outside, adjust to first position, and adjust length
if k1 < 0
k2 = k2 + k1
k1 = 0
end
# Help ruby always return empty array when asking for a sub array
result = o[ k1, k2 ]
result.nil? ? [] : result
else
fail(Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
end
end
# Evaluates <hsh>[] with support for one or more arguments. If more than one argument is used, the result
# is an array with each lookup.
# @note
# Does not flatten its keys to enable looking up with a structure
#
def access_Hash(o, scope, keys)
# Look up key in hash, if key is nil, try alternate form (:undef) before giving up.
# This is done because the hash may have been produced by 3x logic and may thus contain :undef.
result = keys.collect do |k|
o.fetch(k) { |key| key.nil? ? o[:undef] : nil }
end
case result.size
when 0
fail(Issues::BAD_HASH_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
when 1
result.pop
else
# remove nil elements and return
result.compact!
result
end
end
def access_PEnumType(o, scope, keys)
keys.flatten!
assert_keys(keys, o, 1, Float::INFINITY, String)
Types::TypeFactory.enum(*keys)
end
def access_PVariantType(o, scope, keys)
keys.flatten!
assert_keys(keys, o, 1, Float::INFINITY, Types::PAnyType)
Types::TypeFactory.variant(*keys)
end
def access_PSemVerType(o, scope, keys)
keys.flatten!
assert_keys(keys, o, 1, Float::INFINITY, String, Semantic::VersionRange)
Types::TypeFactory.sem_ver(*keys)
end
def access_PTupleType(o, scope, keys)
keys.flatten!
if Types::TypeFactory.is_range_parameter?(keys[-2]) && Types::TypeFactory.is_range_parameter?(keys[-1])
size_type = Types::TypeFactory.range(keys[-2], keys[-1])
keys = keys[0, keys.size - 2]
elsif Types::TypeFactory.is_range_parameter?(keys[-1])
size_type = Types::TypeFactory.range(keys[-1], :default)
keys = keys[0, keys.size - 1]
end
assert_keys(keys, o, 1, Float::INFINITY, Types::PAnyType)
Types::TypeFactory.tuple(keys, size_type)
end
def access_PCallableType(o, scope, keys)
Types::TypeFactory.callable(*keys)
end
def access_PStructType(o, scope, keys)
assert_keys(keys, o, 1, 1, Hash)
Types::TypeFactory.struct(keys[0])
end
def access_PStringType(o, scope, keys)
keys.flatten!
case keys.size
when 1
size_t = collection_size_t(0, keys[0])
when 2
size_t = collection_size_t(0, keys[0], keys[1])
else
fail(Issues::BAD_STRING_SLICE_ARITY, @semantic, {:actual => keys.size})
end
Types::TypeFactory.string(size_t)
end
# Asserts type of each key and calls fail with BAD_TYPE_SPECIFICATION
# @param keys [Array<Object>] the evaluated keys
# @param o [Object] evaluated LHS reported as :base_type
# @param min [Integer] the minimum number of keys (typically 1)
# @param max [Numeric] the maximum number of keys (use same as min, specific number, or Float::INFINITY)
# @param allowed_classes [Class] a variable number of classes that each key must be an instance of (any)
# @api private
#
def assert_keys(keys, o, min, max, *allowed_classes)
size = keys.size
unless size.between?(min, max || Float::INFINITY)
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>1, :max => max, :actual => keys.size)
end
keys.each_with_index do |k, i|
unless allowed_classes.any? {|clazz| k.is_a?(clazz) }
bad_type_specialization_key_type(o, i, k, *allowed_classes)
end
end
end
def bad_access_key_type(lhs, key_index, actual, *expected_classes)
fail(Issues::BAD_SLICE_KEY_TYPE, @semantic.keys[key_index], {
:left_value => lhs,
:actual => bad_key_type_name(actual),
:expected_classes => expected_classes
})
end
def bad_key_type_name(actual)
case actual
when nil
'Undef'
when :default
'Default'
else
Types::TypeCalculator.generalize(Types::TypeCalculator.infer(actual)).to_s
end
end
def bad_type_specialization_key_type(type, key_index, actual, *expected_classes)
label_provider = Model::ModelLabelProvider.new()
expected = expected_classes.map {|c| label_provider.label(c) }.join(' or ')
fail(Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[key_index], {
:type => type,
:message => "Cannot use #{bad_key_type_name(actual)} where #{expected} is expected"
})
end
def access_PPatternType(o, scope, keys)
keys.flatten!
assert_keys(keys, o, 1, Float::INFINITY, String, Regexp, Types::PPatternType, Types::PRegexpType)
Types::TypeFactory.pattern(*keys)
end
def access_POptionalType(o, scope, keys)
keys.flatten!
if keys.size == 1
type = keys[0]
unless type.is_a?(Types::PAnyType)
if type.is_a?(String)
type = Types::TypeFactory.string(nil, type)
else
fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Optional-Type', :actual => type.class})
end
end
Types::POptionalType.new(type)
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Optional-Type', :min => 1, :actual => keys.size})
end
end
def access_PObjectType(o, scope, keys)
keys.flatten!
if keys.size == 1
Types::TypeFactory.object(keys[0])
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Object-Type', :min => 1, :actual => keys.size})
end
end
def access_PTypeSetType(o, scope, keys)
keys.flatten!
if keys.size == 1
Types::TypeFactory.type_set(keys[0])
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'TypeSet-Type', :min => 1, :actual => keys.size})
end
end
def access_PNotUndefType(o, scope, keys)
keys.flatten!
case keys.size
when 0
Types::TypeFactory.not_undef
when 1
type = keys[0]
case type
when String
type = Types::TypeFactory.string(nil, type)
when Types::PAnyType
type = nil if type.class == Types::PAnyType
else
fail(Issues::BAD_NOT_UNDEF_SLICE_TYPE, @semantic.keys[0], {:base_type => 'NotUndef-Type', :actual => type.class})
end
Types::TypeFactory.not_undef(type)
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'NotUndef-Type', :min => 0, :max => 1, :actual => keys.size})
end
end
def access_PType(o, scope, keys)
keys.flatten!
if keys.size == 1
unless keys[0].is_a?(Types::PAnyType)
fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Type-Type', :actual => keys[0].class})
end
Types::PType.new(keys[0])
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Type-Type', :min => 1, :actual => keys.size})
end
end
def access_PIterableType(o, scope, keys)
keys.flatten!
if keys.size == 1
unless keys[0].is_a?(Types::PAnyType)
fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Iterable-Type', :actual => keys[0].class})
end
Types::PIterableType.new(keys[0])
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Iterable-Type', :min => 1, :actual => keys.size})
end
end
def access_PIteratorType(o, scope, keys)
keys.flatten!
if keys.size == 1
unless keys[0].is_a?(Types::PAnyType)
fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Iterator-Type', :actual => keys[0].class})
end
Types::PIteratorType.new(keys[0])
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Iterator-Type', :min => 1, :actual => keys.size})
end
end
def access_PRuntimeType(o, scope, keys)
keys.flatten!
assert_keys(keys, o, 2, 2, String, String)
# create runtime type based on runtime and name of class, (not inference of key's type)
Types::TypeFactory.runtime(*keys)
end
def access_PIntegerType(o, scope, keys)
keys.flatten!
unless keys.size.between?(1, 2)
fail(Issues::BAD_INTEGER_SLICE_ARITY, @semantic, {:actual => keys.size})
end
keys.each_with_index do |x, index|
fail(Issues::BAD_INTEGER_SLICE_TYPE, @semantic.keys[index],
{:actual => x.class}) unless (x.is_a?(Integer) || x == :default)
end
Types::PIntegerType.new(*keys)
end
def access_PFloatType(o, scope, keys)
keys.flatten!
unless keys.size.between?(1, 2)
fail(Issues::BAD_FLOAT_SLICE_ARITY, @semantic, {:actual => keys.size})
end
keys.each_with_index do |x, index|
fail(Issues::BAD_FLOAT_SLICE_TYPE, @semantic.keys[index],
{:actual => x.class}) unless (x.is_a?(Float) || x.is_a?(Integer) || x == :default)
end
from, to = keys
from = from == :default || from.nil? ? nil : Float(from)
to = to == :default || to.nil? ? nil : Float(to)
Types::PFloatType.new(from, to)
end
# A Hash can create a new Hash type, one arg sets value type, two args sets key and value type in new type.
# With 3 or 4 arguments, these are used to create a size constraint.
# It is not possible to create a collection of Hash types directly.
#
def access_PHashType(o, scope, keys)
keys.flatten!
if keys.size == 2 && keys[0].is_a?(Integer) && keys[1].is_a?(Integer)
return Types::PHashType.new(nil, nil, Types::PIntegerType.new(*keys))
end
keys[0,2].each_with_index do |k, index|
unless k.is_a?(Types::PAnyType)
fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[index], {:base_type => 'Hash-Type', :actual => k.class})
end
end
case keys.size
when 2
size_t = nil
when 3
size_t = keys[2]
size_t = Types::PIntegerType.new(size_t) unless size_t.is_a?(Types::PIntegerType)
when 4
size_t = collection_size_t(2, keys[2], keys[3])
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {
:base_type => 'Hash-Type', :min => 2, :max => 4, :actual => keys.size
})
end
Types::PHashType.new(keys[0], keys[1], size_t)
end
# CollectionType is parameterized with a range
def access_PCollectionType(o, scope, keys)
keys.flatten!
case keys.size
when 1
size_t = collection_size_t(0, keys[0])
when 2
size_t = collection_size_t(0, keys[0], keys[1])
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic,
{:base_type => 'Collection-Type', :min => 1, :max => 2, :actual => keys.size})
end
Types::PCollectionType.new(nil, size_t)
end
# An Array can create a new Array type. It is not possible to create a collection of Array types.
#
def access_PArrayType(o, scope, keys)
keys.flatten!
case keys.size
when 1
unless keys[0].is_a?(Types::PAnyType)
fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class})
end
type = keys[0]
size_t = nil
when 2
if keys[0].is_a?(Types::PAnyType)
size_t = collection_size_t(1, keys[1])
type = keys[0]
else
size_t = collection_size_t(0, keys[0], keys[1])
type = nil
end
when 3
if keys[0].is_a?(Types::PAnyType)
size_t = collection_size_t(1, keys[1], keys[2])
type = keys[0]
else
fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class})
end
else
fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic,
{:base_type => 'Array-Type', :min => 1, :max => 3, :actual => keys.size})
end
Types::PArrayType.new(type, size_t)
end
# Produces an PIntegerType (range) given one or two keys.
def collection_size_t(start_index, *keys)
if keys.size == 1 && keys[0].is_a?(Types::PIntegerType)
keys[0]
else
keys.each_with_index do |x, index|
fail(Issues::BAD_COLLECTION_SLICE_TYPE, @semantic.keys[start_index + index],
{:actual => x.class}) unless (x.is_a?(Integer) || x == :default)
end
Types::PIntegerType.new(*keys)
end
end
# A Puppet::Resource represents either just a type (no title), or is a fully qualified type/title.
#
def access_Resource(o, scope, keys)
# To access a Puppet::Resource as if it was a PResourceType, simply infer it, and take the type of
# the parameterized meta type (i.e. Type[Resource[the_resource_type, the_resource_title]])
t = Types::TypeCalculator.infer(o).type
# must map "undefined title" from resource to nil
t.title = nil if t.title == EMPTY_STRING
access(t, scope, *keys)
end
# If a type reference is encountered here, it's an error
def access_PTypeReferenceType(o, scope, keys)
fail(Issues::UNKNOWN_RESOURCE_TYPE, @semantic, {:type_name => o.type_string })
end
# A Resource can create a new more specific Resource type, and/or an array of resource types
# If the given type has title set, it can not be specified further.
# @example
# Resource[File] # => File
# Resource[File, 'foo'] # => File[foo]
# Resource[File. 'foo', 'bar'] # => [File[foo], File[bar]]
# File['foo', 'bar'] # => [File[foo], File[bar]]
# File['foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource
# Resource[File]['foo', 'bar'] # => [File[Foo], File[bar]]
# Resource[File, 'foo', 'bar'] # => [File[foo], File[bar]]
# Resource[File, 'foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource
#
def access_PResourceType(o, scope, keys)
blamed = keys.size == 0 ? @semantic : @semantic.keys[0]
if keys.size == 0
fail(Issues::BAD_TYPE_SLICE_ARITY, blamed,
:base_type => o.to_s, :min => 1, :max => -1, :actual => 0)
end
# Must know which concrete resource type to operate on in all cases.
# It is not allowed to specify the type in an array arg - e.g. Resource[[File, 'foo']]
# type_name is LHS type_name if set, else the first given arg
type_name = o.type_name || Types::TypeFormatter.singleton.capitalize_segments(keys.shift)
type_name = case type_name
when Types::PResourceType
type_name.type_name
when String
type_name
else
# blame given left expression if it defined the type, else the first given key expression
blame = o.type_name.nil? ? @semantic.keys[0] : @semantic.left_expr
fail(Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => bad_key_type_name(type_name)})
end
# type name must conform
if type_name !~ Patterns::CLASSREF_EXT
fail(Issues::ILLEGAL_CLASSREF, blamed, {:name=>type_name})
end
# The result is an array if multiple titles are given, or if titles are specified with an array
# (possibly multiple arrays, and nested arrays).
result_type_array = keys.size > 1 || keys[0].is_a?(Array)
keys_orig_size = keys.size
keys.flatten!
keys.compact!
# If given keys that were just a mix of empty/nil with empty array as a result.
# As opposed to calling the function the wrong way (without any arguments), (configurable issue),
# Return an empty array
#
if keys.empty? && keys_orig_size > 0
optionally_fail(Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed)
return result_type_array ? [] : nil
end
if !o.title.nil?
# lookup resource and return one or more parameter values
resource = find_resource(scope, o.type_name, o.title)
unless resource
fail(Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => o.type_name, :title => o.title})
end
result = keys.map do |k|
unless is_parameter_of_resource?(scope, resource, k)
fail(Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic,
{:type_name => o.type_name, :title => o.title, :param_name=>k})
end
get_resource_parameter_value(scope, resource, k)
end
return result_type_array ? result : result.pop
end
keys = [:no_title] if keys.size < 1 # if there was only a type_name and it was consumed
result = keys.each_with_index.map do |t, i|
unless t.is_a?(String) || t == :no_title
index = keys_orig_size != keys.size ? i+1 : i
fail(Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[index], {
:type => o,
:message => "Cannot use #{bad_key_type_name(t)} where a resource title String is expected"
})
end
Types::PResourceType.new(type_name, t == :no_title ? nil : t)
end
# returns single type if request was for a single entity, else an array of types (possibly empty)
return result_type_array ? result : result.pop
end
def access_PHostClassType(o, scope, keys)
blamed = keys.size == 0 ? @semantic : @semantic.keys[0]
keys_orig_size = keys.size
if keys_orig_size == 0
fail(Issues::BAD_TYPE_SLICE_ARITY, blamed,
:base_type => o.to_s, :min => 1, :max => -1, :actual => 0)
end
# The result is an array if multiple classnames are given, or if classnames are specified with an array
# (possibly multiple arrays, and nested arrays).
result_type_array = keys.size > 1 || keys[0].is_a?(Array)
keys.flatten!
keys.compact!
# If given keys that were just a mix of empty/nil with empty array as a result.
# As opposed to calling the function the wrong way (without any arguments), (configurable issue),
# Return an empty array
#
if keys.empty? && keys_orig_size > 0
optionally_fail(Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed)
return result_type_array ? [] : nil
end
if o.class_name.nil?
# The type argument may be a Resource Type - the Puppet Language allows a reference such as
# Class[Foo], and this is interpreted as Class[Resource[Foo]] - which is ok as long as the resource
# does not have a title. This should probably be deprecated.
#
result = keys.each_with_index.map do |c, i|
name = if c.is_a?(Types::PResourceType) && !c.type_name.nil? && c.title.nil?
# type_name is already downcase. Don't waste time trying to downcase again
c.type_name
elsif c.is_a?(String)
c.downcase
elsif c.is_a?(Types::PTypeReferenceType)
c.type_string.downcase
else
fail(Issues::ILLEGAL_HOSTCLASS_NAME, @semantic.keys[i], {:name => c})
end
if name =~ Patterns::NAME
# Remove leading '::' since all references are global, and 3x runtime does the wrong thing
Types::PHostClassType.new(name.sub(/^::/, EMPTY_STRING))
else
fail(Issues::ILLEGAL_NAME, @semantic.keys[i], {:name=>c})
end
end
else
# lookup class resource and return one or more parameter values
resource = find_resource(scope, 'class', o.class_name)
if resource
result = keys.map do |k|
if is_parameter_of_resource?(scope, resource, k)
get_resource_parameter_value(scope, resource, k)
else
fail(Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic,
{:type_name => 'Class', :title => o.class_name, :param_name=>k})
end
end
else
fail(Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => 'Class', :title => o.class_name})
end
end
# returns single type as type, else an array of types
return result_type_array ? result : result.pop
end
end
end
end