markdown-merge 1.0.3 → 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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.md +13 -0
  4. data/README.md +99 -482
  5. data/lib/markdown/merge/backend_support.rb +200 -0
  6. data/lib/markdown/merge/cleanse/block_spacing.rb +18 -23
  7. data/lib/markdown/merge/cleanse/code_fence_spacing.rb +16 -16
  8. data/lib/markdown/merge/cleanse/condensed_link_refs.rb +36 -30
  9. data/lib/markdown/merge/cleanse/list_marker_duplication.rb +66 -0
  10. data/lib/markdown/merge/cleanse/templating_corruption.rb +86 -0
  11. data/lib/markdown/merge/cleanse.rb +5 -3
  12. data/lib/markdown/merge/code_block_match_refiner.rb +111 -0
  13. data/lib/markdown/merge/code_block_merger.rb +489 -47
  14. data/lib/markdown/merge/comment_tracker.rb +42 -0
  15. data/lib/markdown/merge/conflict_resolver.rb +77 -6
  16. data/lib/markdown/merge/debug_logger.rb +2 -2
  17. data/lib/markdown/merge/document_problems.rb +3 -3
  18. data/lib/markdown/merge/file_aligner.rb +433 -133
  19. data/lib/markdown/merge/file_analysis.rb +387 -51
  20. data/lib/markdown/merge/file_analysis_base.rb +188 -51
  21. data/lib/markdown/merge/gap_line_node.rb +14 -8
  22. data/lib/markdown/merge/link_definition_node.rb +5 -5
  23. data/lib/markdown/merge/link_parser.rb +60 -60
  24. data/lib/markdown/merge/link_reference_rehydrator.rb +14 -14
  25. data/lib/markdown/merge/list_match_refiner.rb +98 -0
  26. data/lib/markdown/merge/list_merger.rb +322 -0
  27. data/lib/markdown/merge/markdown_structure.rb +3 -3
  28. data/lib/markdown/merge/merge_result.rb +321 -4
  29. data/lib/markdown/merge/node_type_normalizer.rb +6 -6
  30. data/lib/markdown/merge/output_builder.rb +101 -19
  31. data/lib/markdown/merge/partial_template_merger.rb +248 -27
  32. data/lib/markdown/merge/preservation_support.rb +291 -0
  33. data/lib/markdown/merge/smart_merger.rb +62 -14
  34. data/lib/markdown/merge/smart_merger_base.rb +929 -60
  35. data/lib/markdown/merge/table_match_algorithm.rb +22 -27
  36. data/lib/markdown/merge/table_match_refiner.rb +6 -10
  37. data/lib/markdown/merge/version.rb +5 -4
  38. data/lib/markdown/merge/whitespace_normalizer.rb +25 -33
  39. data/lib/markdown/merge/wrapper_support.rb +194 -0
  40. data/lib/markdown/merge.rb +669 -122
  41. data/lib/markdown-merge.rb +9 -4
  42. data/sig/markdown/merge.rbs +3 -336
  43. data.tar.gz.sig +0 -0
  44. metadata +104 -93
  45. metadata.gz.sig +0 -0
  46. data/CHANGELOG.md +0 -308
  47. data/CITATION.cff +0 -20
  48. data/CODE_OF_CONDUCT.md +0 -134
  49. data/CONTRIBUTING.md +0 -227
  50. data/FUNDING.md +0 -74
  51. data/LICENSE.txt +0 -21
  52. data/REEK +0 -0
  53. data/RUBOCOP.md +0 -71
  54. data/SECURITY.md +0 -21
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markdown
4
+ module Merge
5
+ module Cleanse
6
+ # Composes targeted repair passes for known templating corruption
7
+ # signatures seen in historical kettle-jem runs.
8
+ class TemplatingCorruption
9
+ PASS_TYPES = [
10
+ ListMarkerDuplication,
11
+ CondensedLinkRefs,
12
+ CodeFenceSpacing,
13
+ BlockSpacing
14
+ ].freeze
15
+
16
+ attr_reader :source, :passes, :issues
17
+
18
+ def initialize(source)
19
+ @source = source.to_s
20
+ @passes = []
21
+ @issues = []
22
+ analyze
23
+ end
24
+
25
+ def malformed?
26
+ issues.any?
27
+ end
28
+
29
+ def issue_count
30
+ issues.length
31
+ end
32
+
33
+ def fix
34
+ current = source
35
+
36
+ PASS_TYPES.each do |pass_type|
37
+ pass = pass_type.new(current)
38
+ next unless pass_issues?(pass)
39
+
40
+ current = pass_output(pass, current)
41
+ end
42
+
43
+ current
44
+ end
45
+
46
+ private
47
+
48
+ def analyze
49
+ current = source
50
+
51
+ PASS_TYPES.each do |pass_type|
52
+ pass = pass_type.new(current)
53
+ @passes << pass
54
+ append_issues(pass)
55
+ current = pass_output(pass, current) if pass_issues?(pass)
56
+ end
57
+ end
58
+
59
+ def append_issues(pass)
60
+ if pass.respond_to?(:issues)
61
+ issues.concat(Array(pass.issues))
62
+ elsif pass_issues?(pass)
63
+ issues << {
64
+ type: pass.class.name.split('::').last.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym,
65
+ description: "#{pass.class.name} detected malformed content"
66
+ }
67
+ end
68
+ end
69
+
70
+ def pass_issues?(pass)
71
+ pass.respond_to?(:malformed?) ? pass.malformed? : pass.respond_to?(:condensed?) && pass.condensed?
72
+ end
73
+
74
+ def pass_output(pass, fallback)
75
+ if pass.respond_to?(:fix)
76
+ pass.fix
77
+ elsif pass.respond_to?(:expand)
78
+ pass.expand
79
+ else
80
+ fallback
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -34,9 +34,11 @@ module Markdown
34
34
  # @see Cleanse::BlockSpacing For fixing missing blank lines between block elements
35
35
  # @api public
36
36
  module Cleanse
37
- autoload :BlockSpacing, "markdown/merge/cleanse/block_spacing"
38
- autoload :CodeFenceSpacing, "markdown/merge/cleanse/code_fence_spacing"
39
- autoload :CondensedLinkRefs, "markdown/merge/cleanse/condensed_link_refs"
37
+ autoload :BlockSpacing, 'markdown/merge/cleanse/block_spacing'
38
+ autoload :CodeFenceSpacing, 'markdown/merge/cleanse/code_fence_spacing'
39
+ autoload :CondensedLinkRefs, 'markdown/merge/cleanse/condensed_link_refs'
40
+ autoload :ListMarkerDuplication, 'markdown/merge/cleanse/list_marker_duplication'
41
+ autoload :TemplatingCorruption, 'markdown/merge/cleanse/templating_corruption'
40
42
  end
41
43
  end
42
44
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markdown
4
+ module Merge
5
+ # Fuzzy matches fenced code blocks by fence info and surrounding markdown context
6
+ # so inner code-block merging can run even when block content itself differs.
7
+ class CodeBlockMatchRefiner < Ast::Merge::MatchRefinerBase
8
+ include Ast::Merge::JaccardSimilarity
9
+
10
+ DEFAULT_THRESHOLD = 0.65
11
+
12
+ def initialize(threshold: DEFAULT_THRESHOLD, **options)
13
+ super(threshold: threshold, node_types: [:code_block], **options)
14
+ end
15
+
16
+ def call(template_nodes, dest_nodes, context = {})
17
+ template_blocks = template_nodes.select { |node| node_type(node).to_s == 'code_block' }
18
+ dest_blocks = dest_nodes.select { |node| node_type(node).to_s == 'code_block' }
19
+ return [] if template_blocks.empty? || dest_blocks.empty?
20
+
21
+ greedy_match(template_blocks, dest_blocks) do |template_node, dest_node|
22
+ compute_similarity(template_node, dest_node, context)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def compute_similarity(template_node, dest_node, context)
29
+ return 0.0 unless normalized_fence_info(template_node) == normalized_fence_info(dest_node)
30
+
31
+ template_analysis = context[:template_analysis]
32
+ dest_analysis = context[:dest_analysis]
33
+ context_score = surrounding_context_similarity(template_node, template_analysis, dest_node, dest_analysis)
34
+ position_score = relative_position_similarity(template_node, template_analysis, dest_node, dest_analysis)
35
+
36
+ 0.75 + (context_score * 0.15) + (position_score * 0.10)
37
+ end
38
+
39
+ def normalized_fence_info(node)
40
+ raw = Ast::Merge::NodeTyping.unwrap(node)
41
+ raw.respond_to?(:fence_info) ? raw.fence_info.to_s.strip.downcase : ''
42
+ end
43
+
44
+ def surrounding_context_similarity(template_node, template_analysis, dest_node, dest_analysis)
45
+ template_context = [preceding_context_text(template_node, template_analysis),
46
+ following_context_text(template_node, template_analysis)].join(' ')
47
+ dest_context = [preceding_context_text(dest_node, dest_analysis),
48
+ following_context_text(dest_node, dest_analysis)].join(' ')
49
+ return 0.0 if template_context.empty? || dest_context.empty?
50
+
51
+ jaccard(extract_tokens(template_context), extract_tokens(dest_context))
52
+ end
53
+
54
+ def relative_position_similarity(template_node, template_analysis, dest_node, dest_analysis)
55
+ template_index = statement_index(template_analysis, template_node)
56
+ dest_index = statement_index(dest_analysis, dest_node)
57
+ template_count = statement_count(template_analysis)
58
+ dest_count = statement_count(dest_analysis)
59
+ return 0.0 unless template_index && dest_index && template_count.positive? && dest_count.positive?
60
+
61
+ template_ratio = template_index.to_f / template_count
62
+ dest_ratio = dest_index.to_f / dest_count
63
+ 1.0 - (template_ratio - dest_ratio).abs
64
+ end
65
+
66
+ def preceding_context_text(node, analysis)
67
+ return '' unless analysis
68
+
69
+ index = statement_index(analysis, node)
70
+ return '' unless index
71
+
72
+ (index - 1).downto(0) do |current_index|
73
+ candidate = analysis.statements[current_index]
74
+ signature = analysis.signature_at(current_index)
75
+ next unless signature.is_a?(Array) && %i[heading paragraph list].include?(signature.first)
76
+
77
+ return candidate.text.to_s
78
+ end
79
+
80
+ ''
81
+ end
82
+
83
+ def following_context_text(node, analysis)
84
+ return '' unless analysis
85
+
86
+ index = statement_index(analysis, node)
87
+ return '' unless index
88
+
89
+ ((index + 1)...analysis.statements.length).each do |current_index|
90
+ candidate = analysis.statements[current_index]
91
+ signature = analysis.signature_at(current_index)
92
+ next unless signature.is_a?(Array) && %i[heading paragraph list].include?(signature.first)
93
+
94
+ return candidate.text.to_s
95
+ end
96
+
97
+ ''
98
+ end
99
+
100
+ def statement_index(analysis, node)
101
+ return unless analysis
102
+
103
+ analysis.statements.index(node)
104
+ end
105
+
106
+ def statement_count(analysis)
107
+ Array(analysis&.statements).length
108
+ end
109
+ end
110
+ end
111
+ end