canon 0.1.23 → 0.2.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +155 -30
  3. data/docs/INDEX.adoc +4 -0
  4. data/docs/advanced/diff-classification.adoc +3 -2
  5. data/docs/advanced/verbose-mode-architecture.adoc +23 -0
  6. data/docs/features/configuration-profiles.adoc +288 -0
  7. data/docs/features/diff-formatting/character-visualization.adoc +153 -454
  8. data/docs/features/diff-formatting/display-filtering.adoc +44 -0
  9. data/docs/features/diff-formatting/display-preprocessing.adoc +656 -0
  10. data/docs/features/diff-formatting/index.adoc +47 -0
  11. data/docs/features/diff-formatting/pretty-diff-mode.adoc +154 -0
  12. data/docs/features/environment-configuration/override-system.adoc +10 -3
  13. data/docs/features/index.adoc +9 -0
  14. data/docs/features/match-options/html-policies.adoc +3 -0
  15. data/docs/features/match-options/index.adoc +32 -42
  16. data/docs/features/match-options/pretty-printed-fixtures.adoc +270 -0
  17. data/docs/guides/choosing-configuration.adoc +22 -0
  18. data/docs/reference/environment-variables.adoc +121 -1
  19. data/docs/reference/options-across-interfaces.adoc +182 -2
  20. data/lib/canon/cli.rb +20 -0
  21. data/lib/canon/commands/diff_command.rb +7 -2
  22. data/lib/canon/commands/format_command.rb +1 -1
  23. data/lib/canon/comparison/html_comparator.rb +29 -19
  24. data/lib/canon/comparison/html_compare_profile.rb +4 -4
  25. data/lib/canon/comparison/markup_comparator.rb +12 -3
  26. data/lib/canon/comparison/match_options/base_resolver.rb +29 -7
  27. data/lib/canon/comparison/match_options/json_resolver.rb +9 -0
  28. data/lib/canon/comparison/match_options/xml_resolver.rb +16 -2
  29. data/lib/canon/comparison/match_options/yaml_resolver.rb +10 -0
  30. data/lib/canon/comparison/match_options.rb +4 -1
  31. data/lib/canon/comparison/whitespace_sensitivity.rb +189 -137
  32. data/lib/canon/comparison/xml_comparator/child_comparison.rb +21 -4
  33. data/lib/canon/comparison/xml_comparator.rb +14 -12
  34. data/lib/canon/comparison/xml_node_comparison.rb +51 -6
  35. data/lib/canon/comparison.rb +52 -9
  36. data/lib/canon/config/env_schema.rb +32 -4
  37. data/lib/canon/config/override_resolver.rb +16 -3
  38. data/lib/canon/config/profile_loader.rb +135 -0
  39. data/lib/canon/config/profiles/metanorma.yml +74 -0
  40. data/lib/canon/config/profiles/metanorma_debug.yml +8 -0
  41. data/lib/canon/config/type_converter.rb +8 -0
  42. data/lib/canon/config.rb +469 -5
  43. data/lib/canon/diff/diff_classifier.rb +41 -11
  44. data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +48 -17
  45. data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +58 -0
  46. data/lib/canon/diff_formatter/diff_detail_formatter.rb +73 -17
  47. data/lib/canon/diff_formatter.rb +493 -36
  48. data/lib/canon/pretty_printer/xml_normalized.rb +395 -0
  49. data/lib/canon/rspec_matchers.rb +36 -0
  50. data/lib/canon/version.rb +1 -1
  51. data/lib/canon/xml/nodes/namespace_node.rb +4 -0
  52. data/lib/canon/xml/nodes/processing_instruction_node.rb +4 -0
  53. data/lib/canon/xml/nodes/root_node.rb +4 -0
  54. data/lib/canon/xml/nodes/text_node.rb +4 -0
  55. data/lib/tasks/performance_helpers.rb +2 -2
  56. metadata +24 -2
@@ -0,0 +1,288 @@
1
+ ---
2
+ title: Configuration Profiles
3
+ parent: Features
4
+ nav_order: 7
5
+ ---
6
+ = Configuration profiles
7
+ :toc:
8
+ :toclevels: 3
9
+
10
+ == Purpose
11
+
12
+ Configuration profiles bundle many settings into a single named preset,
13
+ eliminating repetitive configuration blocks across multiple gems.
14
+ Instead of 60+ lines of per-format settings, use one line:
15
+
16
+ [source,ruby]
17
+ ----
18
+ Canon::Config.instance.profile = :metanorma
19
+ ----
20
+
21
+ Profiles are defined in YAML files and support inheritance, so a debug
22
+ variant can extend a base profile with only the differences.
23
+
24
+ == Built-in profiles
25
+
26
+ [cols="1,3"]
27
+ |===
28
+ | Profile | Description
29
+
30
+ | `:metanorma`
31
+ | Standard Metanorma spec configuration. Sets preprocessing to `:format`,
32
+ match profile to `:spec_friendly`, diff algorithm to `:dom`, canonical
33
+ display format, normalized pretty-print display preprocessing,
34
+ and XML-specific whitespace element lists.
35
+
36
+ | `:metanorma_debug`
37
+ | Extends `:metanorma` with debug output enabled
38
+ (`show_prettyprint_received: true`).
39
+ |===
40
+
41
+ List all available profiles programmatically:
42
+
43
+ [source,ruby]
44
+ ----
45
+ Canon::Config::ProfileLoader.available_profiles
46
+ # => [:metanorma, :metanorma_debug]
47
+ ----
48
+
49
+ == Element-level whitespace classification
50
+
51
+ The metanorma profile's key feature is its **element-level whitespace classification**.
52
+ This controls how whitespace differences within specific elements are treated:
53
+
54
+ **Three-way classification:**
55
+
56
+ * **Preserve** (`:preserve`) — Every whitespace character is significant. Use for elements
57
+ where exact whitespace matters (like `<pre>`, `<code>`).
58
+
59
+ * **Collapse** (`:collapse`) — Presence matters but whitespace form doesn't.
60
+ `" hello "` equals `"hello"`. Differences are formatting-only (informative).
61
+ Use for elements like `<p>`, `<li>`, `<td>` in prose documents.
62
+
63
+ * **Strip** (`:strip`) — Whitespace is structural noise, dropped entirely.
64
+ The default for XML elements not in any list.
65
+
66
+ **Metanorma profile element lists:**
67
+
68
+ [source,ruby]
69
+ ----
70
+ # In metanorma profile
71
+ Canon::Config.instance.profile = :metanorma
72
+ Canon::Config.instance.xml.match.collapse_whitespace_elements
73
+ # => ["p", "title", "name", "td", "th", "dt", "dd", "li", ...]
74
+
75
+ Canon::Config.instance.xml.match.preserve_whitespace_elements
76
+ # => ["body", "passthrough"]
77
+ ----
78
+
79
+ **How it works with `text_content: :normalize`:**
80
+
81
+ [source,ruby]
82
+ ----
83
+ # With metanorma profile: <p> is in collapse_whitespace_elements
84
+ Canon::Config.instance.profile = :metanorma
85
+
86
+ # These are EQUIVALENT (whitespace in <p> is formatting-only)
87
+ Canon::Comparison.equivalent?('<p> hello </p>', '<p>hello</p>')
88
+ # => true
89
+
90
+ # But <body> is in preserve_whitespace_elements — every character matters
91
+ # These are NOT EQUIVALENT (whitespace in <body> is normative)
92
+ Canon::Comparison.equivalent?('<body> hello </body>', '<body>hello</body>')
93
+ # => false
94
+ ----
95
+
96
+ **Why this matters:**
97
+
98
+ In Metanorma/DocBook documents, elements like `<p>`, `<li>`, `<td>` contain prose
99
+ where whitespace formatting (extra spaces, line breaks) is irrelevant. But `<body>`
100
+ or `<passthrough>` contain code or exact whitespace that matters.
101
+
102
+ Without element-level classification, you'd have to choose:
103
+ - `text_content: :normalize` — ignores ALL whitespace, too permissive
104
+ - `text_content: :strict` — requires exact match everywhere, too strict
105
+
106
+ Element-level classification gives you fine-grained control.
107
+
108
+ == Usage
109
+
110
+ === Programmatic (Ruby API)
111
+
112
+ Use a **Symbol** for built-in profiles and a **String** for file paths:
113
+
114
+ [source,ruby]
115
+ ----
116
+ # Built-in profile (Symbol)
117
+ Canon::Config.instance.profile = :metanorma
118
+
119
+ # Local YAML file (String)
120
+ Canon::Config.instance.profile = "/path/to/my_profile.yml"
121
+ Canon::Config.instance.profile = "~/my_canon_profile.yml"
122
+ Canon::Config.instance.profile = "config/canon_profile.yml"
123
+
124
+ # Or in a configure block
125
+ Canon::Config.configure do |cfg|
126
+ cfg.profile = :metanorma
127
+ # Override individual settings after profile if needed
128
+ cfg.xml.diff.verbose_diff = true
129
+ end
130
+
131
+ # Clear the profile (revert to defaults + programmatic values)
132
+ Canon::Config.instance.profile = nil
133
+ ----
134
+
135
+ IMPORTANT: The type of the value determines how it is resolved:
136
+ **Symbols** are looked up as built-in profile names;
137
+ **Strings** are treated as file paths (with `~` expansion and relative
138
+ path resolution against the working directory).
139
+
140
+ Local YAML files can inherit from built-in profiles (see <<inheritance>>).
141
+
142
+ === Environment variable
143
+
144
+ Set `CANON_CONFIG_PROFILE` to apply a profile automatically on
145
+ initialization:
146
+
147
+ [source,bash]
148
+ ----
149
+ # Built-in profile
150
+ CANON_CONFIG_PROFILE=metanorma bundle exec rspec
151
+
152
+ # File path
153
+ CANON_CONFIG_PROFILE=~/my_profile.yml bundle exec rspec
154
+ ----
155
+
156
+ NOTE: `CANON_CONFIG_PROFILE` is distinct from `CANON_PROFILE`, which
157
+ controls the match profile (comparison behavior). The config profile
158
+ controls all settings at once.
159
+
160
+ == Priority chain
161
+
162
+ With profiles, the resolution chain becomes four layers:
163
+
164
+ [source]
165
+ ----
166
+ +------------------------------------+
167
+ | 1. Environment Variables | <- Highest Priority
168
+ | (CANON_XML_DIFF_ALGORITHM) |
169
+ +------------------------------------+
170
+ | overrides
171
+ +------------------------------------+
172
+ | 2. Programmatic Configuration |
173
+ | (config.xml.diff.algorithm=) |
174
+ +------------------------------------+
175
+ | overrides
176
+ +------------------------------------+
177
+ | 3. Profile Values |
178
+ | (from YAML profile file) |
179
+ +------------------------------------+
180
+ | overrides
181
+ +------------------------------------+
182
+ | 4. Default Values | <- Lowest Priority
183
+ | (defined in Canon::Config) |
184
+ +------------------------------------+
185
+ ----
186
+
187
+ This means:
188
+
189
+ * ENV variables always win (useful for CI overrides)
190
+ * Programmatic setter calls override profile values
191
+ * Profile values override built-in defaults
192
+ * Clearing the profile (`cfg.profile = nil`) removes only layer 3
193
+
194
+ [[inheritance]]
195
+ == Profile inheritance
196
+
197
+ A profile can inherit from another using the `inherits` key:
198
+
199
+ [source,yaml]
200
+ ----
201
+ name: my_debug
202
+ inherits: metanorma
203
+
204
+ shared:
205
+ diff:
206
+ verbose_diff: true
207
+ show_prettyprint_received: true
208
+ ----
209
+
210
+ Inheritance rules:
211
+
212
+ * Parent values are loaded first, then child values are deep-merged on top
213
+ * Hashes are merged recursively (child keys override parent keys)
214
+ * Arrays are replaced entirely (not concatenated)
215
+ * Single-parent inheritance only
216
+ * Cycle detection prevents infinite loops
217
+ * Local files can inherit from built-in profiles by name
218
+
219
+ == Creating custom profiles
220
+
221
+ === YAML file format
222
+
223
+ [source,yaml]
224
+ ----
225
+ ---
226
+ name: my_profile # <1>
227
+ description: My custom config # <2>
228
+ inherits: metanorma # <3>
229
+
230
+ shared: # <4>
231
+ preprocessing: format
232
+ match:
233
+ profile: spec_friendly
234
+ diff:
235
+ algorithm: dom
236
+ context_lines: 5
237
+ verbose_diff: false
238
+
239
+ formats: # <5>
240
+ xml:
241
+ match:
242
+ collapse_whitespace_elements:
243
+ - p
244
+ - title
245
+ - td
246
+ preserve_whitespace_elements:
247
+ - body
248
+ - passthrough
249
+ html:
250
+ diff:
251
+ show_raw_inputs: true
252
+ ----
253
+ <1> Profile name (metadata)
254
+ <2> Description (metadata)
255
+ <3> Optional: inherit from another profile (name or path)
256
+ <4> `shared` settings apply to all formats (xml, html, json, yaml, string)
257
+ <5> `formats.<name>` settings override `shared` for that specific format
258
+
259
+ === Attribute mapping
260
+
261
+ Profile YAML keys map directly to Canon configuration accessors:
262
+
263
+ [cols="2,2"]
264
+ |===
265
+ | YAML path | Ruby equivalent
266
+
267
+ | `shared.preprocessing`
268
+ | `cfg.xml.preprocessing = :format`
269
+
270
+ | `shared.match.profile`
271
+ | `cfg.xml.match.profile = :spec_friendly`
272
+
273
+ | `shared.diff.algorithm`
274
+ | `cfg.xml.diff.algorithm = :dom`
275
+
276
+ | `formats.xml.diff.context_lines`
277
+ | `cfg.xml.diff.context_lines = 5`
278
+ |===
279
+
280
+ All `DiffConfig` and `MatchConfig` attributes documented in
281
+ link:../reference/options-across-interfaces.adoc[Options Across Interfaces]
282
+ are supported.
283
+
284
+ == See also
285
+
286
+ * link:environment-configuration/override-system.adoc[Override System] -- ENV variable priority
287
+ * link:match-options/index.adoc[Match Options] -- match profile presets (`:strict`, `:spec_friendly`, etc.)
288
+ * link:../guides/choosing-configuration.adoc[Choosing Configuration] -- decision guide