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