prosereflect 0.1.1 → 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 (68) 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 +141 -191
  6. data/CLAUDE.md +78 -0
  7. data/Gemfile +8 -4
  8. data/Rakefile +3 -3
  9. data/lib/prosereflect/attribute/base.rb +4 -6
  10. data/lib/prosereflect/attribute/bold.rb +2 -4
  11. data/lib/prosereflect/attribute/href.rb +1 -3
  12. data/lib/prosereflect/attribute/id.rb +7 -7
  13. data/lib/prosereflect/attribute.rb +4 -7
  14. data/lib/prosereflect/blockquote.rb +10 -11
  15. data/lib/prosereflect/bullet_list.rb +16 -15
  16. data/lib/prosereflect/code_block.rb +26 -26
  17. data/lib/prosereflect/code_block_wrapper.rb +12 -13
  18. data/lib/prosereflect/document.rb +14 -22
  19. data/lib/prosereflect/hard_break.rb +6 -6
  20. data/lib/prosereflect/heading.rb +14 -15
  21. data/lib/prosereflect/horizontal_rule.rb +14 -14
  22. data/lib/prosereflect/image.rb +23 -23
  23. data/lib/prosereflect/input/html.rb +83 -104
  24. data/lib/prosereflect/input.rb +7 -0
  25. data/lib/prosereflect/list_item.rb +11 -12
  26. data/lib/prosereflect/mark/base.rb +9 -11
  27. data/lib/prosereflect/mark/bold.rb +1 -3
  28. data/lib/prosereflect/mark/code.rb +1 -3
  29. data/lib/prosereflect/mark/italic.rb +1 -3
  30. data/lib/prosereflect/mark/link.rb +1 -3
  31. data/lib/prosereflect/mark/strike.rb +1 -3
  32. data/lib/prosereflect/mark/subscript.rb +1 -3
  33. data/lib/prosereflect/mark/superscript.rb +1 -3
  34. data/lib/prosereflect/mark/underline.rb +1 -3
  35. data/lib/prosereflect/mark.rb +9 -5
  36. data/lib/prosereflect/node.rb +31 -31
  37. data/lib/prosereflect/ordered_list.rb +15 -14
  38. data/lib/prosereflect/output/html.rb +52 -50
  39. data/lib/prosereflect/output.rb +7 -0
  40. data/lib/prosereflect/paragraph.rb +11 -13
  41. data/lib/prosereflect/parser.rb +47 -66
  42. data/lib/prosereflect/table.rb +12 -13
  43. data/lib/prosereflect/table_cell.rb +13 -13
  44. data/lib/prosereflect/table_header.rb +17 -17
  45. data/lib/prosereflect/table_row.rb +12 -12
  46. data/lib/prosereflect/text.rb +11 -11
  47. data/lib/prosereflect/user.rb +15 -15
  48. data/lib/prosereflect/version.rb +1 -1
  49. data/lib/prosereflect.rb +27 -17
  50. data/prosereflect.gemspec +17 -16
  51. data/spec/prosereflect/document_spec.rb +332 -330
  52. data/spec/prosereflect/hard_break_spec.rb +125 -125
  53. data/spec/prosereflect/input/html_spec.rb +522 -522
  54. data/spec/prosereflect/node_spec.rb +183 -182
  55. data/spec/prosereflect/output/html_spec.rb +105 -105
  56. data/spec/prosereflect/paragraph_spec.rb +275 -274
  57. data/spec/prosereflect/parser_spec.rb +185 -180
  58. data/spec/prosereflect/table_cell_spec.rb +183 -183
  59. data/spec/prosereflect/table_row_spec.rb +149 -149
  60. data/spec/prosereflect/table_spec.rb +320 -318
  61. data/spec/prosereflect/text_spec.rb +133 -132
  62. data/spec/prosereflect/user_spec.rb +31 -28
  63. data/spec/prosereflect_spec.rb +28 -26
  64. data/spec/spec_helper.rb +6 -6
  65. data/spec/support/matchers.rb +6 -6
  66. data/spec/support/shared_examples.rb +49 -49
  67. metadata +8 -5
  68. data/spec/prosereflect/version_spec.rb +0 -11
@@ -1,13 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'lutaml/model'
4
-
5
3
  module Prosereflect
6
4
  module Attribute
5
+ autoload :Base, "#{__dir__}/attribute/base"
6
+ autoload :Href, "#{__dir__}/attribute/href"
7
+ autoload :Id, "#{__dir__}/attribute/id"
8
+ autoload :Bold, "#{__dir__}/attribute/bold"
7
9
  end
8
10
  end
9
-
10
- require_relative 'attribute/base'
11
- require_relative 'attribute/href'
12
- require_relative 'attribute/id'
13
- require_relative 'attribute/bold'
@@ -1,21 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'paragraph'
5
-
6
3
  module Prosereflect
7
4
  # It can contain other block-level content like paragraphs, lists, etc.
8
5
  class Blockquote < Node
9
- PM_TYPE = 'blockquote'
6
+ PM_TYPE = "blockquote"
10
7
 
11
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
12
11
  attribute :citation, :string
13
12
  attribute :attrs, :hash
14
13
 
15
14
  key_value do
16
- map 'type', to: :type, render_default: true
17
- map 'content', to: :content
18
- map 'attrs', to: :attrs
15
+ map "type", to: :type, render_default: true
16
+ map "content", to: :content
17
+ map "attrs", to: :attrs
19
18
  end
20
19
 
21
20
  def initialize(attributes = {})
@@ -56,12 +55,12 @@ module Prosereflect
56
55
  # Update citation/source for the blockquote
57
56
  def citation=(source)
58
57
  self.attrs ||= {}
59
- attrs['citation'] = source
58
+ attrs["citation"] = source
60
59
  end
61
60
 
62
61
  # Get citation/source of the blockquote
63
62
  def citation
64
- attrs&.[]('citation')
63
+ attrs&.[]("citation")
65
64
  end
66
65
 
67
66
  # Check if blockquote has a citation
@@ -72,7 +71,7 @@ module Prosereflect
72
71
  # Remove citation
73
72
  def remove_citation
74
73
  self.attrs ||= {}
75
- attrs.delete('citation')
74
+ attrs.delete("citation")
76
75
  end
77
76
 
78
77
  def add_paragraph(text)
@@ -1,26 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'list_item'
5
-
6
3
  module Prosereflect
7
4
  # BulletList class represents an unordered list in ProseMirror.
8
5
  class BulletList < Node
9
- PM_TYPE = 'bullet_list'
6
+ PM_TYPE = "bullet_list"
10
7
 
11
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
12
11
  attribute :bullet_style, :string
13
12
  attribute :attrs, :hash
14
13
 
15
14
  key_value do
16
- map 'type', to: :type, render_default: true
17
- map 'attrs', to: :attrs
18
- map 'content', to: :content
15
+ map "type", to: :type, render_default: true
16
+ map "attrs", to: :attrs
17
+ map "content", to: :content
19
18
  end
20
19
 
21
20
  def initialize(attributes = {})
22
21
  attributes[:content] ||= []
23
- attributes[:attrs] ||= { 'bullet_style' => nil }
22
+ attributes[:attrs] ||= { "bullet_style" => nil }
24
23
  super
25
24
  end
26
25
 
@@ -31,11 +30,11 @@ module Prosereflect
31
30
  def bullet_style=(value)
32
31
  @bullet_style = value
33
32
  self.attrs ||= {}
34
- attrs['bullet_style'] = value
33
+ attrs["bullet_style"] = value
35
34
  end
36
35
 
37
36
  def bullet_style
38
- @bullet_style || attrs&.[]('bullet_style')
37
+ @bullet_style || attrs&.[]("bullet_style")
39
38
  end
40
39
 
41
40
  def add_item(text)
@@ -67,16 +66,18 @@ module Prosereflect
67
66
 
68
67
  # Get text content with proper formatting
69
68
  def text_content
70
- return '' unless content
69
+ return "" unless content
71
70
 
72
- content.map { |item| item.respond_to?(:text_content) ? item.text_content : '' }.join("\n")
71
+ content.map do |item|
72
+ item.respond_to?(:text_content) ? item.text_content : ""
73
+ end.join("\n")
73
74
  end
74
75
 
75
76
  # Override to_h to exclude empty attrs
76
77
  def to_h
77
78
  hash = super
78
- hash['attrs'] ||= {}
79
- hash['attrs']['bullet_style'] = bullet_style
79
+ hash["attrs"] ||= {}
80
+ hash["attrs"]["bullet_style"] = bullet_style
80
81
  hash
81
82
  end
82
83
  end
@@ -1,27 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
-
5
3
  module Prosereflect
6
4
  # CodeBlock class represents a code block in ProseMirror.
7
5
  class CodeBlock < Node
8
- PM_TYPE = 'code_block'
6
+ PM_TYPE = "code_block"
9
7
 
10
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
11
11
  attribute :language, :string
12
12
  attribute :line_numbers, :boolean
13
13
  attribute :attrs, :hash
14
14
 
15
15
  key_value do
16
- map 'type', to: :type, render_default: true
17
- map 'attrs', to: :attrs
18
- map 'content', to: :content
16
+ map "type", to: :type, render_default: true
17
+ map "attrs", to: :attrs
18
+ map "content", to: :content
19
19
  end
20
20
 
21
21
  def initialize(attributes = {})
22
22
  attributes[:attrs] ||= {
23
- 'content' => nil,
24
- 'language' => nil
23
+ "content" => nil,
24
+ "language" => nil,
25
25
  }
26
26
  super
27
27
  end
@@ -33,38 +33,38 @@ module Prosereflect
33
33
  def language=(value)
34
34
  @language = value
35
35
  self.attrs ||= {}
36
- attrs['language'] = value
36
+ attrs["language"] = value
37
37
  end
38
38
 
39
39
  def language
40
- @language || attrs&.[]('language')
40
+ @language || attrs&.[]("language")
41
41
  end
42
42
 
43
43
  def line_numbers=(value)
44
44
  @line_numbers = value
45
45
  self.attrs ||= {}
46
- attrs['line_numbers'] = value
46
+ attrs["line_numbers"] = value
47
47
  end
48
48
 
49
49
  def line_numbers
50
- @line_numbers || attrs&.[]('line_numbers') || false
50
+ @line_numbers || attrs&.[]("line_numbers") || false
51
51
  end
52
52
 
53
53
  def content=(value)
54
54
  @content = value
55
55
  self.attrs ||= {}
56
- attrs['content'] = value
56
+ attrs["content"] = value
57
57
  end
58
58
 
59
59
  def content
60
- @content || attrs&.[]('content')
60
+ @content || attrs&.[]("content")
61
61
  end
62
62
 
63
63
  attr_reader :highlight_lines_str
64
64
 
65
65
  def highlight_lines=(lines)
66
66
  @highlight_lines_str = if lines.is_a?(Array)
67
- lines.join(',')
67
+ lines.join(",")
68
68
  else
69
69
  lines.to_s
70
70
  end
@@ -73,7 +73,7 @@ module Prosereflect
73
73
  def highlight_lines
74
74
  return [] unless @highlight_lines_str
75
75
 
76
- @highlight_lines_str.split(',').map(&:to_i)
76
+ @highlight_lines_str.split(",").map(&:to_i)
77
77
  end
78
78
 
79
79
  def text_content
@@ -82,12 +82,12 @@ module Prosereflect
82
82
 
83
83
  def to_h
84
84
  hash = super
85
- hash['attrs'] = {
86
- 'content' => content,
87
- 'language' => language
85
+ hash["attrs"] = {
86
+ "content" => content,
87
+ "language" => language,
88
88
  }
89
- hash['attrs']['line_numbers'] = line_numbers if line_numbers
90
- hash.delete('content')
89
+ hash["attrs"]["line_numbers"] = line_numbers if line_numbers
90
+ hash.delete("content")
91
91
  hash
92
92
  end
93
93
 
@@ -96,7 +96,7 @@ module Prosereflect
96
96
  {
97
97
  language: language,
98
98
  line_numbers: line_numbers,
99
- highlight_lines: highlight_lines
99
+ highlight_lines: highlight_lines,
100
100
  }.compact
101
101
  end
102
102
 
@@ -118,14 +118,14 @@ module Prosereflect
118
118
  return content if lines.empty?
119
119
 
120
120
  min_indent = lines.reject(&:empty?)
121
- .map { |line| line[/^\s*/].length }
122
- .min || 0
121
+ .map { |line| line[/^\s*/].length }
122
+ .min || 0
123
123
 
124
124
  normalized_lines = lines.map do |line|
125
125
  if line.empty?
126
126
  line
127
127
  else
128
- line[min_indent..] || ''
128
+ line[min_indent..] || ""
129
129
  end
130
130
  end
131
131
 
@@ -1,27 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'code_block'
5
-
6
3
  module Prosereflect
7
4
  # CodeBlockWrapper class represents a pre tag that wraps code blocks in ProseMirror.
8
5
  class CodeBlockWrapper < Node
9
- PM_TYPE = 'code_block_wrapper'
6
+ PM_TYPE = "code_block_wrapper"
10
7
 
11
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
12
11
  attribute :line_numbers, :boolean
13
12
  attribute :attrs, :hash
14
13
 
15
14
  key_value do
16
- map 'type', to: :type, render_default: true
17
- map 'attrs', to: :attrs
18
- map 'content', to: :content
15
+ map "type", to: :type, render_default: true
16
+ map "attrs", to: :attrs
17
+ map "content", to: :content
19
18
  end
20
19
 
21
20
  def initialize(attributes = {})
22
21
  attributes[:content] ||= []
23
22
  attributes[:attrs] = {
24
- 'line_numbers' => false
23
+ "line_numbers" => false,
25
24
  }
26
25
  super
27
26
  end
@@ -33,11 +32,11 @@ module Prosereflect
33
32
  def line_numbers=(value)
34
33
  @line_numbers = value
35
34
  self.attrs ||= {}
36
- attrs['line_numbers'] = value
35
+ attrs["line_numbers"] = value
37
36
  end
38
37
 
39
38
  def line_numbers
40
- @line_numbers || attrs&.[]('line_numbers') || false
39
+ @line_numbers || attrs&.[]("line_numbers") || false
41
40
  end
42
41
 
43
42
  def add_code_block(code = nil)
@@ -57,8 +56,8 @@ module Prosereflect
57
56
 
58
57
  def to_h
59
58
  hash = super
60
- hash['attrs'] = {
61
- 'line_numbers' => line_numbers
59
+ hash["attrs"] = {
60
+ "line_numbers" => line_numbers,
62
61
  }
63
62
  hash
64
63
  end
@@ -1,27 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'table'
5
- require_relative 'paragraph'
6
- require_relative 'image'
7
- require_relative 'bullet_list'
8
- require_relative 'ordered_list'
9
- require_relative 'blockquote'
10
- require_relative 'horizontal_rule'
11
- require_relative 'code_block_wrapper'
12
- require_relative 'heading'
13
- require_relative 'user'
14
-
15
3
  module Prosereflect
16
4
  # Document class represents a ProseMirror document.
17
5
  class Document < Node
18
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
19
- PM_TYPE = 'doc'
6
+ attribute :type, :string, default: -> {
7
+ self.class.send(:const_get, "PM_TYPE")
8
+ }
9
+ PM_TYPE = "doc"
20
10
 
21
11
  key_value do
22
- map 'type', to: :type, render_default: true
23
- map 'content', to: :content
24
- map 'attrs', to: :attrs
12
+ map "type", to: :type, render_default: true
13
+ map "content", to: :content
14
+ map "attrs", to: :attrs
25
15
  end
26
16
 
27
17
  def self.create(attrs = nil)
@@ -33,12 +23,12 @@ module Prosereflect
33
23
  result = super
34
24
 
35
25
  # Handle array of attribute objects specially for serialization
36
- if attrs.is_a?(Array) && attrs.all? { |attr| attr.is_a?(Prosereflect::Attribute::Base) }
26
+ if attrs.is_a?(Array) && attrs.all?(Prosereflect::Attribute::Base)
37
27
  attrs_hash = {}
38
28
  attrs.each do |attr|
39
29
  attrs_hash.merge!(attr.to_h)
40
30
  end
41
- result['attrs'] = attrs_hash unless attrs_hash.empty?
31
+ result["attrs"] = attrs_hash unless attrs_hash.empty?
42
32
  end
43
33
 
44
34
  result
@@ -54,7 +44,7 @@ module Prosereflect
54
44
 
55
45
  # Add a heading to the document
56
46
  def add_heading(level)
57
- heading = Heading.new(attrs: { 'level' => level })
47
+ heading = Heading.new(attrs: { "level" => level })
58
48
  add_child(heading)
59
49
  heading
60
50
  end
@@ -130,9 +120,11 @@ module Prosereflect
130
120
 
131
121
  # Get plain text content from all nodes
132
122
  def text_content
133
- return '' unless content
123
+ return "" unless content
134
124
 
135
- content.map { |node| node.respond_to?(:text_content) ? node.text_content : '' }.join("\n").strip
125
+ content.map do |node|
126
+ node.respond_to?(:text_content) ? node.text_content : ""
127
+ end.join("\n").strip
136
128
  end
137
129
  end
138
130
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
-
5
3
  module Prosereflect
6
4
  class HardBreak < Node
7
- PM_TYPE = 'hard_break'
5
+ PM_TYPE = "hard_break"
8
6
 
9
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
7
+ attribute :type, :string, default: -> {
8
+ self.class.send(:const_get, "PM_TYPE")
9
+ }
10
10
 
11
11
  key_value do
12
- map 'type', to: :type, render_default: true
13
- map 'marks', to: :marks
12
+ map "type", to: :type, render_default: true
13
+ map "marks", to: :marks
14
14
  end
15
15
 
16
16
  def self.create(marks = nil)
@@ -1,21 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'text'
5
-
6
3
  module Prosereflect
7
4
  class Heading < Node
8
- PM_TYPE = 'heading'
5
+ PM_TYPE = "heading"
9
6
 
10
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
7
+ attribute :type, :string, default: -> {
8
+ self.class.send(:const_get, "PM_TYPE")
9
+ }
11
10
  attribute :level, :integer
12
11
  attribute :attrs, :hash
13
12
 
14
13
  key_value do
15
- map 'type', to: :type, render_default: true
16
- map 'content', to: :content
17
- map 'attrs', to: :attrs
18
- map 'marks', to: :marks
14
+ map "type", to: :type, render_default: true
15
+ map "content", to: :content
16
+ map "attrs", to: :attrs
17
+ map "marks", to: :marks
19
18
  end
20
19
 
21
20
  def initialize(params = {})
@@ -25,7 +24,7 @@ module Prosereflect
25
24
  # Extract level from attrs if provided
26
25
  return unless params[:attrs]
27
26
 
28
- @level = params[:attrs]['level']
27
+ @level = params[:attrs]["level"]
29
28
  end
30
29
 
31
30
  def self.create(attrs = nil)
@@ -35,15 +34,15 @@ module Prosereflect
35
34
  def level=(value)
36
35
  @level = value
37
36
  self.attrs ||= {}
38
- attrs['level'] = value
37
+ attrs["level"] = value
39
38
  end
40
39
 
41
40
  def level
42
- @level || attrs&.[]('level')
41
+ @level || attrs&.[]("level")
43
42
  end
44
43
 
45
44
  def text_content
46
- return '' unless content
45
+ return "" unless content
47
46
 
48
47
  content.map(&:text_content).join
49
48
  end
@@ -56,8 +55,8 @@ module Prosereflect
56
55
 
57
56
  def to_h
58
57
  result = super
59
- result['attrs'] ||= {}
60
- result['attrs']['level'] = level if level
58
+ result["attrs"] ||= {}
59
+ result["attrs"]["level"] = level if level
61
60
  result
62
61
  end
63
62
  end
@@ -1,22 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
-
5
3
  module Prosereflect
6
4
  # HorizontalRule class represents a horizontal rule in ProseMirror.
7
5
  class HorizontalRule < Node
8
- PM_TYPE = 'horizontal_rule'
6
+ PM_TYPE = "horizontal_rule"
9
7
 
10
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
11
11
  attribute :style, :string
12
12
  attribute :width, :string
13
13
  attribute :thickness, :integer
14
14
  attribute :attrs, :hash
15
15
 
16
16
  key_value do
17
- map 'type', to: :type, render_default: true
18
- map 'attrs', to: :attrs
19
- map 'content', to: :content
17
+ map "type", to: :type, render_default: true
18
+ map "attrs", to: :attrs
19
+ map "content", to: :content
20
20
  end
21
21
 
22
22
  def initialize(attributes = {})
@@ -31,36 +31,36 @@ module Prosereflect
31
31
  def style=(value)
32
32
  @style = value
33
33
  self.attrs ||= {}
34
- attrs['style'] = value
34
+ attrs["style"] = value
35
35
  end
36
36
 
37
37
  def style
38
- @style || attrs&.[]('style')
38
+ @style || attrs&.[]("style")
39
39
  end
40
40
 
41
41
  def width=(value)
42
42
  @width = value
43
43
  self.attrs ||= {}
44
- attrs['width'] = value
44
+ attrs["width"] = value
45
45
  end
46
46
 
47
47
  def width
48
- @width || attrs&.[]('width')
48
+ @width || attrs&.[]("width")
49
49
  end
50
50
 
51
51
  def thickness=(value)
52
52
  @thickness = value
53
53
  self.attrs ||= {}
54
- attrs['thickness'] = value
54
+ attrs["thickness"] = value
55
55
  end
56
56
 
57
57
  def thickness
58
- @thickness || attrs&.[]('thickness')
58
+ @thickness || attrs&.[]("thickness")
59
59
  end
60
60
 
61
61
  # Override content-related methods since horizontal rules don't have content
62
62
  def add_child(*)
63
- raise NotImplementedError, 'Horizontal rule nodes cannot have children'
63
+ raise NotImplementedError, "Horizontal rule nodes cannot have children"
64
64
  end
65
65
 
66
66
  def content