dotenv-merge 1.0.0 → 1.0.1
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/CHANGELOG.md +31 -1
- data/LICENSE.txt +1 -1
- data/README.md +166 -270
- data/lib/dotenv/merge/env_line.rb +22 -15
- data/lib/dotenv/merge/file_analysis.rb +3 -1
- data/lib/dotenv/merge/merge_result.rb +3 -2
- data/lib/dotenv/merge/smart_merger.rb +123 -50
- data/lib/dotenv/merge/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +13 -7
- metadata.gz.sig +0 -0
|
@@ -6,7 +6,8 @@ module Dotenv
|
|
|
6
6
|
# Parses and categorizes lines as assignments, comments, blank lines, or invalid.
|
|
7
7
|
#
|
|
8
8
|
# Inherits from Ast::Merge::AstNode for a normalized API across all ast-merge
|
|
9
|
-
# content nodes. This provides
|
|
9
|
+
# content nodes. This provides TreeHaver::Node protocol compatibility including
|
|
10
|
+
# #slice, #location, #unwrap, #type, #text, and other standard methods.
|
|
10
11
|
#
|
|
11
12
|
# Dotenv files follow a simple format where each line is one of:
|
|
12
13
|
# - `KEY=value` - Environment variable assignment
|
|
@@ -46,7 +47,7 @@ module Dotenv
|
|
|
46
47
|
attr_reader :line_number
|
|
47
48
|
|
|
48
49
|
# @return [Symbol, nil] The line type (:assignment, :comment, :blank, :invalid)
|
|
49
|
-
attr_reader :
|
|
50
|
+
attr_reader :line_type
|
|
50
51
|
|
|
51
52
|
# @return [String, nil] The environment variable key (for assignments)
|
|
52
53
|
attr_reader :key
|
|
@@ -64,7 +65,7 @@ module Dotenv
|
|
|
64
65
|
def initialize(raw, line_number)
|
|
65
66
|
@raw = raw
|
|
66
67
|
@line_number = line_number
|
|
67
|
-
@
|
|
68
|
+
@line_type = nil
|
|
68
69
|
@key = nil
|
|
69
70
|
@value = nil
|
|
70
71
|
@export = false
|
|
@@ -80,11 +81,17 @@ module Dotenv
|
|
|
80
81
|
super(slice: @raw, location: location)
|
|
81
82
|
end
|
|
82
83
|
|
|
84
|
+
# TreeHaver::Node protocol: type
|
|
85
|
+
# @return [String] "env_line"
|
|
86
|
+
def type
|
|
87
|
+
"env_line"
|
|
88
|
+
end
|
|
89
|
+
|
|
83
90
|
# Generate a unique signature for this line (used for merge matching)
|
|
84
91
|
#
|
|
85
92
|
# @return [Array<Symbol, String>, nil] Signature array [:env, key] for assignments, nil otherwise
|
|
86
93
|
def signature
|
|
87
|
-
return unless @
|
|
94
|
+
return unless @line_type == :assignment
|
|
88
95
|
|
|
89
96
|
[:env, @key]
|
|
90
97
|
end
|
|
@@ -93,28 +100,28 @@ module Dotenv
|
|
|
93
100
|
#
|
|
94
101
|
# @return [Boolean] true if the line is a valid KEY=value assignment
|
|
95
102
|
def assignment?
|
|
96
|
-
@
|
|
103
|
+
@line_type == :assignment
|
|
97
104
|
end
|
|
98
105
|
|
|
99
106
|
# Check if this line is a comment
|
|
100
107
|
#
|
|
101
108
|
# @return [Boolean] true if the line starts with #
|
|
102
109
|
def comment?
|
|
103
|
-
@
|
|
110
|
+
@line_type == :comment
|
|
104
111
|
end
|
|
105
112
|
|
|
106
113
|
# Check if this line is blank (empty or whitespace only)
|
|
107
114
|
#
|
|
108
115
|
# @return [Boolean] true if the line is blank
|
|
109
116
|
def blank?
|
|
110
|
-
@
|
|
117
|
+
@line_type == :blank
|
|
111
118
|
end
|
|
112
119
|
|
|
113
120
|
# Check if this line is invalid (unparseable)
|
|
114
121
|
#
|
|
115
122
|
# @return [Boolean] true if the line could not be parsed
|
|
116
123
|
def invalid?
|
|
117
|
-
@
|
|
124
|
+
@line_type == :invalid
|
|
118
125
|
end
|
|
119
126
|
|
|
120
127
|
# Check if this line has the export prefix
|
|
@@ -144,20 +151,20 @@ module Dotenv
|
|
|
144
151
|
#
|
|
145
152
|
# @return [String] A debug representation of this EnvLine
|
|
146
153
|
def inspect
|
|
147
|
-
"#<#{self.class.name} line=#{@line_number}
|
|
154
|
+
"#<#{self.class.name} line=#{@line_number} line_type=#{@line_type} key=#{@key.inspect}>"
|
|
148
155
|
end
|
|
149
156
|
|
|
150
157
|
private
|
|
151
158
|
|
|
152
|
-
# Parse the raw line content and set
|
|
159
|
+
# Parse the raw line content and set line_type, key, value, and export
|
|
153
160
|
#
|
|
154
161
|
# @return [void]
|
|
155
162
|
def parse!
|
|
156
163
|
stripped = @raw.strip
|
|
157
164
|
if stripped.empty?
|
|
158
|
-
@
|
|
165
|
+
@line_type = :blank
|
|
159
166
|
elsif stripped.start_with?("#")
|
|
160
|
-
@
|
|
167
|
+
@line_type = :comment
|
|
161
168
|
else
|
|
162
169
|
parse_assignment!(stripped)
|
|
163
170
|
end
|
|
@@ -178,14 +185,14 @@ module Dotenv
|
|
|
178
185
|
key_part, value_part = line.split("=", 2)
|
|
179
186
|
key_part = key_part.strip
|
|
180
187
|
if valid_key?(key_part)
|
|
181
|
-
@
|
|
188
|
+
@line_type = :assignment
|
|
182
189
|
@key = key_part
|
|
183
190
|
@value = unquote(value_part || "")
|
|
184
191
|
else
|
|
185
|
-
@
|
|
192
|
+
@line_type = :invalid
|
|
186
193
|
end
|
|
187
194
|
else
|
|
188
|
-
@
|
|
195
|
+
@line_type = :invalid
|
|
189
196
|
end
|
|
190
197
|
end
|
|
191
198
|
|
|
@@ -33,10 +33,12 @@ module Dotenv
|
|
|
33
33
|
# @param source [String] Dotenv source code to analyze
|
|
34
34
|
# @param freeze_token [String] Token for freeze block markers (default: "dotenv-merge")
|
|
35
35
|
# @param signature_generator [Proc, nil] Custom signature generator
|
|
36
|
-
|
|
36
|
+
# @param options [Hash] Additional options (forward compatibility - ignored by FileAnalysis)
|
|
37
|
+
def initialize(source, freeze_token: DEFAULT_FREEZE_TOKEN, signature_generator: nil, **options)
|
|
37
38
|
@source = source
|
|
38
39
|
@freeze_token = freeze_token
|
|
39
40
|
@signature_generator = signature_generator
|
|
41
|
+
# **options captures any additional parameters (e.g., node_typing) for forward compatibility
|
|
40
42
|
|
|
41
43
|
# Parse all lines
|
|
42
44
|
@lines = parse_lines(source)
|
|
@@ -35,8 +35,9 @@ module Dotenv
|
|
|
35
35
|
# Initialize a new merge result
|
|
36
36
|
# @param template_analysis [FileAnalysis] Analysis of the template file
|
|
37
37
|
# @param dest_analysis [FileAnalysis] Analysis of the destination file
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
# @param options [Hash] Additional options for forward compatibility
|
|
39
|
+
def initialize(template_analysis, dest_analysis, **options)
|
|
40
|
+
super(template_analysis: template_analysis, dest_analysis: dest_analysis, **options)
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
# Add content from the template at the given statement index
|
|
@@ -19,78 +19,108 @@ module Dotenv
|
|
|
19
19
|
# add_template_only_nodes: true,
|
|
20
20
|
# )
|
|
21
21
|
# result = merger.merge
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
#
|
|
23
|
+
# @example With node_typing for per-node-type preferences
|
|
24
|
+
# merger = SmartMerger.new(template, dest,
|
|
25
|
+
# node_typing: { "EnvLine" => ->(n) { NodeTyping.with_merge_type(n, :secret) } },
|
|
26
|
+
# preference: { default: :destination, secret: :template })
|
|
27
|
+
class SmartMerger < ::Ast::Merge::SmartMergerBase
|
|
29
28
|
# Initialize a new SmartMerger
|
|
30
29
|
#
|
|
31
30
|
# @param template_content [String] Content of the template dotenv file
|
|
32
31
|
# @param dest_content [String] Content of the destination dotenv file
|
|
33
|
-
# @param
|
|
34
|
-
#
|
|
32
|
+
# @param signature_generator [Proc, nil] Custom signature generator
|
|
33
|
+
# @param preference [Symbol, Hash] :destination, :template, or per-type Hash
|
|
35
34
|
# @param add_template_only_nodes [Boolean] Whether to add template-only env vars
|
|
36
35
|
# (default: false)
|
|
37
36
|
# @param freeze_token [String] Token for freeze block markers
|
|
38
37
|
# (default: "dotenv-merge")
|
|
39
|
-
# @param
|
|
38
|
+
# @param match_refiner [#call, nil] Match refiner for fuzzy matching
|
|
39
|
+
# @param regions [Array<Hash>, nil] Region configurations for nested merging
|
|
40
|
+
# @param region_placeholder [String, nil] Custom placeholder for regions
|
|
41
|
+
# @param node_typing [Hash{Symbol,String => #call}, nil] Node typing configuration
|
|
42
|
+
# for per-node-type merge preferences
|
|
43
|
+
# @param options [Hash] Additional options for forward compatibility
|
|
40
44
|
def initialize(
|
|
41
45
|
template_content,
|
|
42
46
|
dest_content,
|
|
47
|
+
signature_generator: nil,
|
|
43
48
|
preference: :destination,
|
|
44
49
|
add_template_only_nodes: false,
|
|
45
|
-
freeze_token:
|
|
46
|
-
|
|
50
|
+
freeze_token: nil,
|
|
51
|
+
match_refiner: nil,
|
|
52
|
+
regions: nil,
|
|
53
|
+
region_placeholder: nil,
|
|
54
|
+
node_typing: nil,
|
|
55
|
+
**options
|
|
47
56
|
)
|
|
48
|
-
|
|
49
|
-
@add_template_only_nodes = add_template_only_nodes
|
|
50
|
-
|
|
51
|
-
# Parse template
|
|
52
|
-
@template_analysis = FileAnalysis.new(
|
|
57
|
+
super(
|
|
53
58
|
template_content,
|
|
54
|
-
freeze_token: freeze_token,
|
|
55
|
-
signature_generator: signature_generator,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
# Parse destination
|
|
59
|
-
@dest_analysis = FileAnalysis.new(
|
|
60
59
|
dest_content,
|
|
61
|
-
freeze_token: freeze_token,
|
|
62
60
|
signature_generator: signature_generator,
|
|
61
|
+
preference: preference,
|
|
62
|
+
add_template_only_nodes: add_template_only_nodes,
|
|
63
|
+
freeze_token: freeze_token,
|
|
64
|
+
match_refiner: match_refiner,
|
|
65
|
+
regions: regions,
|
|
66
|
+
region_placeholder: region_placeholder,
|
|
67
|
+
node_typing: node_typing,
|
|
68
|
+
**options
|
|
63
69
|
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
protected
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
# @return [Class] The analysis class for dotenv files
|
|
75
|
+
def analysis_class
|
|
76
|
+
FileAnalysis
|
|
66
77
|
end
|
|
67
78
|
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
# @return [String] The default freeze token
|
|
80
|
+
def default_freeze_token
|
|
81
|
+
"dotenv-merge"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Class, nil] No separate resolver class for dotenv
|
|
85
|
+
def resolver_class
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Class, nil] Result class (built with analysis args)
|
|
90
|
+
def result_class
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Build the result with required analysis arguments
|
|
95
|
+
def build_result
|
|
96
|
+
MergeResult.new(@template_analysis, @dest_analysis)
|
|
73
97
|
end
|
|
74
98
|
|
|
75
|
-
#
|
|
99
|
+
# @return [Class] The template parse error class for dotenv
|
|
100
|
+
def template_parse_error_class
|
|
101
|
+
ParseError
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @return [Class] The destination parse error class for dotenv
|
|
105
|
+
def destination_parse_error_class
|
|
106
|
+
ParseError
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Perform the dotenv-specific merge with custom alignment logic
|
|
76
110
|
#
|
|
77
|
-
# @return [MergeResult] The merge result
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
alignment
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
process_alignment(alignment)
|
|
92
|
-
@result
|
|
93
|
-
end
|
|
111
|
+
# @return [MergeResult] The merge result
|
|
112
|
+
def perform_merge
|
|
113
|
+
alignment = align_statements
|
|
114
|
+
|
|
115
|
+
DebugLogger.debug("Alignment complete", {
|
|
116
|
+
total_entries: alignment.size,
|
|
117
|
+
matches: alignment.count { |e| e[:type] == :match },
|
|
118
|
+
template_only: alignment.count { |e| e[:type] == :template_only },
|
|
119
|
+
dest_only: alignment.count { |e| e[:type] == :dest_only },
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
process_alignment(alignment)
|
|
123
|
+
@result
|
|
94
124
|
end
|
|
95
125
|
|
|
96
126
|
private
|
|
@@ -211,8 +241,10 @@ module Dotenv
|
|
|
211
241
|
return
|
|
212
242
|
end
|
|
213
243
|
|
|
214
|
-
#
|
|
215
|
-
|
|
244
|
+
# Resolve preference (handles both Symbol and Hash preferences)
|
|
245
|
+
resolved_pref = resolve_preference(entry[:template_stmt], entry[:dest_stmt])
|
|
246
|
+
|
|
247
|
+
case resolved_pref
|
|
216
248
|
when :template
|
|
217
249
|
@result.add_from_template(entry[:template_index], decision: MergeResult::DECISION_TEMPLATE)
|
|
218
250
|
when :destination
|
|
@@ -222,6 +254,47 @@ module Dotenv
|
|
|
222
254
|
end
|
|
223
255
|
end
|
|
224
256
|
|
|
257
|
+
# Resolve preference for a matched pair
|
|
258
|
+
# @param template_stmt [Object] Template statement
|
|
259
|
+
# @param dest_stmt [Object] Destination statement
|
|
260
|
+
# @return [Symbol] :template or :destination
|
|
261
|
+
def resolve_preference(template_stmt, dest_stmt)
|
|
262
|
+
return @preference if @preference.is_a?(Symbol)
|
|
263
|
+
|
|
264
|
+
# Hash preference - check for node_typing-based merge_types
|
|
265
|
+
if @preference.is_a?(Hash)
|
|
266
|
+
# Apply node_typing if configured
|
|
267
|
+
typed_template = apply_node_typing(template_stmt)
|
|
268
|
+
apply_node_typing(dest_stmt)
|
|
269
|
+
|
|
270
|
+
# Check template merge_type first
|
|
271
|
+
if Ast::Merge::NodeTyping.typed_node?(typed_template)
|
|
272
|
+
merge_type = typed_template.merge_type
|
|
273
|
+
return @preference[merge_type] if @preference.key?(merge_type)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Fall back to default
|
|
277
|
+
return @preference[:default] || :destination
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
:destination
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Apply node typing to a statement if node_typing is configured
|
|
284
|
+
# @param stmt [Object] The statement
|
|
285
|
+
# @return [Object] The statement, possibly wrapped with merge_type
|
|
286
|
+
def apply_node_typing(stmt)
|
|
287
|
+
return stmt unless @node_typing
|
|
288
|
+
return stmt unless stmt
|
|
289
|
+
|
|
290
|
+
# Check by class name
|
|
291
|
+
type_key = stmt.class.name&.split("::")&.last
|
|
292
|
+
callable = @node_typing[type_key] || @node_typing[type_key&.to_sym]
|
|
293
|
+
return callable.call(stmt) if callable
|
|
294
|
+
|
|
295
|
+
stmt
|
|
296
|
+
end
|
|
297
|
+
|
|
225
298
|
# Process a template-only entry
|
|
226
299
|
# @param entry [Hash] Alignment entry
|
|
227
300
|
# @return [void]
|
data/lib/dotenv/merge/version.rb
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dotenv-merge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -43,14 +43,20 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '2.0'
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: 2.0.6
|
|
47
50
|
type: :runtime
|
|
48
51
|
prerelease: false
|
|
49
52
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
53
|
requirements:
|
|
51
54
|
- - "~>"
|
|
52
55
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
56
|
+
version: '2.0'
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 2.0.6
|
|
54
60
|
- !ruby/object:Gem::Dependency
|
|
55
61
|
name: version_gem
|
|
56
62
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -266,10 +272,10 @@ licenses:
|
|
|
266
272
|
- MIT
|
|
267
273
|
metadata:
|
|
268
274
|
homepage_uri: https://dotenv-merge.galtzo.com/
|
|
269
|
-
source_code_uri: https://github.com/kettle-rb/dotenv-merge/tree/v1.0.
|
|
270
|
-
changelog_uri: https://github.com/kettle-rb/dotenv-merge/blob/v1.0.
|
|
275
|
+
source_code_uri: https://github.com/kettle-rb/dotenv-merge/tree/v1.0.1
|
|
276
|
+
changelog_uri: https://github.com/kettle-rb/dotenv-merge/blob/v1.0.1/CHANGELOG.md
|
|
271
277
|
bug_tracker_uri: https://github.com/kettle-rb/dotenv-merge/issues
|
|
272
|
-
documentation_uri: https://www.rubydoc.info/gems/dotenv-merge/1.0.
|
|
278
|
+
documentation_uri: https://www.rubydoc.info/gems/dotenv-merge/1.0.1
|
|
273
279
|
funding_uri: https://github.com/sponsors/pboling
|
|
274
280
|
wiki_uri: https://github.com/kettle-rb/dotenv-merge/wiki
|
|
275
281
|
news_uri: https://www.railsbling.com/tags/dotenv-merge
|
|
@@ -298,7 +304,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
298
304
|
- !ruby/object:Gem::Version
|
|
299
305
|
version: '0'
|
|
300
306
|
requirements: []
|
|
301
|
-
rubygems_version: 4.0.
|
|
307
|
+
rubygems_version: 4.0.3
|
|
302
308
|
specification_version: 4
|
|
303
309
|
summary: "☯️ Intelligent .env file merging using structured parsing"
|
|
304
310
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|