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,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require_relative "conftest"
5
+
6
+ RSpec.describe Prosereflect::Schema::NodeType do
7
+ include_context "test_schema"
8
+
9
+ describe "is_block?" do
10
+ it "returns true for block nodes" do
11
+ expect(schema.node_type("paragraph").is_block?).to be true
12
+ expect(schema.node_type("heading").is_block?).to be true
13
+ expect(schema.node_type("blockquote").is_block?).to be true
14
+ end
15
+
16
+ it "returns false for inline nodes" do
17
+ expect(schema.node_type("text").is_block?).to be false
18
+ end
19
+ end
20
+
21
+ describe "is_inline?" do
22
+ it "returns true for inline nodes" do
23
+ expect(schema.node_type("text").is_inline?).to be true
24
+ end
25
+
26
+ it "returns false for block nodes" do
27
+ expect(schema.node_type("paragraph").is_inline?).to be false
28
+ end
29
+ end
30
+
31
+ describe "text?" do
32
+ it "returns true only for text type" do
33
+ expect(schema.node_type("text").text?).to be true
34
+ expect(schema.node_type("paragraph").text?).to be false
35
+ end
36
+ end
37
+
38
+ describe "is_leaf?" do
39
+ it "returns true for nodes with empty content expression" do
40
+ expect(schema.node_type("text").is_leaf?).to be true
41
+ expect(schema.node_type("hard_break").is_leaf?).to be true
42
+ expect(schema.node_type("image").is_leaf?).to be true
43
+ end
44
+
45
+ it "returns false for nodes with content" do
46
+ expect(schema.node_type("paragraph").is_leaf?).to be false
47
+ end
48
+ end
49
+
50
+ describe "has_required_attrs?" do
51
+ it "returns true when node has required attributes" do
52
+ expect(schema.node_type("image").has_required_attrs?).to be true
53
+ end
54
+
55
+ it "returns false when all attrs have defaults" do
56
+ expect(schema.node_type("heading").has_required_attrs?).to be false
57
+ end
58
+ end
59
+
60
+ describe "default_attrs" do
61
+ it "returns defaults for all attrs" do
62
+ defaults = schema.node_type("heading").default_attrs
63
+ expect(defaults).to eq({ "level" => 1 })
64
+ end
65
+
66
+ it "returns nil if any required attr has no default" do
67
+ defaults = schema.node_type("image").default_attrs
68
+ expect(defaults).to be_nil
69
+ end
70
+ end
71
+
72
+ describe "compute_attrs" do
73
+ it "uses defaults when attrs not provided" do
74
+ attrs = schema.node_type("heading").compute_attrs(nil)
75
+ expect(attrs).to eq({ "level" => 1 })
76
+ end
77
+
78
+ it "overrides defaults with provided values" do
79
+ attrs = schema.node_type("heading").compute_attrs({ "level" => 3 })
80
+ expect(attrs).to eq({ "level" => 3 })
81
+ end
82
+
83
+ it "raises error for missing required attrs" do
84
+ expect do
85
+ schema.node_type("image").compute_attrs({})
86
+ end.to raise_error(Prosereflect::Schema::ValidationError)
87
+ end
88
+ end
89
+
90
+ describe "create" do
91
+ it "creates a node with computed attrs" do
92
+ node = schema.node_type("heading").create({ "level" => 2 }, nil, [])
93
+ expect(node.attrs["level"]).to eq(2)
94
+ end
95
+
96
+ it "raises error for text node" do
97
+ expect do
98
+ schema.node_type("text").create({}, nil, [])
99
+ end.to raise_error(Prosereflect::Schema::Error)
100
+ end
101
+ end
102
+
103
+ describe "valid_content?" do
104
+ it "returns true for valid content" do
105
+ frag = Prosereflect::Schema::Fragment.new([
106
+ schema.node_type("paragraph").create(
107
+ nil, [], []
108
+ ),
109
+ ])
110
+ expect(schema.node_type("doc").valid_content?(frag)).to be true
111
+ end
112
+
113
+ it "returns false for invalid content" do
114
+ frag = Prosereflect::Schema::Fragment.new([
115
+ schema.text(""),
116
+ ])
117
+ expect(schema.node_type("doc").valid_content?(frag)).to be false
118
+ end
119
+ end
120
+
121
+ describe "check_content" do
122
+ it "raises error for invalid content" do
123
+ frag = Prosereflect::Schema::Fragment.new([
124
+ schema.text(""),
125
+ ])
126
+ expect do
127
+ schema.node_type("doc").check_content(frag)
128
+ end.to raise_error(Prosereflect::Schema::ValidationError)
129
+ end
130
+ end
131
+
132
+ describe "allows_mark_type?" do
133
+ it "returns true when mark is in mark_set" do
134
+ expect(schema.node_type("paragraph").allows_mark_type?(schema.mark_type("em"))).to be true
135
+ expect(schema.node_type("paragraph").allows_mark_type?(schema.mark_type("strong"))).to be true
136
+ end
137
+
138
+ it "returns false when mark is not in mark_set" do
139
+ expect(schema.node_type("code_block").allows_mark_type?(schema.mark_type("em"))).to be false
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require_relative "conftest"
5
+
6
+ RSpec.describe Prosereflect::Schema do
7
+ include_context "test_schema"
8
+
9
+ describe "initialization" do
10
+ it "creates a schema with nodes and marks" do
11
+ expect(schema.nodes).to be_a(Hash)
12
+ expect(schema.marks).to be_a(Hash)
13
+ expect(schema.nodes.keys).to include("doc", "paragraph", "text")
14
+ expect(schema.marks.keys).to include("em", "strong", "link")
15
+ end
16
+
17
+ it "raises error for missing top node" do
18
+ expect do
19
+ described_class.new(
20
+ nodes_spec: { "text" => {} },
21
+ marks_spec: {},
22
+ )
23
+ end.to raise_error(Prosereflect::Schema::ValidationError,
24
+ /missing its top node/)
25
+ end
26
+
27
+ it "raises error for missing text type" do
28
+ expect do
29
+ described_class.new(
30
+ nodes_spec: { "doc" => { content: "block+" } },
31
+ marks_spec: {},
32
+ )
33
+ end.to raise_error(Prosereflect::Schema::ValidationError,
34
+ /every schema needs a 'text' type/)
35
+ end
36
+
37
+ it "raises error if text has attrs" do
38
+ expect do
39
+ described_class.new(
40
+ nodes_spec: {
41
+ "doc" => { content: "block+" },
42
+ "text" => { attrs: { "something" => {} } },
43
+ },
44
+ marks_spec: {},
45
+ )
46
+ end.to raise_error(Prosereflect::Schema::ValidationError,
47
+ /text node type should not have attributes/)
48
+ end
49
+
50
+ it "raises error if node and mark share same name" do
51
+ expect do
52
+ described_class.new(
53
+ nodes_spec: {
54
+ "doc" => { content: "block+" },
55
+ "text" => {},
56
+ "bold" => {},
57
+ },
58
+ marks_spec: {
59
+ "bold" => {},
60
+ },
61
+ )
62
+ end.to raise_error(Prosereflect::Schema::ValidationError,
63
+ /can not be both a node and a mark/)
64
+ end
65
+ end
66
+
67
+ describe "node_type" do
68
+ it "returns node type by name" do
69
+ node_type = schema.node_type("paragraph")
70
+ expect(node_type.name).to eq("paragraph")
71
+ end
72
+
73
+ it "raises error for unknown node type" do
74
+ expect do
75
+ schema.node_type("unknown")
76
+ end.to raise_error(Prosereflect::Schema::ValidationError,
77
+ /Unknown node type/)
78
+ end
79
+ end
80
+
81
+ describe "mark_type" do
82
+ it "returns mark type by name" do
83
+ mark_type = schema.mark_type("em")
84
+ expect(mark_type.name).to eq("em")
85
+ end
86
+
87
+ it "raises error for unknown mark type" do
88
+ expect do
89
+ schema.mark_type("unknown")
90
+ end.to raise_error(Prosereflect::Schema::ValidationError,
91
+ /Unknown mark type/)
92
+ end
93
+ end
94
+
95
+ describe "node" do
96
+ it "creates a node by type name" do
97
+ node = schema.node("paragraph")
98
+ expect(node.type.name).to eq("paragraph")
99
+ end
100
+
101
+ it "creates a node with attrs" do
102
+ node = schema.node("heading", { "level" => 2 })
103
+ expect(node.attrs["level"]).to eq(2)
104
+ end
105
+
106
+ it "creates a node with content" do
107
+ text = schema.text("Hello")
108
+ para = schema.node("paragraph", nil, [text])
109
+ expect(para.content.content.first.text).to eq("Hello")
110
+ end
111
+
112
+ it "validates content" do
113
+ text = schema.text("Hello")
114
+ expect do
115
+ # doc requires block+, not inline content directly
116
+ schema.node("doc", nil, [text])
117
+ end.to raise_error(Prosereflect::Schema::ValidationError)
118
+ end
119
+ end
120
+
121
+ describe "text" do
122
+ it "creates a text node" do
123
+ text_node = schema.text("Hello")
124
+ expect(text_node.text).to eq("Hello")
125
+ expect(text_node.type.name).to eq("text")
126
+ end
127
+
128
+ it "creates text with marks" do
129
+ em = schema.mark("em")
130
+ text_node = schema.text("Hello", [em])
131
+ expect(text_node.marks.length).to eq(1)
132
+ end
133
+ end
134
+
135
+ describe "mark" do
136
+ it "creates a mark by name" do
137
+ mark = schema.mark("em")
138
+ expect(mark.type.name).to eq("em")
139
+ end
140
+
141
+ it "creates a mark with attrs" do
142
+ mark = schema.mark("link", { "href" => "http://example.com" })
143
+ expect(mark.attrs["href"]).to eq("http://example.com")
144
+ end
145
+ end
146
+
147
+ describe "node_from_json" do
148
+ it "deserializes a node from JSON" do
149
+ json = {
150
+ "type" => "paragraph",
151
+ "content" => [
152
+ { "type" => "text", "text" => "Hello" },
153
+ ],
154
+ }
155
+
156
+ node = schema.node_from_json(json)
157
+ expect(node.type.name).to eq("paragraph")
158
+ expect(node.content.content.first.text).to eq("Hello")
159
+ end
160
+
161
+ it "deserializes marks" do
162
+ json = {
163
+ "type" => "paragraph",
164
+ "content" => [
165
+ { "type" => "text", "text" => "Hello",
166
+ "marks" => [{ "type" => "em" }] },
167
+ ],
168
+ }
169
+
170
+ node = schema.node_from_json(json)
171
+ expect(node.content.content.first.marks.length).to eq(1)
172
+ end
173
+ end
174
+
175
+ describe "mark_from_json" do
176
+ it "deserializes a mark from JSON" do
177
+ json = { "type" => "em" }
178
+ mark = schema.mark_from_json(json)
179
+ expect(mark.type.name).to eq("em")
180
+ end
181
+
182
+ it "deserializes mark with attrs" do
183
+ json = { "type" => "link", "attrs" => { "href" => "http://example.com" } }
184
+ mark = schema.mark_from_json(json)
185
+ expect(mark.attrs["href"]).to eq("http://example.com")
186
+ end
187
+ end
188
+
189
+ describe "top_node_type" do
190
+ it "returns the top node type" do
191
+ expect(schema.top_node_type.name).to eq("doc")
192
+ end
193
+ end
194
+ end