prosereflect 0.1.1 → 0.3.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.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +97 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +5 -0
  6. data/.gitignore +4 -0
  7. data/.rubocop.yml +19 -1
  8. data/.rubocop_todo.yml +119 -183
  9. data/CLAUDE.md +78 -0
  10. data/Gemfile +8 -4
  11. data/README.adoc +2 -0
  12. data/Rakefile +3 -3
  13. data/docs/Gemfile +10 -0
  14. data/docs/INDEX.adoc +45 -0
  15. data/docs/_advanced/index.adoc +15 -0
  16. data/docs/_advanced/schema.adoc +112 -0
  17. data/docs/_advanced/step-map.adoc +66 -0
  18. data/docs/_advanced/steps.adoc +88 -0
  19. data/docs/_advanced/test-builder.adoc +61 -0
  20. data/docs/_advanced/transform.adoc +92 -0
  21. data/docs/_config.yml +174 -0
  22. data/docs/_features/html-input.adoc +69 -0
  23. data/docs/_features/html-output.adoc +45 -0
  24. data/docs/_features/index.adoc +15 -0
  25. data/docs/_features/marks.adoc +86 -0
  26. data/docs/_features/node-types.adoc +124 -0
  27. data/docs/_features/user-mentions.adoc +47 -0
  28. data/docs/_guides/custom-nodes.adoc +107 -0
  29. data/docs/_guides/index.adoc +13 -0
  30. data/docs/_guides/round-trip-html.adoc +91 -0
  31. data/docs/_guides/serialization.adoc +109 -0
  32. data/docs/_pages/index.adoc +67 -0
  33. data/docs/_reference/document-api.adoc +49 -0
  34. data/docs/_reference/index.adoc +14 -0
  35. data/docs/_reference/node-api.adoc +79 -0
  36. data/docs/_reference/schema-api.adoc +95 -0
  37. data/docs/_reference/transform-api.adoc +77 -0
  38. data/docs/_understanding/document-model.adoc +65 -0
  39. data/docs/_understanding/fragment.adoc +52 -0
  40. data/docs/_understanding/index.adoc +14 -0
  41. data/docs/_understanding/resolved-position.adoc +53 -0
  42. data/docs/_understanding/slice.adoc +54 -0
  43. data/docs/lychee.toml +63 -0
  44. data/lib/prosereflect/attribute/base.rb +4 -6
  45. data/lib/prosereflect/attribute/bold.rb +2 -4
  46. data/lib/prosereflect/attribute/href.rb +1 -3
  47. data/lib/prosereflect/attribute/id.rb +7 -7
  48. data/lib/prosereflect/attribute.rb +4 -7
  49. data/lib/prosereflect/blockquote.rb +19 -11
  50. data/lib/prosereflect/bullet_list.rb +36 -29
  51. data/lib/prosereflect/code_block.rb +23 -27
  52. data/lib/prosereflect/code_block_wrapper.rb +12 -13
  53. data/lib/prosereflect/document.rb +14 -22
  54. data/lib/prosereflect/fragment.rb +249 -0
  55. data/lib/prosereflect/hard_break.rb +6 -6
  56. data/lib/prosereflect/heading.rb +14 -15
  57. data/lib/prosereflect/horizontal_rule.rb +23 -14
  58. data/lib/prosereflect/image.rb +32 -23
  59. data/lib/prosereflect/input/html.rb +179 -104
  60. data/lib/prosereflect/input.rb +7 -0
  61. data/lib/prosereflect/list_item.rb +11 -12
  62. data/lib/prosereflect/mark/base.rb +9 -11
  63. data/lib/prosereflect/mark/bold.rb +1 -3
  64. data/lib/prosereflect/mark/code.rb +1 -3
  65. data/lib/prosereflect/mark/italic.rb +1 -3
  66. data/lib/prosereflect/mark/link.rb +1 -3
  67. data/lib/prosereflect/mark/strike.rb +1 -3
  68. data/lib/prosereflect/mark/subscript.rb +1 -3
  69. data/lib/prosereflect/mark/superscript.rb +1 -3
  70. data/lib/prosereflect/mark/underline.rb +1 -3
  71. data/lib/prosereflect/mark.rb +9 -5
  72. data/lib/prosereflect/node.rb +171 -33
  73. data/lib/prosereflect/ordered_list.rb +17 -14
  74. data/lib/prosereflect/output/html.rb +279 -50
  75. data/lib/prosereflect/output.rb +7 -0
  76. data/lib/prosereflect/paragraph.rb +11 -13
  77. data/lib/prosereflect/parser.rb +56 -66
  78. data/lib/prosereflect/resolved_pos.rb +256 -0
  79. data/lib/prosereflect/schema/attribute.rb +57 -0
  80. data/lib/prosereflect/schema/content_match.rb +656 -0
  81. data/lib/prosereflect/schema/fragment.rb +166 -0
  82. data/lib/prosereflect/schema/mark.rb +121 -0
  83. data/lib/prosereflect/schema/mark_type.rb +130 -0
  84. data/lib/prosereflect/schema/node.rb +236 -0
  85. data/lib/prosereflect/schema/node_type.rb +274 -0
  86. data/lib/prosereflect/schema/schema_main.rb +190 -0
  87. data/lib/prosereflect/schema/spec.rb +92 -0
  88. data/lib/prosereflect/schema.rb +39 -0
  89. data/lib/prosereflect/table.rb +12 -13
  90. data/lib/prosereflect/table_cell.rb +13 -13
  91. data/lib/prosereflect/table_header.rb +17 -17
  92. data/lib/prosereflect/table_row.rb +12 -12
  93. data/lib/prosereflect/text.rb +35 -11
  94. data/lib/prosereflect/transform/attr_step.rb +157 -0
  95. data/lib/prosereflect/transform/insert_step.rb +115 -0
  96. data/lib/prosereflect/transform/mapping.rb +82 -0
  97. data/lib/prosereflect/transform/mark_step.rb +269 -0
  98. data/lib/prosereflect/transform/replace_around_step.rb +181 -0
  99. data/lib/prosereflect/transform/replace_step.rb +157 -0
  100. data/lib/prosereflect/transform/slice.rb +91 -0
  101. data/lib/prosereflect/transform/step.rb +89 -0
  102. data/lib/prosereflect/transform/step_map.rb +126 -0
  103. data/lib/prosereflect/transform/structure.rb +120 -0
  104. data/lib/prosereflect/transform/transform.rb +341 -0
  105. data/lib/prosereflect/transform.rb +26 -0
  106. data/lib/prosereflect/user.rb +15 -15
  107. data/lib/prosereflect/version.rb +1 -1
  108. data/lib/prosereflect.rb +30 -17
  109. data/prosereflect.gemspec +17 -16
  110. data/spec/fixtures/documents/formatted_text.yaml +14 -0
  111. data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
  112. data/spec/fixtures/documents/lists_doc.yaml +32 -0
  113. data/spec/fixtures/documents/mixed_content.yaml +40 -0
  114. data/spec/fixtures/documents/nested_doc.yaml +20 -0
  115. data/spec/fixtures/documents/simple_doc.yaml +6 -0
  116. data/spec/fixtures/documents/table_doc.yaml +32 -0
  117. data/spec/fixtures/documents/transform_test.yaml +14 -0
  118. data/spec/fixtures/schema/custom_schema.rb +37 -0
  119. data/spec/fixtures/schema/test_schema.rb +46 -0
  120. data/spec/fixtures/test_builder/helpers.rb +212 -0
  121. data/spec/prosereflect/document_spec.rb +332 -330
  122. data/spec/prosereflect/fragment_spec.rb +273 -0
  123. data/spec/prosereflect/hard_break_spec.rb +125 -125
  124. data/spec/prosereflect/input/html_spec.rb +718 -522
  125. data/spec/prosereflect/node_spec.rb +311 -182
  126. data/spec/prosereflect/output/html_spec.rb +105 -105
  127. data/spec/prosereflect/output/whitespace_spec.rb +248 -0
  128. data/spec/prosereflect/paragraph_spec.rb +275 -274
  129. data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
  130. data/spec/prosereflect/parser_spec.rb +185 -180
  131. data/spec/prosereflect/resolved_pos_spec.rb +74 -0
  132. data/spec/prosereflect/schema/conftest.rb +68 -0
  133. data/spec/prosereflect/schema/content_match_spec.rb +237 -0
  134. data/spec/prosereflect/schema/mark_spec.rb +274 -0
  135. data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
  136. data/spec/prosereflect/schema/node_type_spec.rb +142 -0
  137. data/spec/prosereflect/schema/schema_spec.rb +194 -0
  138. data/spec/prosereflect/table_cell_spec.rb +183 -183
  139. data/spec/prosereflect/table_row_spec.rb +149 -149
  140. data/spec/prosereflect/table_spec.rb +320 -318
  141. data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
  142. data/spec/prosereflect/text_spec.rb +133 -132
  143. data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
  144. data/spec/prosereflect/transform/mapping_spec.rb +226 -0
  145. data/spec/prosereflect/transform/replace_spec.rb +832 -0
  146. data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
  147. data/spec/prosereflect/transform/slice_spec.rb +48 -0
  148. data/spec/prosereflect/transform/step_map_spec.rb +70 -0
  149. data/spec/prosereflect/transform/step_spec.rb +211 -0
  150. data/spec/prosereflect/transform/structure_spec.rb +98 -0
  151. data/spec/prosereflect/transform/transform_spec.rb +238 -0
  152. data/spec/prosereflect/user_spec.rb +31 -28
  153. data/spec/prosereflect_spec.rb +28 -26
  154. data/spec/spec_helper.rb +7 -6
  155. data/spec/support/matchers.rb +6 -6
  156. data/spec/support/shared_examples.rb +49 -49
  157. metadata +96 -5
  158. data/spec/prosereflect/version_spec.rb +0 -11
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "prosereflect/output/html"
5
+
6
+ RSpec.describe Prosereflect::Output::DOMSerializer do # rubocop:disable RSpec/SpecFilePathFormat
7
+ let(:serializer) { described_class.new(nil) }
8
+
9
+ describe "#preserve_whitespace?" do
10
+ it "returns true for code_block nodes" do
11
+ code_block = Prosereflect::CodeBlock.new
12
+ expect(serializer.send(:preserve_whitespace?, code_block)).to be true
13
+ end
14
+
15
+ it "returns true for code_block_wrapper nodes" do
16
+ wrapper = Prosereflect::CodeBlockWrapper.new
17
+ expect(serializer.send(:preserve_whitespace?, wrapper)).to be true
18
+ end
19
+
20
+ it "returns true for pre type nodes" do
21
+ node = Prosereflect::Node.new("pre")
22
+ expect(serializer.send(:preserve_whitespace?, node)).to be true
23
+ end
24
+
25
+ it "returns true for nodes with white-space: pre in style attrs" do
26
+ node = Prosereflect::Node.new("paragraph")
27
+ node.attrs = { "style" => "white-space: pre; color: red" }
28
+ expect(serializer.send(:preserve_whitespace?, node)).to be true
29
+ end
30
+
31
+ it "returns false for paragraph nodes" do
32
+ paragraph = Prosereflect::Paragraph.new
33
+ expect(serializer.send(:preserve_whitespace?, paragraph)).to be false
34
+ end
35
+
36
+ it "returns false for heading nodes" do
37
+ heading = Prosereflect::Heading.new
38
+ heading.level = 1
39
+ expect(serializer.send(:preserve_whitespace?, heading)).to be false
40
+ end
41
+
42
+ it "returns false for nodes without white-space: pre style" do
43
+ node = Prosereflect::Node.new("paragraph")
44
+ node.attrs = { "style" => "color: red" }
45
+ expect(serializer.send(:preserve_whitespace?, node)).to be false
46
+ end
47
+
48
+ it "returns false for nodes with non-string style attrs" do
49
+ node = Prosereflect::Node.new("paragraph")
50
+ node.attrs = { "style" => 123 }
51
+ expect(serializer.send(:preserve_whitespace?, node)).to be false
52
+ end
53
+
54
+ it "returns false when node does not respond to type" do
55
+ expect(serializer.send(:preserve_whitespace?, "not a node")).to be false
56
+ end
57
+
58
+ it "returns false when node does not respond to attrs" do
59
+ node = Prosereflect::Node.new("text")
60
+ allow(node).to receive(:respond_to?).and_call_original
61
+ expect(serializer.send(:preserve_whitespace?, node)).to be false
62
+ end
63
+ end
64
+
65
+ describe "#whitespace_mode" do
66
+ it "returns :preserve for code_block nodes" do
67
+ code_block = Prosereflect::CodeBlock.new
68
+ expect(serializer.send(:whitespace_mode, code_block)).to eq(:preserve)
69
+ end
70
+
71
+ it "returns :preserve for code_block_wrapper nodes" do
72
+ wrapper = Prosereflect::CodeBlockWrapper.new
73
+ expect(serializer.send(:whitespace_mode, wrapper)).to eq(:preserve)
74
+ end
75
+
76
+ it "returns :collapse for paragraph nodes" do
77
+ paragraph = Prosereflect::Paragraph.new
78
+ expect(serializer.send(:whitespace_mode, paragraph)).to eq(:collapse)
79
+ end
80
+
81
+ it "returns :collapse for heading nodes" do
82
+ heading = Prosereflect::Heading.new
83
+ heading.level = 2
84
+ expect(serializer.send(:whitespace_mode, heading)).to eq(:collapse)
85
+ end
86
+
87
+ it "returns :preserve for nodes with white-space: pre style" do
88
+ node = Prosereflect::Node.new("paragraph")
89
+ node.attrs = { "style" => "white-space: pre" }
90
+ expect(serializer.send(:whitespace_mode, node)).to eq(:preserve)
91
+ end
92
+ end
93
+
94
+ describe "#collapse_whitespace" do
95
+ it "collapses multiple spaces into one" do
96
+ expect(serializer.send(:collapse_whitespace, "hello world")).to eq("hello world")
97
+ end
98
+
99
+ it "collapses tabs into a single space" do
100
+ expect(serializer.send(:collapse_whitespace, "hello\tworld")).to eq("hello world")
101
+ end
102
+
103
+ it "collapses mixed tabs and spaces into a single space" do
104
+ expect(serializer.send(:collapse_whitespace, "hello \t world")).to eq("hello world")
105
+ end
106
+
107
+ it "collapses leading spaces" do
108
+ expect(serializer.send(:collapse_whitespace, " hello")).to eq(" hello")
109
+ end
110
+
111
+ it "collapses trailing spaces" do
112
+ expect(serializer.send(:collapse_whitespace, "hello ")).to eq("hello ")
113
+ end
114
+
115
+ it "collapses both leading and trailing spaces" do
116
+ expect(serializer.send(:collapse_whitespace, " hello world ")).to eq(" hello world ")
117
+ end
118
+
119
+ it "handles a single space string" do
120
+ expect(serializer.send(:collapse_whitespace, " ")).to eq(" ")
121
+ end
122
+
123
+ it "handles an empty string" do
124
+ expect(serializer.send(:collapse_whitespace, "")).to eq("")
125
+ end
126
+
127
+ it "does not modify a string with no extra whitespace" do
128
+ expect(serializer.send(:collapse_whitespace, "hello world")).to eq("hello world")
129
+ end
130
+ end
131
+
132
+ describe "#normalize_whitespace" do
133
+ it "replaces tabs with spaces" do
134
+ expect(serializer.send(:normalize_whitespace, "hello\tworld")).to eq("hello world")
135
+ end
136
+
137
+ it "replaces newlines with spaces" do
138
+ expect(serializer.send(:normalize_whitespace, "hello\nworld")).to eq("hello world")
139
+ end
140
+
141
+ it "replaces carriage returns with spaces" do
142
+ expect(serializer.send(:normalize_whitespace, "hello\rworld")).to eq("hello world")
143
+ end
144
+
145
+ it "replaces mixed whitespace with a single space" do
146
+ expect(serializer.send(:normalize_whitespace, "hello \t\n\r world")).to eq("hello world")
147
+ end
148
+
149
+ it "collapses multiple spaces into one" do
150
+ expect(serializer.send(:normalize_whitespace, "hello world")).to eq("hello world")
151
+ end
152
+
153
+ it "handles a string with only whitespace" do
154
+ expect(serializer.send(:normalize_whitespace, "\t\n\r")).to eq(" ")
155
+ end
156
+
157
+ it "handles an empty string" do
158
+ expect(serializer.send(:normalize_whitespace, "")).to eq("")
159
+ end
160
+
161
+ it "does not modify a string with no extra whitespace" do
162
+ expect(serializer.send(:normalize_whitespace, "hello world")).to eq("hello world")
163
+ end
164
+ end
165
+
166
+ describe "#process_text_whitespace" do
167
+ context "when node preserves whitespace" do
168
+ it "returns text unchanged for code_block nodes" do
169
+ code_block = Prosereflect::CodeBlock.new
170
+ text = " hello\n world "
171
+ expect(serializer.send(:process_text_whitespace, text, code_block)).to eq(" hello\n world ")
172
+ end
173
+
174
+ it "returns text unchanged for code_block_wrapper nodes" do
175
+ wrapper = Prosereflect::CodeBlockWrapper.new
176
+ text = " hello\n world "
177
+ expect(serializer.send(:process_text_whitespace, text, wrapper)).to eq(" hello\n world ")
178
+ end
179
+
180
+ it "returns text unchanged for nodes with white-space: pre style" do
181
+ node = Prosereflect::Node.new("paragraph")
182
+ node.attrs = { "style" => "white-space: pre" }
183
+ text = " hello\n world "
184
+ expect(serializer.send(:process_text_whitespace, text, node)).to eq(" hello\n world ")
185
+ end
186
+ end
187
+
188
+ context "when node collapses whitespace" do
189
+ it "collapses whitespace for paragraph nodes" do
190
+ paragraph = Prosereflect::Paragraph.new
191
+ text = "hello world"
192
+ expect(serializer.send(:process_text_whitespace, text, paragraph)).to eq("hello world")
193
+ end
194
+
195
+ it "collapses whitespace for heading nodes" do
196
+ heading = Prosereflect::Heading.new
197
+ heading.level = 1
198
+ text = "hello world"
199
+ expect(serializer.send(:process_text_whitespace, text, heading)).to eq("hello world")
200
+ end
201
+
202
+ it "collapses tabs and spaces for regular nodes" do
203
+ paragraph = Prosereflect::Paragraph.new
204
+ text = " hello \t world "
205
+ expect(serializer.send(:process_text_whitespace, text, paragraph)).to eq(" hello world ")
206
+ end
207
+ end
208
+ end
209
+
210
+ describe "DOMSerializer serialization" do
211
+ it "serializes a single node via #serialize_node" do
212
+ paragraph = Prosereflect::Paragraph.new
213
+ paragraph.add_text("Hello")
214
+
215
+ result = serializer.serialize_node(paragraph)
216
+ expect(result).to include("Hello")
217
+ end
218
+
219
+ it "returns text unchanged via #render_text when no schema is set" do
220
+ text = "bold text"
221
+ bold_mark = Prosereflect::Mark::Bold.new
222
+
223
+ result = serializer.render_text(text, [bold_mark])
224
+ # Without a schema, build_mark_serializers returns {},
225
+ # so apply_mark returns content unmodified
226
+ expect(result).to eq("bold text")
227
+ end
228
+
229
+ it "returns text unchanged via #render_text with multiple marks and no schema" do
230
+ text = "bold italic"
231
+ bold_mark = Prosereflect::Mark::Bold.new
232
+ italic_mark = Prosereflect::Mark::Italic.new
233
+
234
+ result = serializer.render_text(text, [bold_mark, italic_mark])
235
+ expect(result).to eq("bold italic")
236
+ end
237
+
238
+ it "renders text without marks via #render_text" do
239
+ result = serializer.render_text("plain text", [])
240
+ expect(result).to eq("plain text")
241
+ end
242
+
243
+ it "renders text with nil marks via #render_text" do
244
+ result = serializer.render_text("plain text", nil)
245
+ expect(result).to eq("plain text")
246
+ end
247
+ end
248
+ end