prosereflect 0.1.0 → 0.2.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +4 -0
  3. data/.github/workflows/release.yml +5 -0
  4. data/.rubocop.yml +19 -1
  5. data/.rubocop_todo.yml +143 -174
  6. data/CLAUDE.md +78 -0
  7. data/Gemfile +8 -4
  8. data/README.adoc +193 -12
  9. data/Rakefile +3 -3
  10. data/lib/prosereflect/attribute/base.rb +32 -0
  11. data/lib/prosereflect/attribute/bold.rb +18 -0
  12. data/lib/prosereflect/attribute/href.rb +22 -0
  13. data/lib/prosereflect/attribute/id.rb +24 -0
  14. data/lib/prosereflect/attribute.rb +10 -0
  15. data/lib/prosereflect/blockquote.rb +84 -0
  16. data/lib/prosereflect/bullet_list.rb +84 -0
  17. data/lib/prosereflect/code_block.rb +135 -0
  18. data/lib/prosereflect/code_block_wrapper.rb +65 -0
  19. data/lib/prosereflect/document.rb +93 -26
  20. data/lib/prosereflect/hard_break.rb +13 -11
  21. data/lib/prosereflect/heading.rb +63 -0
  22. data/lib/prosereflect/horizontal_rule.rb +70 -0
  23. data/lib/prosereflect/image.rb +126 -0
  24. data/lib/prosereflect/input/html.rb +484 -0
  25. data/lib/prosereflect/input.rb +7 -0
  26. data/lib/prosereflect/list_item.rb +64 -0
  27. data/lib/prosereflect/mark/base.rb +47 -0
  28. data/lib/prosereflect/mark/bold.rb +13 -0
  29. data/lib/prosereflect/mark/code.rb +12 -0
  30. data/lib/prosereflect/mark/italic.rb +13 -0
  31. data/lib/prosereflect/mark/link.rb +16 -0
  32. data/lib/prosereflect/mark/strike.rb +13 -0
  33. data/lib/prosereflect/mark/subscript.rb +13 -0
  34. data/lib/prosereflect/mark/superscript.rb +13 -0
  35. data/lib/prosereflect/mark/underline.rb +13 -0
  36. data/lib/prosereflect/mark.rb +15 -0
  37. data/lib/prosereflect/node.rb +181 -32
  38. data/lib/prosereflect/ordered_list.rb +86 -0
  39. data/lib/prosereflect/output/html.rb +376 -0
  40. data/lib/prosereflect/output.rb +7 -0
  41. data/lib/prosereflect/paragraph.rb +29 -20
  42. data/lib/prosereflect/parser.rb +101 -33
  43. data/lib/prosereflect/table.rb +42 -12
  44. data/lib/prosereflect/table_cell.rb +36 -11
  45. data/lib/prosereflect/table_header.rb +92 -0
  46. data/lib/prosereflect/table_row.rb +34 -11
  47. data/lib/prosereflect/text.rb +15 -19
  48. data/lib/prosereflect/user.rb +63 -0
  49. data/lib/prosereflect/version.rb +1 -1
  50. data/lib/prosereflect.rb +27 -11
  51. data/prosereflect.gemspec +17 -15
  52. data/spec/prosereflect/document_spec.rb +477 -75
  53. data/spec/prosereflect/hard_break_spec.rb +226 -30
  54. data/spec/prosereflect/input/html_spec.rb +797 -0
  55. data/spec/prosereflect/node_spec.rb +307 -137
  56. data/spec/prosereflect/output/html_spec.rb +369 -0
  57. data/spec/prosereflect/paragraph_spec.rb +458 -82
  58. data/spec/prosereflect/parser_spec.rb +311 -93
  59. data/spec/prosereflect/table_cell_spec.rb +282 -71
  60. data/spec/prosereflect/table_row_spec.rb +218 -48
  61. data/spec/prosereflect/table_spec.rb +415 -82
  62. data/spec/prosereflect/text_spec.rb +231 -72
  63. data/spec/prosereflect/user_spec.rb +76 -0
  64. data/spec/prosereflect_spec.rb +30 -23
  65. data/spec/spec_helper.rb +6 -6
  66. data/spec/support/matchers.rb +6 -6
  67. data/spec/support/shared_examples.rb +79 -50
  68. metadata +53 -6
  69. data/debug_loading.rb +0 -34
  70. data/spec/prosereflect/version_spec.rb +0 -11
@@ -1,114 +1,325 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
3
+ require "spec_helper"
4
4
 
5
5
  RSpec.describe Prosereflect::TableCell do
6
- describe 'initialization' do
7
- it 'initializes as a table_cell node' do
8
- cell = described_class.new({ 'type' => 'table_cell' })
9
- expect(cell.type).to eq('table_cell')
6
+ describe "initialization" do
7
+ it "initializes as a table_cell node" do
8
+ cell = described_class.new({ "type" => "table_cell" })
9
+ expect(cell.type).to eq("table_cell")
10
10
  end
11
11
  end
12
12
 
13
- describe '.create' do
14
- it 'creates an empty table cell' do
13
+ describe ".create" do
14
+ it "creates an empty table cell" do
15
15
  cell = described_class.create
16
- expect(cell).to be_a(described_class)
17
- expect(cell.type).to eq('table_cell')
18
- expect(cell.content).to be_empty
19
- end
20
16
 
21
- it 'creates a table cell with attributes' do
22
- attrs = { 'colspan' => 2, 'rowspan' => 1 }
23
- cell = described_class.create(attrs)
24
- expect(cell.attrs).to eq(attrs)
17
+ expected = {
18
+ "type" => "table_cell",
19
+ "content" => [],
20
+ }
21
+
22
+ expect(cell.to_h).to eq(expected)
25
23
  end
26
- end
27
24
 
28
- describe '#paragraphs' do
29
- it 'returns all paragraphs in the cell' do
30
- cell = described_class.create
31
- cell.add_paragraph('Para 1')
32
- cell.add_paragraph('Para 2')
25
+ it "creates a table cell with attributes" do
26
+ cell = described_class.create(attrs: {
27
+ "colspan" => 2,
28
+ "rowspan" => 1,
29
+ "background" => "#f5f5f5",
30
+ "align" => "center",
31
+ "valign" => "middle",
32
+ })
33
33
 
34
- expect(cell.paragraphs.size).to eq(2)
35
- expect(cell.paragraphs).to all(be_a(Prosereflect::Paragraph))
36
- end
34
+ expected = {
35
+ "type" => "table_cell",
36
+ "attrs" => {
37
+ "colspan" => 2,
38
+ "rowspan" => 1,
39
+ "background" => "#f5f5f5",
40
+ "align" => "center",
41
+ "valign" => "middle",
42
+ },
43
+ "content" => [],
44
+ }
37
45
 
38
- it 'returns empty array for cell with no paragraphs' do
39
- cell = described_class.create
40
- expect(cell.paragraphs).to eq([])
46
+ expect(cell.to_h).to eq(expected)
41
47
  end
42
48
  end
43
49
 
44
- describe '#text_content' do
45
- it 'returns concatenated text from all paragraphs with newlines' do
50
+ describe "cell structure" do
51
+ it "creates a cell with simple text content" do
46
52
  cell = described_class.create
47
- cell.add_paragraph('First paragraph')
48
- cell.add_paragraph('Second paragraph')
53
+ cell.add_paragraph("Simple text")
49
54
 
50
- expect(cell.text_content).to eq("First paragraph\nSecond paragraph")
55
+ expected = {
56
+ "type" => "table_cell",
57
+ "content" => [{
58
+ "type" => "paragraph",
59
+ "content" => [{
60
+ "type" => "text",
61
+ "text" => "Simple text",
62
+ }],
63
+ }],
64
+ }
65
+
66
+ expect(cell.to_h).to eq(expected)
51
67
  end
52
68
 
53
- it 'returns empty string for empty cell' do
69
+ it "creates a cell with complex content" do
54
70
  cell = described_class.create
55
- expect(cell.text_content).to eq('')
71
+
72
+ # First paragraph with mixed formatting
73
+ para = cell.add_paragraph("This is ")
74
+ para.add_text("bold", [Prosereflect::Mark::Bold.new])
75
+ para.add_text(" and ")
76
+ para.add_text("italic", [Prosereflect::Mark::Italic.new])
77
+ para.add_hard_break
78
+ para.add_text("with a line break")
79
+
80
+ # Second paragraph with more formatting
81
+ para = cell.add_paragraph
82
+ para.add_text("Status: ", [Prosereflect::Mark::Bold.new])
83
+ para.add_text("Done", [Prosereflect::Mark::Strike.new])
84
+ para.add_text(" → ")
85
+ para.add_text("In Progress", [Prosereflect::Mark::Italic.new])
86
+
87
+ expected = {
88
+ "type" => "table_cell",
89
+ "content" => [{
90
+ "type" => "paragraph",
91
+ "content" => [{
92
+ "type" => "text",
93
+ "text" => "This is ",
94
+ }, {
95
+ "type" => "text",
96
+ "text" => "bold",
97
+ "marks" => [{
98
+ "type" => "bold",
99
+ }],
100
+ }, {
101
+ "type" => "text",
102
+ "text" => " and ",
103
+ }, {
104
+ "type" => "text",
105
+ "text" => "italic",
106
+ "marks" => [{
107
+ "type" => "italic",
108
+ }],
109
+ }, {
110
+ "type" => "hard_break",
111
+ }, {
112
+ "type" => "text",
113
+ "text" => "with a line break",
114
+ }],
115
+ }, {
116
+ "type" => "paragraph",
117
+ "content" => [{
118
+ "type" => "text",
119
+ "text" => "Status: ",
120
+ "marks" => [{
121
+ "type" => "bold",
122
+ }],
123
+ }, {
124
+ "type" => "text",
125
+ "text" => "Done",
126
+ "marks" => [{
127
+ "type" => "strike",
128
+ }],
129
+ }, {
130
+ "type" => "text",
131
+ "text" => " → ",
132
+ }, {
133
+ "type" => "text",
134
+ "text" => "In Progress",
135
+ "marks" => [{
136
+ "type" => "italic",
137
+ }],
138
+ }],
139
+ }],
140
+ }
141
+
142
+ expect(cell.to_h).to eq(expected)
56
143
  end
57
144
  end
58
145
 
59
- describe '#lines' do
60
- it 'splits text content into lines' do
61
- cell = described_class.create
62
- cell.add_paragraph('Line 1')
63
- cell.add_paragraph('Line 2')
64
-
65
- expect(cell.lines).to eq(['Line 1', 'Line 2'])
146
+ describe "cell operations" do
147
+ let(:cell) do
148
+ c = described_class.create
149
+ c.add_paragraph("First paragraph")
150
+ c.add_paragraph("Second paragraph")
151
+ c.add_paragraph("Third paragraph")
152
+ c
66
153
  end
67
154
 
68
- it 'returns empty array for empty cell' do
69
- cell = described_class.create
70
- expect(cell.lines).to eq([])
155
+ describe "#paragraphs" do
156
+ it "returns all paragraphs in the cell" do
157
+ expect(cell.paragraphs.size).to eq(3)
158
+ expect(cell.paragraphs).to all(be_a(Prosereflect::Paragraph))
159
+ expect(cell.paragraphs.map(&:text_content)).to eq([
160
+ "First paragraph",
161
+ "Second paragraph",
162
+ "Third paragraph",
163
+ ])
164
+ end
165
+
166
+ it "returns empty array for cell with no paragraphs" do
167
+ empty_cell = described_class.create
168
+ expect(empty_cell.paragraphs).to eq([])
169
+ end
71
170
  end
72
171
 
73
- it 'handles multi-line paragraphs' do
74
- cell = described_class.create
75
- para = cell.add_paragraph('First line')
76
- para.add_hard_break
77
- para.add_text('Second line')
172
+ describe "#text_content" do
173
+ it "returns concatenated text from all paragraphs with newlines" do
174
+ expect(cell.text_content).to eq("First paragraph\nSecond paragraph\nThird paragraph")
175
+ end
176
+
177
+ it "returns empty string for empty cell" do
178
+ empty_cell = described_class.create
179
+ expect(empty_cell.text_content).to eq("")
180
+ end
181
+
182
+ it "handles complex formatting and line breaks" do
183
+ cell = described_class.create
184
+ para = cell.add_paragraph("Line 1")
185
+ para.add_hard_break
186
+ para.add_text("Line 2", [Prosereflect::Mark::Bold.new])
187
+ para.add_hard_break
188
+ para.add_text("Line 3", [Prosereflect::Mark::Italic.new])
78
189
 
79
- expect(cell.lines).to eq(['First line', 'Second line'])
190
+ expect(cell.text_content).to eq("Line 1\nLine 2\nLine 3")
191
+ end
80
192
  end
81
- end
82
193
 
83
- describe '#add_paragraph' do
84
- it 'adds a paragraph with text' do
85
- cell = described_class.create
86
- paragraph = cell.add_paragraph('Test content')
194
+ describe "#lines" do
195
+ it "splits text content into lines" do
196
+ expect(cell.lines).to eq([
197
+ "First paragraph",
198
+ "Second paragraph",
199
+ "Third paragraph",
200
+ ])
201
+ end
202
+
203
+ it "returns empty array for empty cell" do
204
+ empty_cell = described_class.create
205
+ expect(empty_cell.lines).to eq([])
206
+ end
207
+
208
+ it "handles hard breaks within paragraphs" do
209
+ cell = described_class.create
210
+ para = cell.add_paragraph("First line")
211
+ para.add_hard_break
212
+ para.add_text("Second line")
213
+ cell.add_paragraph("Third line")
87
214
 
88
- expect(cell.paragraphs.size).to eq(1)
89
- expect(paragraph).to be_a(Prosereflect::Paragraph)
90
- expect(paragraph.text_content).to eq('Test content')
215
+ expect(cell.lines).to eq([
216
+ "First line",
217
+ "Second line",
218
+ "Third line",
219
+ ])
220
+ end
91
221
  end
92
222
 
93
- it 'adds an empty paragraph' do
94
- cell = described_class.create
95
- paragraph = cell.add_paragraph
223
+ describe "#add_paragraph" do
224
+ it "adds a paragraph with text and formatting" do
225
+ cell = described_class.create
226
+ para = cell.add_paragraph("Main text")
227
+ para.add_text(" (", [Prosereflect::Mark::Bold.new])
228
+ para.add_text("important", [Prosereflect::Mark::Bold.new, Prosereflect::Mark::Italic.new])
229
+ para.add_text(")", [Prosereflect::Mark::Bold.new])
230
+
231
+ expected = {
232
+ "type" => "table_cell",
233
+ "content" => [{
234
+ "type" => "paragraph",
235
+ "content" => [{
236
+ "type" => "text",
237
+ "text" => "Main text",
238
+ }, {
239
+ "type" => "text",
240
+ "text" => " (",
241
+ "marks" => [{
242
+ "type" => "bold",
243
+ }],
244
+ }, {
245
+ "type" => "text",
246
+ "text" => "important",
247
+ "marks" => [{
248
+ "type" => "bold",
249
+ }, {
250
+ "type" => "italic",
251
+ }],
252
+ }, {
253
+ "type" => "text",
254
+ "text" => ")",
255
+ "marks" => [{
256
+ "type" => "bold",
257
+ }],
258
+ }],
259
+ }],
260
+ }
261
+
262
+ expect(cell.to_h).to eq(expected)
263
+ end
264
+
265
+ it "adds multiple paragraphs with mixed content" do
266
+ cell = described_class.create
96
267
 
97
- expect(cell.paragraphs.size).to eq(1)
98
- expect(paragraph).to be_a(Prosereflect::Paragraph)
99
- expect(paragraph.text_content).to eq('')
268
+ # First paragraph with hard break
269
+ para = cell.add_paragraph("Title")
270
+ para.add_hard_break
271
+ para.add_text("Subtitle", [Prosereflect::Mark::Italic.new])
272
+
273
+ # Second paragraph with mixed formatting
274
+ para = cell.add_paragraph
275
+ para.add_text("Note: ", [Prosereflect::Mark::Bold.new])
276
+ para.add_text("This is important")
277
+
278
+ expected = {
279
+ "type" => "table_cell",
280
+ "content" => [{
281
+ "type" => "paragraph",
282
+ "content" => [{
283
+ "type" => "text",
284
+ "text" => "Title",
285
+ }, {
286
+ "type" => "hard_break",
287
+ }, {
288
+ "type" => "text",
289
+ "text" => "Subtitle",
290
+ "marks" => [{
291
+ "type" => "italic",
292
+ }],
293
+ }],
294
+ }, {
295
+ "type" => "paragraph",
296
+ "content" => [{
297
+ "type" => "text",
298
+ "text" => "Note: ",
299
+ "marks" => [{
300
+ "type" => "bold",
301
+ }],
302
+ }, {
303
+ "type" => "text",
304
+ "text" => "This is important",
305
+ }],
306
+ }],
307
+ }
308
+
309
+ expect(cell.to_h).to eq(expected)
310
+ end
100
311
  end
101
312
  end
102
313
 
103
- describe 'serialization' do
104
- it 'converts to hash representation' do
105
- cell = described_class.create
106
- cell.add_paragraph('Test content')
314
+ describe "serialization" do
315
+ it "converts to hash representation" do
316
+ cell = described_class.new
317
+ cell.add_paragraph("Test content")
107
318
 
108
319
  hash = cell.to_h
109
- expect(hash['type']).to eq('table_cell')
110
- expect(hash['content'].size).to eq(1)
111
- expect(hash['content'][0]['type']).to eq('paragraph')
320
+ expect(hash["type"]).to eq("table_cell")
321
+ expect(hash["content"].size).to eq(1)
322
+ expect(hash["content"][0]["type"]).to eq("paragraph")
112
323
  end
113
324
  end
114
325
  end