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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/lib/markbridge/all.rb +9 -0
- data/lib/markbridge/ast/align.rb +24 -0
- data/lib/markbridge/ast/attachment.rb +42 -0
- data/lib/markbridge/ast/bold.rb +13 -0
- data/lib/markbridge/ast/code.rb +27 -0
- data/lib/markbridge/ast/color.rb +25 -0
- data/lib/markbridge/ast/document.rb +27 -0
- data/lib/markbridge/ast/element.rb +47 -0
- data/lib/markbridge/ast/email.rb +27 -0
- data/lib/markbridge/ast/event.rb +59 -0
- data/lib/markbridge/ast/heading.rb +23 -0
- data/lib/markbridge/ast/horizontal_rule.rb +12 -0
- data/lib/markbridge/ast/image.rb +35 -0
- data/lib/markbridge/ast/italic.rb +13 -0
- data/lib/markbridge/ast/line_break.rb +12 -0
- data/lib/markbridge/ast/list.rb +52 -0
- data/lib/markbridge/ast/list_item.rb +13 -0
- data/lib/markbridge/ast/markdown_text.rb +37 -0
- data/lib/markbridge/ast/mention.rb +29 -0
- data/lib/markbridge/ast/node.rb +19 -0
- data/lib/markbridge/ast/paragraph.rb +13 -0
- data/lib/markbridge/ast/poll.rb +74 -0
- data/lib/markbridge/ast/quote.rb +46 -0
- data/lib/markbridge/ast/size.rb +25 -0
- data/lib/markbridge/ast/spoiler.rb +27 -0
- data/lib/markbridge/ast/strikethrough.rb +13 -0
- data/lib/markbridge/ast/subscript.rb +13 -0
- data/lib/markbridge/ast/superscript.rb +13 -0
- data/lib/markbridge/ast/text.rb +38 -0
- data/lib/markbridge/ast/underline.rb +13 -0
- data/lib/markbridge/ast/upload.rb +74 -0
- data/lib/markbridge/ast/url.rb +27 -0
- data/lib/markbridge/ast.rb +42 -0
- data/lib/markbridge/configuration.rb +11 -0
- data/lib/markbridge/gem_loader.rb +23 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/base.rb +37 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/reordering.rb +17 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/strict.rb +12 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/tag_reconciler.rb +121 -0
- data/lib/markbridge/parsers/bbcode/errors/max_depth_exceeded_error.rb +13 -0
- data/lib/markbridge/parsers/bbcode/handler_registry.rb +160 -0
- data/lib/markbridge/parsers/bbcode/handlers/align_handler.rb +26 -0
- data/lib/markbridge/parsers/bbcode/handlers/attachment_handler.rb +104 -0
- data/lib/markbridge/parsers/bbcode/handlers/base_handler.rb +44 -0
- data/lib/markbridge/parsers/bbcode/handlers/code_handler.rb +25 -0
- data/lib/markbridge/parsers/bbcode/handlers/color_handler.rb +31 -0
- data/lib/markbridge/parsers/bbcode/handlers/email_handler.rb +25 -0
- data/lib/markbridge/parsers/bbcode/handlers/image_handler.rb +51 -0
- data/lib/markbridge/parsers/bbcode/handlers/list_handler.rb +36 -0
- data/lib/markbridge/parsers/bbcode/handlers/list_item_handler.rb +26 -0
- data/lib/markbridge/parsers/bbcode/handlers/quote_handler.rb +64 -0
- data/lib/markbridge/parsers/bbcode/handlers/raw_handler.rb +48 -0
- data/lib/markbridge/parsers/bbcode/handlers/self_closing_handler.rb +28 -0
- data/lib/markbridge/parsers/bbcode/handlers/simple_handler.rb +28 -0
- data/lib/markbridge/parsers/bbcode/handlers/size_handler.rb +31 -0
- data/lib/markbridge/parsers/bbcode/handlers/spoiler_handler.rb +28 -0
- data/lib/markbridge/parsers/bbcode/handlers/url_handler.rb +24 -0
- data/lib/markbridge/parsers/bbcode/parser.rb +123 -0
- data/lib/markbridge/parsers/bbcode/parser_state.rb +93 -0
- data/lib/markbridge/parsers/bbcode/peekable_enumerator.rb +126 -0
- data/lib/markbridge/parsers/bbcode/raw_content_collector.rb +35 -0
- data/lib/markbridge/parsers/bbcode/raw_content_result.rb +25 -0
- data/lib/markbridge/parsers/bbcode/scanner.rb +231 -0
- data/lib/markbridge/parsers/bbcode/tokens/tag_end_token.rb +21 -0
- data/lib/markbridge/parsers/bbcode/tokens/tag_start_token.rb +23 -0
- data/lib/markbridge/parsers/bbcode/tokens/text_token.rb +23 -0
- data/lib/markbridge/parsers/bbcode/tokens/token.rb +16 -0
- data/lib/markbridge/parsers/bbcode.rb +56 -0
- data/lib/markbridge/parsers/html/handler_registry.rb +87 -0
- data/lib/markbridge/parsers/html/handlers/base_handler.rb +27 -0
- data/lib/markbridge/parsers/html/handlers/image_handler.rb +40 -0
- data/lib/markbridge/parsers/html/handlers/list_handler.rb +29 -0
- data/lib/markbridge/parsers/html/handlers/list_item_handler.rb +26 -0
- data/lib/markbridge/parsers/html/handlers/paragraph_handler.rb +17 -0
- data/lib/markbridge/parsers/html/handlers/quote_handler.rb +28 -0
- data/lib/markbridge/parsers/html/handlers/raw_handler.rb +33 -0
- data/lib/markbridge/parsers/html/handlers/simple_handler.rb +26 -0
- data/lib/markbridge/parsers/html/handlers/url_handler.rb +27 -0
- data/lib/markbridge/parsers/html/parser.rb +113 -0
- data/lib/markbridge/parsers/html.rb +30 -0
- data/lib/markbridge/parsers/media_wiki/inline_parser.rb +332 -0
- data/lib/markbridge/parsers/media_wiki/parser.rb +279 -0
- data/lib/markbridge/parsers/media_wiki.rb +15 -0
- data/lib/markbridge/parsers/text_formatter/handler_registry.rb +130 -0
- data/lib/markbridge/parsers/text_formatter/handlers/attachment_handler.rb +33 -0
- data/lib/markbridge/parsers/text_formatter/handlers/attribute_handler.rb +40 -0
- data/lib/markbridge/parsers/text_formatter/handlers/base_handler.rb +45 -0
- data/lib/markbridge/parsers/text_formatter/handlers/code_handler.rb +28 -0
- data/lib/markbridge/parsers/text_formatter/handlers/email_handler.rb +27 -0
- data/lib/markbridge/parsers/text_formatter/handlers/image_handler.rb +32 -0
- data/lib/markbridge/parsers/text_formatter/handlers/list_handler.rb +31 -0
- data/lib/markbridge/parsers/text_formatter/handlers/quote_handler.rb +33 -0
- data/lib/markbridge/parsers/text_formatter/handlers/simple_handler.rb +37 -0
- data/lib/markbridge/parsers/text_formatter/handlers/url_handler.rb +29 -0
- data/lib/markbridge/parsers/text_formatter/parser.rb +132 -0
- data/lib/markbridge/parsers/text_formatter.rb +31 -0
- data/lib/markbridge/processors/discourse_markdown/code_block_tracker.rb +199 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/base.rb +57 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/event.rb +73 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/mention.rb +57 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/poll.rb +90 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/upload.rb +123 -0
- data/lib/markbridge/processors/discourse_markdown/scanner.rb +199 -0
- data/lib/markbridge/processors/discourse_markdown.rb +16 -0
- data/lib/markbridge/processors.rb +8 -0
- data/lib/markbridge/renderers/discourse/builders/list_item_builder.rb +83 -0
- data/lib/markbridge/renderers/discourse/markdown_escaper.rb +468 -0
- data/lib/markbridge/renderers/discourse/render_context.rb +80 -0
- data/lib/markbridge/renderers/discourse/renderer.rb +63 -0
- data/lib/markbridge/renderers/discourse/rendering_interface.rb +86 -0
- data/lib/markbridge/renderers/discourse/tag.rb +29 -0
- data/lib/markbridge/renderers/discourse/tag_library.rb +67 -0
- data/lib/markbridge/renderers/discourse/tags/align_tag.rb +24 -0
- data/lib/markbridge/renderers/discourse/tags/attachment_tag.rb +46 -0
- data/lib/markbridge/renderers/discourse/tags/bold_tag.rb +18 -0
- data/lib/markbridge/renderers/discourse/tags/code_tag.rb +54 -0
- data/lib/markbridge/renderers/discourse/tags/color_tag.rb +27 -0
- data/lib/markbridge/renderers/discourse/tags/email_tag.rb +24 -0
- data/lib/markbridge/renderers/discourse/tags/event_tag.rb +49 -0
- data/lib/markbridge/renderers/discourse/tags/heading_tag.rb +21 -0
- data/lib/markbridge/renderers/discourse/tags/horizontal_rule_tag.rb +16 -0
- data/lib/markbridge/renderers/discourse/tags/image_tag.rb +29 -0
- data/lib/markbridge/renderers/discourse/tags/italic_tag.rb +18 -0
- data/lib/markbridge/renderers/discourse/tags/line_break_tag.rb +16 -0
- data/lib/markbridge/renderers/discourse/tags/list_item_tag.rb +87 -0
- data/lib/markbridge/renderers/discourse/tags/list_tag.rb +39 -0
- data/lib/markbridge/renderers/discourse/tags/mention_tag.rb +34 -0
- data/lib/markbridge/renderers/discourse/tags/paragraph_tag.rb +21 -0
- data/lib/markbridge/renderers/discourse/tags/poll_tag.rb +51 -0
- data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +32 -0
- data/lib/markbridge/renderers/discourse/tags/size_tag.rb +27 -0
- data/lib/markbridge/renderers/discourse/tags/spoiler_tag.rb +24 -0
- data/lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb +18 -0
- data/lib/markbridge/renderers/discourse/tags/subscript_tag.rb +19 -0
- data/lib/markbridge/renderers/discourse/tags/superscript_tag.rb +19 -0
- data/lib/markbridge/renderers/discourse/tags/underline_tag.rb +19 -0
- data/lib/markbridge/renderers/discourse/tags/upload_tag.rb +80 -0
- data/lib/markbridge/renderers/discourse/tags/url_tag.rb +24 -0
- data/lib/markbridge/renderers/discourse.rb +50 -0
- data/lib/markbridge/version.rb +5 -0
- data/lib/markbridge.rb +201 -0
- 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,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,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,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,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,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
|