markdown-merge 1.0.3 → 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/markdown/merge/version.rb +3 -4
- data/lib/markdown/merge.rb +538 -137
- data/lib/markdown-merge.rb +3 -4
- data.tar.gz.sig +0 -0
- metadata +28 -289
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -308
- 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 -1064
- data/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/markdown/merge/cleanse/block_spacing.rb +0 -253
- data/lib/markdown/merge/cleanse/code_fence_spacing.rb +0 -294
- data/lib/markdown/merge/cleanse/condensed_link_refs.rb +0 -405
- data/lib/markdown/merge/cleanse.rb +0 -42
- data/lib/markdown/merge/code_block_merger.rb +0 -300
- data/lib/markdown/merge/conflict_resolver.rb +0 -128
- data/lib/markdown/merge/debug_logger.rb +0 -26
- data/lib/markdown/merge/document_problems.rb +0 -190
- data/lib/markdown/merge/file_aligner.rb +0 -196
- data/lib/markdown/merge/file_analysis.rb +0 -353
- data/lib/markdown/merge/file_analysis_base.rb +0 -629
- data/lib/markdown/merge/freeze_node.rb +0 -93
- data/lib/markdown/merge/gap_line_node.rb +0 -136
- data/lib/markdown/merge/link_definition_formatter.rb +0 -49
- data/lib/markdown/merge/link_definition_node.rb +0 -157
- data/lib/markdown/merge/link_parser.rb +0 -421
- data/lib/markdown/merge/link_reference_rehydrator.rb +0 -320
- data/lib/markdown/merge/markdown_structure.rb +0 -123
- data/lib/markdown/merge/merge_result.rb +0 -166
- data/lib/markdown/merge/node_type_normalizer.rb +0 -126
- data/lib/markdown/merge/output_builder.rb +0 -166
- data/lib/markdown/merge/partial_template_merger.rb +0 -334
- data/lib/markdown/merge/smart_merger.rb +0 -221
- data/lib/markdown/merge/smart_merger_base.rb +0 -621
- data/lib/markdown/merge/table_match_algorithm.rb +0 -504
- data/lib/markdown/merge/table_match_refiner.rb +0 -136
- data/lib/markdown/merge/whitespace_normalizer.rb +0 -251
- data/sig/markdown/merge.rbs +0 -341
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Markdown
|
|
4
|
-
module Merge
|
|
5
|
-
# Normalizes whitespace in markdown documents.
|
|
6
|
-
#
|
|
7
|
-
# Supports multiple normalization modes:
|
|
8
|
-
# - `:basic` (or `true`) - Collapse excessive blank lines (3+ → 2)
|
|
9
|
-
# - `:link_refs` - Also remove blank lines between consecutive link reference definitions
|
|
10
|
-
# - `:strict` - All of the above normalizations
|
|
11
|
-
#
|
|
12
|
-
# Uses {LinkParser} for detecting link reference definitions, which supports:
|
|
13
|
-
# - Standard definitions: `[label]: url`
|
|
14
|
-
# - Definitions with titles: `[label]: url "title"`
|
|
15
|
-
# - Angle-bracketed URLs: `[label]: <url>`
|
|
16
|
-
# - Emoji in labels: `[🎨logo]: url`
|
|
17
|
-
#
|
|
18
|
-
# @example Basic normalization (default)
|
|
19
|
-
# content = "Hello\n\n\n\nWorld"
|
|
20
|
-
# normalized = WhitespaceNormalizer.normalize(content)
|
|
21
|
-
# # => "Hello\n\nWorld"
|
|
22
|
-
#
|
|
23
|
-
# @example With link_refs mode
|
|
24
|
-
# content = "[link1]: url1\n\n[link2]: url2"
|
|
25
|
-
# normalized = WhitespaceNormalizer.normalize(content, mode: :link_refs)
|
|
26
|
-
# # => "[link1]: url1\n[link2]: url2"
|
|
27
|
-
#
|
|
28
|
-
# @example With problem tracking
|
|
29
|
-
# normalizer = WhitespaceNormalizer.new(content, mode: :link_refs)
|
|
30
|
-
# result = normalizer.normalize
|
|
31
|
-
# normalizer.problems.by_category(:link_ref_spacing)
|
|
32
|
-
#
|
|
33
|
-
class WhitespaceNormalizer
|
|
34
|
-
# Valid normalization modes
|
|
35
|
-
MODES = %i[basic link_refs strict].freeze
|
|
36
|
-
|
|
37
|
-
# @return [String] The original content
|
|
38
|
-
attr_reader :content
|
|
39
|
-
|
|
40
|
-
# @return [Symbol] The normalization mode
|
|
41
|
-
attr_reader :mode
|
|
42
|
-
|
|
43
|
-
# @return [DocumentProblems] Problems found during normalization
|
|
44
|
-
attr_reader :problems
|
|
45
|
-
|
|
46
|
-
class << self
|
|
47
|
-
# Normalize whitespace in content (class method for convenience).
|
|
48
|
-
#
|
|
49
|
-
# @param content [String] Content to normalize
|
|
50
|
-
# @param mode [Symbol, Boolean] Normalization mode (:basic, :link_refs, :strict, or true for :basic)
|
|
51
|
-
# @return [String] Normalized content
|
|
52
|
-
def normalize(content, mode: :basic)
|
|
53
|
-
new(content, mode: mode).normalize
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Initialize a new normalizer.
|
|
58
|
-
#
|
|
59
|
-
# @param content [String] Content to normalize
|
|
60
|
-
# @param mode [Symbol, Boolean] Normalization mode (:basic, :link_refs, :strict, or true for :basic)
|
|
61
|
-
def initialize(content, mode: :basic)
|
|
62
|
-
@content = content
|
|
63
|
-
@mode = normalize_mode(mode)
|
|
64
|
-
@problems = DocumentProblems.new
|
|
65
|
-
@link_parser = LinkParser.new
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Normalize whitespace based on the configured mode.
|
|
69
|
-
#
|
|
70
|
-
# @return [String] Normalized content
|
|
71
|
-
def normalize
|
|
72
|
-
result = content.dup
|
|
73
|
-
|
|
74
|
-
# Always collapse excessive blank lines (3+ → 2)
|
|
75
|
-
result = collapse_excessive_blank_lines(result)
|
|
76
|
-
|
|
77
|
-
# Remove blank lines between link refs if mode requires it
|
|
78
|
-
if @mode == :link_refs || @mode == :strict
|
|
79
|
-
result = remove_blank_lines_between_link_refs(result)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
result
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Check if normalization made any changes.
|
|
86
|
-
#
|
|
87
|
-
# @return [Boolean] true if content had whitespace issues
|
|
88
|
-
def changed?
|
|
89
|
-
!@problems.empty?
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Get count of normalizations performed.
|
|
93
|
-
#
|
|
94
|
-
# @return [Integer] Number of whitespace issues fixed
|
|
95
|
-
def normalization_count
|
|
96
|
-
@problems.count
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
private
|
|
100
|
-
|
|
101
|
-
# Normalize mode parameter to a symbol.
|
|
102
|
-
#
|
|
103
|
-
# @param mode [Symbol, Boolean] Input mode
|
|
104
|
-
# @return [Symbol] Normalized mode
|
|
105
|
-
def normalize_mode(mode)
|
|
106
|
-
case mode
|
|
107
|
-
when true
|
|
108
|
-
:basic
|
|
109
|
-
when false
|
|
110
|
-
:basic # Still do basic normalization
|
|
111
|
-
when Symbol
|
|
112
|
-
raise ArgumentError, "Unknown mode: #{mode}. Valid modes: #{MODES.join(", ")}" unless MODES.include?(mode)
|
|
113
|
-
|
|
114
|
-
mode
|
|
115
|
-
else
|
|
116
|
-
raise ArgumentError, "Mode must be a Symbol or Boolean, got: #{mode.class}"
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Collapse 3+ consecutive newlines to 2.
|
|
121
|
-
#
|
|
122
|
-
# This detects runs of blank lines (empty lines) and collapses them.
|
|
123
|
-
# Note: A blank line is a line containing only whitespace.
|
|
124
|
-
# 3+ consecutive newlines means 2+ blank lines.
|
|
125
|
-
#
|
|
126
|
-
# @param text [String] Text to process
|
|
127
|
-
# @return [String] Processed text
|
|
128
|
-
def collapse_excessive_blank_lines(text)
|
|
129
|
-
lines = text.lines
|
|
130
|
-
result = []
|
|
131
|
-
consecutive_blank_count = 0
|
|
132
|
-
problem_start_line = nil
|
|
133
|
-
line_number = 0
|
|
134
|
-
|
|
135
|
-
lines.each do |line|
|
|
136
|
-
line_number += 1
|
|
137
|
-
|
|
138
|
-
if line.chomp.empty?
|
|
139
|
-
consecutive_blank_count += 1
|
|
140
|
-
# The problem starts at the line BEFORE the first blank line
|
|
141
|
-
# (i.e., the line that ends with the first \n of the excessive sequence)
|
|
142
|
-
problem_start_line ||= line_number - 1
|
|
143
|
-
|
|
144
|
-
# Only add up to 1 blank line (which creates the standard paragraph gap)
|
|
145
|
-
if consecutive_blank_count <= 1
|
|
146
|
-
result << line
|
|
147
|
-
end
|
|
148
|
-
# Skip adding lines when consecutive_blank_count >= 2
|
|
149
|
-
else
|
|
150
|
-
# Record problem if we had 2+ blank lines (which means 3+ newlines)
|
|
151
|
-
# consecutive_blank_count is the count of blank lines, so >= 2 means excessive
|
|
152
|
-
if consecutive_blank_count >= 2
|
|
153
|
-
@problems.add(
|
|
154
|
-
:excessive_whitespace,
|
|
155
|
-
severity: :warning,
|
|
156
|
-
line: problem_start_line,
|
|
157
|
-
newline_count: consecutive_blank_count + 1, # +1 because first line ends with \n too
|
|
158
|
-
collapsed_to: 2,
|
|
159
|
-
)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
consecutive_blank_count = 0
|
|
163
|
-
problem_start_line = nil
|
|
164
|
-
result << line
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Handle trailing blank lines
|
|
169
|
-
if consecutive_blank_count >= 2
|
|
170
|
-
@problems.add(
|
|
171
|
-
:excessive_whitespace,
|
|
172
|
-
severity: :warning,
|
|
173
|
-
line: problem_start_line,
|
|
174
|
-
newline_count: consecutive_blank_count + 1,
|
|
175
|
-
collapsed_to: 2,
|
|
176
|
-
)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
result.join
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# Remove blank lines between consecutive link reference definitions.
|
|
183
|
-
#
|
|
184
|
-
# Uses {LinkParser} to detect link definitions, supporting:
|
|
185
|
-
# - Standard: `[label]: url`
|
|
186
|
-
# - With title: `[label]: url "title"`
|
|
187
|
-
# - Angle-bracketed: `[label]: <url>`
|
|
188
|
-
# - Emoji labels: `[🎨logo]: url`
|
|
189
|
-
#
|
|
190
|
-
# @param text [String] Text to process
|
|
191
|
-
# @return [String] Processed text
|
|
192
|
-
def remove_blank_lines_between_link_refs(text)
|
|
193
|
-
lines = text.lines
|
|
194
|
-
result = []
|
|
195
|
-
i = 0
|
|
196
|
-
|
|
197
|
-
while i < lines.length
|
|
198
|
-
line = lines[i]
|
|
199
|
-
result << line
|
|
200
|
-
|
|
201
|
-
# Check if current line is a link ref definition using LinkParser
|
|
202
|
-
if link_definition_line?(line)
|
|
203
|
-
# Look ahead for blank lines followed by another link ref
|
|
204
|
-
j = i + 1
|
|
205
|
-
while j < lines.length
|
|
206
|
-
next_line = lines[j]
|
|
207
|
-
if next_line.chomp.empty?
|
|
208
|
-
# Check if there's a link ref definition after the blank line(s)
|
|
209
|
-
k = j + 1
|
|
210
|
-
while k < lines.length && lines[k].chomp.empty?
|
|
211
|
-
k += 1
|
|
212
|
-
end
|
|
213
|
-
if k < lines.length && link_definition_line?(lines[k])
|
|
214
|
-
# Skip all blank lines between link refs
|
|
215
|
-
blanks_skipped = k - j
|
|
216
|
-
@problems.add(
|
|
217
|
-
:link_ref_spacing,
|
|
218
|
-
severity: :info,
|
|
219
|
-
line: j + 1,
|
|
220
|
-
blank_lines_removed: blanks_skipped,
|
|
221
|
-
)
|
|
222
|
-
j = k
|
|
223
|
-
else
|
|
224
|
-
# Not followed by a link ref, keep the blank line
|
|
225
|
-
break
|
|
226
|
-
end
|
|
227
|
-
else
|
|
228
|
-
break
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
i = j
|
|
232
|
-
else
|
|
233
|
-
i += 1
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
result.join
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# Check if a line is a link reference definition using LinkParser.
|
|
241
|
-
#
|
|
242
|
-
# @param line [String] Line to check
|
|
243
|
-
# @return [Boolean] true if line is a link definition
|
|
244
|
-
def link_definition_line?(line)
|
|
245
|
-
# Use LinkParser to attempt parsing the line as a definition
|
|
246
|
-
result = @link_parser.parse_definition_line(line.chomp)
|
|
247
|
-
!result.nil?
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
end
|
data/sig/markdown/merge.rbs
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
module Markdown
|
|
2
|
-
module Merge
|
|
3
|
-
VERSION: String
|
|
4
|
-
|
|
5
|
-
# Base error class for Markdown::Merge
|
|
6
|
-
class Error < Ast::Merge::Error
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
# Raised when a Markdown file has parsing errors
|
|
10
|
-
class ParseError < Ast::Merge::ParseError
|
|
11
|
-
def initialize: (?String? message, ?content: String?, ?errors: Array[untyped]) -> void
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Raised when the template file has syntax errors
|
|
15
|
-
class TemplateParseError < ParseError
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Raised when the destination file has syntax errors
|
|
19
|
-
class DestinationParseError < ParseError
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Debug logging utility for Markdown::Merge operations
|
|
23
|
-
module DebugLogger
|
|
24
|
-
extend Ast::Merge::DebugLogger
|
|
25
|
-
|
|
26
|
-
def self.env_var_name: () -> String
|
|
27
|
-
def self.env_var_name=: (String name) -> String
|
|
28
|
-
def self.log_prefix: () -> String
|
|
29
|
-
def self.log_prefix=: (String prefix) -> String
|
|
30
|
-
def self.enabled?: () -> bool
|
|
31
|
-
def self.debug: (*untyped args) -> void
|
|
32
|
-
def self.info: (*untyped args) -> void
|
|
33
|
-
def self.warning: (*untyped args) -> void
|
|
34
|
-
def self.time: [T] (String label) { () -> T } -> T
|
|
35
|
-
def self.log_node: (untyped node, ?label: String) -> void
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Represents a frozen block of Markdown content
|
|
39
|
-
class FreezeNode < Ast::Merge::FreezeNodeBase
|
|
40
|
-
def initialize: (
|
|
41
|
-
start_line: Integer,
|
|
42
|
-
end_line: Integer,
|
|
43
|
-
content: String,
|
|
44
|
-
start_marker: String,
|
|
45
|
-
end_marker: String,
|
|
46
|
-
?nodes: Array[untyped],
|
|
47
|
-
?reason: String?
|
|
48
|
-
) -> void
|
|
49
|
-
|
|
50
|
-
def signature: () -> Array[Symbol | String]
|
|
51
|
-
def full_text: () -> String
|
|
52
|
-
def line_count: () -> Integer
|
|
53
|
-
def contains_type?: (Symbol type_symbol) -> bool
|
|
54
|
-
def inspect: () -> String
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Base class for file analysis for Markdown files
|
|
58
|
-
class FileAnalysisBase
|
|
59
|
-
include Ast::Merge::FileAnalyzable
|
|
60
|
-
|
|
61
|
-
DEFAULT_FREEZE_TOKEN: String
|
|
62
|
-
|
|
63
|
-
attr_reader document: untyped
|
|
64
|
-
attr_reader statements: Array[untyped]
|
|
65
|
-
|
|
66
|
-
def initialize: (
|
|
67
|
-
String source,
|
|
68
|
-
?freeze_token: String,
|
|
69
|
-
?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?,
|
|
70
|
-
**untyped parser_options
|
|
71
|
-
) -> void
|
|
72
|
-
|
|
73
|
-
# Abstract methods - subclasses must implement
|
|
74
|
-
def parse_document: (String source) -> untyped
|
|
75
|
-
def next_sibling: (untyped node) -> untyped?
|
|
76
|
-
|
|
77
|
-
def valid?: () -> bool
|
|
78
|
-
def compute_node_signature: (untyped node) -> Array[untyped]?
|
|
79
|
-
def fallthrough_node?: (untyped value) -> bool
|
|
80
|
-
def parser_node?: (untyped value) -> bool
|
|
81
|
-
def compute_parser_signature: (untyped node) -> Array[untyped]?
|
|
82
|
-
def safe_string_content: (untyped node) -> String
|
|
83
|
-
def extract_text_content: (untyped node) -> String
|
|
84
|
-
def extract_table_header_content: (untyped node) -> String
|
|
85
|
-
def source_range: (Integer start_line, Integer end_line) -> String
|
|
86
|
-
def count_children: (untyped node) -> Integer
|
|
87
|
-
|
|
88
|
-
private
|
|
89
|
-
|
|
90
|
-
def extract_and_integrate_all_nodes: () -> Array[untyped]
|
|
91
|
-
def collect_top_level_nodes: () -> Array[untyped]
|
|
92
|
-
def build_freeze_blocks: (Array[untyped] nodes) -> Array[FreezeNode]
|
|
93
|
-
def integrate_freeze_blocks: (Array[untyped] nodes, Array[FreezeNode] freeze_blocks) -> Array[untyped]
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Aligns Markdown block elements between template and destination files
|
|
97
|
-
class FileAligner
|
|
98
|
-
attr_reader template_analysis: FileAnalysisBase
|
|
99
|
-
attr_reader dest_analysis: FileAnalysisBase
|
|
100
|
-
attr_reader match_refiner: (^(Array[untyped], Array[untyped], Hash[Symbol, untyped]) -> Array[MatchResult])?
|
|
101
|
-
|
|
102
|
-
def initialize: (
|
|
103
|
-
FileAnalysisBase template_analysis,
|
|
104
|
-
FileAnalysisBase dest_analysis,
|
|
105
|
-
?match_refiner: (^(Array[untyped], Array[untyped], Hash[Symbol, untyped]) -> Array[MatchResult])?
|
|
106
|
-
) -> void
|
|
107
|
-
|
|
108
|
-
def align: () -> Array[Hash[Symbol, untyped]]
|
|
109
|
-
|
|
110
|
-
private
|
|
111
|
-
|
|
112
|
-
def build_signature_map: (Array[untyped] statements, FileAnalysisBase analysis) -> Hash[Array[untyped], Array[Integer]]
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Match result from refiners
|
|
116
|
-
class MatchResult
|
|
117
|
-
attr_reader template_node: untyped
|
|
118
|
-
attr_reader dest_node: untyped
|
|
119
|
-
attr_reader score: Float
|
|
120
|
-
|
|
121
|
-
def initialize: (
|
|
122
|
-
template_node: untyped,
|
|
123
|
-
dest_node: untyped,
|
|
124
|
-
score: Float
|
|
125
|
-
) -> void
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Resolves conflicts between matching Markdown elements
|
|
129
|
-
class ConflictResolver < Ast::Merge::ConflictResolverBase
|
|
130
|
-
def initialize: (
|
|
131
|
-
preference: Symbol,
|
|
132
|
-
template_analysis: FileAnalysisBase,
|
|
133
|
-
dest_analysis: FileAnalysisBase
|
|
134
|
-
) -> void
|
|
135
|
-
|
|
136
|
-
private
|
|
137
|
-
|
|
138
|
-
def resolve_node_pair: (
|
|
139
|
-
untyped template_node,
|
|
140
|
-
untyped dest_node,
|
|
141
|
-
template_index: Integer,
|
|
142
|
-
dest_index: Integer
|
|
143
|
-
) -> Hash[Symbol, untyped]
|
|
144
|
-
|
|
145
|
-
def content_identical?: (untyped template_node, untyped dest_node) -> bool
|
|
146
|
-
def node_to_text: (untyped node, FileAnalysisBase analysis) -> String
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Represents the result of a Markdown merge operation
|
|
150
|
-
class MergeResult < Ast::Merge::MergeResultBase
|
|
151
|
-
def initialize: (
|
|
152
|
-
content: String?,
|
|
153
|
-
?conflicts: Array[Hash[Symbol, untyped]],
|
|
154
|
-
?frozen_blocks: Array[Hash[Symbol, untyped]],
|
|
155
|
-
?stats: Hash[Symbol, untyped]
|
|
156
|
-
) -> void
|
|
157
|
-
|
|
158
|
-
def content: () -> String?
|
|
159
|
-
def content?: () -> bool
|
|
160
|
-
def content_string: () -> String?
|
|
161
|
-
def success?: () -> bool
|
|
162
|
-
def conflicts?: () -> bool
|
|
163
|
-
def has_frozen_blocks?: () -> bool
|
|
164
|
-
def nodes_added: () -> Integer
|
|
165
|
-
def nodes_removed: () -> Integer
|
|
166
|
-
def nodes_modified: () -> Integer
|
|
167
|
-
def merge_time_ms: () -> Float?
|
|
168
|
-
def frozen_count: () -> Integer
|
|
169
|
-
def inspect: () -> String
|
|
170
|
-
def to_s: () -> String
|
|
171
|
-
|
|
172
|
-
private
|
|
173
|
-
|
|
174
|
-
def default_stats: () -> Hash[Symbol, untyped]
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Algorithm for computing match scores between two Markdown tables
|
|
178
|
-
class TableMatchAlgorithm
|
|
179
|
-
DEFAULT_WEIGHTS: Hash[Symbol, Float]
|
|
180
|
-
FIRST_COLUMN_SIMILARITY_THRESHOLD: Float
|
|
181
|
-
|
|
182
|
-
attr_reader position_a: Integer?
|
|
183
|
-
attr_reader position_b: Integer?
|
|
184
|
-
attr_reader total_tables_a: Integer
|
|
185
|
-
attr_reader total_tables_b: Integer
|
|
186
|
-
attr_reader weights: Hash[Symbol, Float]
|
|
187
|
-
|
|
188
|
-
def initialize: (
|
|
189
|
-
?position_a: Integer?,
|
|
190
|
-
?position_b: Integer?,
|
|
191
|
-
?total_tables_a: Integer,
|
|
192
|
-
?total_tables_b: Integer,
|
|
193
|
-
?weights: Hash[Symbol, Float]
|
|
194
|
-
) -> void
|
|
195
|
-
|
|
196
|
-
def call: (untyped table_a, untyped table_b) -> Float
|
|
197
|
-
|
|
198
|
-
private
|
|
199
|
-
|
|
200
|
-
def levenshtein_distance: (String a, String b) -> Integer
|
|
201
|
-
def levenshtein_similarity: (String a, String b) -> Float
|
|
202
|
-
def extract_rows: (untyped table) -> Array[Array[String]]
|
|
203
|
-
def extract_cell_text: (untyped cell) -> String
|
|
204
|
-
def compute_header_match: (Array[Array[String]] rows_a, Array[Array[String]] rows_b) -> Float
|
|
205
|
-
def compute_first_column_match: (Array[Array[String]] rows_a, Array[Array[String]] rows_b) -> Float
|
|
206
|
-
def compute_row_content_match: (Array[Array[String]] rows_a, Array[Array[String]] rows_b) -> Float
|
|
207
|
-
def compute_total_cells_match: (Array[Array[String]] rows_a, Array[Array[String]] rows_b) -> Float
|
|
208
|
-
def compute_position_score: () -> Float
|
|
209
|
-
def weighted_average: (Hash[Symbol, Float] scores) -> Float
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# Match refiner for Markdown tables that didn't match by exact signature
|
|
213
|
-
class TableMatchRefiner < Ast::Merge::MatchRefinerBase
|
|
214
|
-
attr_reader algorithm_options: Hash[Symbol, untyped]
|
|
215
|
-
|
|
216
|
-
def initialize: (
|
|
217
|
-
?threshold: Float,
|
|
218
|
-
?algorithm_options: Hash[Symbol, untyped],
|
|
219
|
-
**untyped options
|
|
220
|
-
) -> void
|
|
221
|
-
|
|
222
|
-
def call: (
|
|
223
|
-
Array[untyped] template_nodes,
|
|
224
|
-
Array[untyped] dest_nodes,
|
|
225
|
-
?Hash[Symbol, untyped] context
|
|
226
|
-
) -> Array[MatchResult]
|
|
227
|
-
|
|
228
|
-
private
|
|
229
|
-
|
|
230
|
-
def extract_tables: (Array[untyped] nodes) -> Array[untyped]
|
|
231
|
-
def table_node?: (untyped node) -> bool
|
|
232
|
-
def compute_table_similarity: (
|
|
233
|
-
untyped t_table,
|
|
234
|
-
untyped d_table,
|
|
235
|
-
Integer t_idx,
|
|
236
|
-
Integer d_idx,
|
|
237
|
-
Integer total_t,
|
|
238
|
-
Integer total_d
|
|
239
|
-
) -> Float
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
# Merges fenced code blocks using language-specific *-merge gems
|
|
243
|
-
class CodeBlockMerger
|
|
244
|
-
DEFAULT_MERGERS: Hash[String, ^(String, String, Symbol, **untyped) -> Hash[Symbol, untyped]]
|
|
245
|
-
|
|
246
|
-
attr_reader mergers: Hash[String, ^(String, String, Symbol, **untyped) -> Hash[Symbol, untyped]]
|
|
247
|
-
attr_reader enabled: bool
|
|
248
|
-
|
|
249
|
-
def initialize: (
|
|
250
|
-
?mergers: Hash[String, ^(String, String, Symbol, **untyped) -> Hash[Symbol, untyped]],
|
|
251
|
-
?enabled: bool
|
|
252
|
-
) -> void
|
|
253
|
-
|
|
254
|
-
def supports_language?: (String? language) -> bool
|
|
255
|
-
def merge_code_blocks: (
|
|
256
|
-
untyped template_node,
|
|
257
|
-
untyped dest_node,
|
|
258
|
-
preference: Symbol,
|
|
259
|
-
**untyped opts
|
|
260
|
-
) -> Hash[Symbol, untyped]
|
|
261
|
-
|
|
262
|
-
# Class methods for specific mergers
|
|
263
|
-
def self.merge_with_prism: (String template, String dest, Symbol preference, **untyped opts) -> Hash[Symbol, untyped]
|
|
264
|
-
def self.merge_with_psych: (String template, String dest, Symbol preference, **untyped opts) -> Hash[Symbol, untyped]
|
|
265
|
-
def self.merge_with_json: (String template, String dest, Symbol preference, **untyped opts) -> Hash[Symbol, untyped]
|
|
266
|
-
def self.merge_with_toml: (String template, String dest, Symbol preference, **untyped opts) -> Hash[Symbol, untyped]
|
|
267
|
-
|
|
268
|
-
private
|
|
269
|
-
|
|
270
|
-
def extract_language: (untyped node) -> String?
|
|
271
|
-
def extract_content: (untyped node) -> String
|
|
272
|
-
def rebuild_code_block: (String language, String content, untyped reference_node) -> String
|
|
273
|
-
def not_merged: (String reason) -> Hash[Symbol, untyped]
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Builds markdown output from merge operations
|
|
277
|
-
class OutputBuilder
|
|
278
|
-
@parts: Array[String]
|
|
279
|
-
@preserve_formatting: bool
|
|
280
|
-
|
|
281
|
-
def initialize: (?preserve_formatting: bool) -> void
|
|
282
|
-
def add_node_source: (untyped node, FileAnalysisBase analysis) -> void
|
|
283
|
-
def add_link_definition: (LinkDefinitionNode node) -> void
|
|
284
|
-
def add_gap_line: (?count: Integer) -> void
|
|
285
|
-
def add_raw: (String text) -> void
|
|
286
|
-
def to_s: () -> String
|
|
287
|
-
def empty?: () -> bool
|
|
288
|
-
def clear: () -> void
|
|
289
|
-
|
|
290
|
-
private
|
|
291
|
-
|
|
292
|
-
def extract_source: (untyped node, FileAnalysisBase analysis) -> String?
|
|
293
|
-
def extract_parser_node_source: (untyped node, FileAnalysisBase analysis) -> String?
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
# Formats link reference definitions for output
|
|
297
|
-
module LinkDefinitionFormatter
|
|
298
|
-
def self.format: (LinkDefinitionNode node) -> String
|
|
299
|
-
def self.format_all: (Array[LinkDefinitionNode] nodes, ?separator: String) -> String
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
# Base class for smart Markdown file merging
|
|
303
|
-
class SmartMergerBase
|
|
304
|
-
attr_reader template_analysis: FileAnalysisBase
|
|
305
|
-
attr_reader dest_analysis: FileAnalysisBase
|
|
306
|
-
attr_reader aligner: FileAligner
|
|
307
|
-
attr_reader resolver: ConflictResolver
|
|
308
|
-
attr_reader code_block_merger: CodeBlockMerger?
|
|
309
|
-
|
|
310
|
-
def initialize: (
|
|
311
|
-
String template_content,
|
|
312
|
-
String dest_content,
|
|
313
|
-
?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?,
|
|
314
|
-
?preference: Symbol,
|
|
315
|
-
?add_template_only_nodes: bool,
|
|
316
|
-
?inner_merge_code_blocks: (bool | CodeBlockMerger),
|
|
317
|
-
?freeze_token: String,
|
|
318
|
-
?match_refiner: (^(Array[untyped], Array[untyped], Hash[Symbol, untyped]) -> Array[MatchResult])?,
|
|
319
|
-
**untyped parser_options
|
|
320
|
-
) -> void
|
|
321
|
-
|
|
322
|
-
# Abstract methods - subclasses must implement
|
|
323
|
-
def create_file_analysis: (String content, **untyped options) -> FileAnalysisBase
|
|
324
|
-
def node_to_source: (untyped node, FileAnalysisBase analysis) -> String
|
|
325
|
-
|
|
326
|
-
def template_parse_error_class: () -> Class
|
|
327
|
-
def destination_parse_error_class: () -> Class
|
|
328
|
-
|
|
329
|
-
def merge: () -> String
|
|
330
|
-
def merge_result: () -> MergeResult
|
|
331
|
-
|
|
332
|
-
private
|
|
333
|
-
|
|
334
|
-
def build_result_content: (Array[Hash[Symbol, untyped]] alignment) -> String
|
|
335
|
-
def process_alignment_entry: (Hash[Symbol, untyped] entry) -> String?
|
|
336
|
-
def handle_match: (Hash[Symbol, untyped] entry) -> String?
|
|
337
|
-
def handle_template_only: (Hash[Symbol, untyped] entry) -> String?
|
|
338
|
-
def handle_dest_only: (Hash[Symbol, untyped] entry) -> String?
|
|
339
|
-
end
|
|
340
|
-
end
|
|
341
|
-
end
|