coradoc-markdown 1.0.10 → 1.0.12
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/coradoc/markdown/parser/ast_processor.rb +8 -8
- data/lib/coradoc/markdown/parser/block_parser.rb +30 -25
- data/lib/coradoc/markdown/parser/inline_parser.rb +5 -4
- data/lib/coradoc/markdown/parser/parsanol_atoms.rb +135 -0
- data/lib/coradoc/markdown/parser_util.rb +2 -2
- data/lib/coradoc/markdown/serializer/config.rb +4 -10
- data/lib/coradoc/markdown/serializer/registry.rb +1 -1
- data/lib/coradoc/markdown/serializer/runner.rb +1 -1
- data/lib/coradoc/markdown/transform/from_core_model.rb +13 -0
- data/lib/coradoc/markdown/transformer.rb +5 -5
- data/lib/coradoc/markdown/version.rb +1 -1
- data/lib/coradoc/markdown.rb +13 -5
- metadata +5 -4
- data/lib/coradoc/markdown/parser/parslet_extras.rb +0 -215
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 232eab66f6d33edae8a3cca57f5ebd16cca11ed96c4dd9a51611b9669410595b
|
|
4
|
+
data.tar.gz: 1b0f0051729a0e226826c66d49bed0c4227732491b0bbf6148f42df524b1e95f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d20d1132ed4f527557207c11736d3ad10289fecf47a480341ed3b768bee9b2e812195fcf77d377a374b9c82804044c8c2aa701688c7e08706070ef62f957545c
|
|
7
|
+
data.tar.gz: 24547cfb89cbe67a61f0bb63cee59495d2520d61df54ed03141963f5245e2cd5b1c537b80b2903a55b9cfa36d51639fc85f170a963686a6b71faff2a95845b11
|
|
@@ -84,8 +84,8 @@ module Coradoc
|
|
|
84
84
|
node.map { |child| process_node(child) }
|
|
85
85
|
when Hash
|
|
86
86
|
process_hash(node)
|
|
87
|
-
when
|
|
88
|
-
# Process escape sequences in
|
|
87
|
+
when Parsanol::Slice
|
|
88
|
+
# Process escape sequences in Parsanol::Slice values
|
|
89
89
|
process_escapes(node.to_s)
|
|
90
90
|
else
|
|
91
91
|
node
|
|
@@ -161,7 +161,7 @@ module Coradoc
|
|
|
161
161
|
return value if value.nil?
|
|
162
162
|
|
|
163
163
|
case value
|
|
164
|
-
when
|
|
164
|
+
when Parsanol::Slice
|
|
165
165
|
end
|
|
166
166
|
text = value.to_s
|
|
167
167
|
|
|
@@ -229,13 +229,13 @@ module Coradoc
|
|
|
229
229
|
result
|
|
230
230
|
end
|
|
231
231
|
|
|
232
|
-
# Process a text value (
|
|
232
|
+
# Process a text value (Parsanol::Slice or String)
|
|
233
233
|
# Only processes escape sequences without changing structure
|
|
234
234
|
def process_text_value(value)
|
|
235
235
|
return value if value.nil?
|
|
236
236
|
|
|
237
237
|
case value
|
|
238
|
-
when
|
|
238
|
+
when Parsanol::Slice
|
|
239
239
|
end
|
|
240
240
|
process_escapes(value.to_s)
|
|
241
241
|
end
|
|
@@ -374,7 +374,7 @@ module Coradoc
|
|
|
374
374
|
joined = extract_text_content(result)
|
|
375
375
|
{ ln: joined }
|
|
376
376
|
end
|
|
377
|
-
rescue
|
|
377
|
+
rescue Parsanol::ParseFailed
|
|
378
378
|
# If parsing fails, return original text in ln structure
|
|
379
379
|
{ ln: text }
|
|
380
380
|
end
|
|
@@ -390,7 +390,7 @@ module Coradoc
|
|
|
390
390
|
|
|
391
391
|
# Pattern to match HTML tags with markdown attribute
|
|
392
392
|
# Captures: tag name, markdown value, content, closing tag
|
|
393
|
-
pattern = %r{<(#{HTML_TAG_PATTERN})\s+([^>]*?)markdown\s*=\s*["']([^"']+)["']([^>]*)>(.*?)</\1>}
|
|
393
|
+
pattern = %r{<(#{HTML_TAG_PATTERN})\s+([^>]*?)markdown\s*=\s*["']([^"']+)["']([^>]*)>(.*?)</\1>}imo
|
|
394
394
|
|
|
395
395
|
text.gsub(pattern) do |_match|
|
|
396
396
|
tag_name = ::Regexp.last_match(1)
|
|
@@ -441,7 +441,7 @@ module Coradoc
|
|
|
441
441
|
|
|
442
442
|
# Convert result back to string representation
|
|
443
443
|
inline_result_to_string(result)
|
|
444
|
-
rescue
|
|
444
|
+
rescue Parsanol::ParseFailed
|
|
445
445
|
content
|
|
446
446
|
end
|
|
447
447
|
end
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
module Coradoc
|
|
4
4
|
module Markdown
|
|
5
5
|
module Parser
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class BlockParser < Parslet::Parser
|
|
9
|
-
using ParsletExtras
|
|
6
|
+
# Load-time DSL side effect (sanctioned require_relative exception)
|
|
7
|
+
require_relative 'parsanol_atoms'
|
|
10
8
|
|
|
9
|
+
class BlockParser < Parsanol::Parser
|
|
11
10
|
# NOTE: Debug method for parser development. Outputs current parse position
|
|
12
11
|
# and capture context. Only called during parser debugging sessions.
|
|
13
12
|
def debug(msg)
|
|
@@ -23,16 +22,16 @@ module Coradoc
|
|
|
23
22
|
|
|
24
23
|
rule(:whitespace) { match[" \t"] }
|
|
25
24
|
# NOTE: repeat(1) before EOF (any.absent?) because infinite loop otherwise
|
|
26
|
-
rule(:blank_line) { (whitespace.repeat(1) >> any.absent? | whitespace.repeat >> line_ending).ignore }
|
|
25
|
+
rule(:blank_line) { ((whitespace.repeat(1) >> any.absent?) | (whitespace.repeat >> line_ending)).ignore }
|
|
27
26
|
rule(:blank_line_verbatim) do
|
|
28
|
-
whitespace.repeat(1).as(:ln) >> any.absent? | whitespace.repeat.as(:ln) >> line_ending
|
|
27
|
+
(whitespace.repeat(1).as(:ln) >> any.absent?) | (whitespace.repeat.as(:ln) >> line_ending)
|
|
29
28
|
end
|
|
30
29
|
rule(:line_char) { match["^\r\n"] }
|
|
31
30
|
rule(:line_verbatim) { line_char.repeat(1).as(:ln) >> line_ending_or_eof }
|
|
32
31
|
|
|
33
32
|
rule(:non_indent_space) { str(' ').repeat(0, 3) }
|
|
34
33
|
|
|
35
|
-
# Block nesting is the tricky part, but
|
|
34
|
+
# Block nesting is the tricky part, but Parsanol's `dynamic` and `scope`
|
|
36
35
|
# make it possible to be aware of what blocks we're already in, and implement
|
|
37
36
|
# a check for whether we're still inside of those blocks on the beginning of
|
|
38
37
|
# every line. The rules that match the line run inside of the innermost
|
|
@@ -53,11 +52,17 @@ module Coradoc
|
|
|
53
52
|
|
|
54
53
|
def open_block(kind, cont_rule)
|
|
55
54
|
dynamic do |_src, ctx|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
caps = ctx.captures
|
|
56
|
+
# Chain-read before the write: picks up the enclosing
|
|
57
|
+
# block's continuation rule when one is in scope (Parsanol's
|
|
58
|
+
# Scope#[] searches parent frames; #key? is chain-aware).
|
|
59
|
+
caps[:cont] =
|
|
60
|
+
if caps.key?(:cont)
|
|
61
|
+
caps[:cont] >> cont_rule
|
|
62
|
+
else
|
|
63
|
+
cont_rule
|
|
64
|
+
end
|
|
65
|
+
caps[:block] = kind
|
|
61
66
|
any.present? | any.absent?
|
|
62
67
|
end
|
|
63
68
|
end
|
|
@@ -222,7 +227,7 @@ module Coradoc
|
|
|
222
227
|
end
|
|
223
228
|
|
|
224
229
|
rule(:paragraph_line) do
|
|
225
|
-
line_char.repeat(1).as(:ln) >> any.absent? | line_char.repeat.as(:ln) >> line_ending
|
|
230
|
+
(line_char.repeat(1).as(:ln) >> any.absent?) | (line_char.repeat.as(:ln) >> line_ending)
|
|
226
231
|
end
|
|
227
232
|
|
|
228
233
|
rule(:paragraph_continued_line) do
|
|
@@ -286,8 +291,8 @@ module Coradoc
|
|
|
286
291
|
rule(:ial_key_value) do
|
|
287
292
|
match['\\w\\-'].repeat(1) >> str('=') >>
|
|
288
293
|
(
|
|
289
|
-
str('"') >> match['^"'].repeat(0) >> str('"') |
|
|
290
|
-
str("'") >> match["^'"].repeat(0) >> str("'") |
|
|
294
|
+
(str('"') >> match['^"'].repeat(0) >> str('"')) |
|
|
295
|
+
(str("'") >> match["^'"].repeat(0) >> str("'")) |
|
|
291
296
|
match['^\\s\\}'].repeat(1)
|
|
292
297
|
)
|
|
293
298
|
end
|
|
@@ -322,8 +327,8 @@ module Coradoc
|
|
|
322
327
|
rule(:extension_option) do
|
|
323
328
|
match['\\w\\-'].repeat(1) >> str('=') >>
|
|
324
329
|
(
|
|
325
|
-
str('"') >> match['^"'].repeat(0) >> str('"') |
|
|
326
|
-
str("'") >> match["^'"].repeat(0) >> str("'") |
|
|
330
|
+
(str('"') >> match['^"'].repeat(0) >> str('"')) |
|
|
331
|
+
(str("'") >> match["^'"].repeat(0) >> str("'")) |
|
|
327
332
|
match['^\\s/\\}'].repeat(1)
|
|
328
333
|
)
|
|
329
334
|
end
|
|
@@ -466,11 +471,11 @@ module Coradoc
|
|
|
466
471
|
# List item continuation line (indented content that's not a block)
|
|
467
472
|
# Excludes lines that look like nested list markers
|
|
468
473
|
rule(:list_continuation_line) do
|
|
469
|
-
(str(' ') | str("\t")) >>
|
|
474
|
+
((str(' ') | str("\t")) >>
|
|
470
475
|
nested_list_marker.absent? >>
|
|
471
|
-
line_verbatim |
|
|
472
|
-
nested_list_marker.absent? >>
|
|
473
|
-
line_verbatim
|
|
476
|
+
line_verbatim) |
|
|
477
|
+
(nested_list_marker.absent? >>
|
|
478
|
+
line_verbatim)
|
|
474
479
|
end
|
|
475
480
|
|
|
476
481
|
# Nested list marker detection (for 4-space indented lists)
|
|
@@ -513,8 +518,8 @@ module Coradoc
|
|
|
513
518
|
|
|
514
519
|
# Nested block (indented list, etc.)
|
|
515
520
|
rule(:nested_block) do
|
|
516
|
-
(str(' ') | str("\t")) >> nested_unordered_list |
|
|
517
|
-
(str(' ') | str("\t")) >> nested_ordered_list
|
|
521
|
+
((str(' ') | str("\t")) >> nested_unordered_list) |
|
|
522
|
+
((str(' ') | str("\t")) >> nested_ordered_list)
|
|
518
523
|
end
|
|
519
524
|
|
|
520
525
|
# Nested unordered list (4-space indented)
|
|
@@ -727,7 +732,7 @@ module Coradoc
|
|
|
727
732
|
def self.parse(filename)
|
|
728
733
|
content = File.read(filename)
|
|
729
734
|
new.parse(content)
|
|
730
|
-
rescue
|
|
735
|
+
rescue Parsanol::ParseFailed => e
|
|
731
736
|
puts e.parse_failure_cause.ascii_tree
|
|
732
737
|
end
|
|
733
738
|
|
|
@@ -735,7 +740,7 @@ module Coradoc
|
|
|
735
740
|
def self.parse_with_processing(content)
|
|
736
741
|
ast = new.parse(content)
|
|
737
742
|
AstProcessor.process(ast)
|
|
738
|
-
rescue
|
|
743
|
+
rescue Parsanol::ParseFailed => e
|
|
739
744
|
puts e.parse_failure_cause.ascii_tree
|
|
740
745
|
nil
|
|
741
746
|
end
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
module Coradoc
|
|
4
4
|
module Markdown
|
|
5
5
|
module Parser
|
|
6
|
-
|
|
6
|
+
# Load-time DSL side effect (sanctioned require_relative exception)
|
|
7
|
+
require_relative 'parsanol_atoms'
|
|
8
|
+
|
|
7
9
|
autoload :HtmlEntities, "#{__dir__}/html_entities"
|
|
8
10
|
|
|
9
|
-
class InlineParser <
|
|
10
|
-
using ParsletExtras
|
|
11
|
+
class InlineParser < Parsanol::Parser
|
|
11
12
|
|
|
12
13
|
rule(:line_ending) { (str("\n") | str("\r\n") | str("\r")).ignore }
|
|
13
14
|
rule(:line_ending_or_eof) { line_ending | any.absent? }
|
|
@@ -266,7 +267,7 @@ module Coradoc
|
|
|
266
267
|
end
|
|
267
268
|
|
|
268
269
|
def parse(io, options = {})
|
|
269
|
-
process_emphasis(super
|
|
270
|
+
process_emphasis(super)
|
|
270
271
|
end
|
|
271
272
|
end
|
|
272
273
|
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'parsanol'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module Markdown
|
|
7
|
+
module Parser
|
|
8
|
+
# Custom grammar atoms and DSL surface for the Markdown parser,
|
|
9
|
+
# built on Parsanol::Atoms::Custom — the sanctioned extension
|
|
10
|
+
# point. Replaces the former parslet_extras module, which
|
|
11
|
+
# reached into Parslet internals via refinements.
|
|
12
|
+
module ParsanolAtoms
|
|
13
|
+
# Matches +parslet+ but yields a fixed +value+.
|
|
14
|
+
class Output < Parsanol::Atoms::Custom
|
|
15
|
+
attr_reader :parslet, :value
|
|
16
|
+
|
|
17
|
+
def initialize(parslet, value)
|
|
18
|
+
@parslet = parslet
|
|
19
|
+
@value = value
|
|
20
|
+
super()
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def try_match(source, context, consume_all)
|
|
24
|
+
success, = parslet.apply(source, context, consume_all)
|
|
25
|
+
[success, success ? value : nil]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Matches +parslet+ and yields the callable applied to the
|
|
30
|
+
# flattened match value.
|
|
31
|
+
class DynamicOutput < Parsanol::Atoms::Custom
|
|
32
|
+
include Parsanol::Atoms::CanFlatten
|
|
33
|
+
|
|
34
|
+
attr_reader :parslet, :callable
|
|
35
|
+
|
|
36
|
+
def initialize(parslet, callable)
|
|
37
|
+
@parslet = parslet
|
|
38
|
+
@callable = callable
|
|
39
|
+
super()
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def try_match(source, context, consume_all)
|
|
43
|
+
success, value = parslet.apply(source, context, consume_all)
|
|
44
|
+
return [false, nil] unless success
|
|
45
|
+
|
|
46
|
+
[true, callable.call(flatten(value))]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Positive/negative lookbehind: succeeds (zero-width) when the
|
|
51
|
+
# preceding +number+ characters are/are not matched by
|
|
52
|
+
# +bound_parslet+. Character-accurate for multibyte input —
|
|
53
|
+
# rewinds over UTF-8 continuation bytes.
|
|
54
|
+
class Lookbehind < Parsanol::Atoms::Custom
|
|
55
|
+
attr_reader :positive, :number, :bound_parslet
|
|
56
|
+
|
|
57
|
+
def initialize(bound_parslet, number, positive: true)
|
|
58
|
+
@positive = positive
|
|
59
|
+
@number = number
|
|
60
|
+
@bound_parslet = bound_parslet
|
|
61
|
+
super()
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def error_msgs
|
|
65
|
+
@error_msgs ||= {
|
|
66
|
+
positive: ['Input should be preceded by ', bound_parslet],
|
|
67
|
+
negative: ['Input should not be preceded by ', bound_parslet]
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def try_match(source, context, _consume_all)
|
|
72
|
+
rewind_pos = source.bytepos
|
|
73
|
+
if rewind_pos.zero?
|
|
74
|
+
return [true, nil] unless positive
|
|
75
|
+
|
|
76
|
+
return context.err_at(self, source, error_msgs[:positive], source.pos)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
source.bytepos = rewind_chars(source, number)
|
|
80
|
+
error_pos = source.pos
|
|
81
|
+
success, = bound_parslet.apply(source, context, false)
|
|
82
|
+
|
|
83
|
+
if positive
|
|
84
|
+
return [true, nil] if success
|
|
85
|
+
|
|
86
|
+
context.err_at(self, source, error_msgs[:positive], error_pos)
|
|
87
|
+
elsif success
|
|
88
|
+
context.err_at(self, source, error_msgs[:negative], error_pos)
|
|
89
|
+
else
|
|
90
|
+
[true, nil]
|
|
91
|
+
end
|
|
92
|
+
ensure
|
|
93
|
+
source.bytepos = rewind_pos
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def rewind_chars(source, nchars)
|
|
99
|
+
input = source.input
|
|
100
|
+
pos = source.bytepos
|
|
101
|
+
nchars.times do
|
|
102
|
+
pos -= 1
|
|
103
|
+
pos -= 1 while pos.positive? && (input.getbyte(pos) & 0xC0) == 0x80
|
|
104
|
+
end
|
|
105
|
+
[pos, 0].max
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Additive DSL surface (no overrides): exposes the custom atoms on
|
|
114
|
+
# every atom the way the former parslet_extras refinements did.
|
|
115
|
+
module Parsanol
|
|
116
|
+
module Atoms
|
|
117
|
+
module DSL
|
|
118
|
+
def output(value)
|
|
119
|
+
Coradoc::Markdown::Parser::ParsanolAtoms::Output.new(self, value)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def dynamic_output(callable)
|
|
123
|
+
Coradoc::Markdown::Parser::ParsanolAtoms::DynamicOutput.new(self, callable)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def precedes?(num = 1)
|
|
127
|
+
Coradoc::Markdown::Parser::ParsanolAtoms::Lookbehind.new(self, num, positive: true)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def does_not_precede?(num = 1)
|
|
131
|
+
Coradoc::Markdown::Parser::ParsanolAtoms::Lookbehind.new(self, num, positive: false)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -71,11 +71,11 @@ module Coradoc
|
|
|
71
71
|
def self.extract_quoted_value(scanner, handle_escapes: false)
|
|
72
72
|
if scanner.scan(/"([^"\\]*(?:\\.[^"\\]*)*)"/)
|
|
73
73
|
value = scanner[1]
|
|
74
|
-
value = value.gsub(
|
|
74
|
+
value = value.gsub('\"', '"') if handle_escapes
|
|
75
75
|
value
|
|
76
76
|
elsif scanner.scan(/'([^'\\]*(?:\\.[^'\\]*)*)'/)
|
|
77
77
|
value = scanner[1]
|
|
78
|
-
value = value.gsub(
|
|
78
|
+
value = value.gsub('\\\'', "'") if handle_escapes
|
|
79
79
|
value
|
|
80
80
|
elsif scanner.scan(/(\S+)/)
|
|
81
81
|
scanner[1]
|
|
@@ -48,7 +48,7 @@ module Coradoc
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def with(overrides)
|
|
51
|
-
self.class.new(**to_h
|
|
51
|
+
self.class.new(**to_h, **symbolize(overrides))
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
private
|
|
@@ -58,15 +58,9 @@ module Coradoc
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def validate_options!(resolved)
|
|
61
|
-
unless %i[github container html gfm_alert].include?(resolved.fetch(:admonition_style))
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
unless %i[html flatten].include?(resolved.fetch(:definition_list_nested))
|
|
65
|
-
raise ArgumentError, "Unknown definition_list_nested: #{resolved[:definition_list_nested].inspect}"
|
|
66
|
-
end
|
|
67
|
-
unless [true, false].include?(resolved.fetch(:suppress_comments))
|
|
68
|
-
raise ArgumentError, 'suppress_comments must be boolean'
|
|
69
|
-
end
|
|
61
|
+
raise ArgumentError, "Unknown admonition_style: #{resolved[:admonition_style].inspect}" unless %i[github container html gfm_alert].include?(resolved.fetch(:admonition_style))
|
|
62
|
+
raise ArgumentError, "Unknown definition_list_nested: #{resolved[:definition_list_nested].inspect}" unless %i[html flatten].include?(resolved.fetch(:definition_list_nested))
|
|
63
|
+
raise ArgumentError, 'suppress_comments must be boolean' unless [true, false].include?(resolved.fetch(:suppress_comments))
|
|
70
64
|
return if [true, false].include?(resolved.fetch(:autolinks))
|
|
71
65
|
|
|
72
66
|
raise ArgumentError, 'autolinks must be boolean'
|
|
@@ -55,6 +55,8 @@ module Coradoc
|
|
|
55
55
|
Coradoc::Markdown::Text.new(content: model.title.to_s)
|
|
56
56
|
when Coradoc::CoreModel::CommentLine
|
|
57
57
|
Coradoc::Markdown::Comment.new(text: model.text.to_s)
|
|
58
|
+
when Coradoc::CoreModel::Include
|
|
59
|
+
transform_include(model)
|
|
58
60
|
when Coradoc::CoreModel::TextContent
|
|
59
61
|
model.text.to_s
|
|
60
62
|
when Array
|
|
@@ -66,6 +68,17 @@ module Coradoc
|
|
|
66
68
|
|
|
67
69
|
private
|
|
68
70
|
|
|
71
|
+
# Markdown has no transclusion — an unresolved include edge is
|
|
72
|
+
# preserved as an HTML comment so no information is lost and
|
|
73
|
+
# serialization never crashes on graph-mode documents.
|
|
74
|
+
def transform_include(element)
|
|
75
|
+
Coradoc::Markdown::Comment.new(text: include_directive_text(element))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def include_directive_text(element)
|
|
79
|
+
"include::#{element.target}[#{element.raw_options}]"
|
|
80
|
+
end
|
|
81
|
+
|
|
69
82
|
def transform_structural_element(element)
|
|
70
83
|
case element
|
|
71
84
|
when CoreModel::DocumentElement
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'parsanol'
|
|
4
4
|
|
|
5
5
|
module Coradoc
|
|
6
6
|
module Markdown
|
|
7
7
|
autoload :ParserUtil, "#{__dir__}/parser_util"
|
|
8
8
|
|
|
9
|
-
# Transformer converts
|
|
9
|
+
# Transformer converts Parsanol AST into Markdown Document Model objects.
|
|
10
10
|
#
|
|
11
11
|
# This transformer takes the raw output from the BlockParser/InlineParser
|
|
12
12
|
# and converts it into semantic model objects (Heading, Paragraph, etc.)
|
|
13
13
|
#
|
|
14
|
-
class Transformer <
|
|
14
|
+
class Transformer < Parsanol::Transform
|
|
15
15
|
# ATX Heading: # Heading
|
|
16
16
|
rule(heading: simple(:heading), text: simple(:text)) do
|
|
17
17
|
Heading.new(level: heading.to_s.length, text: text.to_s.strip)
|
|
@@ -198,7 +198,7 @@ module Coradoc
|
|
|
198
198
|
when Array
|
|
199
199
|
# Transform each item
|
|
200
200
|
element.map { |e| transform_element(e) }.compact
|
|
201
|
-
when
|
|
201
|
+
when Parsanol::Slice
|
|
202
202
|
Text.new(content: element.to_s)
|
|
203
203
|
else
|
|
204
204
|
Text.new(content: element.to_s)
|
|
@@ -536,7 +536,7 @@ module Coradoc
|
|
|
536
536
|
else
|
|
537
537
|
content.values.map { |v| extract_text(v) }.join
|
|
538
538
|
end
|
|
539
|
-
when
|
|
539
|
+
when Parsanol::Slice
|
|
540
540
|
content.to_s
|
|
541
541
|
else
|
|
542
542
|
content.to_s
|
data/lib/coradoc/markdown.rb
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# This gem provides Markdown support for the Coradoc document processing library.
|
|
6
6
|
# It includes:
|
|
7
7
|
# - Markdown Document Model (Coradoc::Markdown::*)
|
|
8
|
-
# - Markdown Parser (CommonMark-compliant,
|
|
8
|
+
# - Markdown Parser (CommonMark-compliant, Parsanol-based)
|
|
9
9
|
# - Markdown Serializer (round-trip capable)
|
|
10
10
|
# - Kramdown extensions support (IAL, ALD, math, TOC)
|
|
11
11
|
#
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
# # Serialize back to Markdown
|
|
19
19
|
# output = Coradoc::Markdown.serialize(document)
|
|
20
20
|
|
|
21
|
-
require '
|
|
21
|
+
require 'parsanol'
|
|
22
|
+
require 'parsanol/convenience'
|
|
22
23
|
require 'lutaml/model'
|
|
23
24
|
|
|
24
25
|
# Coradoc document transformation library.
|
|
@@ -91,7 +92,7 @@ module Coradoc
|
|
|
91
92
|
module Parser
|
|
92
93
|
autoload :BlockParser, 'coradoc/markdown/parser/block_parser'
|
|
93
94
|
autoload :InlineParser, 'coradoc/markdown/parser/inline_parser'
|
|
94
|
-
autoload :
|
|
95
|
+
autoload :ParsanolAtoms, 'coradoc/markdown/parser/parsanol_atoms'
|
|
95
96
|
autoload :HTML_ENTITIES, 'coradoc/markdown/parser/html_entities'
|
|
96
97
|
autoload :AstProcessor, 'coradoc/markdown/parser/ast_processor'
|
|
97
98
|
autoload :FrontmatterParser, 'coradoc/markdown/parser/frontmatter_parser'
|
|
@@ -127,9 +128,9 @@ module Coradoc
|
|
|
127
128
|
# @param filename [String] Path to the Markdown file
|
|
128
129
|
# @param options [Hash] Parsing options (see #parse)
|
|
129
130
|
# @return [Array] The parsed AST
|
|
130
|
-
def from_file(filename, **
|
|
131
|
+
def from_file(filename, **)
|
|
131
132
|
content = File.read(filename)
|
|
132
|
-
parse(content, **
|
|
133
|
+
parse(content, **)
|
|
133
134
|
end
|
|
134
135
|
|
|
135
136
|
# Parse inline Markdown content
|
|
@@ -150,6 +151,13 @@ module Coradoc
|
|
|
150
151
|
Serializer.serialize(document, options)
|
|
151
152
|
end
|
|
152
153
|
|
|
154
|
+
# Markdown preserves unresolved include edges as HTML comments
|
|
155
|
+
# (Transform::FromCoreModel#transform_include) — lossy formatting,
|
|
156
|
+
# but never silent content loss.
|
|
157
|
+
def preserves_unresolved_includes?
|
|
158
|
+
true
|
|
159
|
+
end
|
|
160
|
+
|
|
153
161
|
# Check if this format can transform the given model to CoreModel
|
|
154
162
|
#
|
|
155
163
|
# @param model [Object] The model to check
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coradoc-markdown
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -38,7 +38,7 @@ dependencies:
|
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: 0.8.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
41
|
+
name: parsanol
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
@@ -94,7 +94,7 @@ dependencies:
|
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '0'
|
|
96
96
|
description: Provides Markdown parsing and serialization capabilities for Coradoc.
|
|
97
|
-
Includes the Markdown Document Model, a CommonMark-compliant
|
|
97
|
+
Includes the Markdown Document Model, a CommonMark-compliant Parsanol-based parser,
|
|
98
98
|
and round-trip capable serializer.
|
|
99
99
|
email:
|
|
100
100
|
- open.source@ribose.com
|
|
@@ -152,7 +152,7 @@ files:
|
|
|
152
152
|
- lib/coradoc/markdown/parser/frontmatter_parser.rb
|
|
153
153
|
- lib/coradoc/markdown/parser/html_entities.rb
|
|
154
154
|
- lib/coradoc/markdown/parser/inline_parser.rb
|
|
155
|
-
- lib/coradoc/markdown/parser/
|
|
155
|
+
- lib/coradoc/markdown/parser/parsanol_atoms.rb
|
|
156
156
|
- lib/coradoc/markdown/parser_util.rb
|
|
157
157
|
- lib/coradoc/markdown/serializer.rb
|
|
158
158
|
- lib/coradoc/markdown/serializer/builder.rb
|
|
@@ -232,6 +232,7 @@ metadata:
|
|
|
232
232
|
homepage_uri: https://github.com/metanorma/coradoc
|
|
233
233
|
source_code_uri: https://github.com/metanorma/coradoc
|
|
234
234
|
changelog_uri: https://github.com/metanorma/coradoc/releases
|
|
235
|
+
rubygems_mfa_required: 'true'
|
|
235
236
|
rdoc_options: []
|
|
236
237
|
require_paths:
|
|
237
238
|
- lib
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'parslet'
|
|
4
|
-
require 'parslet/convenience'
|
|
5
|
-
|
|
6
|
-
module Coradoc
|
|
7
|
-
module Markdown
|
|
8
|
-
module Parser
|
|
9
|
-
module ParsletExtras
|
|
10
|
-
refine Parslet::Source do
|
|
11
|
-
def rewind(nchars)
|
|
12
|
-
# https://github.com/ruby/strscan/issues/122
|
|
13
|
-
self.charpos = @str.charpos - nchars
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def charpos=(pos)
|
|
17
|
-
@str.reset
|
|
18
|
-
@str.getch while @str.charpos < pos
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def charpos
|
|
22
|
-
@str.charpos
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def peek_byte
|
|
26
|
-
@str.peek(1)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
refine Parslet::Scope do
|
|
31
|
-
attr_reader :current
|
|
32
|
-
|
|
33
|
-
def key?(...)
|
|
34
|
-
@current.key?(...)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def has_key?(...)
|
|
38
|
-
@current.key?(...)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def root
|
|
42
|
-
scope = current
|
|
43
|
-
scope = scope.parent while scope.parent
|
|
44
|
-
scope
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
refine Parslet::Scope::Binding do
|
|
49
|
-
def key?(...)
|
|
50
|
-
@hash.key?(...)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def has_key?(...)
|
|
54
|
-
@hash.key?(...)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def initialize_copy(original)
|
|
58
|
-
super
|
|
59
|
-
@hash = @hash.clone
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# like Named but returning other things
|
|
64
|
-
class Output < Parslet::Atoms::Base
|
|
65
|
-
attr_reader :parslet, :value
|
|
66
|
-
|
|
67
|
-
def initialize(parslet, value)
|
|
68
|
-
super()
|
|
69
|
-
|
|
70
|
-
@parslet = parslet
|
|
71
|
-
@value = value
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def apply(source, context, consume_all)
|
|
75
|
-
success, = result = parslet.apply(source, context, consume_all)
|
|
76
|
-
|
|
77
|
-
return result unless success
|
|
78
|
-
|
|
79
|
-
succ(@value)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def to_s_inner(prec)
|
|
83
|
-
"#{value}:#{parslet.to_s(prec)}"
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
class DynamicOutput < Parslet::Atoms::Base
|
|
88
|
-
attr_reader :parslet, :callable
|
|
89
|
-
|
|
90
|
-
def initialize(parslet, callable)
|
|
91
|
-
super()
|
|
92
|
-
|
|
93
|
-
@parslet = parslet
|
|
94
|
-
@callable = callable
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def apply(source, context, consume_all)
|
|
98
|
-
success, value = result = parslet.apply(source, context, consume_all)
|
|
99
|
-
|
|
100
|
-
return result unless success
|
|
101
|
-
|
|
102
|
-
succ(@callable.call(flatten(value)))
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def to_s_inner(prec)
|
|
106
|
-
"#{callable}:#{parslet.to_s(prec)}"
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
class Lookbehind < Parslet::Atoms::Base
|
|
111
|
-
using ParsletExtras
|
|
112
|
-
attr_reader :positive
|
|
113
|
-
attr_reader :number, :bound_parslet
|
|
114
|
-
|
|
115
|
-
def initialize(bound_parslet, number, positive: true)
|
|
116
|
-
super()
|
|
117
|
-
|
|
118
|
-
# Model positive and negative lookbehind by testing this flag.
|
|
119
|
-
@positive = positive
|
|
120
|
-
@number = number
|
|
121
|
-
@bound_parslet = bound_parslet
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def error_msgs
|
|
125
|
-
@error_msgs ||= {
|
|
126
|
-
positive: ['Input should be preceded by ', bound_parslet],
|
|
127
|
-
negative: ['Input should not be preceded by ', bound_parslet]
|
|
128
|
-
}
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def try(source, context, consume_all)
|
|
132
|
-
rewind_pos = source.bytepos
|
|
133
|
-
if source.bytepos.zero?
|
|
134
|
-
return succ(nil) unless positive
|
|
135
|
-
|
|
136
|
-
return context.err_at(self, source, error_msgs[:positive], source.pos)
|
|
137
|
-
end
|
|
138
|
-
source.rewind(number)
|
|
139
|
-
error_pos = source.pos
|
|
140
|
-
|
|
141
|
-
success, = bound_parslet.apply(source, context, consume_all)
|
|
142
|
-
|
|
143
|
-
if positive
|
|
144
|
-
return succ(nil) if success
|
|
145
|
-
|
|
146
|
-
context.err_at(self, source, error_msgs[:positive], error_pos)
|
|
147
|
-
else
|
|
148
|
-
return succ(nil) unless success
|
|
149
|
-
|
|
150
|
-
context.err_at(self, source, error_msgs[:negative], error_pos)
|
|
151
|
-
end
|
|
152
|
-
ensure
|
|
153
|
-
source.bytepos = rewind_pos
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def to_s_inner(prec)
|
|
157
|
-
@char = positive ? '&' : '!'
|
|
158
|
-
"<#{@char}<#{number}<#{bound_parslet.to_s(prec)}"
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Like Dynamic but does not return a further parslet, just a reject/accept boolean
|
|
163
|
-
module ::Parslet
|
|
164
|
-
module Atoms
|
|
165
|
-
class Check < ::Parslet::Atoms::Base
|
|
166
|
-
attr_reader :block
|
|
167
|
-
|
|
168
|
-
def initialize(block)
|
|
169
|
-
super()
|
|
170
|
-
@block = block
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def cached?
|
|
174
|
-
false
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def try(source, context, _consume_all)
|
|
178
|
-
[block.call(source, context), nil]
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def to_s_inner(_prec)
|
|
182
|
-
'check { ... }'
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
refine ::Parslet do
|
|
189
|
-
def check(&block)
|
|
190
|
-
::Parslet::Atoms::Check.new(block)
|
|
191
|
-
end
|
|
192
|
-
module_function :check
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
refine ::Parslet::Atoms::DSL do
|
|
196
|
-
def output(value)
|
|
197
|
-
Output.new(self, value)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def dynamic_output(value)
|
|
201
|
-
DynamicOutput.new(self, value)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def precedes?(num = 1)
|
|
205
|
-
Lookbehind.new(self, num, positive: true)
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def does_not_precede?(num = 1)
|
|
209
|
-
Lookbehind.new(self, num, positive: false)
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
end
|