gitlab-glfm-markdown 0.0.29-aarch64-linux-musl → 0.0.31-aarch64-linux-musl
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/Cargo.lock +17 -15
- data/README.md +38 -36
- data/ext/glfm_markdown/Cargo.lock +1028 -0
- data/ext/glfm_markdown/Cargo.toml +5 -3
- data/ext/glfm_markdown/extconf.rb +2 -0
- data/ext/glfm_markdown/src/glfm.rs +280 -47
- data/ext/glfm_markdown/src/lib.rs +2 -0
- data/ext/glfm_markdown/src/main.rs +17 -0
- data/lib/glfm_markdown/3.1/glfm_markdown.so +0 -0
- data/lib/glfm_markdown/3.2/glfm_markdown.so +0 -0
- data/lib/glfm_markdown/3.3/glfm_markdown.so +0 -0
- data/lib/glfm_markdown/3.4/glfm_markdown.so +0 -0
- data/lib/glfm_markdown/version.rb +1 -1
- metadata +3 -2
@@ -1,6 +1,6 @@
|
|
1
1
|
[package]
|
2
2
|
name = "glfm_markdown"
|
3
|
-
version = "0.0.
|
3
|
+
version = "0.0.31"
|
4
4
|
edition = "2021"
|
5
5
|
authors = ["digitalmoksha <bwalker@gitlab.com>"]
|
6
6
|
description = "GitLab Flavored Markdown parser and formatter. 100% CommonMark-compatible. Experimental."
|
@@ -14,10 +14,12 @@ name = "glfm_markdown"
|
|
14
14
|
required-features = ["cli"]
|
15
15
|
|
16
16
|
[dependencies]
|
17
|
-
clap = { version = "4.
|
18
|
-
comrak = { version = "0.
|
17
|
+
clap = { version = "=4.4.18", optional = true, features = ["derive", "string"] }
|
18
|
+
comrak = { version = "0.39.0", default-features = false, features = ["shortcodes"] }
|
19
19
|
magnus = "0.6.2"
|
20
20
|
rb-sys = { version = "0.9.86", default-features = false, features = ["stable-api-compiled-fallback"] }
|
21
|
+
regex = "1.11.1"
|
22
|
+
lazy_static = "1.5.0"
|
21
23
|
|
22
24
|
[features]
|
23
25
|
cli = ["clap", "comrak/syntect"]
|
@@ -1,4 +1,16 @@
|
|
1
|
-
|
1
|
+
use comrak::html::{ChildRendering, Context};
|
2
|
+
use comrak::nodes::{AstNode, NodeValue};
|
3
|
+
use comrak::{create_formatter, html, parse_document, Arena, Plugins};
|
4
|
+
use lazy_static::lazy_static;
|
5
|
+
use regex::Regex;
|
6
|
+
use std::io;
|
7
|
+
use std::io::{BufWriter, Write};
|
8
|
+
|
9
|
+
lazy_static! {
|
10
|
+
static ref PLACEHOLDER_REGEX: Regex = Regex::new(r"%(\{|%7B)(\w{1,30})(}|%7D)").unwrap();
|
11
|
+
}
|
12
|
+
|
13
|
+
#[derive(Debug, Clone)]
|
2
14
|
pub struct RenderOptions {
|
3
15
|
pub alerts: bool,
|
4
16
|
pub autolink: bool,
|
@@ -38,56 +50,277 @@ pub struct RenderOptions {
|
|
38
50
|
pub wikilinks_title_after_pipe: bool,
|
39
51
|
pub wikilinks_title_before_pipe: bool,
|
40
52
|
|
53
|
+
/// Only use default comrak HTML formatting
|
54
|
+
pub default_html: bool,
|
55
|
+
|
56
|
+
/// Detect and mark potential placeholder variables, which
|
57
|
+
/// have the format `%{PLACEHOLDER}`
|
58
|
+
pub placeholder_detection: bool,
|
59
|
+
|
41
60
|
pub debug: bool,
|
42
61
|
}
|
43
62
|
|
63
|
+
pub struct RenderUserData {
|
64
|
+
pub default_html: bool,
|
65
|
+
pub placeholder_detection: bool,
|
66
|
+
pub debug: bool,
|
67
|
+
}
|
68
|
+
|
69
|
+
impl From<&RenderOptions> for RenderUserData {
|
70
|
+
fn from(options: &RenderOptions) -> Self {
|
71
|
+
RenderUserData {
|
72
|
+
default_html: options.default_html,
|
73
|
+
placeholder_detection: options.placeholder_detection,
|
74
|
+
debug: options.debug,
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
impl From<&RenderOptions> for comrak::Options<'_> {
|
80
|
+
fn from(options: &RenderOptions) -> Self {
|
81
|
+
let mut comrak_options = comrak::Options::default();
|
82
|
+
|
83
|
+
comrak_options.extension.alerts = options.alerts;
|
84
|
+
comrak_options.extension.autolink = options.autolink;
|
85
|
+
comrak_options.extension.description_lists = options.description_lists;
|
86
|
+
comrak_options.extension.footnotes = options.footnotes;
|
87
|
+
// comrak_options.extension.front_matter_delimiter = options.front_matter_delimiter;
|
88
|
+
comrak_options.extension.greentext = options.greentext;
|
89
|
+
comrak_options.extension.header_ids = options.header_ids.clone();
|
90
|
+
comrak_options.extension.math_code = options.math_code;
|
91
|
+
comrak_options.extension.math_dollars = options.math_dollars;
|
92
|
+
comrak_options.extension.multiline_block_quotes = options.multiline_block_quotes;
|
93
|
+
comrak_options.extension.shortcodes = options.gemojis;
|
94
|
+
comrak_options.extension.spoiler = options.spoiler;
|
95
|
+
comrak_options.extension.strikethrough = options.strikethrough;
|
96
|
+
comrak_options.extension.subscript = options.subscript;
|
97
|
+
comrak_options.extension.superscript = options.superscript;
|
98
|
+
comrak_options.extension.table = options.table;
|
99
|
+
comrak_options.extension.tagfilter = options.tagfilter;
|
100
|
+
comrak_options.extension.tasklist = options.tasklist;
|
101
|
+
comrak_options.extension.underline = options.underline;
|
102
|
+
comrak_options.extension.wikilinks_title_after_pipe = options.wikilinks_title_after_pipe;
|
103
|
+
comrak_options.extension.wikilinks_title_before_pipe = options.wikilinks_title_before_pipe;
|
104
|
+
|
105
|
+
comrak_options.render.escape = options.escape;
|
106
|
+
comrak_options.render.escaped_char_spans = options.escaped_char_spans;
|
107
|
+
comrak_options.render.figure_with_caption = options.figure_with_caption;
|
108
|
+
comrak_options.render.full_info_string = options.full_info_string;
|
109
|
+
comrak_options.render.gfm_quirks = options.gfm_quirks;
|
110
|
+
comrak_options.render.github_pre_lang = options.github_pre_lang;
|
111
|
+
comrak_options.render.hardbreaks = options.hardbreaks;
|
112
|
+
comrak_options.render.ignore_empty_links = options.ignore_empty_links;
|
113
|
+
comrak_options.render.ignore_setext = options.ignore_setext;
|
114
|
+
comrak_options.render.sourcepos = options.sourcepos;
|
115
|
+
// comrak_options.render.syntax_highlighting = options.syntax_highlighting;
|
116
|
+
|
117
|
+
comrak_options.render.unsafe_ = options.unsafe_;
|
118
|
+
|
119
|
+
// comrak_options.parse.default_info_string = options.default_info_string;
|
120
|
+
comrak_options.parse.relaxed_autolinks = options.relaxed_autolinks;
|
121
|
+
comrak_options.parse.relaxed_tasklist_matching = options.relaxed_tasklist_character;
|
122
|
+
comrak_options.parse.smart = options.smart;
|
123
|
+
|
124
|
+
comrak_options
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
44
128
|
pub fn render(text: String, options: RenderOptions) -> String {
|
45
|
-
|
129
|
+
render_with_plugins(text, options, &comrak::Plugins::default())
|
130
|
+
}
|
131
|
+
|
132
|
+
fn render_with_plugins(text: String, render_options: RenderOptions, plugins: &Plugins) -> String {
|
133
|
+
let user_data = RenderUserData::from(&render_options);
|
134
|
+
let options = comrak::Options::from(&render_options);
|
135
|
+
|
136
|
+
if user_data.default_html {
|
137
|
+
return comrak::markdown_to_html_with_plugins(&text, &options, plugins);
|
138
|
+
}
|
139
|
+
|
140
|
+
let arena = Arena::new();
|
141
|
+
let root = parse_document(&arena, &text, &options);
|
142
|
+
let mut bw = BufWriter::new(Vec::new());
|
143
|
+
|
144
|
+
CustomFormatter::format_document_with_plugins(root, &options, &mut bw, plugins, user_data)
|
145
|
+
.unwrap();
|
146
|
+
String::from_utf8(bw.into_inner().unwrap()).unwrap()
|
46
147
|
}
|
47
148
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
149
|
+
// The important thing to remember is that this overrides the default behavior of the
|
150
|
+
// specified nodes. If we do override a node, then it's our responsibility to ensure that
|
151
|
+
// any changes in the `comrak` code for those nodes is backported to here, such as when
|
152
|
+
// `figcaption` support was added.
|
153
|
+
// One idea to limit that would be having the ability to specify attributes that would
|
154
|
+
// be inserted when a node is rendered. That would allow us to (in many cases) just
|
155
|
+
// inject the changes we need. Such a feature would need to be added to `comrak`.
|
156
|
+
create_formatter!(CustomFormatter<RenderUserData>, {
|
157
|
+
NodeValue::Text(_) => |context, node, entering| {
|
158
|
+
return render_text(context, node, entering);
|
159
|
+
},
|
160
|
+
NodeValue::Link(_) => |context, node, entering| {
|
161
|
+
return render_link(context, node, entering);
|
162
|
+
},
|
163
|
+
NodeValue::Image(_) => |context, node, entering| {
|
164
|
+
return render_image(context, node, entering);
|
165
|
+
}
|
166
|
+
});
|
167
|
+
|
168
|
+
fn render_image<'a>(
|
169
|
+
context: &mut Context<RenderUserData>,
|
170
|
+
node: &'a AstNode<'a>,
|
171
|
+
entering: bool,
|
172
|
+
) -> io::Result<ChildRendering> {
|
173
|
+
let NodeValue::Image(ref nl) = node.data.borrow().value else {
|
174
|
+
panic!("Attempt to render invalid node as image")
|
175
|
+
};
|
176
|
+
|
177
|
+
if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(nl.url.as_str())) {
|
178
|
+
return html::format_node_default(context, node, entering);
|
179
|
+
}
|
180
|
+
|
181
|
+
if entering {
|
182
|
+
if context.options.render.figure_with_caption {
|
183
|
+
context.write_all(b"<figure>")?;
|
184
|
+
}
|
185
|
+
context.write_all(b"<img")?;
|
186
|
+
html::render_sourcepos(context, node)?;
|
187
|
+
context.write_all(b" src=\"")?;
|
188
|
+
let url = nl.url.as_bytes();
|
189
|
+
if context.options.render.unsafe_ || !html::dangerous_url(url) {
|
190
|
+
if let Some(rewriter) = &context.options.extension.image_url_rewriter {
|
191
|
+
context.escape_href(rewriter.to_html(&nl.url).as_bytes())?;
|
192
|
+
} else {
|
193
|
+
context.escape_href(url)?;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
context.write_all(b"\"")?;
|
198
|
+
|
199
|
+
if PLACEHOLDER_REGEX.is_match(nl.url.as_str()) {
|
200
|
+
context.write_all(b" data-placeholder")?;
|
201
|
+
}
|
202
|
+
|
203
|
+
context.write_all(b" alt=\"")?;
|
204
|
+
|
205
|
+
return Ok(ChildRendering::Plain);
|
206
|
+
} else {
|
207
|
+
if !nl.title.is_empty() {
|
208
|
+
context.write_all(b"\" title=\"")?;
|
209
|
+
context.escape(nl.title.as_bytes())?;
|
210
|
+
}
|
211
|
+
context.write_all(b"\" />")?;
|
212
|
+
if context.options.render.figure_with_caption {
|
213
|
+
if !nl.title.is_empty() {
|
214
|
+
context.write_all(b"<figcaption>")?;
|
215
|
+
context.escape(nl.title.as_bytes())?;
|
216
|
+
context.write_all(b"</figcaption>")?;
|
217
|
+
}
|
218
|
+
context.write_all(b"</figure>")?;
|
219
|
+
};
|
220
|
+
}
|
221
|
+
|
222
|
+
Ok(ChildRendering::HTML)
|
223
|
+
}
|
224
|
+
|
225
|
+
fn render_link<'a>(
|
226
|
+
context: &mut Context<RenderUserData>,
|
227
|
+
node: &'a AstNode<'a>,
|
228
|
+
entering: bool,
|
229
|
+
) -> io::Result<ChildRendering> {
|
230
|
+
let NodeValue::Link(ref nl) = node.data.borrow().value else {
|
231
|
+
panic!("Attempt to render invalid node as link")
|
232
|
+
};
|
233
|
+
|
234
|
+
if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(nl.url.as_str())) {
|
235
|
+
return html::format_node_default(context, node, entering);
|
236
|
+
}
|
237
|
+
|
238
|
+
let parent_node = node.parent();
|
239
|
+
|
240
|
+
if !context.options.parse.relaxed_autolinks
|
241
|
+
|| (parent_node.is_none()
|
242
|
+
|| !matches!(
|
243
|
+
parent_node.unwrap().data.borrow().value,
|
244
|
+
NodeValue::Link(..)
|
245
|
+
))
|
246
|
+
{
|
247
|
+
if entering {
|
248
|
+
context.write_all(b"<a")?;
|
249
|
+
html::render_sourcepos(context, node)?;
|
250
|
+
context.write_all(b" href=\"")?;
|
251
|
+
let url = nl.url.as_bytes();
|
252
|
+
if context.options.render.unsafe_ || !html::dangerous_url(url) {
|
253
|
+
if let Some(rewriter) = &context.options.extension.link_url_rewriter {
|
254
|
+
context.escape_href(rewriter.to_html(&nl.url).as_bytes())?;
|
255
|
+
} else {
|
256
|
+
context.escape_href(url)?;
|
257
|
+
}
|
258
|
+
}
|
259
|
+
context.write_all(b"\"")?;
|
260
|
+
|
261
|
+
if !nl.title.is_empty() {
|
262
|
+
context.write_all(b" title=\"")?;
|
263
|
+
context.escape(nl.title.as_bytes())?;
|
264
|
+
}
|
265
|
+
|
266
|
+
if PLACEHOLDER_REGEX.is_match(nl.url.as_str()) {
|
267
|
+
context.write_all(b" data-placeholder")?;
|
268
|
+
}
|
269
|
+
|
270
|
+
context.write_all(b">")?;
|
271
|
+
} else {
|
272
|
+
context.write_all(b"</a>")?;
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
Ok(ChildRendering::HTML)
|
277
|
+
}
|
278
|
+
|
279
|
+
fn render_text<'a>(
|
280
|
+
context: &mut Context<RenderUserData>,
|
281
|
+
node: &'a AstNode<'a>,
|
282
|
+
entering: bool,
|
283
|
+
) -> io::Result<ChildRendering> {
|
284
|
+
let NodeValue::Text(ref literal) = node.data.borrow().value else {
|
285
|
+
panic!("Attempt to render invalid node as text")
|
286
|
+
};
|
287
|
+
|
288
|
+
if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(literal)) {
|
289
|
+
return html::format_node_default(context, node, entering);
|
290
|
+
}
|
291
|
+
|
292
|
+
// Don't currently support placeholders in the text inside links or images.
|
293
|
+
// If the text has an underscore in it, then the parser will not combine
|
294
|
+
// the multiple text nodes in `comrak`'s `postprocess_text_nodes`, breaking up
|
295
|
+
// the placeholder into multiple text nodes.
|
296
|
+
// For example, `[%{a_b}](link)`.
|
297
|
+
let parent = node.parent().unwrap();
|
298
|
+
if matches!(
|
299
|
+
parent.data.borrow().value,
|
300
|
+
NodeValue::Link(_) | NodeValue::Image(_)
|
301
|
+
) {
|
302
|
+
return html::format_node_default(context, node, entering);
|
303
|
+
}
|
304
|
+
|
305
|
+
if entering {
|
306
|
+
let mut cursor: usize = 0;
|
307
|
+
|
308
|
+
for mat in PLACEHOLDER_REGEX.find_iter(literal) {
|
309
|
+
if mat.start() > cursor {
|
310
|
+
context.escape(literal[cursor..mat.start()].as_bytes())?;
|
311
|
+
}
|
312
|
+
|
313
|
+
context.write_all(b"<span data-placeholder>")?;
|
314
|
+
context.escape(literal[mat.start()..mat.end()].as_bytes())?;
|
315
|
+
context.write_all(b"</span>")?;
|
316
|
+
|
317
|
+
cursor = mat.end();
|
318
|
+
}
|
319
|
+
|
320
|
+
if cursor < literal.len() {
|
321
|
+
context.escape(literal[cursor..literal.len()].as_bytes())?;
|
322
|
+
}
|
323
|
+
}
|
324
|
+
|
325
|
+
Ok(ChildRendering::HTML)
|
93
326
|
}
|
@@ -53,6 +53,8 @@ pub fn render_to_html_rs(text: String, options: RHash) -> String {
|
|
53
53
|
wikilinks_title_after_pipe: get_bool_opt("wikilinks_title_after_pipe", options),
|
54
54
|
wikilinks_title_before_pipe: get_bool_opt("wikilinks_title_before_pipe", options),
|
55
55
|
|
56
|
+
default_html: get_bool_opt("default_html", options),
|
57
|
+
placeholder_detection: get_bool_opt("placeholder_detection", options),
|
56
58
|
debug: get_bool_opt("debug", options),
|
57
59
|
};
|
58
60
|
|
@@ -32,6 +32,10 @@ struct Args {
|
|
32
32
|
#[arg(long)]
|
33
33
|
escaped_char_spans: bool,
|
34
34
|
|
35
|
+
/// Render the image as a figure element with the title as its caption
|
36
|
+
#[arg(long)]
|
37
|
+
figure_with_caption: bool,
|
38
|
+
|
35
39
|
/// Enable 'footnotes' extension
|
36
40
|
#[arg(long)]
|
37
41
|
footnotes: bool,
|
@@ -158,6 +162,15 @@ struct Args {
|
|
158
162
|
#[arg(long)]
|
159
163
|
wikilinks_title_before_pipe: bool,
|
160
164
|
|
165
|
+
/// Only use default comrak HTML formatting
|
166
|
+
#[arg(long)]
|
167
|
+
default_html: bool,
|
168
|
+
|
169
|
+
/// Detect and marks potential placeholder variables, which
|
170
|
+
/// have the format `%{PLACEHOLDER}`
|
171
|
+
#[arg(long)]
|
172
|
+
placeholder_detection: bool,
|
173
|
+
|
161
174
|
/// Show debug information
|
162
175
|
#[arg(long)]
|
163
176
|
debug: bool,
|
@@ -184,6 +197,7 @@ fn main() {
|
|
184
197
|
description_lists: cli.description_lists,
|
185
198
|
escape: cli.escape,
|
186
199
|
escaped_char_spans: cli.escaped_char_spans,
|
200
|
+
figure_with_caption: cli.figure_with_caption,
|
187
201
|
footnotes: cli.footnotes,
|
188
202
|
// front_matter_delimiter:
|
189
203
|
full_info_string: cli.full_info_string,
|
@@ -214,6 +228,9 @@ fn main() {
|
|
214
228
|
unsafe_: cli.unsafe_,
|
215
229
|
wikilinks_title_after_pipe: cli.wikilinks_title_after_pipe,
|
216
230
|
wikilinks_title_before_pipe: cli.wikilinks_title_before_pipe,
|
231
|
+
|
232
|
+
default_html: cli.default_html,
|
233
|
+
placeholder_detection: cli.placeholder_detection,
|
217
234
|
debug: cli.debug,
|
218
235
|
};
|
219
236
|
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-glfm-markdown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.31
|
5
5
|
platform: aarch64-linux-musl
|
6
6
|
authors:
|
7
7
|
- Brett Walker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rb_sys
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- Cargo.lock
|
63
63
|
- LICENSE
|
64
64
|
- README.md
|
65
|
+
- ext/glfm_markdown/Cargo.lock
|
65
66
|
- ext/glfm_markdown/Cargo.toml
|
66
67
|
- ext/glfm_markdown/extconf.rb
|
67
68
|
- ext/glfm_markdown/src/glfm.rs
|