cfndsl 1.2.0 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -3
- data/.travis.yml +5 -2
- data/CHANGELOG.md +49 -2
- data/README.md +1 -1
- data/cfndsl.gemspec +2 -2
- data/lib/cfndsl/aws/patches/900_SageMakerTags_patch.json +41 -0
- data/lib/cfndsl/aws/resource_specification.json +76883 -33363
- data/lib/cfndsl/globals.rb +1 -0
- data/lib/cfndsl/jsonable.rb +5 -4
- data/lib/cfndsl/orchestration_template.rb +12 -7
- data/lib/cfndsl/plurals.rb +2 -2
- data/lib/cfndsl/rake_task.rb +6 -6
- data/lib/cfndsl/ref_check.rb +2 -2
- data/lib/cfndsl/runner.rb +2 -2
- data/lib/cfndsl/specification.rb +11 -13
- data/lib/cfndsl/types.rb +83 -72
- data/lib/cfndsl/version.rb +1 -1
- data/lib/deep_merge/core.rb +6 -6
- data/sample/config_service.rb +1 -1
- data/sample/vpc_example.rb +3 -3
- data/sample/vpc_with_vpn_example.rb +3 -3
- data/spec/cli_spec.rb +2 -0
- metadata +7 -7
- data/lib/cfndsl/aws/patches/800_List_types_patch.json +0 -115
data/lib/cfndsl/globals.rb
CHANGED
data/lib/cfndsl/jsonable.rb
CHANGED
|
@@ -136,12 +136,13 @@ module CfnDsl
|
|
|
136
136
|
check_names
|
|
137
137
|
hash = {}
|
|
138
138
|
instance_variables.each do |var|
|
|
139
|
-
name = var[1
|
|
139
|
+
name = var[1..]
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
case name
|
|
142
|
+
when /^__/
|
|
142
143
|
# if a variable starts with double underscore, strip one off
|
|
143
|
-
name = name[1
|
|
144
|
-
|
|
144
|
+
name = name[1..]
|
|
145
|
+
when /^_/
|
|
145
146
|
# Hide variables that start with single underscore
|
|
146
147
|
name = nil
|
|
147
148
|
end
|
|
@@ -72,18 +72,24 @@ module CfnDsl
|
|
|
72
72
|
resource_name = name.gsub(/::/, '_')
|
|
73
73
|
type_module.const_set(resource_name, resource)
|
|
74
74
|
info['Properties'].each_pair do |pname, ptype|
|
|
75
|
+
# handle bogus List defined as Type
|
|
76
|
+
unless ptype.is_a?(Array)
|
|
77
|
+
pclass = type_module.const_get ptype
|
|
78
|
+
if pclass.is_a?(Array)
|
|
79
|
+
ptype = pclass
|
|
80
|
+
else
|
|
81
|
+
create_property_def(resource, pname, pclass)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
75
85
|
if ptype.is_a? Array
|
|
76
86
|
pclass = type_module.const_get ptype.first
|
|
77
87
|
create_array_property_def(resource, pname, pclass, info)
|
|
78
|
-
else
|
|
79
|
-
pclass = type_module.const_get ptype
|
|
80
|
-
create_property_def(resource, pname, pclass)
|
|
81
88
|
end
|
|
82
89
|
end
|
|
83
90
|
resource_name
|
|
84
91
|
end
|
|
85
92
|
|
|
86
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
87
93
|
def create_array_property_def(resource, pname, pclass, info)
|
|
88
94
|
singular_name = CfnDsl::Plurals.singularize pname
|
|
89
95
|
plural_name = singular_name == pname ? CfnDsl::Plurals.pluralize(pname) : pname
|
|
@@ -111,7 +117,6 @@ module CfnDsl
|
|
|
111
117
|
# Singular form understands concatenation and Fn::If property
|
|
112
118
|
create_singular_property_def(resource, pname, pclass, singular_name) if singular_name
|
|
113
119
|
end
|
|
114
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
|
115
120
|
|
|
116
121
|
def create_resource_accessor(accessor, resource, type)
|
|
117
122
|
class_eval do
|
|
@@ -245,7 +250,7 @@ module CfnDsl
|
|
|
245
250
|
end
|
|
246
251
|
end
|
|
247
252
|
|
|
248
|
-
# rubocop:disable Metrics/
|
|
253
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
249
254
|
def _check_refs(container_name, method, source_containers)
|
|
250
255
|
container = instance_variable_get("@#{container_name}s")
|
|
251
256
|
return [] unless container
|
|
@@ -279,7 +284,7 @@ module CfnDsl
|
|
|
279
284
|
|
|
280
285
|
invalids
|
|
281
286
|
end
|
|
282
|
-
# rubocop:enable Metrics/
|
|
287
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
283
288
|
|
|
284
289
|
def validate
|
|
285
290
|
errors = check_refs || []
|
data/lib/cfndsl/plurals.rb
CHANGED
|
@@ -20,7 +20,7 @@ module CfnDsl
|
|
|
20
20
|
@singles = @plurals.invert
|
|
21
21
|
|
|
22
22
|
def pluralize(name)
|
|
23
|
-
@plurals.fetch(name.to_s) { |key| key
|
|
23
|
+
@plurals.fetch(name.to_s) { |key| "#{key}s" }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def singularize(name)
|
|
@@ -29,7 +29,7 @@ module CfnDsl
|
|
|
29
29
|
when /List$/
|
|
30
30
|
key[0..-5]
|
|
31
31
|
when /ies$/
|
|
32
|
-
key[0..-4]
|
|
32
|
+
"#{key[0..-4]}y"
|
|
33
33
|
when /s$/
|
|
34
34
|
key[0..-2]
|
|
35
35
|
else
|
data/lib/cfndsl/rake_task.rb
CHANGED
|
@@ -79,7 +79,7 @@ module CfnDsl
|
|
|
79
79
|
# then any existing file is considered sufficient, and 'latest' is the version used for downloading
|
|
80
80
|
#
|
|
81
81
|
# @todo Add capability to provide a user spec/patches dir
|
|
82
|
-
def specification(name: nil,
|
|
82
|
+
def specification(file:, name: nil, version: nil)
|
|
83
83
|
if name
|
|
84
84
|
desc 'Update Resource Specification' unless ::Rake.application.last_description
|
|
85
85
|
task name, [:cfn_spec_version] => file
|
|
@@ -218,7 +218,7 @@ module CfnDsl
|
|
|
218
218
|
end
|
|
219
219
|
|
|
220
220
|
def verbose
|
|
221
|
-
(Rake.verbose? || cfndsl_opts&.fetch(:verbose, false)) &&
|
|
221
|
+
(Rake.verbose? || cfndsl_opts&.fetch(:verbose, false)) && $stderr
|
|
222
222
|
end
|
|
223
223
|
|
|
224
224
|
def generate(opts)
|
|
@@ -238,8 +238,8 @@ module CfnDsl
|
|
|
238
238
|
verbose&.puts("Writing to #{type}")
|
|
239
239
|
end
|
|
240
240
|
|
|
241
|
-
def outputter(opts)
|
|
242
|
-
opts[:output].nil? ? yield(
|
|
241
|
+
def outputter(opts, &block)
|
|
242
|
+
opts[:output].nil? ? yield($stdout) : file_output(opts[:output], &block)
|
|
243
243
|
end
|
|
244
244
|
|
|
245
245
|
def model(filename)
|
|
@@ -253,8 +253,8 @@ module CfnDsl
|
|
|
253
253
|
cfndsl_opts.fetch(:extras, [])
|
|
254
254
|
end
|
|
255
255
|
|
|
256
|
-
def file_output(path)
|
|
257
|
-
File.open(File.expand_path(path), 'w')
|
|
256
|
+
def file_output(path, &block)
|
|
257
|
+
File.open(File.expand_path(path), 'w', &block)
|
|
258
258
|
end
|
|
259
259
|
end
|
|
260
260
|
# rubocop:enable Metrics/ClassLength
|
data/lib/cfndsl/ref_check.rb
CHANGED
|
@@ -10,7 +10,7 @@ module RefCheck
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
# Build up a set of references.
|
|
13
|
-
# rubocop:disable Metrics/
|
|
13
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
14
14
|
def build_references(refs = [], origin = nil, method = :all_refs)
|
|
15
15
|
if respond_to?(method)
|
|
16
16
|
send(method).each do |ref|
|
|
@@ -30,7 +30,7 @@ module RefCheck
|
|
|
30
30
|
|
|
31
31
|
refs
|
|
32
32
|
end
|
|
33
|
-
# rubocop:enable Metrics/
|
|
33
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
34
34
|
|
|
35
35
|
def ref_children
|
|
36
36
|
[]
|
data/lib/cfndsl/runner.rb
CHANGED
|
@@ -117,14 +117,14 @@ module CfnDsl
|
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
filename = File.expand_path(ARGV[0])
|
|
120
|
-
verbose = options[:verbose] &&
|
|
120
|
+
verbose = options[:verbose] && $stderr
|
|
121
121
|
|
|
122
122
|
verbose.puts "Using specification file #{CfnDsl.specification_file}" if verbose
|
|
123
123
|
|
|
124
124
|
require_relative 'cloudformation'
|
|
125
125
|
model = CfnDsl.eval_file_with_extras(filename, options[:extras], verbose)
|
|
126
126
|
|
|
127
|
-
output =
|
|
127
|
+
output = $stdout
|
|
128
128
|
if options[:output] != '-'
|
|
129
129
|
verbose.puts("Writing to #{options[:output]}") if verbose
|
|
130
130
|
output = File.open(File.expand_path(options[:output]), 'w')
|
data/lib/cfndsl/specification.rb
CHANGED
|
@@ -70,20 +70,18 @@ module CfnDsl
|
|
|
70
70
|
next unless %w[ResourceTypes PropertyTypes].include?(top_level_type)
|
|
71
71
|
|
|
72
72
|
patches.each_pair do |property_type_name, patch_details|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
patch_details = patch_details['patch']
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
Hana::Patch.new(patch_details['operations']).apply(applies_to) if patch_required?(patch_details)
|
|
82
|
-
rescue Hana::Patch::MissingTargetException => e
|
|
83
|
-
raise "Failed specification patch #{top_level_type} #{property_type_name} from #{from_file}" if fail_patches
|
|
84
|
-
|
|
85
|
-
warn "Ignoring failed specification patch #{top_level_type} #{property_type_name} from #{from_file} - #{e.class.name}:#{e.message}"
|
|
73
|
+
applies_to = spec[top_level_type]
|
|
74
|
+
unless property_type_name == 'patch'
|
|
75
|
+
# Patch applies within a specific property type
|
|
76
|
+
applies_to = applies_to[property_type_name]
|
|
77
|
+
patch_details = patch_details['patch']
|
|
86
78
|
end
|
|
79
|
+
|
|
80
|
+
Hana::Patch.new(patch_details['operations']).apply(applies_to) if patch_required?(patch_details)
|
|
81
|
+
rescue Hana::Patch::MissingTargetException => e
|
|
82
|
+
raise "Failed specification patch #{top_level_type} #{property_type_name} from #{from_file}" if fail_patches
|
|
83
|
+
|
|
84
|
+
warn "Ignoring failed specification patch #{top_level_type} #{property_type_name} from #{from_file} - #{e.class.name}:#{e.message}"
|
|
87
85
|
end
|
|
88
86
|
end
|
|
89
87
|
end
|
data/lib/cfndsl/types.rb
CHANGED
|
@@ -9,6 +9,18 @@ module CfnDsl
|
|
|
9
9
|
# Types helper
|
|
10
10
|
# rubocop:disable Metrics/ModuleLength
|
|
11
11
|
module Types
|
|
12
|
+
def self.extract_list_type(root_name, property_info)
|
|
13
|
+
# Tag is a reused type, but not quite primitive
|
|
14
|
+
# and not all resources use the general form
|
|
15
|
+
if property_info['ItemType'] == 'Tag'
|
|
16
|
+
['Tag']
|
|
17
|
+
elsif (property_info['ItemType'] == 'Json') && (property_info['Type'] == 'List')
|
|
18
|
+
['Json']
|
|
19
|
+
else
|
|
20
|
+
Array(root_name + property_info['ItemType'])
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
12
24
|
def self.extract_from_resource_spec(fail_patches: false)
|
|
13
25
|
spec = Specification.load_file(fail_patches: fail_patches)
|
|
14
26
|
resources = extract_resources spec.resources
|
|
@@ -16,35 +28,31 @@ module CfnDsl
|
|
|
16
28
|
{ 'Resources' => resources, 'Types' => types, 'Version' => spec.version, 'File' => spec.file }
|
|
17
29
|
end
|
|
18
30
|
|
|
19
|
-
# rubocop:disable Metrics/
|
|
31
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
20
32
|
def self.extract_resources(spec)
|
|
21
33
|
spec.each_with_object({}) do |(resource_name, resource_info), resources|
|
|
22
34
|
properties = resource_info['Properties'].each_with_object({}) do |(property_name, property_info), extracted|
|
|
23
35
|
# some json incorrectly labelled as Type -> Json instead of PrimitiveType
|
|
24
36
|
# also, AWS now has the concept of Map which cfndsl had never defined
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
property_type = resource_name.split('::').join + property_info['Type']
|
|
45
|
-
else
|
|
46
|
-
warn "could not extract resource type from #{resource_name}"
|
|
47
|
-
end
|
|
37
|
+
property_type =
|
|
38
|
+
if property_info['Type'] == 'Map' || property_info['Type'] == 'Json'
|
|
39
|
+
'Json'
|
|
40
|
+
elsif property_info['PrimitiveType']
|
|
41
|
+
property_info['PrimitiveType']
|
|
42
|
+
elsif property_info['PrimitiveItemType']
|
|
43
|
+
Array(property_info['PrimitiveItemType'])
|
|
44
|
+
elsif property_info['PrimitiveTypes']
|
|
45
|
+
property_info['PrimitiveTypes'][0]
|
|
46
|
+
elsif property_info['ItemType']
|
|
47
|
+
extract_list_type(resource_name.split('::').join, property_info)
|
|
48
|
+
elsif property_info['Type']
|
|
49
|
+
# Special types (defined below) are joined with their parent
|
|
50
|
+
# resource name for uniqueness and connection
|
|
51
|
+
resource_name.split('::').join + property_info['Type']
|
|
52
|
+
else
|
|
53
|
+
warn "could not extract resource type for property #{property_name} from #{resource_name}, assuming Json"
|
|
54
|
+
'Json'
|
|
55
|
+
end
|
|
48
56
|
extracted[property_name] = property_type
|
|
49
57
|
extracted
|
|
50
58
|
end
|
|
@@ -52,7 +60,6 @@ module CfnDsl
|
|
|
52
60
|
resources
|
|
53
61
|
end
|
|
54
62
|
end
|
|
55
|
-
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
|
|
56
63
|
|
|
57
64
|
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
|
|
58
65
|
def self.extract_types(spec)
|
|
@@ -76,43 +83,43 @@ module CfnDsl
|
|
|
76
83
|
root_resource_name = root_resource ? root_resource[1].gsub(/::/, '') : property_name
|
|
77
84
|
property_name = property_name.gsub(/::|\./, '')
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
elsif nested_prop_info['PrimitiveType']
|
|
89
|
-
nested_prop_type = nested_prop_info['PrimitiveType']
|
|
90
|
-
elsif nested_prop_info['PrimitiveItemType']
|
|
91
|
-
nested_prop_type = Array(nested_prop_info['PrimitiveItemType'])
|
|
92
|
-
elsif nested_prop_info['PrimitiveItemTypes']
|
|
93
|
-
nested_prop_type = Array(nested_prop_info['PrimitiveItemTypes'])
|
|
94
|
-
elsif nested_prop_info['Types']
|
|
95
|
-
nested_prop_type = Array(nested_prop_info['Types'])
|
|
96
|
-
elsif nested_prop_info['ItemType']
|
|
97
|
-
# Tag is a reused type, but not quite primitive
|
|
98
|
-
# and not all resources use the general form
|
|
86
|
+
properties =
|
|
87
|
+
if property_info.key?('PrimitiveType')
|
|
88
|
+
property_info['PrimitiveType']
|
|
89
|
+
elsif property_info.key?('ItemType')
|
|
90
|
+
extract_list_type(root_resource_name, property_info)
|
|
91
|
+
elsif property_info.key?('Type')
|
|
92
|
+
property_info['Type']
|
|
93
|
+
elsif property_info.key?('Properties')
|
|
94
|
+
property_info['Properties'].each_with_object({}) do |(nested_prop_name, nested_prop_info), extracted|
|
|
99
95
|
nested_prop_type =
|
|
100
|
-
if nested_prop_info['
|
|
101
|
-
|
|
96
|
+
if nested_prop_info['Type'] == 'Map' || nested_prop_info['Type'] == 'Json'
|
|
97
|
+
# The Map type and the incorrectly labelled Json type
|
|
98
|
+
'Json'
|
|
99
|
+
elsif nested_prop_info['PrimitiveType']
|
|
100
|
+
nested_prop_info['PrimitiveType']
|
|
101
|
+
elsif nested_prop_info['PrimitiveItemType']
|
|
102
|
+
Array(nested_prop_info['PrimitiveItemType'])
|
|
103
|
+
elsif nested_prop_info['PrimitiveItemTypes']
|
|
104
|
+
Array(nested_prop_info['PrimitiveItemTypes'])
|
|
105
|
+
elsif nested_prop_info['Types']
|
|
106
|
+
Array(nested_prop_info['Types'])
|
|
107
|
+
elsif nested_prop_info['ItemType']
|
|
108
|
+
extract_list_type(root_resource_name, nested_prop_info)
|
|
109
|
+
elsif nested_prop_info['Type']
|
|
110
|
+
root_resource_name + nested_prop_info['Type']
|
|
102
111
|
else
|
|
103
|
-
|
|
112
|
+
warn "could not extract property type for #{nested_prop_name} from #{property_name}, assuming Json"
|
|
113
|
+
p nested_prop_info
|
|
114
|
+
'Json'
|
|
104
115
|
end
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
nested_prop_type = root_resource_name + nested_prop_info['Type']
|
|
108
|
-
else
|
|
109
|
-
warn "could not extract property type from #{property_name}"
|
|
110
|
-
p nested_prop_info
|
|
116
|
+
extracted[nested_prop_name] = nested_prop_type
|
|
117
|
+
extracted
|
|
111
118
|
end
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
else
|
|
120
|
+
# No Type information typically just a plain JSON object
|
|
121
|
+
'Json'
|
|
114
122
|
end
|
|
115
|
-
end
|
|
116
123
|
types[property_name] = properties
|
|
117
124
|
types
|
|
118
125
|
end
|
|
@@ -152,27 +159,33 @@ module CfnDsl
|
|
|
152
159
|
classes = {}
|
|
153
160
|
|
|
154
161
|
# Go through and declare all of the types first
|
|
155
|
-
types_list['Types'].
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
types_list['Types'].each_pair do |typename, type_val|
|
|
163
|
+
unless type_def.const_defined?(typename)
|
|
164
|
+
if type_val.is_a?(Array)
|
|
165
|
+
type_def.const_set(typename, type_val)
|
|
166
|
+
else
|
|
167
|
+
classes[typename] = type_def.const_set(typename, Class.new(type_def::Type))
|
|
168
|
+
end
|
|
161
169
|
end
|
|
162
170
|
end
|
|
163
171
|
|
|
164
172
|
# Now go through them again and define attribute setter methods
|
|
165
|
-
classes.each_pair do |typename,
|
|
173
|
+
classes.each_pair do |typename, klass|
|
|
166
174
|
typeval = types_list['Types'][typename]
|
|
167
175
|
next unless typeval.respond_to?(:each_pair)
|
|
168
176
|
|
|
169
177
|
typeval.each_pair do |attr_name, attr_type|
|
|
170
178
|
attr_method = attr_name
|
|
171
179
|
variable = "@#{attr_name}".to_sym
|
|
172
|
-
|
|
180
|
+
|
|
181
|
+
# handle bogus List defined as Type
|
|
182
|
+
unless attr_type.is_a?(Array)
|
|
183
|
+
attr_class = type_def.const_get(attr_type)
|
|
184
|
+
attr_type = attr_class if attr_class.is_a?(Array)
|
|
185
|
+
end
|
|
173
186
|
|
|
174
187
|
if attr_type.is_a?(Array)
|
|
175
|
-
|
|
188
|
+
attr_class = type_def.const_get(attr_type[0])
|
|
176
189
|
singular_method = CfnDsl::Plurals.singularize(attr_name)
|
|
177
190
|
|
|
178
191
|
if singular_method == attr_name
|
|
@@ -180,16 +193,14 @@ module CfnDsl
|
|
|
180
193
|
attr_method = CfnDsl::Plurals.pluralize(attr_name)
|
|
181
194
|
end
|
|
182
195
|
|
|
183
|
-
define_array_method(
|
|
184
|
-
|
|
185
|
-
else
|
|
186
|
-
klass = type_def.const_get(attr_type)
|
|
196
|
+
define_array_method(attr_class, singular_method, klass, variable) if singular_method != attr_method
|
|
187
197
|
end
|
|
188
198
|
|
|
189
|
-
|
|
199
|
+
klass.class_eval do
|
|
190
200
|
CfnDsl.method_names(attr_method) do |inner_method|
|
|
191
201
|
define_method(inner_method) do |value = nil, *_rest, &block|
|
|
192
|
-
|
|
202
|
+
# noinspection RubyScope
|
|
203
|
+
value ||= attr_class.new
|
|
193
204
|
instance_variable_set(variable, value)
|
|
194
205
|
value.instance_eval(&block) if block
|
|
195
206
|
value
|
data/lib/cfndsl/version.rb
CHANGED
data/lib/deep_merge/core.rb
CHANGED
|
@@ -120,16 +120,16 @@ module DeepMerge
|
|
|
120
120
|
puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug
|
|
121
121
|
if dest[src_key]
|
|
122
122
|
puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug
|
|
123
|
-
dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(debug_indent: di
|
|
123
|
+
dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(debug_indent: "#{di} "))
|
|
124
124
|
else # dest[src_key] doesn't exist so we want to create and overwrite it (but we do this via deep_merge!)
|
|
125
125
|
puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug
|
|
126
|
-
#
|
|
126
|
+
# NOTE: we rescue here b/c some classes respond to "dup" but don't implement it (Numeric, TrueClass, FalseClass, NilClass among maybe others)
|
|
127
127
|
begin
|
|
128
128
|
src_dup = src_value.dup # we dup src_value if possible because we're going to merge into it (since dest is empty)
|
|
129
129
|
rescue TypeError
|
|
130
130
|
src_dup = src_value
|
|
131
131
|
end
|
|
132
|
-
dest[src_key] = deep_merge!(src_value, src_dup, options.merge(debug_indent: di
|
|
132
|
+
dest[src_key] = deep_merge!(src_value, src_dup, options.merge(debug_indent: "#{di} "))
|
|
133
133
|
end
|
|
134
134
|
elsif dest.is_a?(Array) && extend_existing_arrays
|
|
135
135
|
dest.push(source)
|
|
@@ -181,12 +181,12 @@ module DeepMerge
|
|
|
181
181
|
list = []
|
|
182
182
|
dest.each_index do |i|
|
|
183
183
|
list[i] = deep_merge!(source[i] || {}, dest[i],
|
|
184
|
-
options.merge(debug_indent: di
|
|
184
|
+
options.merge(debug_indent: "#{di} "))
|
|
185
185
|
end
|
|
186
|
-
list += source[dest.count
|
|
186
|
+
list += source[dest.count..] if source.count > dest.count
|
|
187
187
|
dest = list
|
|
188
188
|
elsif keep_array_duplicates
|
|
189
|
-
dest
|
|
189
|
+
dest.concat(source)
|
|
190
190
|
else
|
|
191
191
|
dest |= source
|
|
192
192
|
end
|