markbridge 0.1.3 → 0.2.1
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 +4 -4
- data/LICENSE.txt +1 -1
- data/lib/markbridge/ast/details.rb +24 -0
- data/lib/markbridge/ast/element.rb +63 -0
- data/lib/markbridge/ast.rb +1 -0
- data/lib/markbridge/conversion.rb +40 -0
- data/lib/markbridge/parse.rb +20 -0
- data/lib/markbridge/parsers/bbcode/handler_registry.rb +25 -2
- data/lib/markbridge/parsers/bbcode/handlers/raw_handler.rb +13 -2
- data/lib/markbridge/parsers/html/handler_registry.rb +97 -17
- data/lib/markbridge/parsers/html/handlers/self_closing_handler.rb +26 -0
- data/lib/markbridge/parsers/html/handlers/span_handler.rb +74 -0
- data/lib/markbridge/parsers/html/parser.rb +88 -18
- data/lib/markbridge/parsers/html.rb +2 -0
- data/lib/markbridge/parsers/media_wiki/inline_parser.rb +21 -8
- data/lib/markbridge/parsers/media_wiki/parser.rb +13 -5
- data/lib/markbridge/parsers/text_formatter/handler_registry.rb +27 -4
- data/lib/markbridge/parsers/text_formatter/handlers/attachment_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/attribute_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/base_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/code_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/email_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/image_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/list_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/quote_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/simple_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/table_cell_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/handlers/url_handler.rb +1 -1
- data/lib/markbridge/parsers/text_formatter/parser.rb +17 -3
- data/lib/markbridge/renderers/discourse/identity_escaper.rb +37 -0
- data/lib/markbridge/renderers/discourse/markdown_escaper.rb +83 -8
- data/lib/markbridge/renderers/discourse/postprocessor.rb +53 -0
- data/lib/markbridge/renderers/discourse/render_context.rb +14 -40
- data/lib/markbridge/renderers/discourse/renderer.rb +15 -5
- data/lib/markbridge/renderers/discourse/rendering_interface.rb +4 -3
- data/lib/markbridge/renderers/discourse/tag_library.rb +42 -2
- data/lib/markbridge/renderers/discourse/tags/align_tag.rb +2 -2
- data/lib/markbridge/renderers/discourse/tags/code_tag.rb +5 -3
- data/lib/markbridge/renderers/discourse/tags/details_tag.rb +46 -0
- data/lib/markbridge/renderers/discourse/tags/heading_tag.rb +1 -1
- data/lib/markbridge/renderers/discourse/tags/paragraph_tag.rb +5 -2
- data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +4 -3
- data/lib/markbridge/renderers/discourse/tags/underline_tag.rb +13 -0
- data/lib/markbridge/renderers/discourse.rb +3 -0
- data/lib/markbridge/version.rb +1 -1
- data/lib/markbridge.rb +274 -111
- metadata +9 -11
- data/lib/markbridge/configuration.rb +0 -11
- data/lib/markbridge/processors/discourse_markdown/code_block_tracker.rb +0 -218
- data/lib/markbridge/processors/discourse_markdown/detectors/base.rb +0 -63
- data/lib/markbridge/processors/discourse_markdown/detectors/event.rb +0 -64
- data/lib/markbridge/processors/discourse_markdown/detectors/mention.rb +0 -57
- data/lib/markbridge/processors/discourse_markdown/detectors/poll.rb +0 -52
- data/lib/markbridge/processors/discourse_markdown/detectors/upload.rb +0 -98
- data/lib/markbridge/processors/discourse_markdown/scanner.rb +0 -194
- data/lib/markbridge/processors/discourse_markdown.rb +0 -16
- data/lib/markbridge/processors.rb +0 -8
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Markbridge
|
|
4
|
-
module Processors
|
|
5
|
-
module DiscourseMarkdown
|
|
6
|
-
# Result of scanning Discourse Markdown
|
|
7
|
-
# @attr_reader markdown [String] the processed markdown with placeholders
|
|
8
|
-
# @attr_reader nodes [Array<AST::Node>] extracted AST nodes in order of appearance
|
|
9
|
-
ScanResult = Data.define(:markdown, :nodes)
|
|
10
|
-
|
|
11
|
-
# Single-pass scanner for Discourse Markdown that extracts specific constructs
|
|
12
|
-
# (mentions, polls, events, uploads) while preserving all other content unchanged.
|
|
13
|
-
#
|
|
14
|
-
# The scanner respects code blocks (fenced, indented, and inline) and will not
|
|
15
|
-
# extract constructs that appear within code.
|
|
16
|
-
#
|
|
17
|
-
# @example Basic usage
|
|
18
|
-
# scanner = Scanner.new
|
|
19
|
-
# result = scanner.scan("Hello @gerhard!")
|
|
20
|
-
# result.nodes.first # => AST::Mention
|
|
21
|
-
#
|
|
22
|
-
# @example With custom tag library for rendering
|
|
23
|
-
# scanner = Scanner.new(tag_library: my_library)
|
|
24
|
-
# result = scanner.scan(input)
|
|
25
|
-
# result.markdown # => "Hello <<MENTION:1>>!"
|
|
26
|
-
#
|
|
27
|
-
# @example With mention type resolver
|
|
28
|
-
# scanner = Scanner.new(mention_resolver: ->(name) {
|
|
29
|
-
# groups.include?(name) ? :group : :user
|
|
30
|
-
# })
|
|
31
|
-
# result = scanner.scan("@Testers and @gerhard")
|
|
32
|
-
# result.nodes[0].type # => :group
|
|
33
|
-
# result.nodes[1].type # => :user
|
|
34
|
-
class Scanner
|
|
35
|
-
# Default detectors in priority order
|
|
36
|
-
DEFAULT_DETECTORS = [
|
|
37
|
-
Detectors::Poll,
|
|
38
|
-
Detectors::Event,
|
|
39
|
-
Detectors::Upload,
|
|
40
|
-
Detectors::Mention,
|
|
41
|
-
].freeze
|
|
42
|
-
|
|
43
|
-
# Characters that can start a construct (for fast bailout)
|
|
44
|
-
TRIGGER_CHARS = Set.new(["@", "[", "!"]).freeze
|
|
45
|
-
|
|
46
|
-
# @param detectors [Array<Class>] detector classes to use (instantiated automatically)
|
|
47
|
-
# @param tag_library [Renderers::Discourse::TagLibrary, nil] tag library for rendering placeholders
|
|
48
|
-
# @param mention_resolver [#call, nil] callable that takes a name and returns :user or :group
|
|
49
|
-
def initialize(detectors: DEFAULT_DETECTORS, tag_library: nil, mention_resolver: nil)
|
|
50
|
-
@detector_instances = build_detectors(detectors, mention_resolver)
|
|
51
|
-
@tag_library = tag_library
|
|
52
|
-
# @code_tracker / @result / @nodes / @node_index / @pos / @input /
|
|
53
|
-
# @line_start are set by #scan before use; no defensive init needed.
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Scan input and extract constructs.
|
|
57
|
-
#
|
|
58
|
-
# @param input [String] Discourse Markdown input
|
|
59
|
-
# @return [ScanResult] result containing processed markdown and extracted nodes
|
|
60
|
-
def scan(input)
|
|
61
|
-
@code_tracker = CodeBlockTracker.new
|
|
62
|
-
@result = +""
|
|
63
|
-
@nodes = []
|
|
64
|
-
@node_index = 0
|
|
65
|
-
@pos = 0
|
|
66
|
-
@input = input.to_s
|
|
67
|
-
@line_start = true
|
|
68
|
-
|
|
69
|
-
scan_input
|
|
70
|
-
|
|
71
|
-
ScanResult.new(markdown: @result, nodes: @nodes)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
def build_detectors(detectors, mention_resolver)
|
|
77
|
-
detectors.map do |klass|
|
|
78
|
-
if klass == Detectors::Mention
|
|
79
|
-
klass.new(type_resolver: mention_resolver)
|
|
80
|
-
else
|
|
81
|
-
klass.new
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def scan_input
|
|
87
|
-
while @pos < @input.length
|
|
88
|
-
# Check for fenced code block boundary at line start
|
|
89
|
-
if @line_start
|
|
90
|
-
next if advance_code_boundary(:check_fenced_boundary)
|
|
91
|
-
next if advance_code_boundary(:check_indented_boundary)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Check for inline code boundary. check_inline_boundary's
|
|
95
|
-
# own fenced/indented guard means we don't need to pre-check
|
|
96
|
-
# here — it'll just return nil in those cases.
|
|
97
|
-
if @input[@pos] == "`"
|
|
98
|
-
new_pos = @code_tracker.check_inline_boundary(@input, @pos)
|
|
99
|
-
if new_pos
|
|
100
|
-
@result << @input[@pos...new_pos]
|
|
101
|
-
@pos = new_pos
|
|
102
|
-
@line_start = false
|
|
103
|
-
next
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# If in code, pass through unchanged
|
|
108
|
-
if @code_tracker.in_code?
|
|
109
|
-
@result << @input[@pos]
|
|
110
|
-
@line_start = @input[@pos] == "\n"
|
|
111
|
-
@pos += 1
|
|
112
|
-
next
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Fast path: only try detectors if current char could start a construct
|
|
116
|
-
char = @input[@pos]
|
|
117
|
-
if TRIGGER_CHARS.include?(char)
|
|
118
|
-
match = detect_at_position
|
|
119
|
-
if match
|
|
120
|
-
handle_match(match)
|
|
121
|
-
next
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
@result << char
|
|
126
|
-
@line_start = char == "\n"
|
|
127
|
-
@pos += 1
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def advance_code_boundary(method)
|
|
132
|
-
new_pos = @code_tracker.public_send(method, @input, @pos, line_start: true)
|
|
133
|
-
return false unless new_pos
|
|
134
|
-
|
|
135
|
-
# check_fenced_boundary / check_indented_boundary always stop
|
|
136
|
-
# at pos_after_line, which is either after a "\n" or at EOF.
|
|
137
|
-
# After-newline → @line_start should be true; at EOF the
|
|
138
|
-
# outer `while @pos < @input.length` exits and @line_start
|
|
139
|
-
# is unobservable. Setting true unconditionally drops the
|
|
140
|
-
# `@input[new_pos - 1] == "\n"` dance.
|
|
141
|
-
@result << @input[@pos...new_pos]
|
|
142
|
-
@pos = new_pos
|
|
143
|
-
@line_start = true
|
|
144
|
-
true
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def detect_at_position
|
|
148
|
-
@detector_instances.each do |detector|
|
|
149
|
-
match = detector.detect(@input, @pos)
|
|
150
|
-
return match if match
|
|
151
|
-
end
|
|
152
|
-
nil
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def handle_match(match)
|
|
156
|
-
node = match.node
|
|
157
|
-
@nodes << node
|
|
158
|
-
@result << render_placeholder(node)
|
|
159
|
-
|
|
160
|
-
# Every detector shipped today matches content that ends on a
|
|
161
|
-
# non-newline byte (`]`, `)`, `_`, alphanumeric), so @line_start
|
|
162
|
-
# is always false after a successful match. If a future custom
|
|
163
|
-
# detector produces a match whose end_pos sits right after
|
|
164
|
-
# "\n", re-introduce the `@input[@pos - 1] == "\n"` check.
|
|
165
|
-
@pos = match.end_pos
|
|
166
|
-
@line_start = false
|
|
167
|
-
@node_index += 1
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def render_placeholder(node)
|
|
171
|
-
if @tag_library
|
|
172
|
-
tag = @tag_library[node.class]
|
|
173
|
-
return tag.render(node, nil) if tag
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
default_placeholder(node)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def default_placeholder(node)
|
|
180
|
-
case node
|
|
181
|
-
when AST::Mention
|
|
182
|
-
"<<MENTION:#{@node_index}:#{node.name}>>"
|
|
183
|
-
when AST::Poll
|
|
184
|
-
"<<POLL:#{@node_index}:#{node.name}>>"
|
|
185
|
-
when AST::Event
|
|
186
|
-
"<<EVENT:#{@node_index}:#{node.name}>>"
|
|
187
|
-
when AST::Upload
|
|
188
|
-
"<<UPLOAD:#{@node_index}:#{node.sha1}>>"
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "discourse_markdown/code_block_tracker"
|
|
4
|
-
require_relative "discourse_markdown/detectors/base"
|
|
5
|
-
require_relative "discourse_markdown/detectors/mention"
|
|
6
|
-
require_relative "discourse_markdown/detectors/poll"
|
|
7
|
-
require_relative "discourse_markdown/detectors/event"
|
|
8
|
-
require_relative "discourse_markdown/detectors/upload"
|
|
9
|
-
require_relative "discourse_markdown/scanner"
|
|
10
|
-
|
|
11
|
-
module Markbridge
|
|
12
|
-
module Processors
|
|
13
|
-
module DiscourseMarkdown
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|