markbridge 0.3.0 → 0.4.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 +4 -4
- data/lib/markbridge/ast/code.rb +8 -1
- data/lib/markbridge/ast/element.rb +28 -0
- data/lib/markbridge/ast/node.rb +41 -0
- data/lib/markbridge/normalizer/report.rb +41 -0
- data/lib/markbridge/normalizer/rule_set.rb +163 -0
- data/lib/markbridge/normalizer/text_projection.rb +37 -0
- data/lib/markbridge/normalizer/walker.rb +255 -0
- data/lib/markbridge/normalizer.rb +187 -0
- data/lib/markbridge/parsers/bbcode/handlers/code_handler.rb +17 -0
- data/lib/markbridge/parsers/html/handler_registry.rb +1 -0
- data/lib/markbridge/parsers/html/handlers/heading_handler.rb +29 -0
- data/lib/markbridge/parsers/html/handlers/raw_handler.rb +55 -4
- data/lib/markbridge/parsers/html.rb +1 -0
- data/lib/markbridge/parsers/media_wiki/parser.rb +2 -2
- data/lib/markbridge/parsers/text_formatter/handlers/code_handler.rb +3 -1
- data/lib/markbridge/renderers/discourse/html_block_safety.rb +31 -0
- data/lib/markbridge/renderers/discourse/renderer.rb +36 -6
- data/lib/markbridge/renderers/discourse/rendering_interface.rb +4 -0
- data/lib/markbridge/renderers/discourse/tag.rb +10 -0
- data/lib/markbridge/renderers/discourse/tag_library.rb +59 -4
- data/lib/markbridge/renderers/discourse/tags/code_tag.rb +4 -0
- data/lib/markbridge/renderers/discourse/tags/details_tag.rb +6 -2
- data/lib/markbridge/renderers/discourse/tags/event_tag.rb +2 -4
- data/lib/markbridge/renderers/discourse/tags/poll_tag.rb +2 -4
- data/lib/markbridge/renderers/discourse/tags/spoiler_tag.rb +4 -0
- data/lib/markbridge/renderers/discourse.rb +1 -0
- data/lib/markbridge/rspec.rb +46 -0
- data/lib/markbridge/version.rb +1 -1
- data/lib/markbridge.rb +65 -13
- metadata +9 -1
|
@@ -18,11 +18,9 @@ module Markbridge
|
|
|
18
18
|
# end
|
|
19
19
|
# end
|
|
20
20
|
class PollTag < Tag
|
|
21
|
-
def render(element,
|
|
21
|
+
def render(element, _interface)
|
|
22
22
|
body = element.raw || build_poll_bbcode(element)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"#{body}\n\n"
|
|
23
|
+
"\n\n#{body}\n\n"
|
|
26
24
|
end
|
|
27
25
|
|
|
28
26
|
private
|
|
@@ -11,6 +11,10 @@ module Markbridge
|
|
|
11
11
|
child_context = interface.with_parent(element)
|
|
12
12
|
content = interface.render_children(element, context: child_context)
|
|
13
13
|
|
|
14
|
+
# A spoiler with nothing to hide renders to nothing — an empty
|
|
15
|
+
# [spoiler] shell would only add noise to the output.
|
|
16
|
+
return "" if content.empty?
|
|
17
|
+
|
|
14
18
|
return render_html(element.title, content) if interface.html_mode?
|
|
15
19
|
|
|
16
20
|
if element.title
|
|
@@ -7,6 +7,7 @@ require_relative "discourse/rendering_interface"
|
|
|
7
7
|
require_relative "discourse/markdown_escaper"
|
|
8
8
|
require_relative "discourse/identity_escaper"
|
|
9
9
|
require_relative "discourse/html_escaper"
|
|
10
|
+
require_relative "discourse/html_block_safety"
|
|
10
11
|
require_relative "discourse/postprocessor"
|
|
11
12
|
|
|
12
13
|
# Builders
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../markbridge"
|
|
4
|
+
|
|
5
|
+
# Shared RSpec examples for consumers who write their own renderer
|
|
6
|
+
# tags. Require this file from your spec setup (RSpec itself must
|
|
7
|
+
# already be loaded):
|
|
8
|
+
#
|
|
9
|
+
# require "markbridge/rspec"
|
|
10
|
+
#
|
|
11
|
+
# and put a custom tag under the html_mode contract:
|
|
12
|
+
#
|
|
13
|
+
# RSpec.describe MyQuoteTag do
|
|
14
|
+
# it_behaves_like "an html_mode safe tag" do
|
|
15
|
+
# let(:tag) { described_class.new }
|
|
16
|
+
# let(:element) do
|
|
17
|
+
# element = MyQuote.new
|
|
18
|
+
# element << Markbridge::AST::Text.new("body *with* sigils")
|
|
19
|
+
# element
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# The example renders +element+ with +tag+ in html_mode and fails when
|
|
25
|
+
# the output would break inside a CommonMark HTML block — the same
|
|
26
|
+
# check Markbridge runs against its own tags. Give +element+ children
|
|
27
|
+
# whose text contains Markdown sigils, so a tag that passes them
|
|
28
|
+
# through unprotected is caught. When the tag needs a customized
|
|
29
|
+
# renderer to resolve its children (for example a custom tag library),
|
|
30
|
+
# override +markbridge_renderer+ with your configured renderer.
|
|
31
|
+
RSpec.shared_examples "an html_mode safe tag" do
|
|
32
|
+
let(:markbridge_renderer) { Markbridge::Renderers::Discourse::Renderer.new }
|
|
33
|
+
|
|
34
|
+
let(:markbridge_html_mode_interface) do
|
|
35
|
+
context = Markbridge::Renderers::Discourse::RenderContext.new([], html_mode: true)
|
|
36
|
+
Markbridge::Renderers::Discourse::RenderingInterface.new(markbridge_renderer, context)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "renders html_mode output that is safe inside an HTML block" do
|
|
40
|
+
output = tag.render(element, markbridge_html_mode_interface)
|
|
41
|
+
|
|
42
|
+
expect(Markbridge::Renderers::Discourse::HtmlBlockSafety.safe?(output)).to be(true),
|
|
43
|
+
"Expected #{tag.class} to render raw HTML or a \\n\\n-wrapped " \
|
|
44
|
+
"Markdown island in html_mode, got: #{output.inspect}"
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/markbridge/version.rb
CHANGED
data/lib/markbridge.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "markbridge/parse"
|
|
|
5
5
|
require_relative "markbridge/conversion"
|
|
6
6
|
|
|
7
7
|
require_relative "markbridge/ast"
|
|
8
|
+
require_relative "markbridge/normalizer"
|
|
8
9
|
require_relative "markbridge/renderers/discourse"
|
|
9
10
|
|
|
10
11
|
module Markbridge
|
|
@@ -44,11 +45,21 @@ module Markbridge
|
|
|
44
45
|
# {Conversion} with an empty +markdown+ string, and surface the
|
|
45
46
|
# exceptions via {Conversion#errors}.
|
|
46
47
|
# @yieldparam ast [AST::Document] mutate before rendering (optional)
|
|
48
|
+
# @param normalize [Boolean, Normalizer] apply target-format nesting
|
|
49
|
+
# rules between the +yield+ hook and render. +true+ (default) uses the
|
|
50
|
+
# shared default normalizer; a {Normalizer} is used as-is; +false+
|
|
51
|
+
# skips normalization. See {Normalizer}.
|
|
47
52
|
# @return [Conversion]
|
|
48
|
-
def bbcode_to_markdown(
|
|
53
|
+
def bbcode_to_markdown(
|
|
54
|
+
input,
|
|
55
|
+
handlers: nil,
|
|
56
|
+
renderer: nil,
|
|
57
|
+
raise_on_error: true,
|
|
58
|
+
normalize: true
|
|
59
|
+
)
|
|
49
60
|
parse = parse_bbcode(input, handlers:)
|
|
50
61
|
yield(parse.ast) if block_given?
|
|
51
|
-
build_conversion(parse, renderer:, raise_on_error:)
|
|
62
|
+
build_conversion(parse, renderer:, raise_on_error:, normalize:)
|
|
52
63
|
end
|
|
53
64
|
|
|
54
65
|
# Parse HTML to AST.
|
|
@@ -85,11 +96,12 @@ module Markbridge
|
|
|
85
96
|
# {Conversion} with an empty +markdown+ string, and surface the
|
|
86
97
|
# exceptions via {Conversion#errors}.
|
|
87
98
|
# @yieldparam ast [AST::Document] mutate before rendering (optional)
|
|
99
|
+
# @param normalize [Boolean, Normalizer] see {.bbcode_to_markdown}
|
|
88
100
|
# @return [Conversion]
|
|
89
|
-
def html_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true)
|
|
101
|
+
def html_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true, normalize: true)
|
|
90
102
|
parse = parse_html(input, handlers:)
|
|
91
103
|
yield(parse.ast) if block_given?
|
|
92
|
-
build_conversion(parse, renderer:, raise_on_error:)
|
|
104
|
+
build_conversion(parse, renderer:, raise_on_error:, normalize:)
|
|
93
105
|
end
|
|
94
106
|
|
|
95
107
|
# Parse s9e/TextFormatter XML to AST.
|
|
@@ -122,11 +134,18 @@ module Markbridge
|
|
|
122
134
|
# @param renderer [Renderers::Discourse::Renderer, nil] custom renderer
|
|
123
135
|
# @param raise_on_error [Boolean] see {.bbcode_to_markdown}
|
|
124
136
|
# @yieldparam ast [AST::Document] mutate before rendering (optional)
|
|
137
|
+
# @param normalize [Boolean, Normalizer] see {.bbcode_to_markdown}
|
|
125
138
|
# @return [Conversion]
|
|
126
|
-
def text_formatter_xml_to_markdown(
|
|
139
|
+
def text_formatter_xml_to_markdown(
|
|
140
|
+
input,
|
|
141
|
+
handlers: nil,
|
|
142
|
+
renderer: nil,
|
|
143
|
+
raise_on_error: true,
|
|
144
|
+
normalize: true
|
|
145
|
+
)
|
|
127
146
|
parse = parse_text_formatter_xml(input, handlers:)
|
|
128
147
|
yield(parse.ast) if block_given?
|
|
129
|
-
build_conversion(parse, renderer:, raise_on_error:)
|
|
148
|
+
build_conversion(parse, renderer:, raise_on_error:, normalize:)
|
|
130
149
|
end
|
|
131
150
|
|
|
132
151
|
# Parse MediaWiki wikitext to AST.
|
|
@@ -155,11 +174,18 @@ module Markbridge
|
|
|
155
174
|
# @param renderer [Renderers::Discourse::Renderer, nil] custom renderer
|
|
156
175
|
# @param raise_on_error [Boolean] see {.bbcode_to_markdown}
|
|
157
176
|
# @yieldparam ast [AST::Document] mutate before rendering (optional)
|
|
177
|
+
# @param normalize [Boolean, Normalizer] see {.bbcode_to_markdown}
|
|
158
178
|
# @return [Conversion]
|
|
159
|
-
def mediawiki_to_markdown(
|
|
179
|
+
def mediawiki_to_markdown(
|
|
180
|
+
input,
|
|
181
|
+
handlers: nil,
|
|
182
|
+
renderer: nil,
|
|
183
|
+
raise_on_error: true,
|
|
184
|
+
normalize: true
|
|
185
|
+
)
|
|
160
186
|
parse = parse_mediawiki(input, handlers:)
|
|
161
187
|
yield(parse.ast) if block_given?
|
|
162
|
-
build_conversion(parse, renderer:, raise_on_error:)
|
|
188
|
+
build_conversion(parse, renderer:, raise_on_error:, normalize:)
|
|
163
189
|
end
|
|
164
190
|
|
|
165
191
|
# Convert input in the given format. Thin dispatcher over the
|
|
@@ -211,8 +237,17 @@ module Markbridge
|
|
|
211
237
|
# @param format [Symbol] :discourse (only renderer currently shipped)
|
|
212
238
|
# @param renderer [Renderers::Discourse::Renderer, nil]
|
|
213
239
|
# @param raise_on_error [Boolean]
|
|
240
|
+
# @param normalize [Boolean, Normalizer] see {.bbcode_to_markdown}.
|
|
241
|
+
# Normalization is idempotent, so re-rendering an already-normalized
|
|
242
|
+
# {Parse} is a no-op.
|
|
214
243
|
# @return [Conversion]
|
|
215
|
-
def render(
|
|
244
|
+
def render(
|
|
245
|
+
parse_or_ast,
|
|
246
|
+
format: :discourse,
|
|
247
|
+
renderer: nil,
|
|
248
|
+
raise_on_error: true,
|
|
249
|
+
normalize: true
|
|
250
|
+
)
|
|
216
251
|
raise ArgumentError, "unknown render format #{format.inspect}" unless format == :discourse
|
|
217
252
|
|
|
218
253
|
parse =
|
|
@@ -228,7 +263,7 @@ module Markbridge
|
|
|
228
263
|
raise ArgumentError, "expected Parse or AST::Node, got #{parse_or_ast.class}"
|
|
229
264
|
end
|
|
230
265
|
|
|
231
|
-
build_conversion(parse, renderer:, raise_on_error:)
|
|
266
|
+
build_conversion(parse, renderer:, raise_on_error:, normalize:)
|
|
232
267
|
end
|
|
233
268
|
|
|
234
269
|
# Build a configured Discourse {Renderers::Discourse::Renderer}
|
|
@@ -241,8 +276,10 @@ module Markbridge
|
|
|
241
276
|
# library to start from. Defaults to a fresh {TagLibrary.default}.
|
|
242
277
|
# When supplied, it is +dup+'d before any +tags:+ / +unregister:+
|
|
243
278
|
# mutation, so the caller's library is left untouched.
|
|
244
|
-
# @param unregister [Array<Class>, nil] AST classes
|
|
245
|
-
# the library
|
|
279
|
+
# @param unregister [Array<Class>, nil] AST classes whose binding to
|
|
280
|
+
# drop from the library. A built-in class then renders as just its
|
|
281
|
+
# children; a subclass of a bound class falls back to the nearest
|
|
282
|
+
# ancestor's tag (see {Renderers::Discourse::TagLibrary#unregister}).
|
|
246
283
|
# @param escaper [#escape, nil] when given, used as-is; +escape:+,
|
|
247
284
|
# +escape_hard_line_breaks:+, and +allow:+ are then ignored.
|
|
248
285
|
# @param escape [Boolean] when +false+, the renderer is built with
|
|
@@ -297,13 +334,28 @@ module Markbridge
|
|
|
297
334
|
}
|
|
298
335
|
end
|
|
299
336
|
|
|
300
|
-
def build_conversion(parse, renderer:, raise_on_error:)
|
|
337
|
+
def build_conversion(parse, renderer:, raise_on_error:, normalize:)
|
|
338
|
+
parse = apply_normalization(parse, normalize)
|
|
301
339
|
renderer ||= Renderers::Discourse::Renderer.new
|
|
302
340
|
markdown, errors = render_through(renderer, parse.ast, raise_on_error:)
|
|
303
341
|
|
|
304
342
|
Conversion.new(parsed: parse, markdown:, errors:)
|
|
305
343
|
end
|
|
306
344
|
|
|
345
|
+
# Normalize +parse.ast+ in place (target-format nesting rules) and fold
|
|
346
|
+
# any report into +diagnostics[:normalization]+. Returns the +parse+ to
|
|
347
|
+
# render — a new one carrying the report when something changed, else
|
|
348
|
+
# the original unchanged.
|
|
349
|
+
def apply_normalization(parse, normalize)
|
|
350
|
+
return parse unless normalize
|
|
351
|
+
|
|
352
|
+
normalizer = normalize.is_a?(Normalizer) ? normalize : Normalizer.shared_default
|
|
353
|
+
report = normalizer.normalize(parse.ast)
|
|
354
|
+
return parse if report.empty?
|
|
355
|
+
|
|
356
|
+
parse.with(diagnostics: parse.diagnostics.merge(normalization: report))
|
|
357
|
+
end
|
|
358
|
+
|
|
307
359
|
def build_escaper(escape:, escape_hard_line_breaks:, allow:)
|
|
308
360
|
if escape == false
|
|
309
361
|
if escape_hard_line_breaks || allow
|
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.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Discourse Team
|
|
@@ -58,6 +58,11 @@ files:
|
|
|
58
58
|
- lib/markbridge/gem_loader.rb
|
|
59
59
|
- lib/markbridge/html.rb
|
|
60
60
|
- lib/markbridge/mediawiki.rb
|
|
61
|
+
- lib/markbridge/normalizer.rb
|
|
62
|
+
- lib/markbridge/normalizer/report.rb
|
|
63
|
+
- lib/markbridge/normalizer/rule_set.rb
|
|
64
|
+
- lib/markbridge/normalizer/text_projection.rb
|
|
65
|
+
- lib/markbridge/normalizer/walker.rb
|
|
61
66
|
- lib/markbridge/parse.rb
|
|
62
67
|
- lib/markbridge/parsers/bbcode.rb
|
|
63
68
|
- lib/markbridge/parsers/bbcode/closing_strategies/base.rb
|
|
@@ -98,6 +103,7 @@ files:
|
|
|
98
103
|
- lib/markbridge/parsers/html.rb
|
|
99
104
|
- lib/markbridge/parsers/html/handler_registry.rb
|
|
100
105
|
- lib/markbridge/parsers/html/handlers/base_handler.rb
|
|
106
|
+
- lib/markbridge/parsers/html/handlers/heading_handler.rb
|
|
101
107
|
- lib/markbridge/parsers/html/handlers/image_handler.rb
|
|
102
108
|
- lib/markbridge/parsers/html/handlers/list_handler.rb
|
|
103
109
|
- lib/markbridge/parsers/html/handlers/list_item_handler.rb
|
|
@@ -132,6 +138,7 @@ files:
|
|
|
132
138
|
- lib/markbridge/parsers/text_formatter/parser.rb
|
|
133
139
|
- lib/markbridge/renderers/discourse.rb
|
|
134
140
|
- lib/markbridge/renderers/discourse/builders/list_item_builder.rb
|
|
141
|
+
- lib/markbridge/renderers/discourse/html_block_safety.rb
|
|
135
142
|
- lib/markbridge/renderers/discourse/html_escaper.rb
|
|
136
143
|
- lib/markbridge/renderers/discourse/identity_escaper.rb
|
|
137
144
|
- lib/markbridge/renderers/discourse/markdown_escaper.rb
|
|
@@ -171,6 +178,7 @@ files:
|
|
|
171
178
|
- lib/markbridge/renderers/discourse/tags/underline_tag.rb
|
|
172
179
|
- lib/markbridge/renderers/discourse/tags/upload_tag.rb
|
|
173
180
|
- lib/markbridge/renderers/discourse/tags/url_tag.rb
|
|
181
|
+
- lib/markbridge/rspec.rb
|
|
174
182
|
- lib/markbridge/textformatter.rb
|
|
175
183
|
- lib/markbridge/version.rb
|
|
176
184
|
homepage: https://github.com/discourse/markbridge
|