red_quilt 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +109 -0
- data/.rubocop_todo.yml +7 -0
- data/CHANGELOG.md +57 -0
- data/README.md +284 -0
- data/Rakefile +8 -0
- data/ast-spec.md +1227 -0
- data/docs/architecture.md +81 -0
- data/docs/arena-usage.md +363 -0
- data/docs/commonmark-conformance.md +241 -0
- data/exe/redquilt +7 -0
- data/lib/red_quilt/arena.rb +366 -0
- data/lib/red_quilt/block_parser.rb +724 -0
- data/lib/red_quilt/blockquote.rb +151 -0
- data/lib/red_quilt/cli.rb +182 -0
- data/lib/red_quilt/diagnostic.rb +47 -0
- data/lib/red_quilt/document.rb +126 -0
- data/lib/red_quilt/extended_autolink_pass.rb +185 -0
- data/lib/red_quilt/footnote_definition.rb +147 -0
- data/lib/red_quilt/footnote_pass.rb +39 -0
- data/lib/red_quilt/footnote_registry.rb +68 -0
- data/lib/red_quilt/indentation.rb +73 -0
- data/lib/red_quilt/inline/builder.rb +674 -0
- data/lib/red_quilt/inline/flanking.rb +120 -0
- data/lib/red_quilt/inline/html_entities.rb +2180 -0
- data/lib/red_quilt/inline/lexer.rb +280 -0
- data/lib/red_quilt/inline/link_scanner.rb +315 -0
- data/lib/red_quilt/inline/token_kind.rb +39 -0
- data/lib/red_quilt/inline/tokens.rb +73 -0
- data/lib/red_quilt/inline.rb +34 -0
- data/lib/red_quilt/inline_pass.rb +53 -0
- data/lib/red_quilt/line.rb +14 -0
- data/lib/red_quilt/lint_pass.rb +71 -0
- data/lib/red_quilt/list.rb +317 -0
- data/lib/red_quilt/node_ref.rb +114 -0
- data/lib/red_quilt/node_type.rb +66 -0
- data/lib/red_quilt/plain_text.rb +46 -0
- data/lib/red_quilt/reference_definition.rb +309 -0
- data/lib/red_quilt/renderer/html.rb +279 -0
- data/lib/red_quilt/renderer/mdast.rb +152 -0
- data/lib/red_quilt/source_map.rb +29 -0
- data/lib/red_quilt/source_span.rb +26 -0
- data/lib/red_quilt/theme.rb +28 -0
- data/lib/red_quilt/themes/default.css +87 -0
- data/lib/red_quilt/version.rb +5 -0
- data/lib/red_quilt.rb +86 -0
- data/mise.toml +2 -0
- data/sig/red_quilt.rbs +45 -0
- metadata +91 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RedQuilt
|
|
4
|
+
module Renderer
|
|
5
|
+
# Builds an MDAST-compatible Hash from the document arena.
|
|
6
|
+
#
|
|
7
|
+
# MDAST (https://github.com/syntax-tree/mdast) is the unified.js AST
|
|
8
|
+
# format for Markdown; emitting it lets external tooling (linters,
|
|
9
|
+
# editor plugins) consume red_quilt output without bespoke adapters.
|
|
10
|
+
class Mdast
|
|
11
|
+
MDAST_TYPE_NAMES = {
|
|
12
|
+
document: "root",
|
|
13
|
+
paragraph: "paragraph",
|
|
14
|
+
heading: "heading",
|
|
15
|
+
thematic_break: "thematicBreak",
|
|
16
|
+
blockquote: "blockquote",
|
|
17
|
+
list: "list",
|
|
18
|
+
list_item: "listItem",
|
|
19
|
+
code_block: "code",
|
|
20
|
+
html_block: "html",
|
|
21
|
+
table: "table",
|
|
22
|
+
table_row: "tableRow",
|
|
23
|
+
table_cell: "tableCell",
|
|
24
|
+
text: "text",
|
|
25
|
+
softbreak: "break",
|
|
26
|
+
hardbreak: "break",
|
|
27
|
+
emphasis: "emphasis",
|
|
28
|
+
strong: "strong",
|
|
29
|
+
code_span: "inlineCode",
|
|
30
|
+
link: "link",
|
|
31
|
+
image: "image",
|
|
32
|
+
html_inline: "html",
|
|
33
|
+
strikethrough: "delete",
|
|
34
|
+
footnote_reference: "footnoteReference",
|
|
35
|
+
footnote_definition: "footnoteDefinition",
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
def initialize(document)
|
|
39
|
+
@document = document
|
|
40
|
+
@arena = document.arena
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render
|
|
44
|
+
node(@document.root_id)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def node(node_id, parent_spread: false)
|
|
50
|
+
type_int = @arena.type(node_id)
|
|
51
|
+
type_sym = NodeType.name_for(type_int)
|
|
52
|
+
|
|
53
|
+
result = { "type" => mdast_type_name(type_sym) }
|
|
54
|
+
|
|
55
|
+
span = @arena.source_span(node_id)
|
|
56
|
+
result["position"] = position(span) if span
|
|
57
|
+
|
|
58
|
+
case type_int
|
|
59
|
+
when NodeType::HEADING
|
|
60
|
+
result["depth"] = @arena.int1(node_id)
|
|
61
|
+
result["children"] = children(node_id)
|
|
62
|
+
when NodeType::LIST
|
|
63
|
+
result["ordered"] = @arena.int1(node_id) == 1
|
|
64
|
+
tight = @arena.int3(node_id) == 1
|
|
65
|
+
result["start"] = @arena.int2(node_id) if result["ordered"]
|
|
66
|
+
result["spread"] = !tight
|
|
67
|
+
result["children"] = children(node_id, parent_spread: !tight)
|
|
68
|
+
when NodeType::LIST_ITEM
|
|
69
|
+
result["spread"] = parent_spread
|
|
70
|
+
result["children"] = children(node_id)
|
|
71
|
+
when NodeType::CODE_BLOCK
|
|
72
|
+
info = @arena.str2(node_id)
|
|
73
|
+
lang = info && !info.empty? ? info.split.first : nil
|
|
74
|
+
result["lang"] = lang
|
|
75
|
+
result["value"] = @arena.text(node_id).to_s
|
|
76
|
+
when NodeType::TEXT
|
|
77
|
+
result["value"] = @arena.text(node_id).to_s
|
|
78
|
+
when NodeType::SOFTBREAK
|
|
79
|
+
result["type"] = "text"
|
|
80
|
+
result["value"] = "\n"
|
|
81
|
+
when NodeType::CODE_SPAN
|
|
82
|
+
result["value"] = @arena.text(node_id).to_s
|
|
83
|
+
when NodeType::LINK
|
|
84
|
+
result["url"] = @arena.str1(node_id).to_s
|
|
85
|
+
title = @arena.str2(node_id)
|
|
86
|
+
result["title"] = title && !title.empty? ? title : nil
|
|
87
|
+
result["children"] = children(node_id)
|
|
88
|
+
when NodeType::IMAGE
|
|
89
|
+
result["url"] = @arena.str1(node_id).to_s
|
|
90
|
+
title = @arena.str2(node_id)
|
|
91
|
+
result["title"] = title && !title.empty? ? title : nil
|
|
92
|
+
result["alt"] = NodeRef.new(@document, node_id).text.to_s
|
|
93
|
+
when NodeType::FOOTNOTE_REFERENCE
|
|
94
|
+
label = @arena.str1(node_id).to_s
|
|
95
|
+
result["identifier"] = label
|
|
96
|
+
result["label"] = label
|
|
97
|
+
when NodeType::FOOTNOTE_DEFINITION
|
|
98
|
+
label = @arena.str1(node_id).to_s
|
|
99
|
+
result["identifier"] = label
|
|
100
|
+
result["label"] = label
|
|
101
|
+
result["children"] = children(node_id)
|
|
102
|
+
when NodeType::HTML_BLOCK, NodeType::HTML_INLINE
|
|
103
|
+
result["value"] = NodeRef.new(@document, node_id).text.to_s
|
|
104
|
+
when NodeType::TABLE
|
|
105
|
+
result["align"] = []
|
|
106
|
+
result["children"] = children(node_id)
|
|
107
|
+
when NodeType::DOCUMENT, NodeType::PARAGRAPH, NodeType::BLOCKQUOTE,
|
|
108
|
+
NodeType::TABLE_ROW, NodeType::TABLE_CELL,
|
|
109
|
+
NodeType::EMPHASIS, NodeType::STRONG, NodeType::STRIKETHROUGH
|
|
110
|
+
result["children"] = children(node_id)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
result
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def children(node_id, parent_spread: false)
|
|
117
|
+
result = []
|
|
118
|
+
@arena.child_ids(node_id).each do |child_id|
|
|
119
|
+
# mdast has no footnotes-section wrapper: footnote definitions are
|
|
120
|
+
# plain root-level nodes, so splice the section's children in.
|
|
121
|
+
if @arena.type(child_id) == NodeType::FOOTNOTES_SECTION
|
|
122
|
+
@arena.child_ids(child_id).each { |def_id| result << node(def_id, parent_spread: parent_spread) }
|
|
123
|
+
else
|
|
124
|
+
result << node(child_id, parent_spread: parent_spread)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
result
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def position(span)
|
|
131
|
+
start_loc = @document.source_map.line_column(span.start_byte)
|
|
132
|
+
end_loc = @document.source_map.line_column(span.end_byte)
|
|
133
|
+
{
|
|
134
|
+
"start" => {
|
|
135
|
+
"line" => start_loc[:line],
|
|
136
|
+
"column" => start_loc[:column],
|
|
137
|
+
"offset" => span.start_byte,
|
|
138
|
+
},
|
|
139
|
+
"end" => {
|
|
140
|
+
"line" => end_loc[:line],
|
|
141
|
+
"column" => end_loc[:column],
|
|
142
|
+
"offset" => span.end_byte,
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def mdast_type_name(type_sym)
|
|
148
|
+
MDAST_TYPE_NAMES.fetch(type_sym, type_sym.to_s)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RedQuilt
|
|
4
|
+
class SourceMap
|
|
5
|
+
def initialize(source)
|
|
6
|
+
@source = source
|
|
7
|
+
@line_starts = build_line_starts(source)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def line_column(byte_offset)
|
|
11
|
+
line = (@line_starts.bsearch_index { |s| s > byte_offset } || @line_starts.length) - 1
|
|
12
|
+
line_start = @line_starts[line]
|
|
13
|
+
column = @source.byteslice(line_start, byte_offset - line_start).to_s.length
|
|
14
|
+
{ line: line + 1, column: column }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def build_line_starts(source)
|
|
20
|
+
starts = [0]
|
|
21
|
+
pos = 0
|
|
22
|
+
while (idx = source.index("\n", pos))
|
|
23
|
+
starts << (idx + 1)
|
|
24
|
+
pos = idx + 1
|
|
25
|
+
end
|
|
26
|
+
starts
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RedQuilt
|
|
4
|
+
class SourceSpan
|
|
5
|
+
attr_reader :start_byte, :end_byte
|
|
6
|
+
|
|
7
|
+
def initialize(start_byte, end_byte)
|
|
8
|
+
@start_byte = start_byte
|
|
9
|
+
@end_byte = end_byte
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def length
|
|
13
|
+
@end_byte - @start_byte
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ==(other)
|
|
17
|
+
other.is_a?(SourceSpan) &&
|
|
18
|
+
other.start_byte == @start_byte &&
|
|
19
|
+
other.end_byte == @end_byte
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
{ start_byte: @start_byte, end_byte: @end_byte }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RedQuilt
|
|
4
|
+
# Optional bundled stylesheets for standalone HTML output. `:none` (the
|
|
5
|
+
# default) embeds no CSS and leaves the bare document untouched; named
|
|
6
|
+
# themes load a stylesheet shipped under lib/red_quilt/themes/.
|
|
7
|
+
module Theme
|
|
8
|
+
DIR = File.expand_path("themes", __dir__)
|
|
9
|
+
private_constant :DIR
|
|
10
|
+
|
|
11
|
+
# Theme names that embed a bundled stylesheet (excludes :none).
|
|
12
|
+
NAMES = %i[default].freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# Returns the CSS for `name`, or nil for :none / nil (no embedded CSS).
|
|
17
|
+
# Raises ArgumentError for an unknown name.
|
|
18
|
+
def css(name)
|
|
19
|
+
name = (name || :none).to_sym
|
|
20
|
+
return nil if name == :none
|
|
21
|
+
unless NAMES.include?(name)
|
|
22
|
+
raise ArgumentError, "unknown theme #{name.inspect} (available: none, #{NAMES.join(', ')})"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
(@cache ||= {})[name] ||= File.read(File.join(DIR, "#{name}.css")).freeze
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* RedQuilt default theme — a compact, readable stylesheet for standalone
|
|
2
|
+
HTML output. Intentionally minimal: no syntax highlighting, no JS. */
|
|
3
|
+
|
|
4
|
+
body {
|
|
5
|
+
max-width: 54rem;
|
|
6
|
+
margin: 0 auto;
|
|
7
|
+
padding: 2rem 1rem 4rem;
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
9
|
+
font-size: 16px;
|
|
10
|
+
line-height: 1.6;
|
|
11
|
+
color: #1f2328;
|
|
12
|
+
background: #ffffff;
|
|
13
|
+
word-wrap: break-word;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
h1, h2, h3, h4, h5, h6 {
|
|
17
|
+
margin: 1.6em 0 0.6em;
|
|
18
|
+
line-height: 1.25;
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
h1 { font-size: 1.9em; }
|
|
23
|
+
h2 { font-size: 1.5em; }
|
|
24
|
+
h3 { font-size: 1.25em; }
|
|
25
|
+
h1, h2 { padding-bottom: 0.3em; border-bottom: 1px solid #d0d7de; }
|
|
26
|
+
|
|
27
|
+
p, ul, ol, blockquote, table, pre { margin: 0 0 1rem; }
|
|
28
|
+
|
|
29
|
+
a { color: #0969da; text-decoration: none; }
|
|
30
|
+
a:hover { text-decoration: underline; }
|
|
31
|
+
|
|
32
|
+
ul, ol { padding-left: 1.6em; }
|
|
33
|
+
li + li { margin-top: 0.2em; }
|
|
34
|
+
|
|
35
|
+
blockquote {
|
|
36
|
+
margin-left: 0;
|
|
37
|
+
padding: 0 1em;
|
|
38
|
+
color: #59636e;
|
|
39
|
+
border-left: 0.25em solid #d0d7de;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
code {
|
|
43
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
44
|
+
font-size: 0.9em;
|
|
45
|
+
background: #eff1f3;
|
|
46
|
+
padding: 0.15em 0.35em;
|
|
47
|
+
border-radius: 4px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pre {
|
|
51
|
+
background: #f6f8fa;
|
|
52
|
+
padding: 1rem;
|
|
53
|
+
border-radius: 6px;
|
|
54
|
+
overflow: auto;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pre code { background: none; padding: 0; font-size: 0.875em; }
|
|
58
|
+
|
|
59
|
+
table { border-collapse: collapse; }
|
|
60
|
+
th, td { padding: 0.4em 0.8em; border: 1px solid #d0d7de; }
|
|
61
|
+
th { background: #f6f8fa; }
|
|
62
|
+
|
|
63
|
+
hr { height: 1px; margin: 2rem 0; border: 0; background: #d0d7de; }
|
|
64
|
+
|
|
65
|
+
img { max-width: 100%; }
|
|
66
|
+
|
|
67
|
+
.footnotes {
|
|
68
|
+
margin-top: 2.5rem;
|
|
69
|
+
padding-top: 1rem;
|
|
70
|
+
font-size: 0.9em;
|
|
71
|
+
color: #59636e;
|
|
72
|
+
border-top: 1px solid #d0d7de;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Follow the OS / browser preference for dark mode. */
|
|
76
|
+
@media (prefers-color-scheme: dark) {
|
|
77
|
+
body { color: #e6edf3; background: #0d1117; }
|
|
78
|
+
a { color: #4493f8; }
|
|
79
|
+
h1, h2 { border-bottom-color: #30363d; }
|
|
80
|
+
blockquote { color: #9198a1; border-left-color: #30363d; }
|
|
81
|
+
code { background: #262c36; }
|
|
82
|
+
pre { background: #161b22; }
|
|
83
|
+
th, td { border-color: #30363d; }
|
|
84
|
+
th { background: #161b22; }
|
|
85
|
+
hr { background: #30363d; }
|
|
86
|
+
.footnotes { color: #9198a1; border-top-color: #30363d; }
|
|
87
|
+
}
|
data/lib/red_quilt.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "red_quilt/version"
|
|
4
|
+
require_relative "red_quilt/node_type"
|
|
5
|
+
require_relative "red_quilt/source_span"
|
|
6
|
+
require_relative "red_quilt/line"
|
|
7
|
+
require_relative "red_quilt/indentation"
|
|
8
|
+
require_relative "red_quilt/source_map"
|
|
9
|
+
require_relative "red_quilt/arena"
|
|
10
|
+
require_relative "red_quilt/node_ref"
|
|
11
|
+
require_relative "red_quilt/diagnostic"
|
|
12
|
+
require_relative "red_quilt/plain_text"
|
|
13
|
+
require_relative "red_quilt/theme"
|
|
14
|
+
require_relative "red_quilt/document"
|
|
15
|
+
require_relative "red_quilt/inline"
|
|
16
|
+
require_relative "red_quilt/inline/html_entities"
|
|
17
|
+
require_relative "red_quilt/reference_definition"
|
|
18
|
+
require_relative "red_quilt/footnote_registry"
|
|
19
|
+
require_relative "red_quilt/footnote_definition"
|
|
20
|
+
require_relative "red_quilt/list"
|
|
21
|
+
require_relative "red_quilt/blockquote"
|
|
22
|
+
require_relative "red_quilt/block_parser"
|
|
23
|
+
require_relative "red_quilt/inline/token_kind"
|
|
24
|
+
require_relative "red_quilt/inline/tokens"
|
|
25
|
+
require_relative "red_quilt/inline/flanking"
|
|
26
|
+
require_relative "red_quilt/inline/lexer"
|
|
27
|
+
require_relative "red_quilt/inline/link_scanner"
|
|
28
|
+
require_relative "red_quilt/inline/builder"
|
|
29
|
+
require_relative "red_quilt/inline_pass"
|
|
30
|
+
require_relative "red_quilt/footnote_pass"
|
|
31
|
+
require_relative "red_quilt/extended_autolink_pass"
|
|
32
|
+
require_relative "red_quilt/lint_pass"
|
|
33
|
+
require_relative "red_quilt/renderer/html"
|
|
34
|
+
require_relative "red_quilt/renderer/mdast"
|
|
35
|
+
|
|
36
|
+
module RedQuilt
|
|
37
|
+
class Error < StandardError; end
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
def parse(source, allow_html: false, disallow_raw_html: false, extended_autolinks: false, footnotes: false, lint: false)
|
|
41
|
+
normalized = normalize_input(source)
|
|
42
|
+
arena = Arena.new(normalized)
|
|
43
|
+
footnote_registry = FootnoteRegistry.new if footnotes
|
|
44
|
+
block_parser = BlockParser.new(arena, footnotes: footnote_registry)
|
|
45
|
+
root_id = block_parser.parse
|
|
46
|
+
document = Document.new(normalized, arena, root_id,
|
|
47
|
+
allow_html: allow_html,
|
|
48
|
+
disallow_raw_html: disallow_raw_html,
|
|
49
|
+
references: block_parser.references,
|
|
50
|
+
footnotes: footnote_registry)
|
|
51
|
+
document.diagnostics.concat(block_parser.diagnostics)
|
|
52
|
+
InlinePass.new(document).apply
|
|
53
|
+
FootnotePass.new(document).apply if footnote_registry
|
|
54
|
+
ExtendedAutolinkPass.new(document).apply if extended_autolinks
|
|
55
|
+
LintPass.new(document).apply if lint
|
|
56
|
+
document
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def render_html(source, allow_html: false, disallow_raw_html: false, extended_autolinks: false, footnotes: false, lint: false)
|
|
60
|
+
parse(source,
|
|
61
|
+
allow_html: allow_html,
|
|
62
|
+
disallow_raw_html: disallow_raw_html,
|
|
63
|
+
extended_autolinks: extended_autolinks,
|
|
64
|
+
footnotes: footnotes,
|
|
65
|
+
lint: lint).to_html
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
NUL_CHAR = 0.chr
|
|
71
|
+
REPLACEMENT_CHAR = 0xFFFD.chr(Encoding::UTF_8)
|
|
72
|
+
private_constant :NUL_CHAR, :REPLACEMENT_CHAR
|
|
73
|
+
|
|
74
|
+
# CommonMark normalization applied before parsing:
|
|
75
|
+
# - line endings: \r\n and lone \r -> \n (spec defines all three as line endings)
|
|
76
|
+
# - NUL (U+0000) -> U+FFFD (spec requires this replacement for security)
|
|
77
|
+
def normalize_input(source)
|
|
78
|
+
# Both substitutions rewrite the whole document, so skip each scan
|
|
79
|
+
# (and its full-string copy) when the trigger byte is absent -- the
|
|
80
|
+
# common case is LF-only text with no NUL.
|
|
81
|
+
source = source.gsub(/\r\n?/, "\n") if source.include?("\r")
|
|
82
|
+
source = source.gsub(NUL_CHAR, REPLACEMENT_CHAR) if source.include?(NUL_CHAR)
|
|
83
|
+
source
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/mise.toml
ADDED
data/sig/red_quilt.rbs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module RedQuilt
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
def self.parse: (String source, ?allow_html: bool) -> Document
|
|
5
|
+
def self.render_html: (String source, ?allow_html: bool) -> String
|
|
6
|
+
|
|
7
|
+
class Document
|
|
8
|
+
attr_reader source: String
|
|
9
|
+
attr_reader arena: Arena
|
|
10
|
+
attr_reader root_id: Integer
|
|
11
|
+
|
|
12
|
+
def root: () -> NodeRef
|
|
13
|
+
def to_html: () -> String
|
|
14
|
+
def to_ast: () -> Hash[Symbol, untyped]
|
|
15
|
+
def allow_html?: () -> bool
|
|
16
|
+
def source_map: () -> SourceMap
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class NodeRef
|
|
20
|
+
include Enumerable[NodeRef]
|
|
21
|
+
|
|
22
|
+
attr_reader document: Document
|
|
23
|
+
attr_reader node_id: Integer
|
|
24
|
+
|
|
25
|
+
def type: () -> Symbol
|
|
26
|
+
def children: () -> Array[NodeRef]
|
|
27
|
+
def walk: () { (NodeRef) -> void } -> void
|
|
28
|
+
def walk: () -> Enumerator[NodeRef, void]
|
|
29
|
+
def find_all: (Symbol type) -> Array[NodeRef]
|
|
30
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
31
|
+
def text: () -> String?
|
|
32
|
+
def source_span: () -> SourceSpan?
|
|
33
|
+
def source_location: () -> { start_line: Integer, start_column: Integer, end_line: Integer, end_column: Integer }?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Arena
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class SourceMap
|
|
40
|
+
def initialize: (String source) -> void
|
|
41
|
+
def line_column: (Integer byte_offset) -> { line: Integer, column: Integer }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
SourceSpan = Data[Integer, Integer]
|
|
45
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: red_quilt
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.6.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- takahashim
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A modern Markdown document processor in pure Ruby, with an arena-style
|
|
13
|
+
AST and full CommonMark spec test suite compliance.
|
|
14
|
+
email:
|
|
15
|
+
- takahashimm@gmail.com
|
|
16
|
+
executables:
|
|
17
|
+
- redquilt
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- ".rspec"
|
|
22
|
+
- ".rubocop.yml"
|
|
23
|
+
- ".rubocop_todo.yml"
|
|
24
|
+
- CHANGELOG.md
|
|
25
|
+
- README.md
|
|
26
|
+
- Rakefile
|
|
27
|
+
- ast-spec.md
|
|
28
|
+
- docs/architecture.md
|
|
29
|
+
- docs/arena-usage.md
|
|
30
|
+
- docs/commonmark-conformance.md
|
|
31
|
+
- exe/redquilt
|
|
32
|
+
- lib/red_quilt.rb
|
|
33
|
+
- lib/red_quilt/arena.rb
|
|
34
|
+
- lib/red_quilt/block_parser.rb
|
|
35
|
+
- lib/red_quilt/blockquote.rb
|
|
36
|
+
- lib/red_quilt/cli.rb
|
|
37
|
+
- lib/red_quilt/diagnostic.rb
|
|
38
|
+
- lib/red_quilt/document.rb
|
|
39
|
+
- lib/red_quilt/extended_autolink_pass.rb
|
|
40
|
+
- lib/red_quilt/footnote_definition.rb
|
|
41
|
+
- lib/red_quilt/footnote_pass.rb
|
|
42
|
+
- lib/red_quilt/footnote_registry.rb
|
|
43
|
+
- lib/red_quilt/indentation.rb
|
|
44
|
+
- lib/red_quilt/inline.rb
|
|
45
|
+
- lib/red_quilt/inline/builder.rb
|
|
46
|
+
- lib/red_quilt/inline/flanking.rb
|
|
47
|
+
- lib/red_quilt/inline/html_entities.rb
|
|
48
|
+
- lib/red_quilt/inline/lexer.rb
|
|
49
|
+
- lib/red_quilt/inline/link_scanner.rb
|
|
50
|
+
- lib/red_quilt/inline/token_kind.rb
|
|
51
|
+
- lib/red_quilt/inline/tokens.rb
|
|
52
|
+
- lib/red_quilt/inline_pass.rb
|
|
53
|
+
- lib/red_quilt/line.rb
|
|
54
|
+
- lib/red_quilt/lint_pass.rb
|
|
55
|
+
- lib/red_quilt/list.rb
|
|
56
|
+
- lib/red_quilt/node_ref.rb
|
|
57
|
+
- lib/red_quilt/node_type.rb
|
|
58
|
+
- lib/red_quilt/plain_text.rb
|
|
59
|
+
- lib/red_quilt/reference_definition.rb
|
|
60
|
+
- lib/red_quilt/renderer/html.rb
|
|
61
|
+
- lib/red_quilt/renderer/mdast.rb
|
|
62
|
+
- lib/red_quilt/source_map.rb
|
|
63
|
+
- lib/red_quilt/source_span.rb
|
|
64
|
+
- lib/red_quilt/theme.rb
|
|
65
|
+
- lib/red_quilt/themes/default.css
|
|
66
|
+
- lib/red_quilt/version.rb
|
|
67
|
+
- mise.toml
|
|
68
|
+
- sig/red_quilt.rbs
|
|
69
|
+
homepage: https://github.com/takahashim/red_quilt
|
|
70
|
+
licenses: []
|
|
71
|
+
metadata:
|
|
72
|
+
homepage_uri: https://github.com/takahashim/red_quilt
|
|
73
|
+
source_code_uri: https://github.com/takahashim/red_quilt
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.3.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 4.0.10
|
|
89
|
+
specification_version: 4
|
|
90
|
+
summary: CommonMark-based Markdown processor written in pure Ruby
|
|
91
|
+
test_files: []
|