dotenv-merge 1.0.2 → 7.1.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/LICENSE.md +13 -0
- data/README.md +101 -515
- data/lib/dotenv/merge/backend.rb +70 -0
- data/lib/dotenv/merge/comment_tracker.rb +112 -0
- data/lib/dotenv/merge/debug_logger.rb +2 -2
- data/lib/dotenv/merge/env_line.rb +10 -14
- data/lib/dotenv/merge/file_analysis.rb +135 -24
- data/lib/dotenv/merge/freeze_node.rb +1 -1
- data/lib/dotenv/merge/merge_result.rb +6 -6
- data/lib/dotenv/merge/smart_merger.rb +360 -137
- data/lib/dotenv/merge/version.rb +5 -4
- data/lib/dotenv/merge.rb +22 -18
- data/lib/dotenv-merge.rb +9 -1
- data/sig/dotenv/merge.rbs +3 -207
- data.tar.gz.sig +0 -0
- metadata +114 -76
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -95
- 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/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/sig/dotenv/merge/env_line.rbs +0 -90
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dotenv
|
|
4
|
+
# StructuredMerge dotenv assignment and comment merge behavior.
|
|
5
|
+
module Merge
|
|
6
|
+
BACKEND_REFERENCE = TreeHaver::BackendReference.new(id: 'dotenv-line', family: 'line')
|
|
7
|
+
|
|
8
|
+
module Backend
|
|
9
|
+
# TreeHaver language wrapper for dotenv files parsed through the line
|
|
10
|
+
# substrate supplied by plain-merge.
|
|
11
|
+
class Language < TreeHaver::Base::Language
|
|
12
|
+
def initialize(name = :dotenv)
|
|
13
|
+
super(name, backend: :line, options: {})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.dotenv
|
|
17
|
+
new(:dotenv)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.env
|
|
21
|
+
new(:env)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Parser adapter that exposes Dotenv::Merge::FileAnalysis via
|
|
26
|
+
# TreeHaver.parser_for(:dotenv, backend_type: :line).
|
|
27
|
+
class Parser < TreeHaver::Base::Parser
|
|
28
|
+
def parse(source)
|
|
29
|
+
raise 'Language not set' unless language
|
|
30
|
+
|
|
31
|
+
attach_line_analysis(Dotenv::Merge::FileAnalysis.new(source), source)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def parse_string(_old_tree, source)
|
|
35
|
+
parse(source)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def attach_line_analysis(analysis, source)
|
|
41
|
+
analysis.instance_variable_set(:@line_analysis, plain_line_analysis(source))
|
|
42
|
+
analysis.define_singleton_method(:line_analysis) { @line_analysis }
|
|
43
|
+
analysis
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def plain_line_analysis(source)
|
|
47
|
+
Plain::Merge.analyze_text(source)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.register_backend!
|
|
53
|
+
TreeHaver::BackendRegistry.register(BACKEND_REFERENCE)
|
|
54
|
+
%i[dotenv env].each do |language_name|
|
|
55
|
+
register_language!(language_name)
|
|
56
|
+
end
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.register_language!(language_name)
|
|
61
|
+
TreeHaver.register_language(
|
|
62
|
+
language_name,
|
|
63
|
+
backend_module: Backend,
|
|
64
|
+
backend_type: :line,
|
|
65
|
+
gem_name: 'dotenv-merge',
|
|
66
|
+
contract: :line
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dotenv
|
|
4
|
+
module Merge
|
|
5
|
+
# Extracts and tracks dotenv comments with their line numbers from source.
|
|
6
|
+
#
|
|
7
|
+
# Inherits shared lookup, query, region-building, and attachment API from
|
|
8
|
+
# +Ast::Merge::Comment::HashTrackerBase+. Only format-specific comment
|
|
9
|
+
# extraction and owner resolution are overridden here.
|
|
10
|
+
#
|
|
11
|
+
# Dotenv supports hash-style comments as either:
|
|
12
|
+
# - full-line comments (`# comment`)
|
|
13
|
+
# - safe inline comments on unquoted assignments (`KEY=value # comment`)
|
|
14
|
+
#
|
|
15
|
+
# This adapter intentionally stays conservative around quoted values. `#`
|
|
16
|
+
# inside quoted values is not treated as a comment, and quoted assignments
|
|
17
|
+
# with trailing comment-like text remain literal value content. That is a
|
|
18
|
+
# deliberate parser boundary for dotenv-merge, not a pending comment-matrix
|
|
19
|
+
# bug to "fix" later without an explicit product decision.
|
|
20
|
+
class CommentTracker < Ast::Merge::Comment::HashTrackerBase
|
|
21
|
+
def initialize(source_or_lines)
|
|
22
|
+
@line_objects = normalize_line_objects(source_or_lines)
|
|
23
|
+
@line_parser = Ast::Merge::Comment::QuotedHashLineParser.new
|
|
24
|
+
super(@line_objects.map(&:raw))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def augment(owners: [], **options)
|
|
28
|
+
Ast::Merge::Comment::Augmenter.new(
|
|
29
|
+
lines: @lines,
|
|
30
|
+
comments: @comments,
|
|
31
|
+
owners: owners,
|
|
32
|
+
style: :hash_comment,
|
|
33
|
+
total_comment_count: @comments.size,
|
|
34
|
+
inline_comment_count: @comments.count { |comment| !comment[:full_line] },
|
|
35
|
+
**options
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def normalize_line_objects(source_or_lines)
|
|
42
|
+
case source_or_lines
|
|
43
|
+
when String
|
|
44
|
+
source_or_lines.lines.each_with_index.map do |line, index|
|
|
45
|
+
EnvLine.new(line.chomp, index + 1)
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
Array(source_or_lines)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def extract_comments
|
|
53
|
+
@line_objects.filter_map do |line|
|
|
54
|
+
if line.comment?
|
|
55
|
+
build_full_line_comment(line)
|
|
56
|
+
elsif line.assignment?
|
|
57
|
+
build_inline_comment(line)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_full_line_comment(line)
|
|
63
|
+
match = line.raw.match(FULL_LINE_COMMENT_REGEX)
|
|
64
|
+
return unless match
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
line: line.line_number,
|
|
68
|
+
indent: match[:indent].length,
|
|
69
|
+
text: match[:text].to_s,
|
|
70
|
+
full_line: true,
|
|
71
|
+
raw: line.raw
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build_inline_comment(line)
|
|
76
|
+
value_part = raw_value_part(line)
|
|
77
|
+
return if value_part.nil?
|
|
78
|
+
|
|
79
|
+
stripped_value = value_part.lstrip
|
|
80
|
+
return if stripped_value.start_with?('"', "'")
|
|
81
|
+
|
|
82
|
+
parsed = @line_parser.parse(value_part)
|
|
83
|
+
return unless parsed&.inline?
|
|
84
|
+
|
|
85
|
+
{
|
|
86
|
+
line: line.line_number,
|
|
87
|
+
indent: leading_indent(line.raw),
|
|
88
|
+
text: parsed.text,
|
|
89
|
+
full_line: false,
|
|
90
|
+
raw: parsed.raw
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def raw_value_part(line)
|
|
95
|
+
raw = line.raw.sub(/\A\s*export\s+/, '')
|
|
96
|
+
_key_part, value_part = raw.split('=', 2)
|
|
97
|
+
value_part
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def leading_indent(raw)
|
|
101
|
+
raw[/\A\s*/].to_s.length
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def owner_line_num(owner)
|
|
105
|
+
return owner.start_line if owner.respond_to?(:start_line) && owner.start_line
|
|
106
|
+
return owner.line_number if owner.respond_to?(:line_number)
|
|
107
|
+
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -16,8 +16,8 @@ module Dotenv
|
|
|
16
16
|
extend Ast::Merge::DebugLogger
|
|
17
17
|
|
|
18
18
|
# Configure for dotenv-merge
|
|
19
|
-
self.env_var_name =
|
|
20
|
-
self.log_prefix =
|
|
19
|
+
self.env_var_name = 'DOTENV_MERGE_DEBUG'
|
|
20
|
+
self.log_prefix = '[dotenv-merge]'
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -38,7 +38,7 @@ module Dotenv
|
|
|
38
38
|
class EnvLine < Ast::Merge::AstNode
|
|
39
39
|
# Prefix for exported environment variables
|
|
40
40
|
# @return [String]
|
|
41
|
-
EXPORT_PREFIX =
|
|
41
|
+
EXPORT_PREFIX = 'export '
|
|
42
42
|
|
|
43
43
|
# @return [String] The original raw line content
|
|
44
44
|
attr_reader :raw
|
|
@@ -75,7 +75,7 @@ module Dotenv
|
|
|
75
75
|
start_line: line_number,
|
|
76
76
|
end_line: line_number,
|
|
77
77
|
start_column: 0,
|
|
78
|
-
end_column: @raw.length
|
|
78
|
+
end_column: @raw.length
|
|
79
79
|
)
|
|
80
80
|
|
|
81
81
|
super(slice: @raw, location: location)
|
|
@@ -84,7 +84,7 @@ module Dotenv
|
|
|
84
84
|
# TreeHaver::Node protocol: type
|
|
85
85
|
# @return [String] "env_line"
|
|
86
86
|
def type
|
|
87
|
-
|
|
87
|
+
'env_line'
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
# Generate a unique signature for this line (used for merge matching)
|
|
@@ -163,7 +163,7 @@ module Dotenv
|
|
|
163
163
|
stripped = @raw.strip
|
|
164
164
|
if stripped.empty?
|
|
165
165
|
@line_type = :blank
|
|
166
|
-
elsif stripped.start_with?(
|
|
166
|
+
elsif stripped.start_with?('#')
|
|
167
167
|
@line_type = :comment
|
|
168
168
|
else
|
|
169
169
|
parse_assignment!(stripped)
|
|
@@ -181,13 +181,13 @@ module Dotenv
|
|
|
181
181
|
line = line[EXPORT_PREFIX.length..]
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
-
if line.include?(
|
|
185
|
-
key_part, value_part = line.split(
|
|
184
|
+
if line.include?('=')
|
|
185
|
+
key_part, value_part = line.split('=', 2)
|
|
186
186
|
key_part = key_part.strip
|
|
187
187
|
if valid_key?(key_part)
|
|
188
188
|
@line_type = :assignment
|
|
189
189
|
@key = key_part
|
|
190
|
-
@value = unquote(value_part ||
|
|
190
|
+
@value = unquote(value_part || '')
|
|
191
191
|
else
|
|
192
192
|
@line_type = :invalid
|
|
193
193
|
end
|
|
@@ -214,14 +214,10 @@ module Dotenv
|
|
|
214
214
|
value = value.strip
|
|
215
215
|
|
|
216
216
|
# Double-quoted: process escape sequences
|
|
217
|
-
if value.start_with?('"') && value.end_with?('"')
|
|
218
|
-
return process_escape_sequences(value[1..-2])
|
|
219
|
-
end
|
|
217
|
+
return process_escape_sequences(value[1..-2]) if value.start_with?('"') && value.end_with?('"')
|
|
220
218
|
|
|
221
219
|
# Single-quoted: literal value, no escape processing
|
|
222
|
-
if value.start_with?("'") && value.end_with?("'")
|
|
223
|
-
return value[1..-2]
|
|
224
|
-
end
|
|
220
|
+
return value[1..-2] if value.start_with?("'") && value.end_with?("'")
|
|
225
221
|
|
|
226
222
|
# Unquoted: strip inline comments
|
|
227
223
|
strip_inline_comment(value)
|
|
@@ -239,7 +235,7 @@ module Dotenv
|
|
|
239
235
|
.gsub('\t', "\t")
|
|
240
236
|
.gsub('\r', "\r")
|
|
241
237
|
.gsub('\"', '"')
|
|
242
|
-
.gsub(
|
|
238
|
+
.gsub('\\\\', '\\')
|
|
243
239
|
end
|
|
244
240
|
|
|
245
241
|
# Strip inline comments from unquoted values
|
|
@@ -26,7 +26,10 @@ module Dotenv
|
|
|
26
26
|
|
|
27
27
|
# Default freeze token for identifying freeze blocks
|
|
28
28
|
# @return [String]
|
|
29
|
-
DEFAULT_FREEZE_TOKEN =
|
|
29
|
+
DEFAULT_FREEZE_TOKEN = 'dotenv-merge'
|
|
30
|
+
|
|
31
|
+
# @return [CommentTracker] Comment tracker for this file
|
|
32
|
+
attr_reader :comment_tracker
|
|
30
33
|
|
|
31
34
|
# Initialize file analysis with dotenv parser
|
|
32
35
|
#
|
|
@@ -34,7 +37,7 @@ module Dotenv
|
|
|
34
37
|
# @param freeze_token [String] Token for freeze block markers (default: "dotenv-merge")
|
|
35
38
|
# @param signature_generator [Proc, nil] Custom signature generator
|
|
36
39
|
# @param options [Hash] Additional options (forward compatibility - ignored by FileAnalysis)
|
|
37
|
-
def initialize(source, freeze_token: DEFAULT_FREEZE_TOKEN, signature_generator: nil, **
|
|
40
|
+
def initialize(source, freeze_token: DEFAULT_FREEZE_TOKEN, signature_generator: nil, **_options)
|
|
38
41
|
@source = source
|
|
39
42
|
@freeze_token = freeze_token
|
|
40
43
|
@signature_generator = signature_generator
|
|
@@ -43,16 +46,19 @@ module Dotenv
|
|
|
43
46
|
# Parse all lines
|
|
44
47
|
@lines = parse_lines(source)
|
|
45
48
|
|
|
49
|
+
# Initialize comment tracking before freeze block integration
|
|
50
|
+
@comment_tracker = CommentTracker.new(@lines)
|
|
51
|
+
|
|
46
52
|
# Extract and integrate freeze blocks
|
|
47
53
|
@statements = extract_and_integrate_statements
|
|
48
54
|
|
|
49
|
-
DebugLogger.debug(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
DebugLogger.debug('FileAnalysis initialized', {
|
|
56
|
+
signature_generator: signature_generator ? 'custom' : 'default',
|
|
57
|
+
lines_count: @lines.size,
|
|
58
|
+
statements_count: @statements.size,
|
|
59
|
+
freeze_blocks: freeze_blocks.size,
|
|
60
|
+
assignments: assignment_lines.size
|
|
61
|
+
})
|
|
56
62
|
end
|
|
57
63
|
|
|
58
64
|
# Check if parse was successful (dotenv always succeeds, may have invalid lines)
|
|
@@ -61,12 +67,115 @@ module Dotenv
|
|
|
61
67
|
true
|
|
62
68
|
end
|
|
63
69
|
|
|
70
|
+
# Get shared comment capability information for this analysis.
|
|
71
|
+
#
|
|
72
|
+
# @return [Ast::Merge::Comment::Capability]
|
|
73
|
+
def comment_capability
|
|
74
|
+
@comment_capability ||= comment_tracker.augment(owners: []).capability
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Describe how dotenv merges currently own and emit comments.
|
|
78
|
+
#
|
|
79
|
+
# Dotenv comment handling is source-augmented and emitted through the
|
|
80
|
+
# synthetic merge layer.
|
|
81
|
+
#
|
|
82
|
+
# @return [Ast::Merge::Comment::SupportStyle]
|
|
83
|
+
def comment_support_style
|
|
84
|
+
@comment_support_style ||= shared_comment_support_style(
|
|
85
|
+
source: :dotenv_source,
|
|
86
|
+
style: :hash_comment,
|
|
87
|
+
read_strategy: :source_augmented_portable_write
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get all tracked comments converted to shared Ast::Merge comment nodes.
|
|
92
|
+
#
|
|
93
|
+
# @return [Array<Ast::Merge::Comment::Line>]
|
|
94
|
+
def comment_nodes
|
|
95
|
+
comment_tracker.comment_nodes
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Get a shared Ast::Merge comment node at a specific line.
|
|
99
|
+
#
|
|
100
|
+
# @param line_num [Integer] 1-based line number
|
|
101
|
+
# @return [Ast::Merge::Comment::Line, nil]
|
|
102
|
+
def comment_node_at(line_num)
|
|
103
|
+
comment_tracker.comment_node_at(line_num)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Get comments in a line range converted to a shared comment region.
|
|
107
|
+
#
|
|
108
|
+
# @param range [Range] Range of 1-based line numbers
|
|
109
|
+
# @param kind [Symbol] Region kind (:leading, :inline, :orphan, etc.)
|
|
110
|
+
# @param full_line_only [Boolean] Whether to keep only full-line comments
|
|
111
|
+
# @return [Ast::Merge::Comment::Region]
|
|
112
|
+
def comment_region_for_range(range, kind:, full_line_only: false)
|
|
113
|
+
comment_tracker.comment_region_for_range(
|
|
114
|
+
range,
|
|
115
|
+
kind: kind,
|
|
116
|
+
full_line_only: full_line_only
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Build a passive shared comment augmenter for this analysis.
|
|
121
|
+
#
|
|
122
|
+
# @param owners [Array<#start_line,#end_line>, nil] Owners used for attachment inference
|
|
123
|
+
# @param options [Hash] Additional augmenter options
|
|
124
|
+
# @return [Ast::Merge::Comment::Augmenter]
|
|
125
|
+
def comment_augmenter(owners: nil, **options)
|
|
126
|
+
comment_tracker.augment(
|
|
127
|
+
owners: owners || comment_augmenter_default_owners,
|
|
128
|
+
**options
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Build a passive shared comment attachment for an owner.
|
|
133
|
+
#
|
|
134
|
+
# @param owner [Object] Structural owner for the attachment
|
|
135
|
+
# @param options [Hash] Additional metadata / lookup overrides
|
|
136
|
+
# @return [Ast::Merge::Comment::Attachment]
|
|
137
|
+
def comment_attachment_for(owner, **options)
|
|
138
|
+
shared_comment_attachment_for(
|
|
139
|
+
owner,
|
|
140
|
+
tracker_attachment: comment_augmenter(**options).attachment_for(owner),
|
|
141
|
+
**options
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @return [Symbol]
|
|
146
|
+
def comment_attachment_strategy
|
|
147
|
+
:tracker_layout_merge
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def ruleset_owner_selector
|
|
151
|
+
:assignment_lines_plus_freeze_blocks
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def ruleset_match_key
|
|
155
|
+
:env_key
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def ruleset_render_family
|
|
159
|
+
:dotenv_assignments
|
|
160
|
+
end
|
|
161
|
+
|
|
64
162
|
# Get assignment lines (not in freeze blocks)
|
|
65
163
|
# @return [Array<EnvLine>]
|
|
66
164
|
def assignment_lines
|
|
67
165
|
@statements.select { |stmt| stmt.is_a?(EnvLine) && stmt.assignment? }
|
|
68
166
|
end
|
|
69
167
|
|
|
168
|
+
# Get merge-relevant structural owners in source order.
|
|
169
|
+
# For dotenv this means assignment lines plus integrated freeze blocks,
|
|
170
|
+
# excluding standalone comments, blanks, and invalid lines.
|
|
171
|
+
#
|
|
172
|
+
# @return [Array<EnvLine, FreezeNode>]
|
|
173
|
+
def structural_owners
|
|
174
|
+
@structural_owners ||= @statements.select do |stmt|
|
|
175
|
+
stmt.is_a?(FreezeNode) || (stmt.is_a?(EnvLine) && stmt.assignment?)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
70
179
|
# Get all assignment lines including those in freeze blocks
|
|
71
180
|
# @return [Array<EnvLine>]
|
|
72
181
|
def all_assignments
|
|
@@ -95,7 +204,7 @@ module Dotenv
|
|
|
95
204
|
end
|
|
96
205
|
end
|
|
97
206
|
|
|
98
|
-
#
|
|
207
|
+
# NOTE: fallthrough_node? is inherited from FileAnalyzable.
|
|
99
208
|
# EnvLine inherits from AstNode and FreezeNode inherits from FreezeNodeBase,
|
|
100
209
|
# both of which are recognized by the base implementation.
|
|
101
210
|
|
|
@@ -114,6 +223,10 @@ module Dotenv
|
|
|
114
223
|
|
|
115
224
|
private
|
|
116
225
|
|
|
226
|
+
def comment_augmenter_default_owners
|
|
227
|
+
structural_owners
|
|
228
|
+
end
|
|
229
|
+
|
|
117
230
|
# Parse source into EnvLine objects
|
|
118
231
|
# @param source [String] Source content
|
|
119
232
|
# @return [Array<EnvLine>]
|
|
@@ -145,17 +258,17 @@ module Dotenv
|
|
|
145
258
|
@lines.each do |line|
|
|
146
259
|
next unless line.comment?
|
|
147
260
|
|
|
148
|
-
|
|
149
|
-
marker_type = ::Regexp.last_match(1) # 'freeze' or 'unfreeze'
|
|
150
|
-
reason = ::Regexp.last_match(2)&.strip
|
|
151
|
-
reason = nil if reason&.empty?
|
|
261
|
+
next unless line.raw =~ pattern
|
|
152
262
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
263
|
+
marker_type = ::Regexp.last_match(1) # 'freeze' or 'unfreeze'
|
|
264
|
+
reason = ::Regexp.last_match(2)&.strip
|
|
265
|
+
reason = nil if reason && reason.empty?
|
|
266
|
+
|
|
267
|
+
markers << {
|
|
268
|
+
type: marker_type.to_sym,
|
|
269
|
+
line: line.line_number,
|
|
270
|
+
reason: reason
|
|
271
|
+
}
|
|
159
272
|
end
|
|
160
273
|
|
|
161
274
|
markers
|
|
@@ -182,7 +295,7 @@ module Dotenv
|
|
|
182
295
|
start_line: open_marker[:line],
|
|
183
296
|
end_line: marker[:line],
|
|
184
297
|
analysis: self,
|
|
185
|
-
reason: open_marker[:reason]
|
|
298
|
+
reason: open_marker[:reason]
|
|
186
299
|
)
|
|
187
300
|
open_marker = nil
|
|
188
301
|
else
|
|
@@ -191,9 +304,7 @@ module Dotenv
|
|
|
191
304
|
end
|
|
192
305
|
end
|
|
193
306
|
|
|
194
|
-
if open_marker
|
|
195
|
-
DebugLogger.warning("Unclosed freeze block starting at line #{open_marker[:line]}")
|
|
196
|
-
end
|
|
307
|
+
DebugLogger.warning("Unclosed freeze block starting at line #{open_marker[:line]}") if open_marker
|
|
197
308
|
|
|
198
309
|
blocks
|
|
199
310
|
end
|
|
@@ -43,7 +43,7 @@ module Dotenv
|
|
|
43
43
|
# Get a signature for this freeze block
|
|
44
44
|
# @return [Array] Signature based on normalized content
|
|
45
45
|
def signature
|
|
46
|
-
[:FreezeNode, content.gsub(/\s+/,
|
|
46
|
+
[:FreezeNode, content.gsub(/\s+/, ' ').strip]
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# Get environment variable lines within the freeze block
|
|
@@ -50,7 +50,7 @@ module Dotenv
|
|
|
50
50
|
|
|
51
51
|
lines = extract_lines(statement)
|
|
52
52
|
@lines.concat(lines)
|
|
53
|
-
@decisions << {decision: decision, source: :template, index: index, lines: lines.length}
|
|
53
|
+
@decisions << { decision: decision, source: :template, index: index, lines: lines.length }
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Add content from the destination at the given statement index
|
|
@@ -63,7 +63,7 @@ module Dotenv
|
|
|
63
63
|
|
|
64
64
|
lines = extract_lines(statement)
|
|
65
65
|
@lines.concat(lines)
|
|
66
|
-
@decisions << {decision: decision, source: :destination, index: index, lines: lines.length}
|
|
66
|
+
@decisions << { decision: decision, source: :destination, index: index, lines: lines.length }
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
# Add content from a freeze block
|
|
@@ -77,7 +77,7 @@ module Dotenv
|
|
|
77
77
|
source: :destination,
|
|
78
78
|
start_line: freeze_node.start_line,
|
|
79
79
|
end_line: freeze_node.end_line,
|
|
80
|
-
lines: lines.length
|
|
80
|
+
lines: lines.length
|
|
81
81
|
}
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -87,13 +87,13 @@ module Dotenv
|
|
|
87
87
|
# @return [void]
|
|
88
88
|
def add_raw(lines, decision:)
|
|
89
89
|
@lines.concat(lines)
|
|
90
|
-
@decisions << {decision: decision, source: :raw, lines: lines.length}
|
|
90
|
+
@decisions << { decision: decision, source: :raw, lines: lines.length }
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# Convert the merged result to a string
|
|
94
94
|
# @return [String] The merged dotenv content
|
|
95
95
|
def to_s
|
|
96
|
-
return
|
|
96
|
+
return '' if @lines.empty?
|
|
97
97
|
|
|
98
98
|
# Join with newlines and ensure file ends with newline
|
|
99
99
|
result = @lines.join("\n")
|
|
@@ -114,7 +114,7 @@ module Dotenv
|
|
|
114
114
|
{
|
|
115
115
|
total_decisions: @decisions.length,
|
|
116
116
|
total_lines: @lines.length,
|
|
117
|
-
by_decision: counts
|
|
117
|
+
by_decision: counts
|
|
118
118
|
}
|
|
119
119
|
end
|
|
120
120
|
|