gitlab-glfm-markdown 0.0.39 → 0.0.40
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 +3 -3
- data/README.md +1 -1
- data/ext/gitlab_glfm_markdown/Cargo.toml +2 -2
- data/ext/gitlab_glfm_markdown/src/formatter.rs +113 -92
- data/lib/gitlab_glfm_markdown/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 958f895a64867a4cf0facd03a03f363398263613493019627df9ed9481dc0d74
|
|
4
|
+
data.tar.gz: c180c4b058f5c823b2dc00ad32bf63de261a070cba034326d0f29e1131ea6f53
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1887cab2213baf059cf86655333308a4cd021bed71438a16c32f6a234dde93c29cc0b87522bc6057d6f87acf4150634c712f98ce273032845a90899c6a4806f
|
|
7
|
+
data.tar.gz: e7c2789aa86e38930c55d9d31520d947bee79cb093338213259d0820b8015abe1d3419ba1ce282af35495ca73fa66846745f983fec574921d97b301f725d9b7a
|
data/Cargo.lock
CHANGED
|
@@ -221,9 +221,9 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
|
|
221
221
|
|
|
222
222
|
[[package]]
|
|
223
223
|
name = "comrak"
|
|
224
|
-
version = "0.
|
|
224
|
+
version = "0.49.0"
|
|
225
225
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
226
|
-
checksum = "
|
|
226
|
+
checksum = "ab87129dce2f2d7e75e753b1df0e5093b27dec8fa5970b6eb51280faacb25bd6"
|
|
227
227
|
dependencies = [
|
|
228
228
|
"caseless",
|
|
229
229
|
"emojis",
|
|
@@ -307,7 +307,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|
|
307
307
|
|
|
308
308
|
[[package]]
|
|
309
309
|
name = "gitlab-glfm-markdown"
|
|
310
|
-
version = "0.0.
|
|
310
|
+
version = "0.0.40"
|
|
311
311
|
dependencies = [
|
|
312
312
|
"clap",
|
|
313
313
|
"comrak",
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://gitlab.com/gitlab-org/ruby/gems/gitlab-glfm-markdown/-/releases)
|
|
5
5
|
|
|
6
6
|
Implements GLFM (as used by GitLab) using the Rust-based Markdown parser
|
|
7
|
-
[Comrak](https://github.com/kivikakk/comrak) (0.
|
|
7
|
+
[Comrak](https://github.com/kivikakk/comrak) (0.49.0), providing a Ruby interface.
|
|
8
8
|
|
|
9
9
|
This project is still in constant flux, so interfaces and functionality can change at any time.
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "gitlab-glfm-markdown"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.40"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["digitalmoksha <bwalker@gitlab.com>", "Asherah Connor <aconnor@gitlab.com>"]
|
|
6
6
|
description = "GitLab Flavored Markdown parser and formatter. 100% CommonMark-compatible. Experimental."
|
|
@@ -15,7 +15,7 @@ required-features = ["cli"]
|
|
|
15
15
|
|
|
16
16
|
[dependencies]
|
|
17
17
|
clap = { version = "=4.4.18", optional = true, features = ["derive", "string"] }
|
|
18
|
-
comrak = { version = "0.
|
|
18
|
+
comrak = { version = "0.49.0", default-features = false, features = ["shortcodes"] }
|
|
19
19
|
magnus = "0.8.2"
|
|
20
20
|
rb-sys = { version = "0.9.117", default-features = false, features = ["stable-api-compiled-fallback"] }
|
|
21
21
|
regex = "1.11.1"
|
|
@@ -2,8 +2,8 @@ use std::fmt::{self, Write};
|
|
|
2
2
|
use std::sync::LazyLock;
|
|
3
3
|
|
|
4
4
|
use comrak::html::{collect_text, format_node_default, render_sourcepos, ChildRendering, Context};
|
|
5
|
-
use comrak::nodes::{
|
|
6
|
-
use comrak::{create_formatter, html, node_matches};
|
|
5
|
+
use comrak::nodes::{ListType, NodeHeading, NodeLink, NodeList, NodeTaskItem, NodeValue};
|
|
6
|
+
use comrak::{create_formatter, html, node_matches, Node};
|
|
7
7
|
use regex::Regex;
|
|
8
8
|
|
|
9
9
|
use crate::glfm::RenderOptions;
|
|
@@ -11,6 +11,7 @@ use crate::glfm::RenderOptions;
|
|
|
11
11
|
static PLACEHOLDER_REGEX: LazyLock<Regex> =
|
|
12
12
|
LazyLock::new(|| Regex::new(r"%\{(?:\w{1,30})}").unwrap());
|
|
13
13
|
|
|
14
|
+
#[derive(Default)]
|
|
14
15
|
pub struct RenderUserData {
|
|
15
16
|
pub default_html: bool,
|
|
16
17
|
pub inapplicable_tasks: bool,
|
|
@@ -55,8 +56,8 @@ create_formatter!(CustomFormatter<RenderUserData>, {
|
|
|
55
56
|
NodeValue::List(ref nl) => |context, node, entering| {
|
|
56
57
|
return render_list(context, node, entering, nl);
|
|
57
58
|
},
|
|
58
|
-
NodeValue::TaskItem(
|
|
59
|
-
return render_task_item(context, node, entering,
|
|
59
|
+
NodeValue::TaskItem(ref nti) => |context, node, entering| {
|
|
60
|
+
return render_task_item(context, node, entering, nti);
|
|
60
61
|
},
|
|
61
62
|
NodeValue::Escaped => |context, node, entering| {
|
|
62
63
|
return render_escaped(context, node, entering);
|
|
@@ -66,9 +67,9 @@ create_formatter!(CustomFormatter<RenderUserData>, {
|
|
|
66
67
|
},
|
|
67
68
|
});
|
|
68
69
|
|
|
69
|
-
fn render_text
|
|
70
|
+
fn render_text(
|
|
70
71
|
context: &mut Context<RenderUserData>,
|
|
71
|
-
node:
|
|
72
|
+
node: Node<'_>,
|
|
72
73
|
entering: bool,
|
|
73
74
|
literal: &str,
|
|
74
75
|
) -> Result<ChildRendering, fmt::Error> {
|
|
@@ -99,9 +100,9 @@ fn render_text<'a>(
|
|
|
99
100
|
Ok(ChildRendering::HTML)
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
fn render_link
|
|
103
|
+
fn render_link(
|
|
103
104
|
context: &mut Context<RenderUserData>,
|
|
104
|
-
node:
|
|
105
|
+
node: Node<'_>,
|
|
105
106
|
entering: bool,
|
|
106
107
|
nl: &NodeLink,
|
|
107
108
|
) -> Result<ChildRendering, fmt::Error> {
|
|
@@ -148,9 +149,9 @@ fn render_link<'a>(
|
|
|
148
149
|
Ok(ChildRendering::HTML)
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
fn render_image
|
|
152
|
+
fn render_image(
|
|
152
153
|
context: &mut Context<RenderUserData>,
|
|
153
|
-
node:
|
|
154
|
+
node: Node<'_>,
|
|
154
155
|
entering: bool,
|
|
155
156
|
nl: &NodeLink,
|
|
156
157
|
) -> Result<ChildRendering, fmt::Error> {
|
|
@@ -202,9 +203,9 @@ fn render_image<'a>(
|
|
|
202
203
|
|
|
203
204
|
// Overridden to use class `task-list` instead of `contains-task-list`
|
|
204
205
|
// to align with GitLab class usage
|
|
205
|
-
fn render_list
|
|
206
|
+
fn render_list(
|
|
206
207
|
context: &mut Context<RenderUserData>,
|
|
207
|
-
node:
|
|
208
|
+
node: Node<'_>,
|
|
208
209
|
entering: bool,
|
|
209
210
|
nl: &NodeList,
|
|
210
211
|
) -> Result<ChildRendering, fmt::Error> {
|
|
@@ -239,87 +240,51 @@ fn render_list<'a>(
|
|
|
239
240
|
Ok(ChildRendering::HTML)
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
// Overridden to
|
|
243
|
-
|
|
243
|
+
// Overridden to:
|
|
244
|
+
// 1. Detect inapplicable task list items; and,
|
|
245
|
+
// 2. Output checkbox sourcepos.
|
|
246
|
+
fn render_task_item(
|
|
244
247
|
context: &mut Context<RenderUserData>,
|
|
245
|
-
node:
|
|
248
|
+
node: Node<'_>,
|
|
246
249
|
entering: bool,
|
|
247
|
-
|
|
250
|
+
nti: &NodeTaskItem,
|
|
248
251
|
) -> Result<ChildRendering, fmt::Error> {
|
|
249
|
-
if !context.user.inapplicable_tasks {
|
|
250
|
-
return html::format_node_default(context, node, entering);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
let Some(symbol) = symbol else {
|
|
254
|
-
return html::format_node_default(context, node, entering);
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
if symbol == 'x' || symbol == 'X' {
|
|
258
|
-
return html::format_node_default(context, node, entering);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// We only proceed past this point if:
|
|
262
|
-
//
|
|
263
|
-
// * inapplicable_tasks is enabled; and,
|
|
264
|
-
// * the symbol is present (the tasklist didn't contain a ' '), and isn't 'x' or 'X'.
|
|
265
|
-
//
|
|
266
|
-
// There are three possibilities remaining:
|
|
267
|
-
//
|
|
268
|
-
// * the symbol is '~': we write out an inapplicable task item.
|
|
269
|
-
// * the symbol is a different Unicode whitespace: we write out an incomplete task item,
|
|
270
|
-
// per Comrak.
|
|
271
|
-
// * the symbol is something else: we write out the source Markdown that would've been entered,
|
|
272
|
-
// to act like a non-match.
|
|
273
|
-
//
|
|
274
|
-
// TODO: have Comrak accept a list of acceptable tasklist symbols instead. :)
|
|
275
|
-
|
|
276
252
|
let write_li = node
|
|
277
253
|
.parent()
|
|
278
254
|
.map(|p| node_matches!(p, NodeValue::List(_)))
|
|
279
255
|
.unwrap_or_default();
|
|
280
256
|
|
|
281
|
-
if entering {
|
|
282
|
-
if
|
|
283
|
-
context.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if context.options.render.tasklist_classes {
|
|
289
|
-
context.write_str(" task-list-item")?;
|
|
290
|
-
}
|
|
291
|
-
context.write_str("\"")?;
|
|
292
|
-
html::render_sourcepos(context, node)?;
|
|
293
|
-
context.write_str(">")?;
|
|
294
|
-
}
|
|
295
|
-
context.write_str("<input type=\"checkbox\"")?;
|
|
296
|
-
if !write_li {
|
|
297
|
-
html::render_sourcepos(context, node)?;
|
|
298
|
-
}
|
|
299
|
-
if context.options.render.tasklist_classes {
|
|
300
|
-
context.write_str(" class=\"task-list-item-checkbox\"")?;
|
|
301
|
-
}
|
|
257
|
+
if !entering {
|
|
258
|
+
if write_li {
|
|
259
|
+
context.write_str("</li>\n")?;
|
|
260
|
+
}
|
|
261
|
+
return Ok(ChildRendering::HTML);
|
|
262
|
+
}
|
|
302
263
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
context
|
|
322
|
-
}
|
|
264
|
+
match nti.symbol {
|
|
265
|
+
Some('~') => {
|
|
266
|
+
render_task_item_with(
|
|
267
|
+
context,
|
|
268
|
+
node,
|
|
269
|
+
nti,
|
|
270
|
+
if context.user.inapplicable_tasks {
|
|
271
|
+
TaskItemState::Inapplicable
|
|
272
|
+
} else {
|
|
273
|
+
TaskItemState::Checked
|
|
274
|
+
},
|
|
275
|
+
write_li,
|
|
276
|
+
)?;
|
|
277
|
+
}
|
|
278
|
+
None => {
|
|
279
|
+
render_task_item_with(context, node, nti, TaskItemState::Unchecked, write_li)?;
|
|
280
|
+
}
|
|
281
|
+
Some(ws) if ws.is_whitespace() => {
|
|
282
|
+
render_task_item_with(context, node, nti, TaskItemState::Unchecked, write_li)?;
|
|
283
|
+
}
|
|
284
|
+
Some('x' | 'X') => {
|
|
285
|
+
render_task_item_with(context, node, nti, TaskItemState::Checked, write_li)?;
|
|
286
|
+
}
|
|
287
|
+
Some(symbol) => {
|
|
323
288
|
context.cr()?;
|
|
324
289
|
if write_li {
|
|
325
290
|
context.write_str("<li")?;
|
|
@@ -333,16 +298,72 @@ fn render_task_item<'a>(
|
|
|
333
298
|
context.escape(&symbol.to_string())?;
|
|
334
299
|
context.write_str("] ")?;
|
|
335
300
|
}
|
|
336
|
-
} else if write_li {
|
|
337
|
-
context.write_str("</li>\n")?;
|
|
338
301
|
}
|
|
339
302
|
|
|
340
303
|
Ok(ChildRendering::HTML)
|
|
341
304
|
}
|
|
342
305
|
|
|
343
|
-
|
|
306
|
+
#[derive(PartialEq)]
|
|
307
|
+
enum TaskItemState {
|
|
308
|
+
Checked,
|
|
309
|
+
Unchecked,
|
|
310
|
+
Inapplicable,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fn render_task_item_with(
|
|
314
|
+
context: &mut Context<RenderUserData>,
|
|
315
|
+
node: Node<'_>,
|
|
316
|
+
nti: &NodeTaskItem,
|
|
317
|
+
state: TaskItemState,
|
|
318
|
+
write_li: bool,
|
|
319
|
+
) -> fmt::Result {
|
|
320
|
+
context.cr()?;
|
|
321
|
+
if write_li {
|
|
322
|
+
context.write_str("<li")?;
|
|
323
|
+
if context.options.render.tasklist_classes {
|
|
324
|
+
context.write_str(" class=\"")?;
|
|
325
|
+
if state == TaskItemState::Inapplicable {
|
|
326
|
+
context.write_str("inapplicable ")?;
|
|
327
|
+
}
|
|
328
|
+
context.write_str("task-list-item\"")?;
|
|
329
|
+
}
|
|
330
|
+
render_sourcepos(context, node)?;
|
|
331
|
+
context.write_str(">")?;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
context.write_str("<input type=\"checkbox\"")?;
|
|
335
|
+
if !write_li {
|
|
336
|
+
html::render_sourcepos(context, node)?;
|
|
337
|
+
}
|
|
338
|
+
if context.options.render.sourcepos {
|
|
339
|
+
write!(
|
|
340
|
+
context,
|
|
341
|
+
" data-checkbox-sourcepos=\"{:?}\"",
|
|
342
|
+
nti.symbol_sourcepos
|
|
343
|
+
)?;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if context.options.render.tasklist_classes {
|
|
347
|
+
context.write_str(" class=\"task-list-item-checkbox\"")?;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
match state {
|
|
351
|
+
TaskItemState::Checked => {
|
|
352
|
+
context.write_str(" checked=\"\"")?;
|
|
353
|
+
}
|
|
354
|
+
TaskItemState::Unchecked => {}
|
|
355
|
+
TaskItemState::Inapplicable => {
|
|
356
|
+
context.write_str(" data-inapplicable")?;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
context.write_str(" disabled=\"\" /> ")?;
|
|
360
|
+
|
|
361
|
+
Ok(())
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
fn render_escaped(
|
|
344
365
|
context: &mut Context<RenderUserData>,
|
|
345
|
-
node:
|
|
366
|
+
node: Node<'_>,
|
|
346
367
|
entering: bool,
|
|
347
368
|
) -> Result<ChildRendering, fmt::Error> {
|
|
348
369
|
if !context.options.render.escaped_char_spans {
|
|
@@ -353,7 +374,7 @@ fn render_escaped<'a>(
|
|
|
353
374
|
.user
|
|
354
375
|
.only_escape_chars
|
|
355
376
|
.as_ref()
|
|
356
|
-
.
|
|
377
|
+
.is_none_or(|only_escape_chars| {
|
|
357
378
|
with_node_text_content(node, false, |content| {
|
|
358
379
|
content.chars().count() == 1
|
|
359
380
|
&& only_escape_chars.contains(&content.chars().next().unwrap())
|
|
@@ -372,9 +393,9 @@ fn render_escaped<'a>(
|
|
|
372
393
|
Ok(ChildRendering::HTML)
|
|
373
394
|
}
|
|
374
395
|
|
|
375
|
-
fn render_heading
|
|
396
|
+
fn render_heading(
|
|
376
397
|
context: &mut Context<RenderUserData>,
|
|
377
|
-
node:
|
|
398
|
+
node: Node<'_>,
|
|
378
399
|
entering: bool,
|
|
379
400
|
nh: &NodeHeading,
|
|
380
401
|
) -> Result<ChildRendering, fmt::Error> {
|
|
@@ -415,7 +436,7 @@ fn render_heading<'a>(
|
|
|
415
436
|
|
|
416
437
|
/// If the given node has a single text child, apply a function to the text content
|
|
417
438
|
/// of that node. Otherwise, return the given default value.
|
|
418
|
-
fn with_node_text_content<
|
|
439
|
+
fn with_node_text_content<U, F>(node: Node<'_>, default: U, f: F) -> U
|
|
419
440
|
where
|
|
420
441
|
F: FnOnce(&str) -> U,
|
|
421
442
|
{
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.40
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brett Walker
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2025-
|
|
12
|
+
date: 2025-12-10 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rb_sys
|