json-merge 1.1.2 → 7.0.0
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
- checksums.yaml.gz.sig +0 -0
- data/lib/json/merge/version.rb +3 -4
- data/lib/json/merge.rb +396 -114
- data/lib/json-merge.rb +1 -4
- data.tar.gz.sig +0 -0
- metadata +28 -256
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -149
- data/CITATION.cff +0 -20
- data/CODE_OF_CONDUCT.md +0 -134
- data/CONTRIBUTING.md +0 -227
- data/FUNDING.md +0 -74
- data/LICENSE.txt +0 -21
- data/README.md +0 -1036
- data/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/json/merge/conflict_resolver.rb +0 -336
- data/lib/json/merge/debug_logger.rb +0 -41
- data/lib/json/merge/emitter.rb +0 -163
- data/lib/json/merge/file_analysis.rb +0 -190
- data/lib/json/merge/merge_result.rb +0 -136
- data/lib/json/merge/node_wrapper.rb +0 -307
- data/lib/json/merge/object_match_refiner.rb +0 -339
- data/lib/json/merge/smart_merger.rb +0 -150
- data/sig/json/merge.rbs +0 -201
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '07183e1164d6af24dd6dad4a4ded1b57b591bb5e9197bdb2fab3e49af2b176a3'
|
|
4
|
+
data.tar.gz: 3dc67caacf7ed48b31cf0f7860d9f6381fef38c68b7c5671b3a7a327c6e43240
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 79d8e551876b7b8cca367a73ab5d5b0041d35f649737983bb1029b85a5c7388aaa5f6f80605004183f2d107a3dff34292ca5b15ecbe38380b0efb2651d3fd2d5
|
|
7
|
+
data.tar.gz: e16ebd70f7f0b5776d1d21d290f3980031c87aa3adbd9b69c4870010f249ff6b04c5d391554390eb7dc08186b39b3f8f2efe1549a1b1e23c00586ad5d86dffd8
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/lib/json/merge/version.rb
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Json
|
|
4
4
|
module Merge
|
|
5
|
-
# Version information for Json::Merge
|
|
6
5
|
module Version
|
|
7
|
-
|
|
8
|
-
VERSION = "1.1.2"
|
|
6
|
+
VERSION = "7.0.0"
|
|
9
7
|
end
|
|
10
|
-
|
|
8
|
+
|
|
9
|
+
VERSION = Version::VERSION
|
|
11
10
|
end
|
|
12
11
|
end
|
data/lib/json/merge.rb
CHANGED
|
@@ -1,125 +1,407 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "set"
|
|
5
|
-
|
|
6
|
-
# External gems
|
|
7
|
-
# TreeHaver provides a unified cross-Ruby interface to tree-sitter.
|
|
8
|
-
# It handles grammar discovery and backend selection automatically
|
|
9
|
-
# via parser_for(:json). No manual registration needed.
|
|
3
|
+
require "json"
|
|
10
4
|
require "tree_haver"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Shared merge infrastructure
|
|
14
|
-
require "ast/merge"
|
|
15
|
-
|
|
16
|
-
# This gem
|
|
17
|
-
require_relative "merge/version"
|
|
18
|
-
|
|
19
|
-
# Json::Merge provides a JSON file smart merge system using tree-sitter AST analysis.
|
|
20
|
-
# It intelligently merges template and destination JSON files by identifying matching
|
|
21
|
-
# keys and resolving differences using structural signatures.
|
|
22
|
-
#
|
|
23
|
-
# For JSONC (JSON with Comments) support, see the jsonc-merge gem which handles
|
|
24
|
-
# configuration files that include comments
|
|
25
|
-
# (like devcontainer.json, tsconfig.json, VS Code settings, etc.).
|
|
26
|
-
#
|
|
27
|
-
# @example Basic usage
|
|
28
|
-
# template = File.read("template.json")
|
|
29
|
-
# destination = File.read("destination.json")
|
|
30
|
-
# merger = Json::Merge::SmartMerger.new(template, destination)
|
|
31
|
-
# result = merger.merge
|
|
32
|
-
#
|
|
33
|
-
# @example With debug information
|
|
34
|
-
# merger = Json::Merge::SmartMerger.new(template, destination)
|
|
35
|
-
# debug_result = merger.merge_with_debug
|
|
36
|
-
# puts debug_result[:content]
|
|
37
|
-
# puts debug_result[:statistics]
|
|
5
|
+
|
|
38
6
|
module Json
|
|
39
|
-
# Smart merge system for JSON files using tree-sitter AST analysis.
|
|
40
|
-
# Provides intelligent merging by understanding JSON structure
|
|
41
|
-
# rather than treating files as plain text.
|
|
42
|
-
#
|
|
43
|
-
# For JSONC (JSON with Comments) support, use the jsonc-merge gem instead.
|
|
44
|
-
#
|
|
45
|
-
# @see SmartMerger Main entry point for merge operations
|
|
46
|
-
# @see FileAnalysis Analyzes JSON structure
|
|
47
|
-
# @see ConflictResolver Resolves content conflicts
|
|
48
7
|
module Merge
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
8
|
+
PACKAGE_NAME = "json-merge"
|
|
9
|
+
DESTINATION_WINS_ARRAY_POLICY = {
|
|
10
|
+
surface: "array",
|
|
11
|
+
name: "destination_wins_array"
|
|
12
|
+
}.freeze
|
|
13
|
+
TRAILING_COMMA_FALLBACK_POLICY = {
|
|
14
|
+
surface: "fallback",
|
|
15
|
+
name: "trailing_comma_destination_fallback"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def json_feature_profile
|
|
21
|
+
{
|
|
22
|
+
family: "json",
|
|
23
|
+
supported_dialects: %w[json jsonc],
|
|
24
|
+
supported_policies: [DESTINATION_WINS_ARRAY_POLICY, TRAILING_COMMA_FALLBACK_POLICY]
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def json_parse_request(source, dialect)
|
|
29
|
+
TreeHaver::ParserRequest.new(source: source, language: "json", dialect: dialect)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse_json_with_language_pack(source, dialect)
|
|
33
|
+
return unsupported_jsonc_language_pack_result if dialect != "json"
|
|
34
|
+
|
|
35
|
+
backend_result = TreeHaver.parse_with_language_pack(json_parse_request(source, dialect))
|
|
36
|
+
return { ok: false, diagnostics: backend_result[:diagnostics] } unless backend_result[:ok]
|
|
37
|
+
|
|
38
|
+
parse_json(source, dialect)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def parse_json(source, dialect)
|
|
42
|
+
normalized_source = dialect == "jsonc" ? strip_json_comments(source) : source
|
|
43
|
+
allows_comments = dialect == "jsonc"
|
|
44
|
+
return parse_failure("Trailing commas are not supported for #{dialect}.") if detect_trailing_comma(normalized_source)
|
|
45
|
+
|
|
46
|
+
parsed = JSON.parse(normalized_source)
|
|
47
|
+
canonical = JSON.generate(parsed)
|
|
48
|
+
analysis = {
|
|
49
|
+
kind: "json",
|
|
50
|
+
dialect: dialect,
|
|
51
|
+
allows_comments: allows_comments,
|
|
52
|
+
normalized_source: canonical,
|
|
53
|
+
root_kind: json_root_kind(parsed),
|
|
54
|
+
owners: collect_json_owners(parsed)
|
|
55
|
+
}
|
|
56
|
+
{
|
|
57
|
+
ok: true,
|
|
58
|
+
diagnostics: [],
|
|
59
|
+
analysis: analysis
|
|
60
|
+
}
|
|
61
|
+
rescue JSON::ParserError => e
|
|
62
|
+
parse_failure(e.message)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def match_json_owners(template, destination)
|
|
66
|
+
destination_paths = destination[:owners].to_h { |owner| [owner[:path], true] }
|
|
67
|
+
template_paths = template[:owners].to_h { |owner| [owner[:path], true] }
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
matched: template[:owners].filter_map do |owner|
|
|
71
|
+
next unless destination_paths[owner[:path]]
|
|
72
|
+
|
|
73
|
+
{ template_path: owner[:path], destination_path: owner[:path] }
|
|
74
|
+
end,
|
|
75
|
+
unmatched_template: template[:owners].map { |owner| owner[:path] }.reject { |path| destination_paths[path] },
|
|
76
|
+
unmatched_destination: destination[:owners].map { |owner| owner[:path] }.reject { |path| template_paths[path] }
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def merge_json(template_source, destination_source, dialect)
|
|
81
|
+
template_result = parse_json(template_source, dialect)
|
|
82
|
+
return { ok: false, diagnostics: template_result[:diagnostics] } unless template_result[:ok]
|
|
83
|
+
|
|
84
|
+
destination_result = parse_json(destination_source, dialect)
|
|
85
|
+
if destination_result[:ok]
|
|
86
|
+
output = JSON.generate(
|
|
87
|
+
merge_json_values(
|
|
88
|
+
JSON.parse(template_result.dig(:analysis, :normalized_source)),
|
|
89
|
+
JSON.parse(destination_result.dig(:analysis, :normalized_source))
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
diagnostics: [],
|
|
95
|
+
output: output,
|
|
96
|
+
policies: [DESTINATION_WINS_ARRAY_POLICY]
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
fallback_source = try_destination_trailing_comma_fallback(destination_source)
|
|
101
|
+
if fallback_source
|
|
102
|
+
retried = parse_json(fallback_source, dialect)
|
|
103
|
+
if retried[:ok]
|
|
104
|
+
output = JSON.generate(
|
|
105
|
+
merge_json_values(
|
|
106
|
+
JSON.parse(template_result.dig(:analysis, :normalized_source)),
|
|
107
|
+
JSON.parse(retried.dig(:analysis, :normalized_source))
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
return {
|
|
111
|
+
ok: true,
|
|
112
|
+
diagnostics: [
|
|
113
|
+
fallback_applied("stripped trailing commas from destination before retrying json merge.")
|
|
114
|
+
],
|
|
115
|
+
output: output,
|
|
116
|
+
policies: [DESTINATION_WINS_ARRAY_POLICY, TRAILING_COMMA_FALLBACK_POLICY]
|
|
117
|
+
}
|
|
118
|
+
end
|
|
69
119
|
end
|
|
120
|
+
|
|
121
|
+
{
|
|
122
|
+
ok: false,
|
|
123
|
+
diagnostics: destination_result[:diagnostics].map do |diagnostic|
|
|
124
|
+
diagnostic[:category] == "parse_error" ? diagnostic.merge(category: "destination_parse_error") : diagnostic
|
|
125
|
+
end
|
|
126
|
+
}
|
|
70
127
|
end
|
|
71
128
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# puts "Template syntax error: #{e.message}"
|
|
80
|
-
# e.errors.each do |error|
|
|
81
|
-
# puts " #{error.message}"
|
|
82
|
-
# end
|
|
83
|
-
# end
|
|
84
|
-
class TemplateParseError < ParseError; end
|
|
85
|
-
|
|
86
|
-
# Raised when the destination file has syntax errors.
|
|
87
|
-
#
|
|
88
|
-
# @example Handling destination parse errors
|
|
89
|
-
# begin
|
|
90
|
-
# merger = SmartMerger.new(template, destination)
|
|
91
|
-
# result = merger.merge
|
|
92
|
-
# rescue DestinationParseError => e
|
|
93
|
-
# puts "Destination syntax error: #{e.message}"
|
|
94
|
-
# e.errors.each do |error|
|
|
95
|
-
# puts " #{error.message}"
|
|
96
|
-
# end
|
|
97
|
-
# end
|
|
98
|
-
class DestinationParseError < ParseError; end
|
|
99
|
-
|
|
100
|
-
autoload :DebugLogger, "json/merge/debug_logger"
|
|
101
|
-
autoload :Emitter, "json/merge/emitter"
|
|
102
|
-
autoload :FileAnalysis, "json/merge/file_analysis"
|
|
103
|
-
autoload :MergeResult, "json/merge/merge_result"
|
|
104
|
-
autoload :NodeWrapper, "json/merge/node_wrapper"
|
|
105
|
-
autoload :ConflictResolver, "json/merge/conflict_resolver"
|
|
106
|
-
autoload :SmartMerger, "json/merge/smart_merger"
|
|
107
|
-
autoload :ObjectMatchRefiner, "json/merge/object_match_refiner"
|
|
108
|
-
end
|
|
109
|
-
end
|
|
129
|
+
def parse_failure(message)
|
|
130
|
+
{
|
|
131
|
+
ok: false,
|
|
132
|
+
diagnostics: [parse_error(message)]
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
private_class_method :parse_failure
|
|
110
136
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
137
|
+
def unsupported_jsonc_language_pack_result
|
|
138
|
+
{
|
|
139
|
+
ok: false,
|
|
140
|
+
diagnostics: [
|
|
141
|
+
unsupported_feature("tree-sitter-language-pack json parsing currently supports only the json dialect.")
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
private_class_method :unsupported_jsonc_language_pack_result
|
|
146
|
+
|
|
147
|
+
def parse_error(message)
|
|
148
|
+
{ severity: "error", category: "parse_error", message: message }
|
|
149
|
+
end
|
|
150
|
+
private_class_method :parse_error
|
|
151
|
+
|
|
152
|
+
def unsupported_feature(message)
|
|
153
|
+
{ severity: "error", category: "unsupported_feature", message: message }
|
|
154
|
+
end
|
|
155
|
+
private_class_method :unsupported_feature
|
|
156
|
+
|
|
157
|
+
def fallback_applied(message)
|
|
158
|
+
{ severity: "warning", category: "fallback_applied", message: message }
|
|
159
|
+
end
|
|
160
|
+
private_class_method :fallback_applied
|
|
161
|
+
|
|
162
|
+
def json_root_kind(value)
|
|
163
|
+
return "object" if value.is_a?(Hash)
|
|
164
|
+
return "array" if value.is_a?(Array)
|
|
165
|
+
|
|
166
|
+
"scalar"
|
|
167
|
+
end
|
|
168
|
+
private_class_method :json_root_kind
|
|
169
|
+
|
|
170
|
+
def collect_json_owners(value, path = "")
|
|
171
|
+
if value.is_a?(Hash)
|
|
172
|
+
value.keys.sort.flat_map do |key|
|
|
173
|
+
next_path = "#{path}/#{key}"
|
|
174
|
+
[{ path: next_path, owner_kind: "member", match_key: key }] + collect_json_owners(value[key], next_path)
|
|
175
|
+
end
|
|
176
|
+
elsif value.is_a?(Array)
|
|
177
|
+
value.each_with_index.flat_map do |item, index|
|
|
178
|
+
next_path = "#{path}/#{index}"
|
|
179
|
+
[{ path: next_path, owner_kind: "element" }] + collect_json_owners(item, next_path)
|
|
180
|
+
end
|
|
181
|
+
else
|
|
182
|
+
[]
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
private_class_method :collect_json_owners
|
|
186
|
+
|
|
187
|
+
def merge_json_values(template, destination)
|
|
188
|
+
if template.is_a?(Hash) && destination.is_a?(Hash)
|
|
189
|
+
ordered_merge_keys(template, destination).each_with_object({}) do |key, merged|
|
|
190
|
+
if !template.key?(key)
|
|
191
|
+
merged[key] = destination[key]
|
|
192
|
+
elsif !destination.key?(key)
|
|
193
|
+
merged[key] = template[key]
|
|
194
|
+
else
|
|
195
|
+
merged[key] = merge_json_values(template[key], destination[key])
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
else
|
|
199
|
+
destination
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
private_class_method :merge_json_values
|
|
122
203
|
|
|
123
|
-
|
|
124
|
-
|
|
204
|
+
def ordered_merge_keys(template, destination)
|
|
205
|
+
template.keys + destination.keys.reject { |key| template.key?(key) }
|
|
206
|
+
end
|
|
207
|
+
private_class_method :ordered_merge_keys
|
|
208
|
+
|
|
209
|
+
def detect_trailing_comma(source)
|
|
210
|
+
state = scanner_state
|
|
211
|
+
source.each_char.with_index do |char, index|
|
|
212
|
+
next_char = source[index + 1]
|
|
213
|
+
advance_scanner_state(state, char, next_char)
|
|
214
|
+
next if state[:in_line_comment] || state[:in_block_comment] || state[:in_string]
|
|
215
|
+
|
|
216
|
+
if char == ","
|
|
217
|
+
lookahead = source[(index + 1)..]
|
|
218
|
+
next unless lookahead
|
|
219
|
+
|
|
220
|
+
trimmed = lookahead.lstrip
|
|
221
|
+
return true if trimmed.start_with?("]", "}")
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
false
|
|
225
|
+
end
|
|
226
|
+
private_class_method :detect_trailing_comma
|
|
227
|
+
|
|
228
|
+
def strip_json_comments(source)
|
|
229
|
+
result = +""
|
|
230
|
+
state = scanner_state
|
|
231
|
+
index = 0
|
|
232
|
+
while index < source.length
|
|
233
|
+
char = source[index]
|
|
234
|
+
next_char = source[index + 1]
|
|
235
|
+
|
|
236
|
+
if state[:in_line_comment]
|
|
237
|
+
if char == "\n"
|
|
238
|
+
state[:in_line_comment] = false
|
|
239
|
+
result << "\n"
|
|
240
|
+
end
|
|
241
|
+
index += 1
|
|
242
|
+
next
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
if state[:in_block_comment]
|
|
246
|
+
if char == "*" && next_char == "/"
|
|
247
|
+
state[:in_block_comment] = false
|
|
248
|
+
index += 2
|
|
249
|
+
next
|
|
250
|
+
end
|
|
251
|
+
index += 1
|
|
252
|
+
next
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if state[:in_string]
|
|
256
|
+
result << char
|
|
257
|
+
if state[:escaped]
|
|
258
|
+
state[:escaped] = false
|
|
259
|
+
elsif char == "\\"
|
|
260
|
+
state[:escaped] = true
|
|
261
|
+
elsif char == "\""
|
|
262
|
+
state[:in_string] = false
|
|
263
|
+
end
|
|
264
|
+
index += 1
|
|
265
|
+
next
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
if char == "\""
|
|
269
|
+
state[:in_string] = true
|
|
270
|
+
result << char
|
|
271
|
+
index += 1
|
|
272
|
+
next
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
if char == "/" && next_char == "/"
|
|
276
|
+
state[:in_line_comment] = true
|
|
277
|
+
index += 2
|
|
278
|
+
next
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
if char == "/" && next_char == "*"
|
|
282
|
+
state[:in_block_comment] = true
|
|
283
|
+
index += 2
|
|
284
|
+
next
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
result << char
|
|
288
|
+
index += 1
|
|
289
|
+
end
|
|
290
|
+
result
|
|
291
|
+
end
|
|
292
|
+
private_class_method :strip_json_comments
|
|
293
|
+
|
|
294
|
+
def try_destination_trailing_comma_fallback(source)
|
|
295
|
+
stripped = strip_trailing_commas(source)
|
|
296
|
+
return nil if stripped == source
|
|
297
|
+
|
|
298
|
+
stripped
|
|
299
|
+
end
|
|
300
|
+
private_class_method :try_destination_trailing_comma_fallback
|
|
301
|
+
|
|
302
|
+
def strip_trailing_commas(source)
|
|
303
|
+
result = +""
|
|
304
|
+
state = scanner_state
|
|
305
|
+
source.each_char.with_index do |char, index|
|
|
306
|
+
next_char = source[index + 1]
|
|
307
|
+
|
|
308
|
+
if state[:in_line_comment]
|
|
309
|
+
result << char
|
|
310
|
+
state[:in_line_comment] = false if char == "\n"
|
|
311
|
+
next
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if state[:in_block_comment]
|
|
315
|
+
result << char
|
|
316
|
+
if char == "*" && next_char == "/"
|
|
317
|
+
result << next_char
|
|
318
|
+
state[:in_block_comment] = false
|
|
319
|
+
end
|
|
320
|
+
next
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
if state[:in_string]
|
|
324
|
+
result << char
|
|
325
|
+
if state[:escaped]
|
|
326
|
+
state[:escaped] = false
|
|
327
|
+
elsif char == "\\"
|
|
328
|
+
state[:escaped] = true
|
|
329
|
+
elsif char == "\""
|
|
330
|
+
state[:in_string] = false
|
|
331
|
+
end
|
|
332
|
+
next
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
if char == "\""
|
|
336
|
+
state[:in_string] = true
|
|
337
|
+
result << char
|
|
338
|
+
next
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
if char == "/" && next_char == "/"
|
|
342
|
+
state[:in_line_comment] = true
|
|
343
|
+
result << char
|
|
344
|
+
next
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
if char == "/" && next_char == "*"
|
|
348
|
+
state[:in_block_comment] = true
|
|
349
|
+
result << char
|
|
350
|
+
next
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
if char == ","
|
|
354
|
+
lookahead = source[(index + 1)..]
|
|
355
|
+
trimmed = lookahead&.lstrip
|
|
356
|
+
next if trimmed&.start_with?("]", "}")
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
result << char
|
|
360
|
+
end
|
|
361
|
+
result
|
|
362
|
+
end
|
|
363
|
+
private_class_method :strip_trailing_commas
|
|
364
|
+
|
|
365
|
+
def scanner_state
|
|
366
|
+
{
|
|
367
|
+
in_string: false,
|
|
368
|
+
in_line_comment: false,
|
|
369
|
+
in_block_comment: false,
|
|
370
|
+
escaped: false
|
|
371
|
+
}
|
|
372
|
+
end
|
|
373
|
+
private_class_method :scanner_state
|
|
374
|
+
|
|
375
|
+
def advance_scanner_state(state, char, next_char)
|
|
376
|
+
if state[:in_line_comment]
|
|
377
|
+
state[:in_line_comment] = false if char == "\n"
|
|
378
|
+
return
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
if state[:in_block_comment]
|
|
382
|
+
state[:in_block_comment] = false if char == "*" && next_char == "/"
|
|
383
|
+
return
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
if state[:in_string]
|
|
387
|
+
if state[:escaped]
|
|
388
|
+
state[:escaped] = false
|
|
389
|
+
elsif char == "\\"
|
|
390
|
+
state[:escaped] = true
|
|
391
|
+
elsif char == "\""
|
|
392
|
+
state[:in_string] = false
|
|
393
|
+
end
|
|
394
|
+
return
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
if char == "\""
|
|
398
|
+
state[:in_string] = true
|
|
399
|
+
elsif char == "/" && next_char == "/"
|
|
400
|
+
state[:in_line_comment] = true
|
|
401
|
+
elsif char == "/" && next_char == "*"
|
|
402
|
+
state[:in_block_comment] = true
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
private_class_method :advance_scanner_state
|
|
406
|
+
end
|
|
125
407
|
end
|
data/lib/json-merge.rb
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
# See: https://github.com/fxn/zeitwerk#for_gem_extension
|
|
5
|
-
# Hook for other libraries to load this library (e.g. via bundler)
|
|
6
|
-
require "json/merge"
|
|
3
|
+
require_relative "json/merge"
|
data.tar.gz.sig
CHANGED
|
Binary file
|