puppet 4.4.2-x64-mingw32 → 4.5.0-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- data/CONTRIBUTING.md +5 -5
- data/Gemfile +2 -2
- data/LICENSE +2 -2
- data/README.md +5 -0
- data/ext/project_data.yaml +2 -0
- data/lib/hiera_puppet.rb +6 -14
- data/lib/puppet/application/agent.rb +2 -3
- data/lib/puppet/data_providers/hiera_config.rb +2 -4
- data/lib/puppet/data_providers/hiera_interpolate.rb +12 -154
- data/lib/puppet/data_providers/json_data_provider_factory.rb +0 -7
- data/lib/puppet/data_providers/yaml_data_provider_factory.rb +2 -8
- data/lib/puppet/defaults.rb +70 -7
- data/lib/puppet/functions.rb +69 -0
- data/lib/puppet/functions/dig.rb +39 -0
- data/lib/puppet/functions/lest.rb +53 -0
- data/lib/puppet/functions/lookup.rb +40 -27
- data/lib/puppet/functions/new.rb +502 -0
- data/lib/puppet/functions/regsubst.rb +11 -10
- data/lib/puppet/functions/then.rb +74 -0
- data/lib/puppet/functions/type.rb +4 -4
- data/lib/puppet/functions/with.rb +1 -1
- data/lib/puppet/indirector/catalog/compiler.rb +2 -0
- data/lib/puppet/indirector/resource_type/parser.rb +5 -0
- data/lib/puppet/indirector/rest.rb +5 -1
- data/lib/puppet/loaders.rb +2 -0
- data/lib/puppet/metatype/manager.rb +19 -2
- data/lib/puppet/module_tool/applications/application.rb +1 -1
- data/lib/puppet/module_tool/skeleton/templates/generator/Gemfile +6 -2
- data/lib/puppet/module_tool/skeleton/templates/generator/Rakefile +19 -4
- data/lib/puppet/module_tool/skeleton/templates/generator/{tests → examples}/init.pp.erb +1 -1
- data/lib/puppet/module_tool/skeleton/templates/generator/spec/classes/init_spec.rb.erb +0 -1
- data/lib/puppet/network/http/api/master/v3/environment.rb +6 -2
- data/lib/puppet/parser/ast/pops_bridge.rb +20 -3
- data/lib/puppet/parser/compiler/catalog_validator/relationship_validator.rb +24 -2
- data/lib/puppet/parser/e4_parser_adapter.rb +13 -12
- data/lib/puppet/parser/environment_compiler.rb +2 -2
- data/lib/puppet/parser/resource.rb +14 -5
- data/lib/puppet/parser/scope.rb +18 -15
- data/lib/puppet/plugins/data_providers/data_provider.rb +19 -8
- data/lib/puppet/pops.rb +6 -0
- data/lib/puppet/pops/adapters.rb +5 -1
- data/lib/puppet/pops/evaluator/access_operator.rb +52 -14
- data/lib/puppet/pops/evaluator/compare_operator.rb +34 -4
- data/lib/puppet/pops/evaluator/evaluator_impl.rb +75 -22
- data/lib/puppet/pops/evaluator/literal_evaluator.rb +7 -6
- data/lib/puppet/pops/evaluator/runtime3_converter.rb +13 -1
- data/lib/puppet/pops/evaluator/runtime3_support.rb +14 -4
- data/lib/puppet/pops/functions/dispatcher.rb +1 -1
- data/lib/puppet/pops/issues.rb +18 -2
- data/lib/puppet/pops/loader/base_loader.rb +48 -7
- data/lib/puppet/pops/loader/dependency_loader.rb +27 -2
- data/lib/puppet/pops/loader/loader.rb +12 -0
- data/lib/puppet/pops/loader/predefined_loader.rb +29 -0
- data/lib/puppet/pops/loader/runtime3_type_loader.rb +57 -0
- data/lib/puppet/pops/loader/static_loader.rb +92 -5
- data/lib/puppet/pops/loader/type_definition_instantiator.rb +25 -3
- data/lib/puppet/pops/loaders.rb +84 -14
- data/lib/puppet/pops/lookup/explainer.rb +38 -1
- data/lib/puppet/pops/lookup/interpolation.rb +115 -0
- data/lib/puppet/pops/lookup/sub_lookup.rb +86 -0
- data/lib/puppet/pops/model/ast_transformer.rb +8 -1
- data/lib/puppet/pops/model/factory.rb +31 -8
- data/lib/puppet/pops/model/model.rb +8 -0
- data/lib/puppet/pops/model/model_label_provider.rb +1 -0
- data/lib/puppet/pops/model/model_meta.rb +7 -1
- data/lib/puppet/pops/model/model_tree_dumper.rb +4 -0
- data/lib/puppet/pops/parser/egrammar.ra +24 -7
- data/lib/puppet/pops/parser/eparser.rb +863 -798
- data/lib/puppet/pops/parser/evaluating_parser.rb +4 -0
- data/lib/puppet/pops/parser/locator.rb +8 -4
- data/lib/puppet/pops/pcore.rb +30 -0
- data/lib/puppet/pops/types/class_loader.rb +2 -4
- data/lib/puppet/pops/types/implementation_registry.rb +146 -0
- data/lib/puppet/pops/types/iterable.rb +4 -4
- data/lib/puppet/pops/types/p_object_type.rb +846 -0
- data/lib/puppet/pops/types/p_runtime_type.rb +102 -0
- data/lib/puppet/pops/types/p_sem_ver_range_type.rb +164 -0
- data/lib/puppet/pops/types/p_sem_ver_type.rb +113 -0
- data/lib/puppet/pops/types/puppet_object.rb +21 -0
- data/lib/puppet/pops/types/ruby_generator.rb +258 -0
- data/lib/puppet/pops/types/string_converter.rb +922 -0
- data/lib/puppet/pops/types/type_calculator.rb +29 -5
- data/lib/puppet/pops/types/type_conversion_error.rb +15 -0
- data/lib/puppet/pops/types/type_factory.rb +49 -16
- data/lib/puppet/pops/types/type_formatter.rb +335 -112
- data/lib/puppet/pops/types/type_mismatch_describer.rb +110 -29
- data/lib/puppet/pops/types/type_parser.rb +205 -197
- data/lib/puppet/pops/types/types.rb +481 -103
- data/lib/puppet/pops/validation.rb +1 -1
- data/lib/puppet/pops/validation/checker4_0.rb +66 -4
- data/lib/puppet/pops/validation/validator_factory_4_0.rb +1 -0
- data/lib/puppet/pops/visitor.rb +3 -1
- data/lib/puppet/property.rb +1 -1
- data/lib/puppet/provider/augeas/augeas.rb +1 -1
- data/lib/puppet/provider/package/pip.rb +64 -20
- data/lib/puppet/provider/package/rpm.rb +112 -0
- data/lib/puppet/provider/package/yum.rb +7 -68
- data/lib/puppet/provider/service/daemontools.rb +3 -3
- data/lib/puppet/provider/service/init.rb +4 -2
- data/lib/puppet/provider/service/runit.rb +3 -3
- data/lib/puppet/provider/service/smf.rb +6 -3
- data/lib/puppet/provider/service/systemd.rb +59 -73
- data/lib/puppet/reference/providers.rb +1 -2
- data/lib/puppet/resource.rb +54 -37
- data/lib/puppet/resource/catalog.rb +31 -29
- data/lib/puppet/resource/type_collection.rb +23 -8
- data/lib/puppet/settings.rb +4 -2
- data/lib/puppet/settings/base_setting.rb +9 -3
- data/lib/puppet/settings/symbolic_enum_setting.rb +17 -0
- data/lib/puppet/test/test_helper.rb +0 -1
- data/lib/puppet/type.rb +9 -3
- data/lib/puppet/type/exec.rb +17 -17
- data/lib/puppet/type/file.rb +12 -0
- data/lib/puppet/type/file/content.rb +6 -6
- data/lib/puppet/type/file/ensure.rb +4 -4
- data/lib/puppet/type/file/source.rb +4 -4
- data/lib/puppet/type/file/target.rb +2 -2
- data/lib/puppet/type/mount.rb +18 -1
- data/lib/puppet/type/package.rb +3 -3
- data/lib/puppet/type/schedule.rb +4 -4
- data/lib/puppet/type/service.rb +15 -0
- data/lib/puppet/type/sshkey.rb +5 -3
- data/lib/puppet/type/tidy.rb +3 -3
- data/lib/puppet/type/zone.rb +5 -5
- data/lib/puppet/util/feature.rb +1 -1
- data/lib/puppet/util/monkey_patches.rb +8 -0
- data/lib/puppet/util/network_device/cisco/device.rb +16 -6
- data/lib/puppet/util/network_device/cisco/interface.rb +5 -6
- data/lib/puppet/util/plist.rb +3 -3
- data/lib/puppet/version.rb +1 -1
- data/spec/fixtures/unit/application/environments/production/data/common.yaml +13 -0
- data/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/functions/abc/data.rb +2 -1
- data/spec/fixtures/unit/data_providers/environments/production/modules/abc/manifests/init.pp +2 -1
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_json/data/empty_key.json +1 -0
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_json/hiera.yaml +5 -0
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_json/manifests/init.pp +2 -0
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_json/metadata.json +9 -0
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_yaml/data/empty_key.yaml +1 -0
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_yaml/hiera.yaml +5 -0
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_yaml/manifests/init.pp +2 -0
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_yaml/metadata.json +9 -0
- data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_yaml/data/empty.yaml +1 -0
- data/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/type/usee_type.rb +5 -0
- data/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/manifests/init.pp +6 -0
- data/spec/fixtures/unit/provider/service/smf/svcs.out +4 -3
- data/spec/integration/module_tool/tar/mini_spec.rb +27 -27
- data/spec/integration/parser/catalog_spec.rb +14 -2
- data/spec/integration/parser/compiler_spec.rb +94 -3
- data/spec/integration/parser/resource_expressions_spec.rb +1 -1
- data/spec/integration/resource/type_collection_spec.rb +8 -0
- data/spec/lib/puppet_spec/compiler.rb +11 -4
- data/spec/shared_contexts/types_setup.rb +4 -0
- data/spec/unit/application/lookup_spec.rb +91 -9
- data/spec/unit/appmgmt_spec.rb +44 -35
- data/spec/unit/capability_spec.rb +33 -53
- data/spec/unit/data_providers/function_data_provider_spec.rb +19 -1
- data/spec/unit/data_providers/hiera_data_provider_spec.rb +1 -1
- data/spec/unit/defaults_spec.rb +18 -0
- data/spec/unit/functions/assert_type_spec.rb +1 -1
- data/spec/unit/functions/dig_spec.rb +58 -0
- data/spec/unit/functions/lest_spec.rb +34 -0
- data/spec/unit/functions/lookup_spec.rb +108 -2
- data/spec/unit/functions/new_spec.rb +543 -0
- data/spec/unit/functions/regsubst_spec.rb +8 -0
- data/spec/unit/functions/then_spec.rb +40 -0
- data/spec/unit/functions4_spec.rb +78 -10
- data/spec/unit/hiera_puppet_spec.rb +49 -8
- data/spec/unit/indirector/resource_type/parser_spec.rb +5 -0
- data/spec/unit/indirector/rest_spec.rb +12 -0
- data/spec/unit/network/http/api/master/v3/environment_spec.rb +60 -0
- data/spec/unit/node/environment_spec.rb +10 -0
- data/spec/unit/parser/compiler_spec.rb +20 -1
- data/spec/unit/parser/functions/create_resources_spec.rb +2 -2
- data/spec/unit/parser/functions/shared.rb +1 -1
- data/spec/unit/parser/resource_spec.rb +8 -1
- data/spec/unit/parser/scope_spec.rb +45 -0
- data/spec/unit/pops/evaluator/access_ops_spec.rb +14 -0
- data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +13 -5
- data/spec/unit/pops/loaders/static_loader_spec.rb +92 -1
- data/spec/unit/{data_providers/hiera_interpolation_spec.rb → pops/lookup/interpolation_spec.rb} +7 -5
- data/spec/unit/pops/parser/lexer2_spec.rb +2 -9
- data/spec/unit/pops/parser/parse_application_spec.rb +3 -8
- data/spec/unit/pops/parser/parse_basic_expressions_spec.rb +19 -0
- data/spec/unit/pops/parser/parse_capabilities_spec.rb +3 -10
- data/spec/unit/pops/parser/parse_site_spec.rb +19 -10
- data/spec/unit/pops/parser/parser_rspec_helper.rb +0 -4
- data/spec/unit/pops/types/enumeration_spec.rb +13 -12
- data/spec/unit/pops/types/iterable_spec.rb +2 -2
- data/spec/unit/pops/types/p_object_type_spec.rb +1060 -0
- data/spec/unit/pops/types/p_sem_ver_type_spec.rb +285 -0
- data/spec/unit/pops/types/recursion_guard_spec.rb +19 -17
- data/spec/unit/pops/types/ruby_generator_spec.rb +261 -0
- data/spec/unit/pops/types/string_converter_spec.rb +904 -0
- data/spec/unit/pops/types/type_calculator_spec.rb +430 -406
- data/spec/unit/pops/types/type_factory_spec.rb +119 -104
- data/spec/unit/pops/types/type_formatter_spec.rb +73 -6
- data/spec/unit/pops/types/type_mismatch_describer_spec.rb +2 -2
- data/spec/unit/pops/types/type_parser_spec.rb +54 -15
- data/spec/unit/pops/types/types_spec.rb +113 -8
- data/spec/unit/pops/validator/validator_spec.rb +84 -10
- data/spec/unit/provider/package/pip3_spec.rb +9 -270
- data/spec/unit/provider/package/pip_spec.rb +85 -30
- data/spec/unit/provider/package/rpm_spec.rb +160 -3
- data/spec/unit/provider/package/yum_spec.rb +23 -134
- data/spec/unit/provider/service/smf_spec.rb +14 -2
- data/spec/unit/provider/service/systemd_spec.rb +33 -41
- data/spec/unit/resource/capability_finder_spec.rb +10 -2
- data/spec/unit/settings/file_setting_spec.rb +6 -0
- data/spec/unit/transaction/additional_resource_generator_spec.rb +80 -65
- data/spec/unit/type/mount_spec.rb +51 -10
- data/spec/unit/type/service_spec.rb +16 -0
- data/spec/unit/type_spec.rb +14 -0
- data/spec/unit/util/feature_spec.rb +1 -1
- data/spec/unit/util/monkey_patches_spec.rb +60 -0
- data/spec/unit/util/network_device/cisco/device_spec.rb +1 -1
- metadata +63 -11
- data/lib/puppet/pops/types/types_meta.rb +0 -0
- data/spec/integration/provider/package_spec.rb +0 -35
@@ -0,0 +1,102 @@
|
|
1
|
+
module Puppet::Pops
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# @api public
|
5
|
+
class PRuntimeType < PAnyType
|
6
|
+
TYPE_NAME_OR_PATTERN = PVariantType.new([PStringType::NON_EMPTY, PTupleType.new([PRegexpType::DEFAULT, PStringType::NON_EMPTY])])
|
7
|
+
|
8
|
+
attr_reader :runtime, :name_or_pattern
|
9
|
+
|
10
|
+
# Creates a new instance of a Runtime type
|
11
|
+
#
|
12
|
+
# @param runtime [String] the name of the runtime, e.g. 'ruby'
|
13
|
+
# @param name_or_pattern [String,Array(Regexp,String)] name of runtime or two patterns, mapping Puppet name => runtime name
|
14
|
+
# @api public
|
15
|
+
def initialize(runtime, name_or_pattern)
|
16
|
+
unless runtime.nil? || runtime.is_a?(Symbol)
|
17
|
+
runtime = TypeAsserter.assert_instance_of("Runtime 'runtime'", PStringType::NON_EMPTY, runtime).to_sym
|
18
|
+
end
|
19
|
+
@runtime = runtime
|
20
|
+
@name_or_pattern = TypeAsserter.assert_instance_of("Runtime 'name_or_pattern'", TYPE_NAME_OR_PATTERN, name_or_pattern, true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash
|
24
|
+
@runtime.hash ^ @name_or_pattern.hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def eql?(o)
|
28
|
+
self.class == o.class && @runtime == o.runtime && @name_or_pattern == o.name_or_pattern
|
29
|
+
end
|
30
|
+
|
31
|
+
def instance?(o, guard = nil)
|
32
|
+
assignable?(TypeCalculator.infer(o), guard)
|
33
|
+
end
|
34
|
+
|
35
|
+
def iterable?(guard = nil)
|
36
|
+
if @runtime == :ruby && !runtime_type_name.nil?
|
37
|
+
begin
|
38
|
+
c = ClassLoader.provide(self)
|
39
|
+
return c < Iterable unless c.nil?
|
40
|
+
rescue ArgumentError
|
41
|
+
end
|
42
|
+
end
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def iterable_type(guard = nil)
|
47
|
+
iterable?(guard) ? PIterableType.new(self) : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
def runtime_type_name
|
52
|
+
@name_or_pattern.is_a?(String) ? @name_or_pattern : nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
def class_or_module
|
57
|
+
raise "Only ruby classes or modules can be produced by this runtime, got '#{runtime}" unless runtime == :ruby
|
58
|
+
raise 'A pattern based Runtime type cannot produce a class or module' if @name_or_pattern.is_a?(Array)
|
59
|
+
com = ClassLoader.provide(self)
|
60
|
+
raise "The name #{@name_or_pattern} does not represent a ruby class or module" if com.nil?
|
61
|
+
com
|
62
|
+
end
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
def from_puppet_name(puppet_name)
|
66
|
+
if @name_or_pattern.is_a?(Array)
|
67
|
+
substituted = puppet_name.sub(*@name_or_pattern)
|
68
|
+
substituted == puppet_name ? nil : PRuntimeType.new(@runtime, substituted)
|
69
|
+
else
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
DEFAULT = PRuntimeType.new(nil, nil)
|
75
|
+
RUBY = PRuntimeType.new(:ruby, nil)
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
# Assignable if o's has the same runtime and the runtime name resolves to
|
80
|
+
# a class that is the same or subclass of t1's resolved runtime type name
|
81
|
+
# @api private
|
82
|
+
def _assignable?(o, guard)
|
83
|
+
return false unless o.is_a?(PRuntimeType)
|
84
|
+
return false unless @runtime == o.runtime
|
85
|
+
return true if @name_or_pattern.nil? # t1 is wider
|
86
|
+
|
87
|
+
onp = o.name_or_pattern
|
88
|
+
return true if @name_or_pattern == onp
|
89
|
+
return false unless @name_or_pattern.is_a?(String) && onp.is_a?(String)
|
90
|
+
|
91
|
+
# NOTE: This only supports Ruby, must change when/if the set of runtimes is expanded
|
92
|
+
begin
|
93
|
+
c1 = ClassLoader.provide(self)
|
94
|
+
c2 = ClassLoader.provide(o)
|
95
|
+
c1.is_a?(Module) && c2.is_a?(Module) && !!(c2 <= c1)
|
96
|
+
rescue ArgumentError
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Puppet::Pops
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# An unparameterized type that represents all VersionRange instances
|
5
|
+
#
|
6
|
+
# @api public
|
7
|
+
class PSemVerRangeType < PScalarType
|
8
|
+
# Check if a version is included in a version range. The version can be a string or
|
9
|
+
# a `Semantic::SemVer`
|
10
|
+
#
|
11
|
+
# @param range [Semantic::VersionRange] the range to match against
|
12
|
+
# @param version [Semantic::Version,String] the version to match
|
13
|
+
# @return [Boolean] `true` if the range includes the given version
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def self.include?(range, version)
|
17
|
+
case version
|
18
|
+
when Semantic::Version
|
19
|
+
range.include?(version)
|
20
|
+
when String
|
21
|
+
begin
|
22
|
+
range.include?(Semantic::Version.parse(version))
|
23
|
+
rescue Semantic::Version::ValidationFailure
|
24
|
+
false
|
25
|
+
end
|
26
|
+
else
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks if range _a_ is a sub-range of (i.e. completely covered by) range _b_
|
32
|
+
# @param a [Semantic::VersionRange] the first range
|
33
|
+
# @param b [Semantic::VersionRange] the second range
|
34
|
+
#
|
35
|
+
# @return [Boolean] `true` if _a_ is completely covered by _b_
|
36
|
+
def self.covered_by?(a, b)
|
37
|
+
b.begin <= a.begin && (b.end > a.end || b.end == a.end && (!b.exclude_end? || a.exclude_end?))
|
38
|
+
end
|
39
|
+
|
40
|
+
# Merge two ranges so that the result matches all versions matched by both. A merge
|
41
|
+
# is only possible when the ranges are either adjacent or have an overlap.
|
42
|
+
#
|
43
|
+
# @param a [Semantic::VersionRange] the first range
|
44
|
+
# @param b [Semantic::VersionRange] the second range
|
45
|
+
# @return [Semantic::VersionRange,nil] the result of the merge
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
def self.merge(a, b)
|
49
|
+
if a.include?(b.begin) || b.include?(a.begin)
|
50
|
+
max = [a.end, b.end].max
|
51
|
+
exclude_end = false
|
52
|
+
if a.exclude_end?
|
53
|
+
exclude_end = max == a.end && (max > b.end || b.exclude_end?)
|
54
|
+
elsif b.exclude_end?
|
55
|
+
exclude_end = max == b.end && (max > a.end || a.exclude_end?)
|
56
|
+
end
|
57
|
+
Semantic::VersionRange.new([a.begin, b.begin].min, max, exclude_end)
|
58
|
+
elsif a.exclude_end? && a.end == b.begin
|
59
|
+
# Adjacent, a before b
|
60
|
+
Semantic::VersionRange.new(a.begin, b.end, b.exclude_end?)
|
61
|
+
elsif b.exclude_end? && b.end == a.begin
|
62
|
+
# Adjacent, b before a
|
63
|
+
Semantic::VersionRange.new(b.begin, a.end, a.exclude_end?)
|
64
|
+
else
|
65
|
+
# No overlap
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def instance?(o, guard = nil)
|
71
|
+
o.is_a?(Semantic::VersionRange)
|
72
|
+
end
|
73
|
+
|
74
|
+
def eql?(o)
|
75
|
+
self.class == o.class
|
76
|
+
end
|
77
|
+
|
78
|
+
def hash?
|
79
|
+
super ^ @version_range.hash
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.new_function(_, loader)
|
83
|
+
range_expr = "\\A#{range_pattern}\\Z"
|
84
|
+
@@new_function ||= Puppet::Functions.create_loaded_function(:new_VersionRange, loader) do
|
85
|
+
local_types do
|
86
|
+
type 'SemVerRangeString = String[1]'
|
87
|
+
type 'SemVerRangeHash = Struct[{min=>Variant[default,SemVer],Optional[max]=>Variant[default,SemVer],Optional[exclude_max]=>Boolean}]'
|
88
|
+
end
|
89
|
+
|
90
|
+
# Constructs a VersionRange from a String with a format specified by
|
91
|
+
#
|
92
|
+
# https://github.com/npm/node-semver#range-grammar
|
93
|
+
#
|
94
|
+
# The logical or || operator is not implemented since it effectively builds
|
95
|
+
# an array of ranges that may be disparate. The {{Semantic::VersionRange}} inherits
|
96
|
+
# from the standard ruby range. It must be possible to describe that range in terms
|
97
|
+
# of min, max, and exclude max.
|
98
|
+
#
|
99
|
+
# The Puppet Version type is parameterized and accepts multiple ranges so creating such
|
100
|
+
# constraints is still possible. It will just require several parameters rather than one
|
101
|
+
# parameter containing the '||' operator.
|
102
|
+
#
|
103
|
+
dispatch :from_string do
|
104
|
+
param 'SemVerRangeString', :str
|
105
|
+
end
|
106
|
+
|
107
|
+
# Constructs a VersionRange from a min, and a max version. The Boolean argument denotes
|
108
|
+
# whether or not the max version is excluded or included in the range. It is included by
|
109
|
+
# default.
|
110
|
+
#
|
111
|
+
dispatch :from_versions do
|
112
|
+
param 'Variant[default,SemVer]', :min
|
113
|
+
param 'Variant[default,SemVer]', :max
|
114
|
+
optional_param 'Boolean', :exclude_max
|
115
|
+
end
|
116
|
+
|
117
|
+
# Same as #from_versions but each argument is instead given in a Hash
|
118
|
+
#
|
119
|
+
dispatch :from_hash do
|
120
|
+
param 'SemVerRangeHash', :hash_args
|
121
|
+
end
|
122
|
+
|
123
|
+
def from_string(str)
|
124
|
+
Semantic::VersionRange.parse(str)
|
125
|
+
end
|
126
|
+
|
127
|
+
def from_versions(min, max = :default, exclude_max = false)
|
128
|
+
min = Semantic::Version::MIN if min == :default
|
129
|
+
max = Semantic::Version::MAX if max == :default
|
130
|
+
Semantic::VersionRange.new(min, max, exclude_max)
|
131
|
+
end
|
132
|
+
|
133
|
+
def from_hash(hash)
|
134
|
+
from_versions(hash['min'], hash.fetch('max') { :default }, hash.fetch('exclude_max') { false })
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
DEFAULT = PSemVerRangeType.new
|
140
|
+
|
141
|
+
protected
|
142
|
+
|
143
|
+
def _assignable?(o, guard)
|
144
|
+
self == o
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.range_pattern
|
148
|
+
part = '(?<part>[0-9A-Za-z-]+)'
|
149
|
+
parts = "(?<parts>#{part}(?:\\.\\g<part>)*)"
|
150
|
+
|
151
|
+
qualifier = "(?:-#{parts})?(?:\\+\\g<parts>)?"
|
152
|
+
|
153
|
+
xr = '(?<xr>[xX*]|0|[1-9][0-9]*)'
|
154
|
+
partial = "(?<partial>#{xr}(?:\\.\\g<xr>(?:\\.\\g<xr>#{qualifier})?)?)"
|
155
|
+
|
156
|
+
hyphen = "(?:#{partial}\\s+-\\s+\\g<partial>)"
|
157
|
+
simple = "(?<simple>(?:<|>|>=|<=|~|\\^)?\\g<partial>)"
|
158
|
+
|
159
|
+
"#{hyphen}|#{simple}(?:\\s+\\g<simple>)*"
|
160
|
+
end
|
161
|
+
private_class_method :range_pattern
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Puppet::Pops
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# A Puppet Language Type that exposes the {{Semantic::Version}} and {{Semantic::VersionRange}}.
|
5
|
+
# The version type is parameterized with version ranges.
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
class PSemVerType < PScalarType
|
9
|
+
attr_reader :ranges
|
10
|
+
|
11
|
+
def initialize(*ranges)
|
12
|
+
ranges = ranges.map { |range| range.is_a?(Semantic::VersionRange) ? range : Semantic::VersionRange.parse(range) }
|
13
|
+
ranges = merge_ranges(ranges) if ranges.size > 1
|
14
|
+
@ranges = ranges
|
15
|
+
end
|
16
|
+
|
17
|
+
def instance?(o, guard = nil)
|
18
|
+
o.is_a?(Semantic::Version) && (@ranges.empty? || @ranges.any? {|range| range.include?(o) })
|
19
|
+
end
|
20
|
+
|
21
|
+
def eql?(o)
|
22
|
+
self.class == o.class && @ranges == o.ranges
|
23
|
+
end
|
24
|
+
|
25
|
+
def hash?
|
26
|
+
super ^ @ranges.hash
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
def self.new_function(_, loader)
|
31
|
+
@@new_function ||= Puppet::Functions.create_loaded_function(:new_Version, loader) do
|
32
|
+
local_types do
|
33
|
+
type 'PositiveInteger = Integer[0,default]'
|
34
|
+
type 'SemVerQualifier = Pattern[/\A(?<part>[0-9A-Za-z-]+)(?:\.\g<part>)*\Z/]'
|
35
|
+
type 'SemVerString = String[1]'
|
36
|
+
type 'SemVerHash = Struct[{major=>PositiveInteger,minor=>PositiveInteger,patch=>PositiveInteger,Optional[prerelease]=>SemVerQualifier,Optional[build]=>SemVerQualifier}]'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a SemVer from a string as specified by http://semver.org/
|
40
|
+
#
|
41
|
+
dispatch :from_string do
|
42
|
+
param 'SemVerString', :str
|
43
|
+
end
|
44
|
+
|
45
|
+
# Creates a SemVer from integers, prerelease, and build arguments
|
46
|
+
#
|
47
|
+
dispatch :from_args do
|
48
|
+
param 'PositiveInteger', :major
|
49
|
+
param 'PositiveInteger', :minor
|
50
|
+
param 'PositiveInteger', :patch
|
51
|
+
optional_param 'SemVerQualifier', :prerelease
|
52
|
+
optional_param 'SemVerQualifier', :build
|
53
|
+
end
|
54
|
+
|
55
|
+
# Same as #from_args but each argument is instead given in a Hash
|
56
|
+
#
|
57
|
+
dispatch :from_hash do
|
58
|
+
param 'SemVerHash', :hash_args
|
59
|
+
end
|
60
|
+
|
61
|
+
def from_string(str)
|
62
|
+
Semantic::Version.parse(str)
|
63
|
+
end
|
64
|
+
|
65
|
+
def from_args(major, minor, patch, prerelease = nil, build = nil)
|
66
|
+
Semantic::Version.new(major, minor, patch, prerelease, build)
|
67
|
+
end
|
68
|
+
|
69
|
+
def from_hash(hash)
|
70
|
+
Semantic::Version.new(hash['major'], hash['minor'], hash['patch'], hash['prerelease'], hash['build'])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
DEFAULT = PSemVerType.new
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
def _assignable?(o, guard)
|
80
|
+
return false unless o.class == self.class
|
81
|
+
return true if @ranges.empty?
|
82
|
+
return false if o.ranges.empty?
|
83
|
+
|
84
|
+
# All ranges in o must be covered by at least one range in self
|
85
|
+
o.ranges.all? do |o_range|
|
86
|
+
@ranges.any? do |range|
|
87
|
+
PSemVerRangeType.covered_by?(o_range, range)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
def merge_ranges(ranges)
|
94
|
+
result = []
|
95
|
+
until ranges.empty?
|
96
|
+
unmerged = []
|
97
|
+
x = ranges.pop
|
98
|
+
result << ranges.inject(x) do |memo, y|
|
99
|
+
merged = PSemVerRangeType.merge(memo, y)
|
100
|
+
if merged.nil?
|
101
|
+
unmerged << y
|
102
|
+
else
|
103
|
+
memo = merged
|
104
|
+
end
|
105
|
+
memo
|
106
|
+
end
|
107
|
+
ranges = unmerged
|
108
|
+
end
|
109
|
+
result.reverse!
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Puppet::Pops
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# Marker module for implementations that are mapped to Object types
|
5
|
+
# @api public
|
6
|
+
module PuppetObject
|
7
|
+
# Returns all classes that includes this module
|
8
|
+
def self.descendants
|
9
|
+
ObjectSpace.each_object(Class).select { |klass| klass < self }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns the Puppet Type for this instance. The implementing class must
|
13
|
+
# add the {#_ptype} as a class method.
|
14
|
+
#
|
15
|
+
# @return [PObjectType] the type
|
16
|
+
def _ptype
|
17
|
+
self.class._ptype
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
module Puppet::Pops
|
2
|
+
module Types
|
3
|
+
|
4
|
+
# @api private
|
5
|
+
class RubyGenerator < TypeFormatter
|
6
|
+
def remove_common_namespace(namespace_segments, name)
|
7
|
+
segments = name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR)
|
8
|
+
namespace_segments.size.times do |idx|
|
9
|
+
break if segments.empty? || namespace_segments[idx] != segments[0]
|
10
|
+
segments.shift
|
11
|
+
end
|
12
|
+
segments
|
13
|
+
end
|
14
|
+
|
15
|
+
def namespace_relative(namespace_segments, name)
|
16
|
+
remove_common_namespace(namespace_segments, name).join(TypeFormatter::NAME_SEGMENT_SEPARATOR)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_class(obj)
|
20
|
+
@dynamic_classes ||= Hash.new do |hash, key|
|
21
|
+
rp = key.resolved_parent
|
22
|
+
parent_class = rp.is_a?(PObjectType) ? create_class(rp) : Object
|
23
|
+
class_def = ''
|
24
|
+
class_body(key, EMPTY_ARRAY, class_def)
|
25
|
+
cls = Class.new(parent_class)
|
26
|
+
cls.class_eval(class_def)
|
27
|
+
cls.define_singleton_method(:_ptype) { return key }
|
28
|
+
hash[key] = cls
|
29
|
+
end
|
30
|
+
@dynamic_classes[obj]
|
31
|
+
end
|
32
|
+
|
33
|
+
def module_definition(types, comment)
|
34
|
+
object_types, aliased_types = types.partition { |type| type.is_a?(PObjectType) }
|
35
|
+
impl_names = implementation_names(object_types)
|
36
|
+
|
37
|
+
# extract common implementation module prefix
|
38
|
+
common_prefix = []
|
39
|
+
segmented_names = impl_names.map { |impl_name| impl_name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR) }
|
40
|
+
segments = segmented_names[0]
|
41
|
+
segments.size.times do |idx|
|
42
|
+
segment = segments[idx]
|
43
|
+
break unless segmented_names.all? { |sn| sn[idx] == segment }
|
44
|
+
common_prefix << segment
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create class definition of all contained types
|
48
|
+
bld = ''
|
49
|
+
start_module(common_prefix, comment, bld)
|
50
|
+
class_names = []
|
51
|
+
object_types.each_with_index do |type, index|
|
52
|
+
class_names << class_definition(type, common_prefix, bld, impl_names[index])
|
53
|
+
bld << "\n"
|
54
|
+
end
|
55
|
+
|
56
|
+
aliases = Hash[aliased_types.map { |type| [type.name, type.resolved_type] }]
|
57
|
+
end_module(common_prefix, aliases, class_names, bld)
|
58
|
+
bld
|
59
|
+
end
|
60
|
+
|
61
|
+
def start_module(common_prefix, comment, bld)
|
62
|
+
bld << '# ' << comment << "\n"
|
63
|
+
common_prefix.each { |cp| bld << 'module ' << cp << "\n" }
|
64
|
+
end
|
65
|
+
|
66
|
+
def end_module(common_prefix, aliases, class_names, bld)
|
67
|
+
# Emit registration of contained type aliases
|
68
|
+
unless aliases.empty?
|
69
|
+
bld << "Puppet::Pops::Pcore.register_aliases({\n"
|
70
|
+
aliases.each { |name, type| bld << " '" << name << "' => " << TypeFormatter.string(type.to_s) << "\n" }
|
71
|
+
bld.chomp!(",\n")
|
72
|
+
bld << "})\n\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Emit registration of contained types
|
76
|
+
unless class_names.empty?
|
77
|
+
bld << "Puppet::Pops::Pcore.register_implementations(\n"
|
78
|
+
class_names.each { |class_name| bld << ' ' << class_name << ",\n" }
|
79
|
+
bld.chomp!(",\n")
|
80
|
+
bld << ")\n\n"
|
81
|
+
end
|
82
|
+
bld.chomp!("\n")
|
83
|
+
|
84
|
+
common_prefix.size.times { bld << "end\n" }
|
85
|
+
end
|
86
|
+
|
87
|
+
def implementation_names(object_types)
|
88
|
+
object_types.map do |type|
|
89
|
+
ir = Loaders.implementation_registry
|
90
|
+
impl_name = ir.module_name_for_type(type)
|
91
|
+
raise Puppet::Error, "Unable to create an instance of #{type.name}. No mapping exists to runtime object" if impl_name.nil?
|
92
|
+
impl_name[0]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def class_definition(obj, namespace_segments, bld, class_name)
|
97
|
+
module_segments = remove_common_namespace(namespace_segments, class_name)
|
98
|
+
leaf_name = module_segments.pop
|
99
|
+
module_segments.each { |segment| bld << 'module ' << segment << "\n" }
|
100
|
+
bld << 'class ' << leaf_name
|
101
|
+
segments = class_name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR)
|
102
|
+
|
103
|
+
unless obj.parent.nil?
|
104
|
+
ir = Loaders.implementation_registry
|
105
|
+
parent_impl = ir.module_name_for_type(obj.parent)
|
106
|
+
raise Puppet::Error, "Unable to create an instance of #{obj.parent.name}. No mapping exists to runtime object" if parent_impl.nil?
|
107
|
+
bld << ' < ' << namespace_relative(segments, parent_impl[0])
|
108
|
+
end
|
109
|
+
|
110
|
+
bld << "\n"
|
111
|
+
bld << " def self._plocation\n"
|
112
|
+
bld << " loc = Puppet::Util.path_to_uri(\"\#{__FILE__}\")\n"
|
113
|
+
bld << " URI(\"#\{loc}?line=#\{__LINE__.to_i - 3}\")\n"
|
114
|
+
bld << " end\n"
|
115
|
+
|
116
|
+
bld << "\n"
|
117
|
+
bld << " def self._ptype\n"
|
118
|
+
bld << ' @_ptype ||= ' << namespace_relative(segments, obj.class.name) << ".new('" << obj.name << "',\n"
|
119
|
+
bld << TypeFormatter.new.ruby_string('ref', 3, obj.i12n_hash(false)) << " )\n"
|
120
|
+
bld << " end\n"
|
121
|
+
|
122
|
+
class_body(obj, segments, bld)
|
123
|
+
|
124
|
+
bld << "end\n"
|
125
|
+
module_segments.size.times { bld << "end\n" }
|
126
|
+
module_segments << leaf_name
|
127
|
+
module_segments.join(TypeFormatter::NAME_SEGMENT_SEPARATOR)
|
128
|
+
end
|
129
|
+
|
130
|
+
def class_body(obj, segments, bld)
|
131
|
+
if obj.parent.nil?
|
132
|
+
bld << "\n include " << namespace_relative(segments, Puppet::Pops::Types::PuppetObject.name) << "\n\n" # marker interface
|
133
|
+
bld << " def self.ref(type_string)\n"
|
134
|
+
bld << ' ' << namespace_relative(segments, Puppet::Pops::Types::PTypeReferenceType.name) << ".new(type_string)\n"
|
135
|
+
bld << " end\n"
|
136
|
+
end
|
137
|
+
|
138
|
+
# Output constants
|
139
|
+
constants, others = obj.attributes(true).values.partition { |a| a.kind == PObjectType::ATTRIBUTE_KIND_CONSTANT }
|
140
|
+
constants = constants.select { |ca| ca.container.equal?(obj) }
|
141
|
+
unless constants.empty?
|
142
|
+
constants.each { |ca| bld << "\n def self." << ca.name << "\n _ptype['" << ca.name << "'].value\n end\n" }
|
143
|
+
constants.each { |ca| bld << "\n def " << ca.name << "\n self.class." << ca.name << "\n end\n" }
|
144
|
+
end
|
145
|
+
|
146
|
+
init_params = others.reject { |a| a.kind == PObjectType::ATTRIBUTE_KIND_DERIVED }
|
147
|
+
opt, non_opt = init_params.partition { |ip| ip.value? }
|
148
|
+
|
149
|
+
# Output type safe hash constructor
|
150
|
+
bld << "\n def self.from_hash(i12n)\n"
|
151
|
+
bld << ' from_asserted_hash(' << namespace_relative(segments, TypeAsserter.name) << '.assert_instance_of('
|
152
|
+
bld << "'" << obj.label << " initializer', _ptype.i12n_type, i12n))\n end\n\n def self.from_asserted_hash(i12n)\n new(\n"
|
153
|
+
non_opt.each { |ip| bld << " i12n['" << ip.name << "'],\n" }
|
154
|
+
opt.each { |ip| bld << " i12n.fetch('" << ip.name << "') { _ptype['" << ip.name << "'].value },\n" }
|
155
|
+
bld.chomp!(",\n")
|
156
|
+
bld << ")\n end\n"
|
157
|
+
|
158
|
+
# Output type safe constructor
|
159
|
+
bld << "\n def self.create"
|
160
|
+
if init_params.empty?
|
161
|
+
bld << "\n new"
|
162
|
+
else
|
163
|
+
bld << '('
|
164
|
+
non_opt.each { |ip| bld << ip.name << ', ' }
|
165
|
+
opt.each { |ip| bld << ip.name << ' = ' << "_ptype['#{ip.name}'].value" << ', ' }
|
166
|
+
bld.chomp!(', ')
|
167
|
+
bld << ")\n"
|
168
|
+
bld << ' ta = ' << namespace_relative(segments, TypeAsserter.name) << "\n"
|
169
|
+
bld << " attrs = _ptype.attributes(true)\n"
|
170
|
+
init_params.each do |a|
|
171
|
+
bld << " ta.assert_instance_of('" << a.container.name << '[' << a.name << ']'
|
172
|
+
bld << "', attrs['" << a.name << "'].type, " << a.name << ")\n"
|
173
|
+
end
|
174
|
+
bld << ' new('
|
175
|
+
non_opt.each { |a| bld << a.name << ', ' }
|
176
|
+
opt.each { |a| bld << a.name << ', ' }
|
177
|
+
bld.chomp!(', ')
|
178
|
+
bld << ')'
|
179
|
+
end
|
180
|
+
bld << "\n end\n"
|
181
|
+
|
182
|
+
# Output initializer
|
183
|
+
bld << "\n def initialize"
|
184
|
+
unless init_params.empty?
|
185
|
+
bld << '('
|
186
|
+
non_opt.each { |ip| bld << ip.name << ', ' }
|
187
|
+
opt.each { |ip| bld << ip.name << ' = ' << "_ptype['#{ip.name}'].value" << ', ' }
|
188
|
+
bld.chomp!(', ')
|
189
|
+
bld << ')'
|
190
|
+
unless obj.parent.nil?
|
191
|
+
bld << "\n super"
|
192
|
+
super_args = (non_opt + opt).select { |ip| !ip.container.equal?(obj) }
|
193
|
+
unless super_args.empty?
|
194
|
+
bld << '('
|
195
|
+
super_args.each { |ip| bld << ip.name << ', ' }
|
196
|
+
bld.chomp!(', ')
|
197
|
+
bld << ')'
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
bld << "\n"
|
202
|
+
|
203
|
+
init_params.each { |a| bld << ' @' << a.name << ' = ' << a.name << "\n" if a.container.equal?(obj) }
|
204
|
+
bld << " end\n\n"
|
205
|
+
|
206
|
+
# Output attr_readers
|
207
|
+
others.each do |a|
|
208
|
+
next unless a.container.equal?(obj)
|
209
|
+
if a.kind == PObjectType::ATTRIBUTE_KIND_DERIVED || a.kind == PObjectType::ATTRIBUTE_KIND_GIVEN_OR_DERIVED
|
210
|
+
bld << ' def ' << a.name << "\n"
|
211
|
+
bld << " raise Puppet::Error, \"no method is implemented for derived attribute #{a.label}\"\n"
|
212
|
+
bld << " end\n"
|
213
|
+
else
|
214
|
+
bld << ' attr_reader :' << a.name << "\n"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Output function placeholders
|
219
|
+
obj.functions(false).each_value do |func|
|
220
|
+
bld << "\n def " << func.name << "(*args)\n"
|
221
|
+
bld << " # Placeholder for #{func.type}\n"
|
222
|
+
bld << " raise Puppet::Error, \"no method is implemented for #{func.label}\"\n"
|
223
|
+
bld << " end\n"
|
224
|
+
end
|
225
|
+
|
226
|
+
# output hash and equality
|
227
|
+
include_class = obj.include_class_in_equality?
|
228
|
+
if obj.equality.nil?
|
229
|
+
eq_names = obj.attributes(false).values.select { |a| a.kind != PObjectType::ATTRIBUTE_KIND_CONSTANT }.map(&:name)
|
230
|
+
else
|
231
|
+
eq_names = obj.equality
|
232
|
+
end
|
233
|
+
|
234
|
+
unless eq_names.empty? && !include_class
|
235
|
+
bld << "\n def hash\n "
|
236
|
+
bld << 'super.hash ^ ' unless obj.parent.nil?
|
237
|
+
if eq_names.empty?
|
238
|
+
bld << "self.class.hash\n"
|
239
|
+
else
|
240
|
+
bld << '['
|
241
|
+
bld << 'self.class, ' if include_class
|
242
|
+
eq_names.each { |eqn| bld << '@' << eqn << ', ' }
|
243
|
+
bld.chomp!(', ')
|
244
|
+
bld << "].hash\n"
|
245
|
+
end
|
246
|
+
bld << " end\n"
|
247
|
+
|
248
|
+
bld << "\n def eql?(o)\n"
|
249
|
+
bld << " super.eql?(o) &&\n" unless obj.parent.nil?
|
250
|
+
bld << " self.class.eql?(o.class) &&\n" if include_class
|
251
|
+
eq_names.each { |eqn| bld << ' @' << eqn << '.eql?(o.' << eqn << ") &&\n" }
|
252
|
+
bld.chomp!(" &&\n")
|
253
|
+
bld << "\n end\n"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|