gitlab-glfm-markdown 0.0.38-arm64-darwin → 0.0.40-arm64-darwin
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 +5 -5
- data/README.md +11 -9
- data/ext/{glfm_markdown → gitlab_glfm_markdown}/Cargo.lock +5 -5
- data/ext/{glfm_markdown → gitlab_glfm_markdown}/Cargo.toml +4 -4
- data/ext/{glfm_markdown → gitlab_glfm_markdown}/extconf.rb +1 -1
- data/ext/gitlab_glfm_markdown/src/formatter.rs +456 -0
- data/ext/{glfm_markdown → gitlab_glfm_markdown}/src/glfm.rs +17 -32
- data/ext/{glfm_markdown → gitlab_glfm_markdown}/src/main.rs +44 -41
- data/lib/{glfm_markdown.rb → gitlab-glfm-markdown.rb} +3 -5
- data/lib/gitlab_glfm_markdown/3.1/gitlab_glfm_markdown.bundle +0 -0
- data/lib/gitlab_glfm_markdown/3.2/gitlab_glfm_markdown.bundle +0 -0
- data/lib/gitlab_glfm_markdown/3.3/gitlab_glfm_markdown.bundle +0 -0
- data/lib/gitlab_glfm_markdown/3.4/gitlab_glfm_markdown.bundle +0 -0
- data/lib/{glfm_markdown → gitlab_glfm_markdown}/loader.rb +2 -2
- data/lib/{glfm_markdown → gitlab_glfm_markdown}/version.rb +1 -1
- metadata +16 -16
- data/ext/glfm_markdown/src/formatter.rs +0 -366
- data/lib/glfm_markdown/3.1/glfm_markdown.bundle +0 -0
- data/lib/glfm_markdown/3.2/glfm_markdown.bundle +0 -0
- data/lib/glfm_markdown/3.3/glfm_markdown.bundle +0 -0
- data/lib/glfm_markdown/3.4/glfm_markdown.bundle +0 -0
- /data/ext/{glfm_markdown → gitlab_glfm_markdown}/src/lib.rs +0 -0
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
use std::fmt::{self, Write};
|
|
2
|
-
|
|
3
|
-
use comrak::html::{render_sourcepos, ChildRendering, Context};
|
|
4
|
-
use comrak::nodes::{AstNode, ListType, NodeValue};
|
|
5
|
-
use comrak::{create_formatter, html};
|
|
6
|
-
use lazy_static::lazy_static;
|
|
7
|
-
use regex::Regex;
|
|
8
|
-
|
|
9
|
-
// TODO: Use std::sync:LazyLock once we're on 1.80+.
|
|
10
|
-
// https://doc.rust-lang.org/std/sync/struct.LazyLock.html
|
|
11
|
-
lazy_static! {
|
|
12
|
-
static ref PLACEHOLDER_REGEX: Regex = Regex::new(r"%(\{|%7B)(\w{1,30})(}|%7D)").unwrap();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
pub struct RenderUserData {
|
|
16
|
-
pub default_html: bool,
|
|
17
|
-
pub inapplicable_tasks: bool,
|
|
18
|
-
pub placeholder_detection: bool,
|
|
19
|
-
pub only_escape_chars: Option<Vec<char>>,
|
|
20
|
-
pub debug: bool,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// The important thing to remember is that this overrides the default behavior of the
|
|
24
|
-
// specified nodes. If we do override a node, then it's our responsibility to ensure that
|
|
25
|
-
// any changes in the `comrak` code for those nodes is backported to here, such as when
|
|
26
|
-
// `figcaption` support was added.
|
|
27
|
-
// One idea to limit that would be having the ability to specify attributes that would
|
|
28
|
-
// be inserted when a node is rendered. That would allow us to (in many cases) just
|
|
29
|
-
// inject the changes we need. Such a feature would need to be added to `comrak`.
|
|
30
|
-
create_formatter!(CustomFormatter<RenderUserData>, {
|
|
31
|
-
NodeValue::Text(_) => |context, node, entering| {
|
|
32
|
-
return render_text(context, node, entering);
|
|
33
|
-
},
|
|
34
|
-
NodeValue::Link(_) => |context, node, entering| {
|
|
35
|
-
return render_link(context, node, entering);
|
|
36
|
-
},
|
|
37
|
-
NodeValue::Image(_) => |context, node, entering| {
|
|
38
|
-
return render_image(context, node, entering);
|
|
39
|
-
},
|
|
40
|
-
NodeValue::List(_) => |context, node, entering| {
|
|
41
|
-
return render_list(context, node, entering);
|
|
42
|
-
},
|
|
43
|
-
NodeValue::TaskItem(_) => |context, node, entering| {
|
|
44
|
-
return render_task_item(context, node, entering);
|
|
45
|
-
},
|
|
46
|
-
NodeValue::Escaped => |context, node, entering| {
|
|
47
|
-
return render_escaped(context, node, entering);
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
fn render_image<'a>(
|
|
52
|
-
context: &mut Context<RenderUserData>,
|
|
53
|
-
node: &'a AstNode<'a>,
|
|
54
|
-
entering: bool,
|
|
55
|
-
) -> Result<ChildRendering, fmt::Error> {
|
|
56
|
-
let NodeValue::Image(ref nl) = node.data.borrow().value else {
|
|
57
|
-
unreachable!()
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(&nl.url)) {
|
|
61
|
-
return html::format_node_default(context, node, entering);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if entering {
|
|
65
|
-
if context.options.render.figure_with_caption {
|
|
66
|
-
context.write_str("<figure>")?;
|
|
67
|
-
}
|
|
68
|
-
context.write_str("<img")?;
|
|
69
|
-
html::render_sourcepos(context, node)?;
|
|
70
|
-
context.write_str(" src=\"")?;
|
|
71
|
-
if context.options.render.r#unsafe || !html::dangerous_url(&nl.url) {
|
|
72
|
-
if let Some(rewriter) = &context.options.extension.image_url_rewriter {
|
|
73
|
-
context.escape_href(&rewriter.to_html(&nl.url))?;
|
|
74
|
-
} else {
|
|
75
|
-
context.escape_href(&nl.url)?;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
context.write_str("\"")?;
|
|
80
|
-
|
|
81
|
-
if PLACEHOLDER_REGEX.is_match(&nl.url) {
|
|
82
|
-
context.write_str(" data-placeholder")?;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
context.write_str(" alt=\"")?;
|
|
86
|
-
|
|
87
|
-
return Ok(ChildRendering::Plain);
|
|
88
|
-
} else {
|
|
89
|
-
if !nl.title.is_empty() {
|
|
90
|
-
context.write_str("\" title=\"")?;
|
|
91
|
-
context.escape(&nl.title)?;
|
|
92
|
-
}
|
|
93
|
-
context.write_str("\" />")?;
|
|
94
|
-
if context.options.render.figure_with_caption {
|
|
95
|
-
if !nl.title.is_empty() {
|
|
96
|
-
context.write_str("<figcaption>")?;
|
|
97
|
-
context.escape(&nl.title)?;
|
|
98
|
-
context.write_str("</figcaption>")?;
|
|
99
|
-
}
|
|
100
|
-
context.write_str("</figure>")?;
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
Ok(ChildRendering::HTML)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
fn render_link<'a>(
|
|
108
|
-
context: &mut Context<RenderUserData>,
|
|
109
|
-
node: &'a AstNode<'a>,
|
|
110
|
-
entering: bool,
|
|
111
|
-
) -> Result<ChildRendering, fmt::Error> {
|
|
112
|
-
let NodeValue::Link(ref nl) = node.data.borrow().value else {
|
|
113
|
-
unreachable!()
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(&nl.url)) {
|
|
117
|
-
return html::format_node_default(context, node, entering);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
let parent_node = node.parent();
|
|
121
|
-
|
|
122
|
-
if !context.options.parse.relaxed_autolinks
|
|
123
|
-
|| (parent_node.is_none()
|
|
124
|
-
|| !matches!(
|
|
125
|
-
parent_node.unwrap().data.borrow().value,
|
|
126
|
-
NodeValue::Link(..)
|
|
127
|
-
))
|
|
128
|
-
{
|
|
129
|
-
if entering {
|
|
130
|
-
context.write_str("<a")?;
|
|
131
|
-
html::render_sourcepos(context, node)?;
|
|
132
|
-
context.write_str(" href=\"")?;
|
|
133
|
-
if context.options.render.r#unsafe || !html::dangerous_url(&nl.url) {
|
|
134
|
-
if let Some(rewriter) = &context.options.extension.link_url_rewriter {
|
|
135
|
-
context.escape_href(&rewriter.to_html(&nl.url))?;
|
|
136
|
-
} else {
|
|
137
|
-
context.escape_href(&nl.url)?;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
context.write_str("\"")?;
|
|
141
|
-
|
|
142
|
-
if !nl.title.is_empty() {
|
|
143
|
-
context.write_str(" title=\"")?;
|
|
144
|
-
context.escape(&nl.title)?;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if PLACEHOLDER_REGEX.is_match(&nl.url) {
|
|
148
|
-
context.write_str(" data-placeholder")?;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
context.write_str(">")?;
|
|
152
|
-
} else {
|
|
153
|
-
context.write_str("</a>")?;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
Ok(ChildRendering::HTML)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Overridden to use class `task-list` instead of `contains-task-list`
|
|
161
|
-
// to align with GitLab class usage
|
|
162
|
-
fn render_list<'a>(
|
|
163
|
-
context: &mut Context<RenderUserData>,
|
|
164
|
-
node: &'a AstNode<'a>,
|
|
165
|
-
entering: bool,
|
|
166
|
-
) -> Result<ChildRendering, fmt::Error> {
|
|
167
|
-
if !entering || !context.options.render.tasklist_classes {
|
|
168
|
-
return html::format_node_default(context, node, entering);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
let NodeValue::List(ref nl) = node.data.borrow().value else {
|
|
172
|
-
unreachable!()
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
context.cr()?;
|
|
176
|
-
match nl.list_type {
|
|
177
|
-
ListType::Bullet => {
|
|
178
|
-
context.write_str("<ul")?;
|
|
179
|
-
if nl.is_task_list {
|
|
180
|
-
context.write_str(" class=\"task-list\"")?;
|
|
181
|
-
}
|
|
182
|
-
html::render_sourcepos(context, node)?;
|
|
183
|
-
context.write_str(">\n")?;
|
|
184
|
-
}
|
|
185
|
-
ListType::Ordered => {
|
|
186
|
-
context.write_str("<ol")?;
|
|
187
|
-
if nl.is_task_list {
|
|
188
|
-
context.write_str(" class=\"task-list\"")?;
|
|
189
|
-
}
|
|
190
|
-
html::render_sourcepos(context, node)?;
|
|
191
|
-
if nl.start == 1 {
|
|
192
|
-
context.write_str(">\n")?;
|
|
193
|
-
} else {
|
|
194
|
-
writeln!(context, " start=\"{}\">", nl.start)?;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
Ok(ChildRendering::HTML)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Overridden to detect inapplicable task list items
|
|
203
|
-
fn render_task_item<'a>(
|
|
204
|
-
context: &mut Context<RenderUserData>,
|
|
205
|
-
node: &'a AstNode<'a>,
|
|
206
|
-
entering: bool,
|
|
207
|
-
) -> Result<ChildRendering, fmt::Error> {
|
|
208
|
-
if !context.user.inapplicable_tasks {
|
|
209
|
-
return html::format_node_default(context, node, entering);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
let NodeValue::TaskItem(symbol) = node.data.borrow().value else {
|
|
213
|
-
unreachable!()
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
if symbol.is_none() || matches!(symbol, Some('x' | 'X')) {
|
|
217
|
-
return html::format_node_default(context, node, entering);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if entering {
|
|
221
|
-
// Handle an inapplicable task symbol.
|
|
222
|
-
if matches!(symbol, Some('~')) {
|
|
223
|
-
context.cr()?;
|
|
224
|
-
context.write_str("<li")?;
|
|
225
|
-
context.write_str(" class=\"inapplicable")?;
|
|
226
|
-
|
|
227
|
-
if context.options.render.tasklist_classes {
|
|
228
|
-
context.write_str(" task-list-item")?;
|
|
229
|
-
}
|
|
230
|
-
context.write_str("\"")?;
|
|
231
|
-
|
|
232
|
-
html::render_sourcepos(context, node)?;
|
|
233
|
-
context.write_str(">")?;
|
|
234
|
-
context.write_str("<input type=\"checkbox\"")?;
|
|
235
|
-
|
|
236
|
-
if context.options.render.tasklist_classes {
|
|
237
|
-
context.write_str(" class=\"task-list-item-checkbox\"")?;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
context.write_str(" data-inapplicable disabled=\"\"> ")?;
|
|
241
|
-
} else {
|
|
242
|
-
// Don't allow unsupported symbols to render a checkbox
|
|
243
|
-
context.cr()?;
|
|
244
|
-
context.write_str("<li")?;
|
|
245
|
-
|
|
246
|
-
if context.options.render.tasklist_classes {
|
|
247
|
-
context.write_str(" class=\"task-list-item\"")?;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
html::render_sourcepos(context, node)?;
|
|
251
|
-
context.write_str(">")?;
|
|
252
|
-
context.write_str("[")?;
|
|
253
|
-
context.escape(&symbol.unwrap().to_string())?;
|
|
254
|
-
context.write_str("] ")?;
|
|
255
|
-
}
|
|
256
|
-
} else {
|
|
257
|
-
context.write_str("</li>\n")?;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
Ok(ChildRendering::HTML)
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
fn render_text<'a>(
|
|
264
|
-
context: &mut Context<RenderUserData>,
|
|
265
|
-
node: &'a AstNode<'a>,
|
|
266
|
-
entering: bool,
|
|
267
|
-
) -> Result<ChildRendering, fmt::Error> {
|
|
268
|
-
let NodeValue::Text(ref literal) = node.data.borrow().value else {
|
|
269
|
-
unreachable!()
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(literal)) {
|
|
273
|
-
return html::format_node_default(context, node, entering);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Don't currently support placeholders in the text inside links or images.
|
|
277
|
-
// If the text has an underscore in it, then the parser will not combine
|
|
278
|
-
// the multiple text nodes in `comrak`'s `postprocess_text_nodes`, breaking up
|
|
279
|
-
// the placeholder into multiple text nodes.
|
|
280
|
-
// For example, `[%{a_b}](link)`.
|
|
281
|
-
let parent = node.parent().unwrap();
|
|
282
|
-
if matches!(
|
|
283
|
-
parent.data.borrow().value,
|
|
284
|
-
NodeValue::Link(_) | NodeValue::Image(_)
|
|
285
|
-
) {
|
|
286
|
-
return html::format_node_default(context, node, entering);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if entering {
|
|
290
|
-
let mut cursor: usize = 0;
|
|
291
|
-
|
|
292
|
-
for mat in PLACEHOLDER_REGEX.find_iter(literal) {
|
|
293
|
-
if mat.start() > cursor {
|
|
294
|
-
context.escape(&literal[cursor..mat.start()])?;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
context.write_str("<span data-placeholder>")?;
|
|
298
|
-
context.escape(&literal[mat.start()..mat.end()])?;
|
|
299
|
-
context.write_str("</span>")?;
|
|
300
|
-
|
|
301
|
-
cursor = mat.end();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if cursor < literal.len() {
|
|
305
|
-
context.escape(&literal[cursor..literal.len()])?;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
Ok(ChildRendering::HTML)
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
fn render_escaped<'a>(
|
|
313
|
-
context: &mut Context<RenderUserData>,
|
|
314
|
-
node: &'a AstNode<'a>,
|
|
315
|
-
entering: bool,
|
|
316
|
-
) -> Result<ChildRendering, fmt::Error> {
|
|
317
|
-
if !context.options.render.escaped_char_spans {
|
|
318
|
-
return Ok(ChildRendering::HTML);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if context.user.only_escape_chars.is_none()
|
|
322
|
-
|| with_node_text_content(node, false, |content| {
|
|
323
|
-
if content.len() != 1 {
|
|
324
|
-
return false;
|
|
325
|
-
}
|
|
326
|
-
let c = content.chars().next().unwrap();
|
|
327
|
-
context
|
|
328
|
-
.user
|
|
329
|
-
.only_escape_chars
|
|
330
|
-
.as_ref()
|
|
331
|
-
.unwrap()
|
|
332
|
-
.contains(&c)
|
|
333
|
-
})
|
|
334
|
-
{
|
|
335
|
-
if entering {
|
|
336
|
-
context.write_str("<span data-escaped-char")?;
|
|
337
|
-
render_sourcepos(context, node)?;
|
|
338
|
-
context.write_str(">")?;
|
|
339
|
-
} else {
|
|
340
|
-
context.write_str("</span>")?;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
Ok(ChildRendering::HTML)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/// If the given node has a single text child, apply a function to the text content
|
|
348
|
-
/// of that node. Otherwise, return the given default value.
|
|
349
|
-
fn with_node_text_content<'a, U, F>(node: &'a AstNode<'a>, default: U, f: F) -> U
|
|
350
|
-
where
|
|
351
|
-
F: FnOnce(&str) -> U,
|
|
352
|
-
{
|
|
353
|
-
let Some(child) = node.first_child() else {
|
|
354
|
-
return default;
|
|
355
|
-
};
|
|
356
|
-
let Some(last_child) = node.last_child() else {
|
|
357
|
-
return default;
|
|
358
|
-
};
|
|
359
|
-
if !child.same_node(last_child) {
|
|
360
|
-
return default;
|
|
361
|
-
}
|
|
362
|
-
let NodeValue::Text(ref text) = child.data.borrow().value else {
|
|
363
|
-
return default;
|
|
364
|
-
};
|
|
365
|
-
f(text)
|
|
366
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|