canon 0.1.6 → 0.1.7
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
- data/.rubocop_todo.yml +163 -67
- data/README.adoc +400 -7
- data/docs/Gemfile +9 -0
- data/docs/INDEX.adoc +99 -182
- data/docs/_config.yml +100 -0
- data/docs/advanced/diff-classification.adoc +547 -0
- data/docs/advanced/diff-pipeline.adoc +358 -0
- data/docs/advanced/index.adoc +214 -0
- data/docs/advanced/semantic-diff-report.adoc +390 -0
- data/docs/{VERBOSE.adoc → advanced/verbose-mode-architecture.adoc} +51 -53
- data/docs/features/diff-formatting/algorithm-specific-output.adoc +533 -0
- data/docs/{CHARACTER_VISUALIZATION.adoc → features/diff-formatting/character-visualization.adoc} +23 -62
- data/docs/features/diff-formatting/colors-and-symbols.adoc +606 -0
- data/docs/features/diff-formatting/context-and-grouping.adoc +490 -0
- data/docs/features/diff-formatting/display-filtering.adoc +472 -0
- data/docs/features/diff-formatting/index.adoc +140 -0
- data/docs/features/environment-configuration/index.adoc +327 -0
- data/docs/features/environment-configuration/override-system.adoc +436 -0
- data/docs/features/environment-configuration/size-limits.adoc +273 -0
- data/docs/features/index.adoc +173 -0
- data/docs/features/input-validation/index.adoc +521 -0
- data/docs/features/match-options/algorithm-specific-behavior.adoc +365 -0
- data/docs/features/match-options/html-policies.adoc +312 -0
- data/docs/features/match-options/index.adoc +621 -0
- data/docs/getting-started/index.adoc +83 -0
- data/docs/getting-started/quick-start.adoc +76 -0
- data/docs/guides/choosing-configuration.adoc +689 -0
- data/docs/guides/index.adoc +181 -0
- data/docs/{CLI.adoc → interfaces/cli/index.adoc} +18 -13
- data/docs/interfaces/index.adoc +101 -0
- data/docs/{RSPEC.adoc → interfaces/rspec/index.adoc} +242 -31
- data/docs/{RUBY_API.adoc → interfaces/ruby-api/index.adoc} +118 -16
- data/docs/lychee.toml +65 -0
- data/docs/reference/cli-options.adoc +418 -0
- data/docs/reference/environment-variables.adoc +375 -0
- data/docs/reference/index.adoc +204 -0
- data/docs/reference/options-across-interfaces.adoc +417 -0
- data/docs/understanding/algorithms/dom-diff.adoc +389 -0
- data/docs/understanding/algorithms/index.adoc +314 -0
- data/docs/understanding/algorithms/semantic-tree-diff.adoc +533 -0
- data/docs/understanding/architecture.adoc +447 -0
- data/docs/understanding/comparison-pipeline.adoc +317 -0
- data/docs/understanding/formats/html.adoc +380 -0
- data/docs/understanding/formats/index.adoc +261 -0
- data/docs/understanding/formats/json.adoc +390 -0
- data/docs/understanding/formats/xml.adoc +366 -0
- data/docs/understanding/formats/yaml.adoc +504 -0
- data/docs/understanding/index.adoc +130 -0
- data/lib/canon/cli.rb +42 -1
- data/lib/canon/commands/diff_command.rb +108 -23
- data/lib/canon/comparison/compare_profile.rb +101 -0
- data/lib/canon/comparison/comparison_result.rb +41 -2
- data/lib/canon/comparison/html_comparator.rb +292 -71
- data/lib/canon/comparison/html_compare_profile.rb +117 -0
- data/lib/canon/comparison/match_options.rb +42 -4
- data/lib/canon/comparison/strategies/base_match_strategy.rb +99 -0
- data/lib/canon/comparison/strategies/match_strategy_factory.rb +74 -0
- data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +220 -0
- data/lib/canon/comparison/xml_comparator.rb +695 -91
- data/lib/canon/comparison.rb +207 -2
- data/lib/canon/config/env_provider.rb +71 -0
- data/lib/canon/config/env_schema.rb +58 -0
- data/lib/canon/config/override_resolver.rb +55 -0
- data/lib/canon/config/type_converter.rb +59 -0
- data/lib/canon/config.rb +158 -29
- data/lib/canon/data_model.rb +29 -0
- data/lib/canon/diff/diff_classifier.rb +74 -14
- data/lib/canon/diff/diff_context_builder.rb +41 -0
- data/lib/canon/diff/diff_line.rb +18 -2
- data/lib/canon/diff/diff_node.rb +18 -3
- data/lib/canon/diff/diff_node_mapper.rb +71 -12
- data/lib/canon/diff/formatting_detector.rb +53 -0
- data/lib/canon/diff_formatter/by_line/base_formatter.rb +60 -5
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +68 -16
- data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -37
- data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -42
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +116 -31
- data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -37
- data/lib/canon/diff_formatter/by_object/base_formatter.rb +126 -19
- data/lib/canon/diff_formatter/by_object/xml_formatter.rb +30 -1
- data/lib/canon/diff_formatter/debug_output.rb +7 -1
- data/lib/canon/diff_formatter/diff_detail_formatter.rb +674 -57
- data/lib/canon/diff_formatter/legend.rb +42 -0
- data/lib/canon/diff_formatter.rb +78 -9
- data/lib/canon/errors.rb +56 -0
- data/lib/canon/formatters/html_formatter_base.rb +35 -1
- data/lib/canon/formatters/json_formatter.rb +3 -0
- data/lib/canon/formatters/yaml_formatter.rb +3 -0
- data/lib/canon/html/data_model.rb +229 -0
- data/lib/canon/html.rb +9 -0
- data/lib/canon/options/cli_generator.rb +70 -0
- data/lib/canon/options/registry.rb +234 -0
- data/lib/canon/rspec_matchers.rb +34 -13
- data/lib/canon/tree_diff/adapters/html_adapter.rb +316 -0
- data/lib/canon/tree_diff/adapters/json_adapter.rb +204 -0
- data/lib/canon/tree_diff/adapters/xml_adapter.rb +285 -0
- data/lib/canon/tree_diff/adapters/yaml_adapter.rb +213 -0
- data/lib/canon/tree_diff/core/attribute_comparator.rb +84 -0
- data/lib/canon/tree_diff/core/matching.rb +241 -0
- data/lib/canon/tree_diff/core/node_signature.rb +164 -0
- data/lib/canon/tree_diff/core/node_weight.rb +135 -0
- data/lib/canon/tree_diff/core/tree_node.rb +450 -0
- data/lib/canon/tree_diff/matchers/hash_matcher.rb +258 -0
- data/lib/canon/tree_diff/matchers/similarity_matcher.rb +168 -0
- data/lib/canon/tree_diff/matchers/structural_propagator.rb +242 -0
- data/lib/canon/tree_diff/matchers/universal_matcher.rb +220 -0
- data/lib/canon/tree_diff/operation_converter.rb +631 -0
- data/lib/canon/tree_diff/operations/operation.rb +92 -0
- data/lib/canon/tree_diff/operations/operation_detector.rb +626 -0
- data/lib/canon/tree_diff/tree_diff_integrator.rb +140 -0
- data/lib/canon/tree_diff.rb +33 -0
- data/lib/canon/validators/json_validator.rb +3 -1
- data/lib/canon/validators/yaml_validator.rb +3 -1
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/data_model.rb +22 -23
- data/lib/canon/xml/element_matcher.rb +128 -20
- data/lib/canon/xml/namespace_helper.rb +110 -0
- data/lib/canon.rb +3 -0
- metadata +81 -23
- data/_config.yml +0 -116
- data/docs/ADVANCED_TOPICS.adoc +0 -20
- data/docs/BASIC_USAGE.adoc +0 -16
- data/docs/CUSTOMIZING_BEHAVIOR.adoc +0 -19
- data/docs/DIFF_ARCHITECTURE.adoc +0 -435
- data/docs/DIFF_FORMATTING.adoc +0 -540
- data/docs/FORMATS.adoc +0 -447
- data/docs/INPUT_VALIDATION.adoc +0 -477
- data/docs/MATCH_ARCHITECTURE.adoc +0 -463
- data/docs/MATCH_OPTIONS.adoc +0 -719
- data/docs/MODES.adoc +0 -432
- data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +0 -219
- data/docs/OPTIONS.adoc +0 -1387
- data/docs/PREPROCESSING.adoc +0 -491
- data/docs/SEMANTIC_DIFF_REPORT.adoc +0 -528
- data/docs/UNDERSTANDING_CANON.adoc +0 -17
|
@@ -186,31 +186,83 @@ module Canon
|
|
|
186
186
|
diff_line.content)
|
|
187
187
|
when :removed
|
|
188
188
|
line_num = diff_line.line_number + 1
|
|
189
|
+
formatting = diff_line.formatting?
|
|
189
190
|
informative = diff_line.informative?
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
|
|
192
|
+
output << if formatting
|
|
193
|
+
# Formatting-only removal: [ marker in dark gray
|
|
194
|
+
format_unified_line(line_num, nil, "[",
|
|
195
|
+
diff_line.content,
|
|
196
|
+
:black,
|
|
197
|
+
formatting: true)
|
|
198
|
+
elsif informative
|
|
199
|
+
# Informative removal: < marker in blue
|
|
200
|
+
format_unified_line(line_num, nil, "<",
|
|
201
|
+
diff_line.content,
|
|
202
|
+
:blue,
|
|
203
|
+
informative: true)
|
|
204
|
+
else
|
|
205
|
+
# Normative removal: - marker in red
|
|
206
|
+
format_unified_line(line_num, nil, "-",
|
|
207
|
+
diff_line.content,
|
|
208
|
+
:red)
|
|
209
|
+
end
|
|
194
210
|
when :added
|
|
195
211
|
line_num = diff_line.line_number + 1
|
|
212
|
+
formatting = diff_line.formatting?
|
|
196
213
|
informative = diff_line.informative?
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
214
|
+
|
|
215
|
+
output << if formatting
|
|
216
|
+
# Formatting-only addition: ] marker in light gray
|
|
217
|
+
format_unified_line(nil, line_num, "]",
|
|
218
|
+
diff_line.content,
|
|
219
|
+
:white,
|
|
220
|
+
formatting: true)
|
|
221
|
+
elsif informative
|
|
222
|
+
# Informative addition: > marker in cyan
|
|
223
|
+
format_unified_line(nil, line_num, ">",
|
|
224
|
+
diff_line.content,
|
|
225
|
+
:cyan,
|
|
226
|
+
informative: true)
|
|
227
|
+
else
|
|
228
|
+
# Normative addition: + marker in green
|
|
229
|
+
format_unified_line(nil, line_num, "+",
|
|
230
|
+
diff_line.content,
|
|
231
|
+
:green)
|
|
232
|
+
end
|
|
201
233
|
when :changed
|
|
202
234
|
line_num = diff_line.line_number + 1
|
|
235
|
+
formatting = diff_line.formatting?
|
|
203
236
|
informative = diff_line.informative?
|
|
204
237
|
old_content = lines1[diff_line.line_number]
|
|
205
238
|
new_content = diff_line.content
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
|
|
240
|
+
if formatting
|
|
241
|
+
output << format_unified_line(line_num, nil, "[",
|
|
242
|
+
old_content,
|
|
243
|
+
:black,
|
|
244
|
+
formatting: true)
|
|
245
|
+
output << format_unified_line(nil, line_num, "]",
|
|
246
|
+
new_content,
|
|
247
|
+
:white,
|
|
248
|
+
formatting: true)
|
|
249
|
+
elsif informative
|
|
250
|
+
output << format_unified_line(line_num, nil, "<",
|
|
251
|
+
old_content,
|
|
252
|
+
:blue,
|
|
253
|
+
informative: true)
|
|
254
|
+
output << format_unified_line(nil, line_num, ">",
|
|
255
|
+
new_content,
|
|
256
|
+
:cyan,
|
|
257
|
+
informative: true)
|
|
258
|
+
else
|
|
259
|
+
output << format_unified_line(line_num, nil, "-",
|
|
260
|
+
old_content,
|
|
261
|
+
:red)
|
|
262
|
+
output << format_unified_line(nil, line_num, "+",
|
|
263
|
+
new_content,
|
|
264
|
+
:green)
|
|
265
|
+
end
|
|
214
266
|
end
|
|
215
267
|
end
|
|
216
268
|
|
|
@@ -114,43 +114,6 @@ module Canon
|
|
|
114
114
|
output.join("\n")
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
# Format a unified diff line
|
|
118
|
-
#
|
|
119
|
-
# @param old_num [Integer, nil] Line number in old file
|
|
120
|
-
# @param new_num [Integer, nil] Line number in new file
|
|
121
|
-
# @param marker [String] Diff marker
|
|
122
|
-
# @param content [String] Line content
|
|
123
|
-
# @param color [Symbol, nil] Color for diff lines
|
|
124
|
-
# @return [String] Formatted line
|
|
125
|
-
def format_unified_line(old_num, new_num, marker, content, color = nil)
|
|
126
|
-
old_str = old_num ? "%4d" % old_num : " "
|
|
127
|
-
new_str = new_num ? "%4d" % new_num : " "
|
|
128
|
-
marker_part = "#{marker} "
|
|
129
|
-
|
|
130
|
-
visualized_content = if color
|
|
131
|
-
apply_visualization(content,
|
|
132
|
-
color)
|
|
133
|
-
else
|
|
134
|
-
content
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
if @use_color
|
|
138
|
-
yellow_old = colorize(old_str, :yellow)
|
|
139
|
-
yellow_pipe1 = colorize("|", :yellow)
|
|
140
|
-
yellow_new = colorize(new_str, :yellow)
|
|
141
|
-
yellow_pipe2 = colorize("|", :yellow)
|
|
142
|
-
|
|
143
|
-
if color
|
|
144
|
-
colored_marker = colorize(marker, color)
|
|
145
|
-
"#{yellow_old}#{yellow_pipe1}#{yellow_new}#{colored_marker} #{yellow_pipe2} #{visualized_content}"
|
|
146
|
-
else
|
|
147
|
-
"#{yellow_old}#{yellow_pipe1}#{yellow_new}#{marker} #{yellow_pipe2} #{visualized_content}"
|
|
148
|
-
end
|
|
149
|
-
else
|
|
150
|
-
"#{old_str}|#{new_str}#{marker_part}| #{visualized_content}"
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
117
|
# Format token diff lines
|
|
155
118
|
#
|
|
156
119
|
# @param old_line [Integer] Old line number
|
|
@@ -89,48 +89,6 @@ module Canon
|
|
|
89
89
|
output.join("\n")
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
-
# Format a unified diff line
|
|
93
|
-
#
|
|
94
|
-
# @param old_num [Integer, nil] Line number in old file
|
|
95
|
-
# @param new_num [Integer, nil] Line number in new file
|
|
96
|
-
# @param marker [String] Diff marker (' ', '-', '+')
|
|
97
|
-
# @param content [String] Line content
|
|
98
|
-
# @param color [Symbol, nil] Color for diff lines
|
|
99
|
-
# @return [String] Formatted line
|
|
100
|
-
def format_unified_line(old_num, new_num, marker, content, color = nil)
|
|
101
|
-
old_str = old_num ? "%4d" % old_num : " "
|
|
102
|
-
new_str = new_num ? "%4d" % new_num : " "
|
|
103
|
-
marker_part = "#{marker} "
|
|
104
|
-
|
|
105
|
-
# Only apply visualization to diff lines (when color is provided),
|
|
106
|
-
# not context lines
|
|
107
|
-
visualized_content = if color
|
|
108
|
-
apply_visualization(content, color)
|
|
109
|
-
else
|
|
110
|
-
content
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
if @use_color
|
|
114
|
-
# Yellow for line numbers and pipes
|
|
115
|
-
yellow_old = colorize(old_str, :yellow)
|
|
116
|
-
yellow_pipe1 = colorize("|", :yellow)
|
|
117
|
-
yellow_new = colorize(new_str, :yellow)
|
|
118
|
-
yellow_pipe2 = colorize("|", :yellow)
|
|
119
|
-
|
|
120
|
-
if color
|
|
121
|
-
# Colored marker for additions/deletions
|
|
122
|
-
colored_marker = colorize(marker, color)
|
|
123
|
-
"#{yellow_old}#{yellow_pipe1}#{yellow_new}#{colored_marker} #{yellow_pipe2} #{visualized_content}"
|
|
124
|
-
else
|
|
125
|
-
# Context line - apply visualization but no color
|
|
126
|
-
"#{yellow_old}#{yellow_pipe1}#{yellow_new}#{marker} #{yellow_pipe2} #{visualized_content}"
|
|
127
|
-
end
|
|
128
|
-
else
|
|
129
|
-
# No color mode
|
|
130
|
-
"#{old_str}|#{new_str}#{marker_part}| #{visualized_content}"
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
92
|
# Format changed lines with basic character-level diff
|
|
135
93
|
#
|
|
136
94
|
# @param line_num [Integer] Line number
|
|
@@ -99,33 +99,85 @@ module Canon
|
|
|
99
99
|
diff_line.content)
|
|
100
100
|
when :removed
|
|
101
101
|
line_num = diff_line.line_number + 1
|
|
102
|
+
formatting = diff_line.formatting?
|
|
102
103
|
informative = diff_line.informative?
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
|
|
105
|
+
output << if formatting
|
|
106
|
+
# Formatting-only removal: [ marker in dark gray
|
|
107
|
+
format_unified_line(line_num, nil, "[",
|
|
108
|
+
diff_line.content,
|
|
109
|
+
:black,
|
|
110
|
+
formatting: true)
|
|
111
|
+
elsif informative
|
|
112
|
+
# Informative removal: < marker in blue
|
|
113
|
+
format_unified_line(line_num, nil, "<",
|
|
114
|
+
diff_line.content,
|
|
115
|
+
:blue,
|
|
116
|
+
informative: true)
|
|
117
|
+
else
|
|
118
|
+
# Normative removal: - marker in red
|
|
119
|
+
format_unified_line(line_num, nil, "-",
|
|
120
|
+
diff_line.content,
|
|
121
|
+
:red)
|
|
122
|
+
end
|
|
107
123
|
when :added
|
|
108
124
|
line_num = diff_line.line_number + 1
|
|
125
|
+
formatting = diff_line.formatting?
|
|
109
126
|
informative = diff_line.informative?
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
|
|
128
|
+
output << if formatting
|
|
129
|
+
# Formatting-only addition: ] marker in light gray
|
|
130
|
+
format_unified_line(nil, line_num, "]",
|
|
131
|
+
diff_line.content,
|
|
132
|
+
:white,
|
|
133
|
+
formatting: true)
|
|
134
|
+
elsif informative
|
|
135
|
+
# Informative addition: > marker in cyan
|
|
136
|
+
format_unified_line(nil, line_num, ">",
|
|
137
|
+
diff_line.content,
|
|
138
|
+
:cyan,
|
|
139
|
+
informative: true)
|
|
140
|
+
else
|
|
141
|
+
# Normative addition: + marker in green
|
|
142
|
+
format_unified_line(nil, line_num, "+",
|
|
143
|
+
diff_line.content,
|
|
144
|
+
:green)
|
|
145
|
+
end
|
|
114
146
|
when :changed
|
|
115
147
|
line_num = diff_line.line_number + 1
|
|
148
|
+
formatting = diff_line.formatting?
|
|
116
149
|
informative = diff_line.informative?
|
|
117
150
|
# For changed lines, we need both old and new content
|
|
118
151
|
# For now, show as removed + added
|
|
119
152
|
old_content = lines1[diff_line.line_number]
|
|
120
153
|
new_content = diff_line.content
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
154
|
+
|
|
155
|
+
if formatting
|
|
156
|
+
output << format_unified_line(line_num, nil, "[",
|
|
157
|
+
old_content,
|
|
158
|
+
:black,
|
|
159
|
+
formatting: true)
|
|
160
|
+
output << format_unified_line(nil, line_num, "]",
|
|
161
|
+
new_content,
|
|
162
|
+
:white,
|
|
163
|
+
formatting: true)
|
|
164
|
+
elsif informative
|
|
165
|
+
output << format_unified_line(line_num, nil, "<",
|
|
166
|
+
old_content,
|
|
167
|
+
:blue,
|
|
168
|
+
informative: true)
|
|
169
|
+
output << format_unified_line(nil, line_num, ">",
|
|
170
|
+
new_content,
|
|
171
|
+
:cyan,
|
|
172
|
+
informative: true)
|
|
173
|
+
else
|
|
174
|
+
output << format_unified_line(line_num, nil, "-",
|
|
175
|
+
old_content,
|
|
176
|
+
:red)
|
|
177
|
+
output << format_unified_line(nil, line_num, "+",
|
|
178
|
+
new_content,
|
|
179
|
+
:green)
|
|
180
|
+
end
|
|
129
181
|
end
|
|
130
182
|
end
|
|
131
183
|
|
|
@@ -626,6 +678,8 @@ module Canon
|
|
|
626
678
|
|
|
627
679
|
# Format a context
|
|
628
680
|
def format_context(context, diffs, base_line1, base_line2)
|
|
681
|
+
require_relative "../../diff/formatting_detector"
|
|
682
|
+
|
|
629
683
|
output = []
|
|
630
684
|
|
|
631
685
|
(context.start_idx..context.end_idx).each do |idx|
|
|
@@ -639,22 +693,53 @@ module Canon
|
|
|
639
693
|
output << format_unified_line(line1, line2, " ",
|
|
640
694
|
change.old_element)
|
|
641
695
|
when "-"
|
|
642
|
-
|
|
643
|
-
|
|
696
|
+
# Check if removal is formatting-only
|
|
697
|
+
output << if Canon::Diff::FormattingDetector.formatting_only?(
|
|
698
|
+
change.old_element, ""
|
|
699
|
+
)
|
|
700
|
+
format_unified_line(line1, nil, "[",
|
|
701
|
+
change.old_element, :black,
|
|
702
|
+
formatting: true)
|
|
703
|
+
else
|
|
704
|
+
format_unified_line(line1, nil, "-",
|
|
705
|
+
change.old_element, :red)
|
|
706
|
+
end
|
|
644
707
|
when "+"
|
|
645
|
-
|
|
646
|
-
|
|
708
|
+
# Check if addition is formatting-only
|
|
709
|
+
output << if Canon::Diff::FormattingDetector.formatting_only?("",
|
|
710
|
+
change.new_element)
|
|
711
|
+
format_unified_line(nil, line2, "]",
|
|
712
|
+
change.new_element, :white,
|
|
713
|
+
formatting: true)
|
|
714
|
+
else
|
|
715
|
+
format_unified_line(nil, line2, "+",
|
|
716
|
+
change.new_element, :green)
|
|
717
|
+
end
|
|
647
718
|
when "!"
|
|
648
|
-
#
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
719
|
+
# Check if change is formatting-only
|
|
720
|
+
if Canon::Diff::FormattingDetector.formatting_only?(
|
|
721
|
+
change.old_element, change.new_element
|
|
722
|
+
)
|
|
723
|
+
output << format_unified_line(line1, nil, "[",
|
|
724
|
+
change.old_element, :black,
|
|
725
|
+
formatting: true)
|
|
726
|
+
output << format_unified_line(nil, line2, "]",
|
|
727
|
+
change.new_element, :white,
|
|
728
|
+
formatting: true)
|
|
729
|
+
else
|
|
730
|
+
# Token-level highlighting
|
|
731
|
+
old_tokens = tokenize_xml(change.old_element)
|
|
732
|
+
new_tokens = tokenize_xml(change.new_element)
|
|
733
|
+
token_diffs = ::Diff::LCS.sdiff(old_tokens, new_tokens)
|
|
734
|
+
|
|
735
|
+
old_highlighted = build_token_highlighted_text(token_diffs,
|
|
736
|
+
:old)
|
|
737
|
+
new_highlighted = build_token_highlighted_text(token_diffs,
|
|
738
|
+
:new)
|
|
739
|
+
|
|
740
|
+
output << format_token_diff_line(line1, line2, old_highlighted,
|
|
741
|
+
new_highlighted)
|
|
742
|
+
end
|
|
658
743
|
end
|
|
659
744
|
end
|
|
660
745
|
|
|
@@ -722,7 +807,7 @@ module Canon
|
|
|
722
807
|
|
|
723
808
|
# Format a unified diff line
|
|
724
809
|
def format_unified_line(old_num, new_num, marker, content, color = nil,
|
|
725
|
-
informative: false)
|
|
810
|
+
informative: false, formatting: false)
|
|
726
811
|
old_str = old_num ? "%4d" % old_num : " "
|
|
727
812
|
new_str = new_num ? "%4d" % new_num : " "
|
|
728
813
|
marker_part = "#{marker} "
|
|
@@ -113,43 +113,6 @@ module Canon
|
|
|
113
113
|
output.join("\n")
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
-
# Format a unified diff line
|
|
117
|
-
#
|
|
118
|
-
# @param old_num [Integer, nil] Line number in old file
|
|
119
|
-
# @param new_num [Integer, nil] Line number in new file
|
|
120
|
-
# @param marker [String] Diff marker
|
|
121
|
-
# @param content [String] Line content
|
|
122
|
-
# @param color [Symbol, nil] Color for diff lines
|
|
123
|
-
# @return [String] Formatted line
|
|
124
|
-
def format_unified_line(old_num, new_num, marker, content, color = nil)
|
|
125
|
-
old_str = old_num ? "%4d" % old_num : " "
|
|
126
|
-
new_str = new_num ? "%4d" % new_num : " "
|
|
127
|
-
marker_part = "#{marker} "
|
|
128
|
-
|
|
129
|
-
visualized_content = if color
|
|
130
|
-
apply_visualization(content,
|
|
131
|
-
color)
|
|
132
|
-
else
|
|
133
|
-
content
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
if @use_color
|
|
137
|
-
yellow_old = colorize(old_str, :yellow)
|
|
138
|
-
yellow_pipe1 = colorize("|", :yellow)
|
|
139
|
-
yellow_new = colorize(new_str, :yellow)
|
|
140
|
-
yellow_pipe2 = colorize("|", :yellow)
|
|
141
|
-
|
|
142
|
-
if color
|
|
143
|
-
colored_marker = colorize(marker, color)
|
|
144
|
-
"#{yellow_old}#{yellow_pipe1}#{yellow_new}#{colored_marker} #{yellow_pipe2} #{visualized_content}"
|
|
145
|
-
else
|
|
146
|
-
"#{yellow_old}#{yellow_pipe1}#{yellow_new}#{marker} #{yellow_pipe2} #{visualized_content}"
|
|
147
|
-
end
|
|
148
|
-
else
|
|
149
|
-
"#{old_str}|#{new_str}#{marker_part}| #{visualized_content}"
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
116
|
# Format token diff lines
|
|
154
117
|
#
|
|
155
118
|
# @param old_line [Integer] Old line number
|
|
@@ -8,10 +8,12 @@ module Canon
|
|
|
8
8
|
class BaseFormatter
|
|
9
9
|
attr_reader :use_color, :visualization_map
|
|
10
10
|
|
|
11
|
-
def initialize(use_color: true, visualization_map: nil
|
|
11
|
+
def initialize(use_color: true, visualization_map: nil,
|
|
12
|
+
show_diffs: :all)
|
|
12
13
|
@use_color = use_color
|
|
13
14
|
@visualization_map = visualization_map ||
|
|
14
|
-
DiffFormatter::DEFAULT_VISUALIZATION_MAP
|
|
15
|
+
Canon::DiffFormatter::DEFAULT_VISUALIZATION_MAP
|
|
16
|
+
@show_diffs = show_diffs
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
# Format differences for display
|
|
@@ -35,37 +37,90 @@ module Canon
|
|
|
35
37
|
output = []
|
|
36
38
|
output << colorize("Visual Diff:", :cyan, :bold)
|
|
37
39
|
|
|
40
|
+
# Filter differences for display based on show_diffs setting
|
|
41
|
+
filtered_diffs = filter_differences_for_display(diffs_array)
|
|
42
|
+
|
|
38
43
|
# Group differences by path for tree building
|
|
39
|
-
tree = build_diff_tree(
|
|
44
|
+
tree = build_diff_tree(filtered_diffs)
|
|
45
|
+
|
|
46
|
+
# Render tree with line counting
|
|
47
|
+
@line_count = 0
|
|
48
|
+
@max_lines = get_max_diff_lines
|
|
49
|
+
rendered = render_tree(tree)
|
|
50
|
+
|
|
51
|
+
# Add truncation notice if needed
|
|
52
|
+
if @truncated
|
|
53
|
+
rendered += "\n\n"
|
|
54
|
+
rendered += colorize(
|
|
55
|
+
"... Output truncated at #{@max_lines} lines ...", :yellow, :bold
|
|
56
|
+
)
|
|
57
|
+
rendered += "\n"
|
|
58
|
+
rendered += colorize(
|
|
59
|
+
"Increase limit via CANON_MAX_DIFF_LINES or config.diff.max_diff_lines", :yellow
|
|
60
|
+
)
|
|
61
|
+
end
|
|
40
62
|
|
|
41
|
-
|
|
42
|
-
output << render_tree(tree)
|
|
63
|
+
output << rendered
|
|
43
64
|
|
|
44
65
|
output.join("\n")
|
|
45
66
|
end
|
|
46
67
|
|
|
47
68
|
# Factory method to create format-specific formatter
|
|
48
|
-
def self.for_format(format, use_color: true, visualization_map: nil
|
|
69
|
+
def self.for_format(format, use_color: true, visualization_map: nil,
|
|
70
|
+
show_diffs: :all)
|
|
49
71
|
case format
|
|
50
72
|
when :xml, :html
|
|
51
73
|
require_relative "xml_formatter"
|
|
52
74
|
XmlFormatter.new(use_color: use_color,
|
|
53
|
-
visualization_map: visualization_map
|
|
75
|
+
visualization_map: visualization_map,
|
|
76
|
+
show_diffs: show_diffs)
|
|
54
77
|
when :json
|
|
55
78
|
require_relative "json_formatter"
|
|
56
79
|
JsonFormatter.new(use_color: use_color,
|
|
57
|
-
visualization_map: visualization_map
|
|
80
|
+
visualization_map: visualization_map,
|
|
81
|
+
show_diffs: show_diffs)
|
|
58
82
|
when :yaml
|
|
59
83
|
require_relative "yaml_formatter"
|
|
60
84
|
YamlFormatter.new(use_color: use_color,
|
|
61
|
-
visualization_map: visualization_map
|
|
85
|
+
visualization_map: visualization_map,
|
|
86
|
+
show_diffs: show_diffs)
|
|
62
87
|
else
|
|
63
|
-
new(use_color: use_color, visualization_map: visualization_map
|
|
88
|
+
new(use_color: use_color, visualization_map: visualization_map,
|
|
89
|
+
show_diffs: show_diffs)
|
|
64
90
|
end
|
|
65
91
|
end
|
|
66
92
|
|
|
67
93
|
private
|
|
68
94
|
|
|
95
|
+
# Filter differences for display based on show_diffs setting
|
|
96
|
+
#
|
|
97
|
+
# @param differences [Array<Canon::Diff::DiffNode>] Array of differences
|
|
98
|
+
# @return [Array<Canon::Diff::DiffNode>] Filtered differences
|
|
99
|
+
def filter_differences_for_display(differences)
|
|
100
|
+
return differences if @show_diffs.nil? || @show_diffs == :all
|
|
101
|
+
|
|
102
|
+
differences.select do |diff|
|
|
103
|
+
# Handle both DiffNode objects and legacy Hash format
|
|
104
|
+
is_normative = if diff.respond_to?(:normative?)
|
|
105
|
+
diff.normative?
|
|
106
|
+
elsif diff.is_a?(Hash) && diff.key?(:normative)
|
|
107
|
+
diff[:normative]
|
|
108
|
+
else
|
|
109
|
+
# Default to normative if unknown
|
|
110
|
+
true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
case @show_diffs
|
|
114
|
+
when :normative
|
|
115
|
+
is_normative
|
|
116
|
+
when :informative
|
|
117
|
+
!is_normative
|
|
118
|
+
else
|
|
119
|
+
true # Unknown value, show all
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
69
124
|
# Generate success message
|
|
70
125
|
def success_message
|
|
71
126
|
emoji = @use_color ? "✅ " : ""
|
|
@@ -104,7 +159,9 @@ module Canon
|
|
|
104
159
|
parts.each_with_index do |part, index|
|
|
105
160
|
current[part] ||= {}
|
|
106
161
|
if index == parts.length - 1
|
|
107
|
-
|
|
162
|
+
# Support multiple diffs at the same path
|
|
163
|
+
current[part][:__diffs__] ||= []
|
|
164
|
+
current[part][:__diffs__] << diff
|
|
108
165
|
else
|
|
109
166
|
current = current[part]
|
|
110
167
|
end
|
|
@@ -135,9 +192,24 @@ module Canon
|
|
|
135
192
|
|
|
136
193
|
parts = []
|
|
137
194
|
current = node
|
|
195
|
+
visited = Set.new
|
|
138
196
|
|
|
139
197
|
while current.respond_to?(:name)
|
|
198
|
+
# Prevent infinite loops by tracking visited nodes
|
|
199
|
+
break if visited.include?(current.object_id)
|
|
200
|
+
|
|
201
|
+
visited << current.object_id
|
|
202
|
+
|
|
140
203
|
parts.unshift(current.name) if current.name
|
|
204
|
+
|
|
205
|
+
# Stop at document or fragment roots
|
|
206
|
+
break if current.is_a?(Nokogiri::XML::Document) ||
|
|
207
|
+
current.is_a?(Nokogiri::HTML4::Document) ||
|
|
208
|
+
current.is_a?(Nokogiri::HTML5::Document) ||
|
|
209
|
+
current.is_a?(Nokogiri::XML::DocumentFragment) ||
|
|
210
|
+
current.is_a?(Nokogiri::HTML4::DocumentFragment) ||
|
|
211
|
+
current.is_a?(Nokogiri::HTML5::DocumentFragment)
|
|
212
|
+
|
|
141
213
|
current = current.parent if current.respond_to?(:parent)
|
|
142
214
|
end
|
|
143
215
|
|
|
@@ -148,7 +220,7 @@ module Canon
|
|
|
148
220
|
def render_tree(tree, prefix: "", is_last: true)
|
|
149
221
|
output = []
|
|
150
222
|
|
|
151
|
-
sorted_keys = tree.keys.reject { |k| k == :
|
|
223
|
+
sorted_keys = tree.keys.reject { |k| k == :__diffs__ }
|
|
152
224
|
begin
|
|
153
225
|
sorted_keys = sorted_keys.sort_by(&:to_s)
|
|
154
226
|
rescue ArgumentError
|
|
@@ -156,25 +228,50 @@ module Canon
|
|
|
156
228
|
end
|
|
157
229
|
|
|
158
230
|
sorted_keys.each_with_index do |key, index|
|
|
231
|
+
# Check line limit
|
|
232
|
+
if @max_lines&.positive? && @line_count >= @max_lines
|
|
233
|
+
@truncated = true
|
|
234
|
+
break
|
|
235
|
+
end
|
|
236
|
+
|
|
159
237
|
is_last_item = (index == sorted_keys.length - 1)
|
|
160
238
|
connector = is_last_item ? "└── " : "├── "
|
|
161
239
|
continuation = is_last_item ? " " : "│ "
|
|
162
240
|
|
|
163
241
|
value = tree[key]
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if
|
|
167
|
-
# Render
|
|
168
|
-
|
|
242
|
+
diffs = value[:__diffs__] if value.is_a?(Hash)
|
|
243
|
+
|
|
244
|
+
if diffs && !diffs.empty?
|
|
245
|
+
# Render all differences at this path
|
|
246
|
+
diffs.each_with_index do |diff, diff_idx|
|
|
247
|
+
# Use proper connector for each diff
|
|
248
|
+
current_connector = if diff_idx == diffs.length - 1
|
|
249
|
+
connector
|
|
250
|
+
else
|
|
251
|
+
is_last_item ? "├── " : "├── "
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
line = render_diff_node(key, diff, prefix, current_connector)
|
|
255
|
+
output << line
|
|
256
|
+
@line_count += line.count("\n") + 1
|
|
257
|
+
end
|
|
169
258
|
else
|
|
170
259
|
# Render intermediate path
|
|
171
|
-
|
|
260
|
+
line = colorize("#{prefix}#{connector}#{key}:", :cyan)
|
|
261
|
+
output << line
|
|
262
|
+
@line_count += 1
|
|
263
|
+
|
|
172
264
|
# Recurse into subtree
|
|
173
265
|
if value.is_a?(Hash)
|
|
174
|
-
|
|
266
|
+
subtree = render_tree(value, prefix: prefix + continuation,
|
|
175
267
|
is_last: is_last_item)
|
|
268
|
+
output << subtree
|
|
269
|
+
# line_count already updated in recursive call
|
|
176
270
|
end
|
|
177
271
|
end
|
|
272
|
+
|
|
273
|
+
# Check again after adding content
|
|
274
|
+
break if @truncated
|
|
178
275
|
end
|
|
179
276
|
|
|
180
277
|
output.join("\n")
|
|
@@ -193,6 +290,16 @@ module Canon
|
|
|
193
290
|
require "paint"
|
|
194
291
|
"\e[0m#{Paint[text, *colors]}"
|
|
195
292
|
end
|
|
293
|
+
|
|
294
|
+
# Get max diff lines limit
|
|
295
|
+
#
|
|
296
|
+
# @return [Integer, nil] Max diff output lines
|
|
297
|
+
def get_max_diff_lines
|
|
298
|
+
# Try to get from config if available
|
|
299
|
+
config = Canon::Config.instance
|
|
300
|
+
# Default to 10,000 if config not available
|
|
301
|
+
config&.xml&.diff&.max_diff_lines || 10_000
|
|
302
|
+
end
|
|
196
303
|
end
|
|
197
304
|
end
|
|
198
305
|
end
|