markbridge 0.1.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 (144) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/lib/markbridge/all.rb +9 -0
  4. data/lib/markbridge/ast/align.rb +24 -0
  5. data/lib/markbridge/ast/attachment.rb +42 -0
  6. data/lib/markbridge/ast/bold.rb +13 -0
  7. data/lib/markbridge/ast/code.rb +27 -0
  8. data/lib/markbridge/ast/color.rb +25 -0
  9. data/lib/markbridge/ast/document.rb +27 -0
  10. data/lib/markbridge/ast/element.rb +47 -0
  11. data/lib/markbridge/ast/email.rb +27 -0
  12. data/lib/markbridge/ast/event.rb +59 -0
  13. data/lib/markbridge/ast/heading.rb +23 -0
  14. data/lib/markbridge/ast/horizontal_rule.rb +12 -0
  15. data/lib/markbridge/ast/image.rb +35 -0
  16. data/lib/markbridge/ast/italic.rb +13 -0
  17. data/lib/markbridge/ast/line_break.rb +12 -0
  18. data/lib/markbridge/ast/list.rb +52 -0
  19. data/lib/markbridge/ast/list_item.rb +13 -0
  20. data/lib/markbridge/ast/markdown_text.rb +37 -0
  21. data/lib/markbridge/ast/mention.rb +29 -0
  22. data/lib/markbridge/ast/node.rb +19 -0
  23. data/lib/markbridge/ast/paragraph.rb +13 -0
  24. data/lib/markbridge/ast/poll.rb +74 -0
  25. data/lib/markbridge/ast/quote.rb +46 -0
  26. data/lib/markbridge/ast/size.rb +25 -0
  27. data/lib/markbridge/ast/spoiler.rb +27 -0
  28. data/lib/markbridge/ast/strikethrough.rb +13 -0
  29. data/lib/markbridge/ast/subscript.rb +13 -0
  30. data/lib/markbridge/ast/superscript.rb +13 -0
  31. data/lib/markbridge/ast/text.rb +38 -0
  32. data/lib/markbridge/ast/underline.rb +13 -0
  33. data/lib/markbridge/ast/upload.rb +74 -0
  34. data/lib/markbridge/ast/url.rb +27 -0
  35. data/lib/markbridge/ast.rb +42 -0
  36. data/lib/markbridge/configuration.rb +11 -0
  37. data/lib/markbridge/gem_loader.rb +23 -0
  38. data/lib/markbridge/parsers/bbcode/closing_strategies/base.rb +37 -0
  39. data/lib/markbridge/parsers/bbcode/closing_strategies/reordering.rb +17 -0
  40. data/lib/markbridge/parsers/bbcode/closing_strategies/strict.rb +12 -0
  41. data/lib/markbridge/parsers/bbcode/closing_strategies/tag_reconciler.rb +121 -0
  42. data/lib/markbridge/parsers/bbcode/errors/max_depth_exceeded_error.rb +13 -0
  43. data/lib/markbridge/parsers/bbcode/handler_registry.rb +160 -0
  44. data/lib/markbridge/parsers/bbcode/handlers/align_handler.rb +26 -0
  45. data/lib/markbridge/parsers/bbcode/handlers/attachment_handler.rb +104 -0
  46. data/lib/markbridge/parsers/bbcode/handlers/base_handler.rb +44 -0
  47. data/lib/markbridge/parsers/bbcode/handlers/code_handler.rb +25 -0
  48. data/lib/markbridge/parsers/bbcode/handlers/color_handler.rb +31 -0
  49. data/lib/markbridge/parsers/bbcode/handlers/email_handler.rb +25 -0
  50. data/lib/markbridge/parsers/bbcode/handlers/image_handler.rb +51 -0
  51. data/lib/markbridge/parsers/bbcode/handlers/list_handler.rb +36 -0
  52. data/lib/markbridge/parsers/bbcode/handlers/list_item_handler.rb +26 -0
  53. data/lib/markbridge/parsers/bbcode/handlers/quote_handler.rb +64 -0
  54. data/lib/markbridge/parsers/bbcode/handlers/raw_handler.rb +48 -0
  55. data/lib/markbridge/parsers/bbcode/handlers/self_closing_handler.rb +28 -0
  56. data/lib/markbridge/parsers/bbcode/handlers/simple_handler.rb +28 -0
  57. data/lib/markbridge/parsers/bbcode/handlers/size_handler.rb +31 -0
  58. data/lib/markbridge/parsers/bbcode/handlers/spoiler_handler.rb +28 -0
  59. data/lib/markbridge/parsers/bbcode/handlers/url_handler.rb +24 -0
  60. data/lib/markbridge/parsers/bbcode/parser.rb +123 -0
  61. data/lib/markbridge/parsers/bbcode/parser_state.rb +93 -0
  62. data/lib/markbridge/parsers/bbcode/peekable_enumerator.rb +126 -0
  63. data/lib/markbridge/parsers/bbcode/raw_content_collector.rb +35 -0
  64. data/lib/markbridge/parsers/bbcode/raw_content_result.rb +25 -0
  65. data/lib/markbridge/parsers/bbcode/scanner.rb +231 -0
  66. data/lib/markbridge/parsers/bbcode/tokens/tag_end_token.rb +21 -0
  67. data/lib/markbridge/parsers/bbcode/tokens/tag_start_token.rb +23 -0
  68. data/lib/markbridge/parsers/bbcode/tokens/text_token.rb +23 -0
  69. data/lib/markbridge/parsers/bbcode/tokens/token.rb +16 -0
  70. data/lib/markbridge/parsers/bbcode.rb +56 -0
  71. data/lib/markbridge/parsers/html/handler_registry.rb +87 -0
  72. data/lib/markbridge/parsers/html/handlers/base_handler.rb +27 -0
  73. data/lib/markbridge/parsers/html/handlers/image_handler.rb +40 -0
  74. data/lib/markbridge/parsers/html/handlers/list_handler.rb +29 -0
  75. data/lib/markbridge/parsers/html/handlers/list_item_handler.rb +26 -0
  76. data/lib/markbridge/parsers/html/handlers/paragraph_handler.rb +17 -0
  77. data/lib/markbridge/parsers/html/handlers/quote_handler.rb +28 -0
  78. data/lib/markbridge/parsers/html/handlers/raw_handler.rb +33 -0
  79. data/lib/markbridge/parsers/html/handlers/simple_handler.rb +26 -0
  80. data/lib/markbridge/parsers/html/handlers/url_handler.rb +27 -0
  81. data/lib/markbridge/parsers/html/parser.rb +113 -0
  82. data/lib/markbridge/parsers/html.rb +30 -0
  83. data/lib/markbridge/parsers/media_wiki/inline_parser.rb +332 -0
  84. data/lib/markbridge/parsers/media_wiki/parser.rb +279 -0
  85. data/lib/markbridge/parsers/media_wiki.rb +15 -0
  86. data/lib/markbridge/parsers/text_formatter/handler_registry.rb +130 -0
  87. data/lib/markbridge/parsers/text_formatter/handlers/attachment_handler.rb +33 -0
  88. data/lib/markbridge/parsers/text_formatter/handlers/attribute_handler.rb +40 -0
  89. data/lib/markbridge/parsers/text_formatter/handlers/base_handler.rb +45 -0
  90. data/lib/markbridge/parsers/text_formatter/handlers/code_handler.rb +28 -0
  91. data/lib/markbridge/parsers/text_formatter/handlers/email_handler.rb +27 -0
  92. data/lib/markbridge/parsers/text_formatter/handlers/image_handler.rb +32 -0
  93. data/lib/markbridge/parsers/text_formatter/handlers/list_handler.rb +31 -0
  94. data/lib/markbridge/parsers/text_formatter/handlers/quote_handler.rb +33 -0
  95. data/lib/markbridge/parsers/text_formatter/handlers/simple_handler.rb +37 -0
  96. data/lib/markbridge/parsers/text_formatter/handlers/url_handler.rb +29 -0
  97. data/lib/markbridge/parsers/text_formatter/parser.rb +132 -0
  98. data/lib/markbridge/parsers/text_formatter.rb +31 -0
  99. data/lib/markbridge/processors/discourse_markdown/code_block_tracker.rb +199 -0
  100. data/lib/markbridge/processors/discourse_markdown/detectors/base.rb +57 -0
  101. data/lib/markbridge/processors/discourse_markdown/detectors/event.rb +73 -0
  102. data/lib/markbridge/processors/discourse_markdown/detectors/mention.rb +57 -0
  103. data/lib/markbridge/processors/discourse_markdown/detectors/poll.rb +90 -0
  104. data/lib/markbridge/processors/discourse_markdown/detectors/upload.rb +123 -0
  105. data/lib/markbridge/processors/discourse_markdown/scanner.rb +199 -0
  106. data/lib/markbridge/processors/discourse_markdown.rb +16 -0
  107. data/lib/markbridge/processors.rb +8 -0
  108. data/lib/markbridge/renderers/discourse/builders/list_item_builder.rb +83 -0
  109. data/lib/markbridge/renderers/discourse/markdown_escaper.rb +468 -0
  110. data/lib/markbridge/renderers/discourse/render_context.rb +80 -0
  111. data/lib/markbridge/renderers/discourse/renderer.rb +63 -0
  112. data/lib/markbridge/renderers/discourse/rendering_interface.rb +86 -0
  113. data/lib/markbridge/renderers/discourse/tag.rb +29 -0
  114. data/lib/markbridge/renderers/discourse/tag_library.rb +67 -0
  115. data/lib/markbridge/renderers/discourse/tags/align_tag.rb +24 -0
  116. data/lib/markbridge/renderers/discourse/tags/attachment_tag.rb +46 -0
  117. data/lib/markbridge/renderers/discourse/tags/bold_tag.rb +18 -0
  118. data/lib/markbridge/renderers/discourse/tags/code_tag.rb +54 -0
  119. data/lib/markbridge/renderers/discourse/tags/color_tag.rb +27 -0
  120. data/lib/markbridge/renderers/discourse/tags/email_tag.rb +24 -0
  121. data/lib/markbridge/renderers/discourse/tags/event_tag.rb +49 -0
  122. data/lib/markbridge/renderers/discourse/tags/heading_tag.rb +21 -0
  123. data/lib/markbridge/renderers/discourse/tags/horizontal_rule_tag.rb +16 -0
  124. data/lib/markbridge/renderers/discourse/tags/image_tag.rb +29 -0
  125. data/lib/markbridge/renderers/discourse/tags/italic_tag.rb +18 -0
  126. data/lib/markbridge/renderers/discourse/tags/line_break_tag.rb +16 -0
  127. data/lib/markbridge/renderers/discourse/tags/list_item_tag.rb +87 -0
  128. data/lib/markbridge/renderers/discourse/tags/list_tag.rb +39 -0
  129. data/lib/markbridge/renderers/discourse/tags/mention_tag.rb +34 -0
  130. data/lib/markbridge/renderers/discourse/tags/paragraph_tag.rb +21 -0
  131. data/lib/markbridge/renderers/discourse/tags/poll_tag.rb +51 -0
  132. data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +32 -0
  133. data/lib/markbridge/renderers/discourse/tags/size_tag.rb +27 -0
  134. data/lib/markbridge/renderers/discourse/tags/spoiler_tag.rb +24 -0
  135. data/lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb +18 -0
  136. data/lib/markbridge/renderers/discourse/tags/subscript_tag.rb +19 -0
  137. data/lib/markbridge/renderers/discourse/tags/superscript_tag.rb +19 -0
  138. data/lib/markbridge/renderers/discourse/tags/underline_tag.rb +19 -0
  139. data/lib/markbridge/renderers/discourse/tags/upload_tag.rb +80 -0
  140. data/lib/markbridge/renderers/discourse/tags/url_tag.rb +24 -0
  141. data/lib/markbridge/renderers/discourse.rb +50 -0
  142. data/lib/markbridge/version.rb +5 -0
  143. data/lib/markbridge.rb +201 -0
  144. metadata +186 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 43b79ac6557f41fcce9274986295812dd9cbfccf10ce19d0f9b81424ef4f9a83
4
+ data.tar.gz: 63d7b2494828a885ceea8b333251d23e6400c2d84cb0a50e30c3112bb4c2a517
5
+ SHA512:
6
+ metadata.gz: 387f752e9d6ae0eecc1c4e1e48cda6ac9e94390b16b75d8813a29400af10664913ca0dff8d9af2553753ed41ed5a5ed60d366f51246a3c814fb6034dfeb3660c
7
+ data.tar.gz: ea951ad454a6f6bfa86d902725394bf01605bff6307c1a6a6b10918d1267af12d41fd2bd1989151a90a3eec7d0670fbfb79c69e0ae256077ed5897f64870e036
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Gerhard Schlager
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../markbridge"
4
+
5
+ require_relative "ast"
6
+ require_relative "parsers/bbcode"
7
+ require_relative "parsers/html"
8
+ require_relative "parsers/text_formatter"
9
+ require_relative "renderers/discourse"
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents aligned text (left, center, right, justify).
6
+ # Note: Discourse has limited support for alignment.
7
+ #
8
+ # @example Center-aligned text
9
+ # align = AST::Align.new(alignment: "center")
10
+ # align << AST::Text.new("Centered text")
11
+ class Align < Element
12
+ # @return [String, nil] the alignment value (left, center, right, justify)
13
+ attr_reader :alignment
14
+
15
+ # Create a new Align element.
16
+ #
17
+ # @param alignment [String, nil] alignment value
18
+ def initialize(alignment: nil)
19
+ super()
20
+ @alignment = alignment
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents an attachment reference (image/file).
6
+ #
7
+ # @example Attachment by absolute ID
8
+ # attachment = AST::Attachment.new(id: "1234")
9
+ #
10
+ # @example Attachment by post-relative index with filename
11
+ # attachment = AST::Attachment.new(index: "0", filename: "image.jpg")
12
+ #
13
+ # @example Attachment with alt text
14
+ # attachment = AST::Attachment.new(id: "5678", alt: "diagram")
15
+ class Attachment < Node
16
+ # @return [String, Integer, nil] absolute attachment identifier
17
+ attr_reader :id
18
+
19
+ # @return [Integer, nil] post-relative index
20
+ attr_reader :index
21
+
22
+ # @return [String, nil] optional filename
23
+ attr_reader :filename
24
+
25
+ # @return [String, nil] optional alt text for the attachment
26
+ attr_reader :alt
27
+
28
+ # Create a new Attachment node.
29
+ #
30
+ # @param id [String, Integer, nil] absolute attachment identifier
31
+ # @param index [Integer, nil] post-relative index
32
+ # @param filename [String, nil] optional filename/caption
33
+ # @param alt [String, nil] optional alt text
34
+ def initialize(id: nil, index: nil, filename: nil, alt: nil)
35
+ @id = id
36
+ @index = index
37
+ @filename = filename
38
+ @alt = alt
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents bold/strong text formatting.
6
+ #
7
+ # @example
8
+ # bold = AST::Bold.new
9
+ # bold << AST::Text.new("important text")
10
+ class Bold < Element
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents an inline or block code element.
6
+ #
7
+ # @example Inline code
8
+ # code = AST::Code.new
9
+ # code << AST::Text.new("puts 'hello'")
10
+ #
11
+ # @example Code with language for syntax highlighting
12
+ # code = AST::Code.new(language: "ruby")
13
+ # code << AST::Text.new("def hello\n puts 'world'\nend")
14
+ class Code < Element
15
+ # @return [String, nil] the programming language for syntax highlighting
16
+ attr_reader :language
17
+
18
+ # Create a new code element.
19
+ #
20
+ # @param language [String, nil] optional language identifier for syntax highlighting
21
+ def initialize(language: nil)
22
+ super()
23
+ @language = language
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents colored text.
6
+ # Note: Discourse doesn't support inline color by default,
7
+ # but this preserves color information for migration/custom rendering.
8
+ #
9
+ # @example Colored text
10
+ # color = AST::Color.new(color: "red")
11
+ # color << AST::Text.new("Important text")
12
+ class Color < Element
13
+ # @return [String, nil] the color value (name or hex)
14
+ attr_reader :color
15
+
16
+ # Create a new Color element.
17
+ #
18
+ # @param color [String, nil] color name or hex code
19
+ def initialize(color: nil)
20
+ super()
21
+ @color = color
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents the root document node of the AST.
6
+ # This is the top-level container that holds all other elements.
7
+ #
8
+ # @example Creating a document
9
+ # doc = AST::Document.new
10
+ # doc << AST::Text.new("Hello, world!")
11
+ #
12
+ # @example Creating a document with initial children
13
+ # doc = AST::Document.new([
14
+ # AST::Text.new("Hello"),
15
+ # AST::Bold.new
16
+ # ])
17
+ class Document < Element
18
+ # Create a new document node.
19
+ #
20
+ # @param children [Array<Node>] optional array of initial child nodes
21
+ def initialize(children = [])
22
+ super()
23
+ Array(children).each { |c| self << c }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Base class for all AST elements that can contain children.
6
+ # Elements form the structural nodes of the AST tree, while Text nodes are leaves.
7
+ #
8
+ # @example Creating an element with children
9
+ # element = AST::Bold.new
10
+ # element << AST::Text.new("hello")
11
+ # element << AST::Text.new(" world")
12
+ # element.children.size # => 1 (consecutive text nodes are merged)
13
+ class Element < Node
14
+ # @return [Array<Node>] the child nodes of this element
15
+ attr_reader :children
16
+
17
+ def initialize
18
+ @children = []
19
+ end
20
+
21
+ # Add a child node to this element.
22
+ # Consecutive Text nodes are automatically merged for optimization.
23
+ #
24
+ # @param child [Node] the node to add as a child
25
+ # @return [Element] self for method chaining
26
+ # @raise [TypeError] if child is not a Node instance
27
+ #
28
+ # @example Adding children
29
+ # element << AST::Text.new("hello")
30
+ # element << AST::Bold.new
31
+ def <<(child)
32
+ unless child.is_a?(Node)
33
+ actual = child.nil? ? "nil" : child.class
34
+ raise TypeError, "child must be a #{Markbridge::AST::Node} (got #{actual})"
35
+ end
36
+
37
+ if child.is_a?(Text) && children.last.is_a?(Text)
38
+ @children.last.merge(child)
39
+ else
40
+ @children << child
41
+ end
42
+
43
+ self
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents an email link element.
6
+ #
7
+ # @example Email with explicit address
8
+ # email = AST::Email.new(address: "[email protected]")
9
+ # email << AST::Text.new("Contact us")
10
+ #
11
+ # @example Email with text as address
12
+ # email = AST::Email.new(address: "[email protected]")
13
+ # email << AST::Text.new("[email protected]")
14
+ class Email < Element
15
+ # @return [String, nil] the email address for this link
16
+ attr_reader :address
17
+
18
+ # Create a new Email element.
19
+ #
20
+ # @param address [String, nil] the email address for this link
21
+ def initialize(address: nil)
22
+ super()
23
+ @address = address
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents a Discourse event.
6
+ #
7
+ # @example Basic event
8
+ # event = AST::Event.new(
9
+ # name: "Team Meeting",
10
+ # starts_at: "2025-12-15 14:00"
11
+ # )
12
+ #
13
+ # @example Event with all attributes
14
+ # event = AST::Event.new(
15
+ # name: "Conference",
16
+ # starts_at: "2025-12-15 09:00",
17
+ # ends_at: "2025-12-15 17:00",
18
+ # status: "public",
19
+ # timezone: "Europe/Vienna",
20
+ # raw: "[event name=\"Conference\"]...[/event]"
21
+ # )
22
+ class Event < Node
23
+ # @return [String] the event name
24
+ attr_reader :name
25
+
26
+ # @return [String] start date/time
27
+ attr_reader :starts_at
28
+
29
+ # @return [String, nil] end date/time
30
+ attr_reader :ends_at
31
+
32
+ # @return [String, nil] event status (public, private, standalone)
33
+ attr_reader :status
34
+
35
+ # @return [String, nil] timezone
36
+ attr_reader :timezone
37
+
38
+ # @return [String, nil] the original raw BBCode
39
+ attr_reader :raw
40
+
41
+ # Create a new Event node.
42
+ #
43
+ # @param name [String] the event name
44
+ # @param starts_at [String] start date/time
45
+ # @param ends_at [String, nil] end date/time
46
+ # @param status [String, nil] event status
47
+ # @param timezone [String, nil] timezone
48
+ # @param raw [String, nil] the original raw BBCode
49
+ def initialize(name:, starts_at:, ends_at: nil, status: nil, timezone: nil, raw: nil)
50
+ @name = name
51
+ @starts_at = starts_at
52
+ @ends_at = ends_at
53
+ @status = status
54
+ @timezone = timezone
55
+ @raw = raw
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents a heading element with a level (1-6).
6
+ #
7
+ # @example
8
+ # heading = AST::Heading.new(level: 2)
9
+ # heading << AST::Text.new("Section Title")
10
+ class Heading < Element
11
+ # @return [Integer] the heading level (1-6)
12
+ attr_reader :level
13
+
14
+ # Create a new heading element.
15
+ #
16
+ # @param level [Integer] the heading level (1-6)
17
+ def initialize(level:)
18
+ super()
19
+ @level = level
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents a horizontal rule/divider.
6
+ #
7
+ # @example
8
+ # hr = AST::HorizontalRule.new
9
+ class HorizontalRule < Node
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents an image element.
6
+ #
7
+ # @example Basic image
8
+ # image = AST::Image.new(src: "https://example.com/img.png")
9
+ #
10
+ # @example Image with dimensions
11
+ # image = AST::Image.new(src: "https://example.com/img.png", width: 100, height: 100)
12
+ class Image < Element
13
+ # @return [String, nil] the image source URL
14
+ attr_reader :src
15
+
16
+ # @return [Integer, nil] the image width
17
+ attr_reader :width
18
+
19
+ # @return [Integer, nil] the image height
20
+ attr_reader :height
21
+
22
+ # Create a new Image element.
23
+ #
24
+ # @param src [String, nil] the image source URL
25
+ # @param width [Integer, nil] the image width
26
+ # @param height [Integer, nil] the image height
27
+ def initialize(src: nil, width: nil, height: nil)
28
+ super()
29
+ @src = src
30
+ @width = width
31
+ @height = height
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents italic/emphasis text formatting.
6
+ #
7
+ # @example
8
+ # italic = AST::Italic.new
9
+ # italic << AST::Text.new("emphasized text")
10
+ class Italic < Element
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents a line break within text.
6
+ #
7
+ # @example
8
+ # br = AST::LineBreak.new
9
+ class LineBreak < Node
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents an ordered or unordered list element.
6
+ #
7
+ # @example Unordered list
8
+ # list = AST::List.new
9
+ # list << AST::ListItem.new
10
+ #
11
+ # @example Ordered list
12
+ # list = AST::List.new(ordered: true)
13
+ # list << AST::ListItem.new
14
+ class List < Element
15
+ # Add content to this list.
16
+ # - ListItem children are added directly
17
+ # - Other nodes are wrapped in an implicit ListItem
18
+ # - Whitespace-only Text nodes are ignored
19
+ #
20
+ # @param child [Node] the node to add
21
+ # @return [List] self for chaining
22
+ # @raise [TypeError] if child is not a Node
23
+ def <<(child)
24
+ return self if child.is_a?(Text) && child.text.strip.empty?
25
+
26
+ if child.is_a?(ListItem)
27
+ super
28
+ else
29
+ @children << ListItem.new if @children.empty?
30
+ @children.last << child
31
+ end
32
+
33
+ self
34
+ end
35
+
36
+ # Create a new list element.
37
+ #
38
+ # @param ordered [Boolean] whether this is an ordered (numbered) list
39
+ def initialize(ordered: false)
40
+ super()
41
+ @ordered = ordered
42
+ end
43
+
44
+ # Check if this is an ordered list.
45
+ #
46
+ # @return [Boolean] true if this is an ordered (numbered) list
47
+ def ordered?
48
+ @ordered
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents a list item within a {List}.
6
+ #
7
+ # @example
8
+ # item = AST::ListItem.new
9
+ # item << AST::Text.new("First item")
10
+ class ListItem < Element
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents a text node containing pre-formatted Markdown content.
6
+ # Unlike AST::Text, this content will NOT be escaped by the renderer.
7
+ # Use this when you want to pass through Markdown formatting as-is.
8
+ #
9
+ # @example Creating a markdown text node
10
+ # text = AST::MarkdownText.new("**bold** and *italic*")
11
+ #
12
+ # @example Use case: preserving user-provided Markdown
13
+ # # When parsing HTML or other formats that allow embedded Markdown
14
+ # element << AST::MarkdownText.new(markdown_content)
15
+ class MarkdownText < Node
16
+ # @return [String] the markdown text content of this node
17
+ attr_reader :text
18
+
19
+ # Create a new markdown text node with the given content.
20
+ #
21
+ # @param text [String] the markdown text content
22
+ def initialize(text)
23
+ @text = +text
24
+ end
25
+
26
+ # Merge another markdown text node's content into this one.
27
+ # This mutates the current text node by appending the other's text.
28
+ #
29
+ # @param other [MarkdownText] the text node to merge from
30
+ # @return [MarkdownText] self for method chaining
31
+ def merge(other)
32
+ @text << other.text
33
+ self
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents a user or group mention (@username or @groupname).
6
+ #
7
+ # @example User mention
8
+ # mention = AST::Mention.new(name: "gerhard", type: :user)
9
+ #
10
+ # @example Group mention
11
+ # mention = AST::Mention.new(name: "Testers", type: :group)
12
+ class Mention < Node
13
+ # @return [String] the username or group name (without @)
14
+ attr_reader :name
15
+
16
+ # @return [Symbol] the type of mention (:user or :group)
17
+ attr_reader :type
18
+
19
+ # Create a new Mention node.
20
+ #
21
+ # @param name [String] the username or group name (without @)
22
+ # @param type [Symbol] the type of mention (:user or :group), defaults to :user
23
+ def initialize(name:, type: :user)
24
+ @name = name
25
+ @type = type
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Base class for all AST nodes.
6
+ # This is a marker class that serves as the common ancestor for all AST nodes.
7
+ #
8
+ # The AST hierarchy consists of:
9
+ # - {Element} - nodes that can contain children
10
+ # - {Text} - leaf nodes containing text content
11
+ #
12
+ # All node types inherit from this base class to enable type checking
13
+ # and polymorphic operations on the AST tree.
14
+ #
15
+ # @abstract Subclass and add specific behavior
16
+ class Node
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module AST
5
+ # Represents a paragraph element.
6
+ #
7
+ # @example
8
+ # paragraph = AST::Paragraph.new
9
+ # paragraph << AST::Text.new("This is a paragraph.")
10
+ class Paragraph < Element
11
+ end
12
+ end
13
+ end