markbridge 0.1.3 → 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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/lib/markbridge/ast/details.rb +24 -0
  4. data/lib/markbridge/ast/element.rb +63 -0
  5. data/lib/markbridge/ast.rb +1 -0
  6. data/lib/markbridge/conversion.rb +40 -0
  7. data/lib/markbridge/parse.rb +20 -0
  8. data/lib/markbridge/parsers/bbcode/handler_registry.rb +25 -2
  9. data/lib/markbridge/parsers/bbcode/handlers/raw_handler.rb +13 -2
  10. data/lib/markbridge/parsers/html/handler_registry.rb +97 -17
  11. data/lib/markbridge/parsers/html/handlers/self_closing_handler.rb +26 -0
  12. data/lib/markbridge/parsers/html/handlers/span_handler.rb +74 -0
  13. data/lib/markbridge/parsers/html/parser.rb +88 -18
  14. data/lib/markbridge/parsers/html.rb +2 -0
  15. data/lib/markbridge/parsers/media_wiki/inline_parser.rb +21 -8
  16. data/lib/markbridge/parsers/media_wiki/parser.rb +13 -5
  17. data/lib/markbridge/parsers/text_formatter/handler_registry.rb +27 -4
  18. data/lib/markbridge/parsers/text_formatter/handlers/attachment_handler.rb +1 -1
  19. data/lib/markbridge/parsers/text_formatter/handlers/attribute_handler.rb +1 -1
  20. data/lib/markbridge/parsers/text_formatter/handlers/base_handler.rb +1 -1
  21. data/lib/markbridge/parsers/text_formatter/handlers/code_handler.rb +1 -1
  22. data/lib/markbridge/parsers/text_formatter/handlers/email_handler.rb +1 -1
  23. data/lib/markbridge/parsers/text_formatter/handlers/image_handler.rb +1 -1
  24. data/lib/markbridge/parsers/text_formatter/handlers/list_handler.rb +1 -1
  25. data/lib/markbridge/parsers/text_formatter/handlers/quote_handler.rb +1 -1
  26. data/lib/markbridge/parsers/text_formatter/handlers/simple_handler.rb +1 -1
  27. data/lib/markbridge/parsers/text_formatter/handlers/table_cell_handler.rb +1 -1
  28. data/lib/markbridge/parsers/text_formatter/handlers/url_handler.rb +1 -1
  29. data/lib/markbridge/parsers/text_formatter/parser.rb +17 -3
  30. data/lib/markbridge/renderers/discourse/identity_escaper.rb +37 -0
  31. data/lib/markbridge/renderers/discourse/markdown_escaper.rb +83 -8
  32. data/lib/markbridge/renderers/discourse/postprocessor.rb +53 -0
  33. data/lib/markbridge/renderers/discourse/render_context.rb +14 -40
  34. data/lib/markbridge/renderers/discourse/renderer.rb +15 -5
  35. data/lib/markbridge/renderers/discourse/rendering_interface.rb +4 -3
  36. data/lib/markbridge/renderers/discourse/tag_library.rb +42 -2
  37. data/lib/markbridge/renderers/discourse/tags/align_tag.rb +2 -2
  38. data/lib/markbridge/renderers/discourse/tags/code_tag.rb +5 -3
  39. data/lib/markbridge/renderers/discourse/tags/details_tag.rb +46 -0
  40. data/lib/markbridge/renderers/discourse/tags/heading_tag.rb +1 -1
  41. data/lib/markbridge/renderers/discourse/tags/paragraph_tag.rb +5 -2
  42. data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +4 -3
  43. data/lib/markbridge/renderers/discourse/tags/underline_tag.rb +13 -0
  44. data/lib/markbridge/renderers/discourse.rb +3 -0
  45. data/lib/markbridge/version.rb +1 -1
  46. data/lib/markbridge.rb +274 -110
  47. metadata +9 -2
  48. data/lib/markbridge/configuration.rb +0 -11
data/lib/markbridge.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "markbridge/version"
4
- require_relative "markbridge/configuration"
4
+ require_relative "markbridge/parse"
5
+ require_relative "markbridge/conversion"
5
6
 
6
7
  require_relative "markbridge/ast"
7
8
  require_relative "markbridge/renderers/discourse"
@@ -9,158 +10,321 @@ require_relative "markbridge/processors"
9
10
 
10
11
  module Markbridge
11
12
  class << self
12
- # Parse BBCode to AST
13
+ # Parse BBCode to AST.
14
+ #
13
15
  # @param input [String] BBCode source
14
- # @param handlers [HandlerRegistry, nil] custom handler registry or use default
15
- # @return [AST::Document]
16
+ # @param handlers [Parsers::BBCode::HandlerRegistry, nil] custom handlers (defaults to .default)
17
+ # @return [Parse]
16
18
  def parse_bbcode(input, handlers: nil)
17
- handlers ||= default_handlers
18
- parse_with(Parsers::BBCode::Parser, input, handlers:)
19
+ raise ArgumentError, "input cannot be nil" if input.nil?
20
+
21
+ parser = Parsers::BBCode::Parser.new(handlers:)
22
+ ast = parser.parse(input.to_s)
23
+
24
+ Parse.new(
25
+ ast:,
26
+ format: :bbcode,
27
+ unknown_tags: parser.unknown_tags,
28
+ diagnostics: bbcode_diagnostics(parser),
29
+ )
19
30
  end
20
31
 
21
- # Convert BBCode to Discourse Markdown
32
+ # Convert BBCode to Discourse Markdown.
33
+ #
34
+ # If a block is given, it is called with the parsed AST between
35
+ # parse and render — the caller can append/remove/replace nodes
36
+ # before rendering. Mutations to the yielded AST persist in
37
+ # {Conversion#ast}.
38
+ #
22
39
  # @param input [String] BBCode source
23
- # @param handlers [HandlerRegistry, nil] custom handler registry or use default
24
- # @param tag_library [TagLibrary, nil] custom tag library or use default
25
- # @return [String] Markdown output
26
- def bbcode_to_markdown(input, handlers: nil, tag_library: nil)
27
- ast = parse_bbcode(input, handlers:)
28
- render_to_markdown(ast, tag_library:)
40
+ # @param handlers [Parsers::BBCode::HandlerRegistry, nil] custom handlers
41
+ # @param renderer [Renderers::Discourse::Renderer, nil] custom renderer
42
+ # (build with {.discourse_renderer}); defaults to a fresh default Renderer
43
+ # @param raise_on_error [Boolean] when true (default), let render-time
44
+ # exceptions propagate; when false, swallow them, return a
45
+ # {Conversion} with an empty +markdown+ string, and surface the
46
+ # exceptions via {Conversion#errors}.
47
+ # @yieldparam ast [AST::Document] mutate before rendering (optional)
48
+ # @return [Conversion]
49
+ def bbcode_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true)
50
+ parse = parse_bbcode(input, handlers:)
51
+ yield(parse.ast) if block_given?
52
+ build_conversion(parse, renderer:, raise_on_error:)
29
53
  end
30
54
 
31
- # Parse HTML to AST
32
- # @param input [String] HTML source
33
- # @param handlers [HandlerRegistry, nil] custom handler registry or use default
34
- # @return [AST::Document]
55
+ # Parse HTML to AST.
56
+ #
57
+ # @param input [String, Nokogiri::XML::Node] HTML source or
58
+ # pre-parsed Nokogiri tree (e.g. the +DocumentFragment+ returned
59
+ # by +Nokogiri::HTML.fragment+). Passing a pre-parsed tree lets
60
+ # callers run their own Nokogiri-driven pre-processing without
61
+ # forcing Markbridge to re-parse the same bytes.
62
+ # @param handlers [Parsers::HTML::HandlerRegistry, nil] custom handlers
63
+ # @return [Parse]
35
64
  def parse_html(input, handlers: nil)
36
- handlers ||= default_html_handlers
37
- parse_with(Parsers::HTML::Parser, input, handlers:)
65
+ raise ArgumentError, "input cannot be nil" if input.nil?
66
+
67
+ parser = Parsers::HTML::Parser.new(handlers:)
68
+ ast = parser.parse(input)
69
+
70
+ Parse.new(ast:, format: :html, unknown_tags: parser.unknown_tags, diagnostics: {})
38
71
  end
39
72
 
40
- # Convert HTML to Discourse Markdown
41
- # @param input [String] HTML source
42
- # @param handlers [HandlerRegistry, nil] custom handler registry or use default
43
- # @param tag_library [TagLibrary, nil] custom tag library or use default
44
- # @return [String] Markdown output
45
- def html_to_markdown(input, handlers: nil, tag_library: nil)
46
- ast = parse_html(input, handlers:)
47
- render_to_markdown(ast, tag_library:)
73
+ # Convert HTML to Discourse Markdown.
74
+ #
75
+ # If a block is given, it is called with the parsed AST between
76
+ # parse and render the caller can append/remove/replace nodes
77
+ # before rendering. Mutations to the yielded AST persist in
78
+ # {Conversion#ast}.
79
+ #
80
+ # @param input [String, Nokogiri::XML::Node] HTML source or
81
+ # pre-parsed Nokogiri tree (see {.parse_html})
82
+ # @param handlers [Parsers::HTML::HandlerRegistry, nil] custom handlers
83
+ # @param renderer [Renderers::Discourse::Renderer, nil] custom renderer
84
+ # @param raise_on_error [Boolean] when true (default), let render-time
85
+ # exceptions propagate; when false, swallow them, return a
86
+ # {Conversion} with an empty +markdown+ string, and surface the
87
+ # exceptions via {Conversion#errors}.
88
+ # @yieldparam ast [AST::Document] mutate before rendering (optional)
89
+ # @return [Conversion]
90
+ def html_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true)
91
+ parse = parse_html(input, handlers:)
92
+ yield(parse.ast) if block_given?
93
+ build_conversion(parse, renderer:, raise_on_error:)
48
94
  end
49
95
 
50
- # Parse s9e/TextFormatter XML to AST
51
- # @param input [String] XML source in s9e/TextFormatter format
52
- # @param handlers [Parsers::TextFormatter::HandlerRegistry, nil] custom handler registry or use default
53
- # @return [AST::Document]
96
+ # Parse s9e/TextFormatter XML to AST.
97
+ #
98
+ # @param input [String, Nokogiri::XML::Node] XML source or
99
+ # pre-parsed Nokogiri tree. A +Nokogiri::XML::Document+ is
100
+ # unwrapped via +#root+; any other node is treated as the root.
101
+ # @param handlers [Parsers::TextFormatter::HandlerRegistry, nil] custom handlers
102
+ # @return [Parse]
54
103
  def parse_text_formatter_xml(input, handlers: nil)
55
- handlers ||= default_text_formatter_handlers
56
- parse_with(Parsers::TextFormatter::Parser, input, handlers:)
104
+ raise ArgumentError, "input cannot be nil" if input.nil?
105
+
106
+ parser = Parsers::TextFormatter::Parser.new(handlers:)
107
+ ast = parser.parse(input)
108
+ unknown_tags = parser.unknown_tags
109
+
110
+ Parse.new(ast:, format: :text_formatter_xml, unknown_tags:, diagnostics: {})
57
111
  end
58
112
 
59
- # Convert s9e/TextFormatter XML to Discourse Markdown
60
- # @param input [String] XML source in s9e/TextFormatter format
61
- # @param handlers [Parsers::TextFormatter::HandlerRegistry, nil] custom handler registry or use default
62
- # @param tag_library [TagLibrary, nil] custom tag library or use default
63
- # @return [String] Markdown output
64
- def text_formatter_xml_to_markdown(input, handlers: nil, tag_library: nil)
65
- ast = parse_text_formatter_xml(input, handlers:)
66
- render_to_markdown(ast, tag_library:)
113
+ # Convert s9e/TextFormatter XML to Discourse Markdown.
114
+ #
115
+ # If a block is given, it is called with the parsed AST between
116
+ # parse and render the caller can append/remove/replace nodes
117
+ # before rendering. Mutations to the yielded AST persist in
118
+ # {Conversion#ast}.
119
+ #
120
+ # @param input [String, Nokogiri::XML::Node] XML source or
121
+ # pre-parsed Nokogiri tree (see {.parse_text_formatter_xml})
122
+ # @param handlers [Parsers::TextFormatter::HandlerRegistry, nil] custom handlers
123
+ # @param renderer [Renderers::Discourse::Renderer, nil] custom renderer
124
+ # @param raise_on_error [Boolean] see {.bbcode_to_markdown}
125
+ # @yieldparam ast [AST::Document] mutate before rendering (optional)
126
+ # @return [Conversion]
127
+ def text_formatter_xml_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true)
128
+ parse = parse_text_formatter_xml(input, handlers:)
129
+ yield(parse.ast) if block_given?
130
+ build_conversion(parse, renderer:, raise_on_error:)
67
131
  end
68
132
 
69
- # Parse MediaWiki wikitext to AST
133
+ # Parse MediaWiki wikitext to AST.
134
+ #
70
135
  # @param input [String] MediaWiki source
71
- # @param inline_tag_registry [Parsers::MediaWiki::InlineTagRegistry, nil] custom registry
72
- # @return [AST::Document]
73
- def parse_mediawiki(input, inline_tag_registry: nil)
136
+ # @param handlers [Parsers::MediaWiki::InlineTagRegistry, nil] custom inline-tag registry
137
+ # @return [Parse]
138
+ def parse_mediawiki(input, handlers: nil)
74
139
  raise ArgumentError, "input cannot be nil" if input.nil?
75
140
 
76
- input = input.to_s
77
- parser = Parsers::MediaWiki::Parser.new(inline_tag_registry:)
78
- parser.parse(input)
141
+ parser = Parsers::MediaWiki::Parser.new(handlers:)
142
+ ast = parser.parse(input.to_s)
143
+
144
+ Parse.new(ast:, format: :mediawiki, unknown_tags: parser.unknown_tags, diagnostics: {})
79
145
  end
80
146
 
81
- # Convert MediaWiki wikitext to Discourse Markdown
147
+ # Convert MediaWiki wikitext to Discourse Markdown.
148
+ #
149
+ # If a block is given, it is called with the parsed AST between
150
+ # parse and render — the caller can append/remove/replace nodes
151
+ # before rendering. Mutations to the yielded AST persist in
152
+ # {Conversion#ast}.
153
+ #
82
154
  # @param input [String] MediaWiki source
83
- # @param inline_tag_registry [Parsers::MediaWiki::InlineTagRegistry, nil] custom registry
84
- # @param tag_library [TagLibrary, nil] custom tag library or use default
85
- # @return [String] Markdown output
86
- def mediawiki_to_markdown(input, inline_tag_registry: nil, tag_library: nil)
87
- ast = parse_mediawiki(input, inline_tag_registry:)
88
- render_to_markdown(ast, tag_library:)
155
+ # @param handlers [Parsers::MediaWiki::InlineTagRegistry, nil]
156
+ # @param renderer [Renderers::Discourse::Renderer, nil] custom renderer
157
+ # @param raise_on_error [Boolean] see {.bbcode_to_markdown}
158
+ # @yieldparam ast [AST::Document] mutate before rendering (optional)
159
+ # @return [Conversion]
160
+ def mediawiki_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true)
161
+ parse = parse_mediawiki(input, handlers:)
162
+ yield(parse.ast) if block_given?
163
+ build_conversion(parse, renderer:, raise_on_error:)
89
164
  end
90
165
 
91
- # Get default handler registry
92
- # @return [Parsers::BBCode::HandlerRegistry]
93
- def default_handlers
94
- @default_handlers ||= Parsers::BBCode::HandlerRegistry.default
166
+ # Convert input in the given format. Thin dispatcher over the
167
+ # four +*_to_markdown+ methods; useful when the format is data-
168
+ # driven (e.g. iterating posts whose +:format+ column varies).
169
+ # An optional block is forwarded to the dispatched method.
170
+ #
171
+ # @param input [String, Nokogiri::XML::Node] source content; the
172
+ # HTML and TextFormatter dispatch targets also accept pre-parsed
173
+ # Nokogiri trees.
174
+ # @param format [Symbol] one of +:bbcode+, +:html+,
175
+ # +:text_formatter_xml+, +:mediawiki+
176
+ # @param kwargs [Hash] forwarded to the underlying convenience method
177
+ # (e.g. +handlers:+, +renderer:+, +raise_on_error:+).
178
+ # @yieldparam ast [AST::Document] mutate before rendering (optional)
179
+ # @return [Conversion]
180
+ def convert(input, format:, **kwargs, &block)
181
+ case format
182
+ when :bbcode
183
+ bbcode_to_markdown(input, **kwargs, &block)
184
+ when :html
185
+ html_to_markdown(input, **kwargs, &block)
186
+ when :text_formatter_xml
187
+ text_formatter_xml_to_markdown(input, **kwargs, &block)
188
+ when :mediawiki
189
+ mediawiki_to_markdown(input, **kwargs, &block)
190
+ else
191
+ raise ArgumentError,
192
+ "unknown format #{format.inspect} " \
193
+ "(expected :bbcode, :html, :text_formatter_xml, or :mediawiki)"
194
+ end
95
195
  end
96
196
 
97
- # Get default HTML handler registry
98
- # @return [Parsers::HTML::HandlerRegistry]
99
- def default_html_handlers
100
- @default_html_handlers ||= Parsers::HTML::HandlerRegistry.default
101
- end
197
+ # Render a {Parse} or a bare AST node to Discourse Markdown.
198
+ # Useful when the caller has mutated the AST between parse and
199
+ # render (e.g. appending attachments not present in the source),
200
+ # or built an AST programmatically.
201
+ #
202
+ # When given a {Parse}, the returned {Conversion} carries the
203
+ # parser's +unknown_tags+, +diagnostics+, and source +format+
204
+ # forward. When given an AST node, those fields default to empty
205
+ # and +format+ is +nil+ — there was no source document, so there
206
+ # is no source format to report. A bare node that isn't already a
207
+ # {AST::Document} is wrapped in one, so {Conversion#ast} is always
208
+ # a Document (and tree helpers like +each_descendant+ are always
209
+ # available on it).
210
+ #
211
+ # @param parse_or_ast [Parse, AST::Node]
212
+ # @param format [Symbol] :discourse (only renderer currently shipped)
213
+ # @param renderer [Renderers::Discourse::Renderer, nil]
214
+ # @param raise_on_error [Boolean]
215
+ # @return [Conversion]
216
+ def render(parse_or_ast, format: :discourse, renderer: nil, raise_on_error: true)
217
+ raise ArgumentError, "unknown render format #{format.inspect}" unless format == :discourse
102
218
 
103
- # Get default tag library
104
- # @return [Renderers::Discourse::TagLibrary]
105
- def default_tag_library
106
- @default_tag_library ||= Renderers::Discourse::TagLibrary.default
107
- end
219
+ parse =
220
+ case parse_or_ast
221
+ when Parse
222
+ parse_or_ast
223
+ when AST::Document
224
+ Parse.new(ast: parse_or_ast, format: nil, unknown_tags: {}, diagnostics: {})
225
+ when AST::Node
226
+ document = AST::Document.new([parse_or_ast])
227
+ Parse.new(ast: document, format: nil, unknown_tags: {}, diagnostics: {})
228
+ else
229
+ raise ArgumentError, "expected Parse or AST::Node, got #{parse_or_ast.class}"
230
+ end
108
231
 
109
- # Get default s9e/TextFormatter handler registry
110
- # @return [Parsers::TextFormatter::HandlerRegistry]
111
- def default_text_formatter_handlers
112
- @default_text_formatter_handlers ||= Parsers::TextFormatter::HandlerRegistry.default
232
+ build_conversion(parse, renderer:, raise_on_error:)
113
233
  end
114
234
 
115
- # Get the global configuration
116
- # @return [Configuration]
117
- def configuration
118
- @configuration ||= Configuration.new
119
- end
235
+ # Build a configured Discourse {Renderers::Discourse::Renderer}
236
+ # for use with the +renderer:+ kwarg on the +*_to_markdown+
237
+ # convenience methods.
238
+ #
239
+ # @param tags [Hash{Class => Tag, nil}, nil] mappings to merge on
240
+ # top of the default library; +nil+ values unregister the class.
241
+ # @param tag_library [Renderers::Discourse::TagLibrary, nil] base
242
+ # library to start from. Defaults to a fresh {TagLibrary.default}.
243
+ # When supplied, it is +dup+'d before any +tags:+ / +unregister:+
244
+ # mutation, so the caller's library is left untouched.
245
+ # @param unregister [Array<Class>, nil] AST classes to drop from
246
+ # the library so they fall through to +render_children+.
247
+ # @param escaper [#escape, nil] when given, used as-is; +escape:+,
248
+ # +escape_hard_line_breaks:+, and +allow:+ are then ignored.
249
+ # @param escape [Boolean] when +false+, the renderer is built with
250
+ # {Renderers::Discourse::IdentityEscaper} (no Markdown escaping).
251
+ # Mutually exclusive with +escape_hard_line_breaks:+ / +allow:+.
252
+ # @param escape_hard_line_breaks [Boolean] forwarded to a fresh
253
+ # {MarkdownEscaper} when no explicit +escaper:+ is given.
254
+ # @param allow [Symbol, Array<Symbol>, nil] block-level constructs to
255
+ # pass through unescaped (e.g. +:lists+, +:bullet_list+,
256
+ # +:ordered_list+, +:atx_heading+, +:block_quote+); forwarded to a
257
+ # fresh {MarkdownEscaper}.
258
+ # @param postprocessor [Renderers::Discourse::Postprocessor, nil] when given,
259
+ # used as-is; +strip_trailing_invisibles:+ is then ignored.
260
+ # @param strip_trailing_invisibles [Boolean] forwarded to a fresh
261
+ # {Renderers::Discourse::Postprocessor} when no explicit
262
+ # +postprocessor:+ is given. Strips NBSP and zero-width format
263
+ # characters from the end of each line.
264
+ # @return [Renderers::Discourse::Renderer]
265
+ def discourse_renderer(
266
+ tags: nil,
267
+ tag_library: nil,
268
+ unregister: nil,
269
+ escaper: nil,
270
+ escape: true,
271
+ escape_hard_line_breaks: false,
272
+ allow: nil,
273
+ postprocessor: nil,
274
+ strip_trailing_invisibles: false
275
+ )
276
+ # Dup the caller's library before mutating so successive
277
+ # +discourse_renderer+ calls against the same +tag_library:+ don't
278
+ # see each other's overrides. +TagLibrary.default+ already returns
279
+ # a fresh instance, so the dup is only needed in the explicit
280
+ # +tag_library:+ branch.
281
+ library = tag_library ? tag_library.dup : Renderers::Discourse::TagLibrary.default
282
+ library.merge!(tags) if tags
283
+ Array(unregister).each { |klass| library.unregister(klass) }
120
284
 
121
- # Configure Markbridge with a block
122
- # @yield [Configuration]
123
- def configure
124
- yield configuration
125
- end
285
+ escaper ||= build_escaper(escape:, escape_hard_line_breaks:, allow:)
286
+ postprocessor ||= Renderers::Discourse::Postprocessor.new(strip_trailing_invisibles:)
126
287
 
127
- # Reset defaults (useful for testing)
128
- def reset_defaults!
129
- @default_handlers = nil
130
- @default_html_handlers = nil
131
- @default_tag_library = nil
132
- @default_text_formatter_handlers = nil
133
- @configuration = nil
288
+ Renderers::Discourse::Renderer.new(tag_library: library, escaper:, postprocessor:)
134
289
  end
135
290
 
136
291
  private
137
292
 
138
- def parse_with(parser_class, input, handlers:)
139
- raise ArgumentError, "input cannot be nil" if input.nil?
140
-
141
- parser = parser_class.new(handlers:)
142
- parser.parse(input.to_s)
293
+ def bbcode_diagnostics(parser)
294
+ {
295
+ auto_closed_tags_count: parser.auto_closed_tags_count,
296
+ depth_exceeded_count: parser.depth_exceeded_count,
297
+ unclosed_raw_tags: parser.unclosed_raw_tags,
298
+ }
143
299
  end
144
300
 
145
- def render_to_markdown(ast, tag_library:)
146
- tag_library ||= default_tag_library
147
- renderer = build_renderer(tag_library:)
148
- cleanup_markdown(renderer.render(ast))
301
+ def build_conversion(parse, renderer:, raise_on_error:)
302
+ renderer ||= Renderers::Discourse::Renderer.new
303
+ markdown, errors = render_through(renderer, parse.ast, raise_on_error:)
304
+
305
+ Conversion.new(parsed: parse, markdown:, errors:)
149
306
  end
150
307
 
151
- def build_renderer(tag_library:)
152
- escaper =
153
- Renderers::Discourse::MarkdownEscaper.new(
154
- escape_hard_line_breaks: configuration.escape_hard_line_breaks,
155
- )
156
- Renderers::Discourse::Renderer.new(tag_library:, escaper:)
308
+ def build_escaper(escape:, escape_hard_line_breaks:, allow:)
309
+ if escape == false
310
+ if escape_hard_line_breaks || allow
311
+ raise ArgumentError,
312
+ "escape: false is mutually exclusive with " \
313
+ "escape_hard_line_breaks: / allow: (those configure " \
314
+ "MarkdownEscaper, which escape: false replaces wholesale)"
315
+ end
316
+ Renderers::Discourse::IdentityEscaper.new
317
+ else
318
+ Renderers::Discourse::MarkdownEscaper.new(escape_hard_line_breaks:, allow:)
319
+ end
157
320
  end
158
321
 
159
- def cleanup_markdown(text)
160
- text
161
- .gsub(/\n{3,}/, "\n\n") # Max 2 consecutive newlines
162
- .gsub(/^[ \t]+$/, "") # Remove whitespace-only lines
163
- .strip # Trim leading/trailing whitespace
322
+ def render_through(renderer, ast, raise_on_error:)
323
+ raw = renderer.render(ast)
324
+ [renderer.postprocessor.call(raw), []]
325
+ rescue StandardError => e
326
+ raise if raise_on_error
327
+ ["", [e]]
164
328
  end
165
329
  end
166
330
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markbridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Discourse Team
@@ -25,6 +25,7 @@ files:
25
25
  - lib/markbridge/ast/bold.rb
26
26
  - lib/markbridge/ast/code.rb
27
27
  - lib/markbridge/ast/color.rb
28
+ - lib/markbridge/ast/details.rb
28
29
  - lib/markbridge/ast/document.rb
29
30
  - lib/markbridge/ast/element.rb
30
31
  - lib/markbridge/ast/email.rb
@@ -53,10 +54,11 @@ files:
53
54
  - lib/markbridge/ast/upload.rb
54
55
  - lib/markbridge/ast/url.rb
55
56
  - lib/markbridge/bbcode.rb
56
- - lib/markbridge/configuration.rb
57
+ - lib/markbridge/conversion.rb
57
58
  - lib/markbridge/gem_loader.rb
58
59
  - lib/markbridge/html.rb
59
60
  - lib/markbridge/mediawiki.rb
61
+ - lib/markbridge/parse.rb
60
62
  - lib/markbridge/parsers/bbcode.rb
61
63
  - lib/markbridge/parsers/bbcode/closing_strategies/base.rb
62
64
  - lib/markbridge/parsers/bbcode/closing_strategies/reordering.rb
@@ -102,7 +104,9 @@ files:
102
104
  - lib/markbridge/parsers/html/handlers/paragraph_handler.rb
103
105
  - lib/markbridge/parsers/html/handlers/quote_handler.rb
104
106
  - lib/markbridge/parsers/html/handlers/raw_handler.rb
107
+ - lib/markbridge/parsers/html/handlers/self_closing_handler.rb
105
108
  - lib/markbridge/parsers/html/handlers/simple_handler.rb
109
+ - lib/markbridge/parsers/html/handlers/span_handler.rb
106
110
  - lib/markbridge/parsers/html/handlers/table_cell_handler.rb
107
111
  - lib/markbridge/parsers/html/handlers/table_handler.rb
108
112
  - lib/markbridge/parsers/html/handlers/table_row_handler.rb
@@ -138,7 +142,9 @@ files:
138
142
  - lib/markbridge/renderers/discourse.rb
139
143
  - lib/markbridge/renderers/discourse/builders/list_item_builder.rb
140
144
  - lib/markbridge/renderers/discourse/html_escaper.rb
145
+ - lib/markbridge/renderers/discourse/identity_escaper.rb
141
146
  - lib/markbridge/renderers/discourse/markdown_escaper.rb
147
+ - lib/markbridge/renderers/discourse/postprocessor.rb
142
148
  - lib/markbridge/renderers/discourse/render_context.rb
143
149
  - lib/markbridge/renderers/discourse/renderer.rb
144
150
  - lib/markbridge/renderers/discourse/rendering_interface.rb
@@ -149,6 +155,7 @@ files:
149
155
  - lib/markbridge/renderers/discourse/tags/bold_tag.rb
150
156
  - lib/markbridge/renderers/discourse/tags/code_tag.rb
151
157
  - lib/markbridge/renderers/discourse/tags/color_tag.rb
158
+ - lib/markbridge/renderers/discourse/tags/details_tag.rb
152
159
  - lib/markbridge/renderers/discourse/tags/email_tag.rb
153
160
  - lib/markbridge/renderers/discourse/tags/event_tag.rb
154
161
  - lib/markbridge/renderers/discourse/tags/heading_tag.rb
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Markbridge
4
- class Configuration
5
- attr_accessor :escape_hard_line_breaks
6
-
7
- def initialize
8
- @escape_hard_line_breaks = false
9
- end
10
- end
11
- end