markdown_it_ruby 0.1.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 +171 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Cargo.lock +1255 -0
- data/Cargo.toml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +37 -0
- data/Rakefile +22 -0
- data/ext/markdown_it_ruby/Cargo.toml +18 -0
- data/ext/markdown_it_ruby/extconf.rb +6 -0
- data/ext/markdown_it_ruby/src/driver/options.rs +298 -0
- data/ext/markdown_it_ruby/src/driver.rs +56 -0
- data/ext/markdown_it_ruby/src/extensions/heading_level_modification.rs +139 -0
- data/ext/markdown_it_ruby/src/extensions/link_with_target.rs +473 -0
- data/ext/markdown_it_ruby/src/extensions/table_decoration.rs +88 -0
- data/ext/markdown_it_ruby/src/extensions.rs +18 -0
- data/ext/markdown_it_ruby/src/lib.rs +35 -0
- data/lib/markdown_it_ruby/version.rb +5 -0
- data/lib/markdown_it_ruby.rb +15 -0
- data/sig/markdown_it_ruby.rbs +4 -0
- metadata +78 -0
@@ -0,0 +1,473 @@
|
|
1
|
+
use markdown_it::parser::core::CoreRule;
|
2
|
+
use markdown_it::plugins::cmark::inline::{autolink::Autolink, link::Link};
|
3
|
+
use markdown_it::plugins::extra::linkify::Linkified;
|
4
|
+
use markdown_it::{MarkdownIt, Node, NodeValue, Renderer};
|
5
|
+
|
6
|
+
use crate::driver::{InternalDomain, MarkdonwItOptions};
|
7
|
+
|
8
|
+
#[derive(Debug)]
|
9
|
+
pub struct LinkWithTarget {
|
10
|
+
pub url: String,
|
11
|
+
pub title: Option<String>,
|
12
|
+
pub target: Option<String>,
|
13
|
+
pub rel: Option<String>,
|
14
|
+
}
|
15
|
+
|
16
|
+
impl NodeValue for LinkWithTarget {
|
17
|
+
fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
|
18
|
+
let mut attrs = node.attrs.clone();
|
19
|
+
attrs.push(("href", self.url.clone()));
|
20
|
+
|
21
|
+
if let Some(target) = self.target.as_ref() {
|
22
|
+
attrs.push(("target", target.to_string()));
|
23
|
+
}
|
24
|
+
if let Some(rel) = self.rel.as_ref() {
|
25
|
+
attrs.push(("rel", rel.to_string()));
|
26
|
+
}
|
27
|
+
|
28
|
+
if let Some(title) = &self.title {
|
29
|
+
attrs.push(("title", title.clone()));
|
30
|
+
}
|
31
|
+
|
32
|
+
fmt.open("a", &attrs);
|
33
|
+
fmt.contents(&node.children);
|
34
|
+
fmt.close("a");
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
impl LinkWithTarget {
|
39
|
+
pub fn new(
|
40
|
+
url: String,
|
41
|
+
title: Option<String>,
|
42
|
+
internal_domain_name: Option<InternalDomain>,
|
43
|
+
) -> Self {
|
44
|
+
// for internal links only
|
45
|
+
if let Some(internal_domain_name) = internal_domain_name {
|
46
|
+
if internal_domain_name.matches(&url) {
|
47
|
+
return LinkWithTarget {
|
48
|
+
url,
|
49
|
+
title,
|
50
|
+
target: None,
|
51
|
+
rel: None,
|
52
|
+
};
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
// open external links in a new tab
|
57
|
+
LinkWithTarget {
|
58
|
+
url,
|
59
|
+
title,
|
60
|
+
target: Some("_blank".to_string()),
|
61
|
+
rel: Some("noopener noreferrer".to_string()),
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
struct LinkTargetRule;
|
67
|
+
|
68
|
+
impl CoreRule for LinkTargetRule {
|
69
|
+
// a custom function that will be invoked once per document.
|
70
|
+
fn run(root: &mut Node, md: &MarkdownIt) {
|
71
|
+
let options = md.ext.get::<MarkdonwItOptions>();
|
72
|
+
let internal_domain = match options {
|
73
|
+
None => None,
|
74
|
+
Some(options) => options.internal_domain(),
|
75
|
+
};
|
76
|
+
|
77
|
+
// walk through AST recursively
|
78
|
+
root.walk_mut(|node, _| {
|
79
|
+
if let Some(link) = node.cast::<Link>() {
|
80
|
+
let link_with_target = LinkWithTarget::new(
|
81
|
+
link.url.clone(),
|
82
|
+
link.title.clone(),
|
83
|
+
internal_domain.clone(),
|
84
|
+
);
|
85
|
+
node.replace::<LinkWithTarget>(link_with_target);
|
86
|
+
} else if let Some(autolink) = node.cast::<Autolink>() {
|
87
|
+
let link_with_target =
|
88
|
+
LinkWithTarget::new(autolink.url.clone(), None, internal_domain.clone());
|
89
|
+
node.replace::<LinkWithTarget>(link_with_target);
|
90
|
+
} else if let Some(linkified) = node.cast::<Linkified>() {
|
91
|
+
let link_with_target =
|
92
|
+
LinkWithTarget::new(linkified.url.clone(), None, internal_domain.clone());
|
93
|
+
node.replace::<LinkWithTarget>(link_with_target);
|
94
|
+
}
|
95
|
+
});
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
pub fn add(md: &mut MarkdownIt) {
|
100
|
+
md.add_rule::<LinkTargetRule>();
|
101
|
+
}
|
102
|
+
|
103
|
+
#[test]
|
104
|
+
fn test_link_with_target() {
|
105
|
+
use std::collections::HashMap;
|
106
|
+
|
107
|
+
let mut md = MarkdownIt::new();
|
108
|
+
|
109
|
+
markdown_it::plugins::cmark::add(&mut md);
|
110
|
+
markdown_it::plugins::extra::add(&mut md);
|
111
|
+
add(&mut md);
|
112
|
+
|
113
|
+
{
|
114
|
+
// without options
|
115
|
+
{
|
116
|
+
// for non-autolink (custom links)
|
117
|
+
{
|
118
|
+
// with scheme part
|
119
|
+
{
|
120
|
+
// with base_url that has scheme part
|
121
|
+
let src = "[some title](https://kyoto.dosue.jp/foo/bar)";
|
122
|
+
let html = md.parse(src).render();
|
123
|
+
assert_eq!(
|
124
|
+
html,
|
125
|
+
"<p><a href=\"https://kyoto.dosue.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n"
|
126
|
+
);
|
127
|
+
}
|
128
|
+
{
|
129
|
+
// with base_url with http scheme
|
130
|
+
let src = "[some title](http://kyoto.dosue.jp/foo/bar)";
|
131
|
+
let html = md.parse(src).render();
|
132
|
+
assert_eq!(
|
133
|
+
html,
|
134
|
+
"<p><a href=\"http://kyoto.dosue.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n"
|
135
|
+
);
|
136
|
+
}
|
137
|
+
{
|
138
|
+
// with subdomain of base_url
|
139
|
+
let src = "[some title](https://fuji.kyoto.dosue.jp/foo/bar)";
|
140
|
+
let html = md.parse(src).render();
|
141
|
+
assert_eq!(
|
142
|
+
html,
|
143
|
+
"<p><a href=\"https://fuji.kyoto.dosue.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n"
|
144
|
+
);
|
145
|
+
}
|
146
|
+
{
|
147
|
+
// with 'parent' domain of base_url
|
148
|
+
// this should be treated as external link
|
149
|
+
let src = "[some title](https://dosue.jp/foo/bar)";
|
150
|
+
let html = md.parse(src).render();
|
151
|
+
assert_eq!(
|
152
|
+
html,
|
153
|
+
"<p><a href=\"https://dosue.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n"
|
154
|
+
);
|
155
|
+
}
|
156
|
+
{
|
157
|
+
// with different domain
|
158
|
+
let src = "[some title](https://nara.jp/foo/bar)";
|
159
|
+
let html = md.parse(src).render();
|
160
|
+
assert_eq!(
|
161
|
+
html,
|
162
|
+
"<p><a href=\"https://nara.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n"
|
163
|
+
);
|
164
|
+
}
|
165
|
+
}
|
166
|
+
{
|
167
|
+
// without sheme part
|
168
|
+
{
|
169
|
+
// domain part is the same as base_url
|
170
|
+
let src = "[some title](kyoto.dosue.jp/foo/bar)";
|
171
|
+
let html = md.parse(src).render();
|
172
|
+
assert_eq!(
|
173
|
+
html,
|
174
|
+
"<p><a href=\"kyoto.dosue.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n"
|
175
|
+
);
|
176
|
+
}
|
177
|
+
{
|
178
|
+
// domain part is different from base_url
|
179
|
+
// NOTE: for simplicity, any url without scheme part is treated as "internal" link
|
180
|
+
let src = "[some title](dosue.jp/foo/bar)";
|
181
|
+
let html = md.parse(src).render();
|
182
|
+
assert_eq!(html, "<p><a href=\"dosue.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n");
|
183
|
+
}
|
184
|
+
{
|
185
|
+
// without domain part
|
186
|
+
let src = "[some title](foo/bar)";
|
187
|
+
let html = md.parse(src).render();
|
188
|
+
assert_eq!(html, "<p><a href=\"foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n");
|
189
|
+
}
|
190
|
+
{
|
191
|
+
// with filename
|
192
|
+
let src = "[some title](foo.jpg)";
|
193
|
+
let html = md.parse(src).render();
|
194
|
+
assert_eq!(html, "<p><a href=\"foo.jpg\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n");
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
198
|
+
{
|
199
|
+
// for autolink urls
|
200
|
+
{
|
201
|
+
// with base_url
|
202
|
+
let src = "<https://kyoto.dosue.jp>";
|
203
|
+
let html = md.parse(src).render();
|
204
|
+
assert_eq!(
|
205
|
+
html,
|
206
|
+
"<p><a href=\"https://kyoto.dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">kyoto.dosue.jp</a></p>\n"
|
207
|
+
);
|
208
|
+
}
|
209
|
+
{
|
210
|
+
// with child domain of base_url
|
211
|
+
let src = "<https://pontocho.kyoto.dosue.jp>";
|
212
|
+
let html = md.parse(src).render();
|
213
|
+
assert_eq!(
|
214
|
+
html,
|
215
|
+
"<p><a href=\"https://pontocho.kyoto.dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">pontocho.kyoto.dosue.jp</a></p>\n"
|
216
|
+
);
|
217
|
+
}
|
218
|
+
{
|
219
|
+
// with parent domain of base_url
|
220
|
+
let src = "<https://dosue.jp>";
|
221
|
+
let html = md.parse(src).render();
|
222
|
+
assert_eq!(
|
223
|
+
html,
|
224
|
+
"<p><a href=\"https://dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">dosue.jp</a></p>\n"
|
225
|
+
);
|
226
|
+
}
|
227
|
+
{
|
228
|
+
// with base_url, with http scheme
|
229
|
+
let src = "<http://kyoto.dosue.jp>";
|
230
|
+
let html = md.parse(src).render();
|
231
|
+
assert_eq!(
|
232
|
+
html,
|
233
|
+
"<p><a href=\"http://kyoto.dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">kyoto.dosue.jp</a></p>\n"
|
234
|
+
);
|
235
|
+
}
|
236
|
+
{
|
237
|
+
// external links
|
238
|
+
let src = "<https://nara.jp>";
|
239
|
+
let html = md.parse(src).render();
|
240
|
+
assert_eq!(html, "<p><a href=\"https://nara.jp\" target=\"_blank\" rel=\"noopener noreferrer\">nara.jp</a></p>\n");
|
241
|
+
}
|
242
|
+
}
|
243
|
+
{
|
244
|
+
// for linkified urls
|
245
|
+
{
|
246
|
+
// with base_url
|
247
|
+
let src = "https://kyoto.dosue.jp";
|
248
|
+
let html = md.parse(src).render();
|
249
|
+
assert_eq!(
|
250
|
+
html,
|
251
|
+
"<p><a href=\"https://kyoto.dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">kyoto.dosue.jp</a></p>\n"
|
252
|
+
);
|
253
|
+
}
|
254
|
+
{
|
255
|
+
// with child domain of base_url
|
256
|
+
let src = "https://pontocho.kyoto.dosue.jp";
|
257
|
+
let html = md.parse(src).render();
|
258
|
+
assert_eq!(
|
259
|
+
html,
|
260
|
+
"<p><a href=\"https://pontocho.kyoto.dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">pontocho.kyoto.dosue.jp</a></p>\n"
|
261
|
+
);
|
262
|
+
}
|
263
|
+
{
|
264
|
+
// with parent domain of base_url
|
265
|
+
let src = "https://dosue.jp";
|
266
|
+
let html = md.parse(src).render();
|
267
|
+
assert_eq!(
|
268
|
+
html,
|
269
|
+
"<p><a href=\"https://dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">dosue.jp</a></p>\n"
|
270
|
+
);
|
271
|
+
}
|
272
|
+
{
|
273
|
+
// with base_url, with http scheme
|
274
|
+
let src = "http://kyoto.dosue.jp";
|
275
|
+
let html = md.parse(src).render();
|
276
|
+
assert_eq!(
|
277
|
+
html,
|
278
|
+
"<p><a href=\"http://kyoto.dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">kyoto.dosue.jp</a></p>\n"
|
279
|
+
);
|
280
|
+
}
|
281
|
+
{
|
282
|
+
// external links
|
283
|
+
let src = "https://nara.jp";
|
284
|
+
let html = md.parse(src).render();
|
285
|
+
assert_eq!(html, "<p><a href=\"https://nara.jp\" target=\"_blank\" rel=\"noopener noreferrer\">nara.jp</a></p>\n");
|
286
|
+
}
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
{
|
291
|
+
// with options
|
292
|
+
let base_url = "https://kyoto.dosue.jp".to_string();
|
293
|
+
let options = MarkdonwItOptions::new(HashMap::from([(
|
294
|
+
"internal_domain_name".to_string(),
|
295
|
+
base_url.to_string(),
|
296
|
+
)]));
|
297
|
+
options.add(&mut md);
|
298
|
+
|
299
|
+
{
|
300
|
+
// for non-autolink (custom links)
|
301
|
+
{
|
302
|
+
// with scheme part
|
303
|
+
{
|
304
|
+
// with base_url that has scheme part
|
305
|
+
let src = "[some title](https://kyoto.dosue.jp/foo/bar)";
|
306
|
+
let html = md.parse(src).render();
|
307
|
+
assert_eq!(
|
308
|
+
html,
|
309
|
+
"<p><a href=\"https://kyoto.dosue.jp/foo/bar\">some title</a></p>\n"
|
310
|
+
);
|
311
|
+
}
|
312
|
+
{
|
313
|
+
// with base_url with http scheme
|
314
|
+
let src = "[some title](http://kyoto.dosue.jp/foo/bar)";
|
315
|
+
let html = md.parse(src).render();
|
316
|
+
assert_eq!(
|
317
|
+
html,
|
318
|
+
"<p><a href=\"http://kyoto.dosue.jp/foo/bar\">some title</a></p>\n"
|
319
|
+
);
|
320
|
+
}
|
321
|
+
{
|
322
|
+
// with subdomain of base_url
|
323
|
+
let src = "[some title](https://fuji.kyoto.dosue.jp/foo/bar)";
|
324
|
+
let html = md.parse(src).render();
|
325
|
+
assert_eq!(
|
326
|
+
html,
|
327
|
+
"<p><a href=\"https://fuji.kyoto.dosue.jp/foo/bar\">some title</a></p>\n"
|
328
|
+
);
|
329
|
+
}
|
330
|
+
{
|
331
|
+
// with 'parent' domain of base_url
|
332
|
+
// this should be treated as external link
|
333
|
+
let src = "[some title](https://dosue.jp/foo/bar)";
|
334
|
+
let html = md.parse(src).render();
|
335
|
+
assert_eq!(
|
336
|
+
html,
|
337
|
+
"<p><a href=\"https://dosue.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n"
|
338
|
+
);
|
339
|
+
}
|
340
|
+
{
|
341
|
+
// with different domain
|
342
|
+
let src = "[some title](https://nara.jp/foo/bar)";
|
343
|
+
let html = md.parse(src).render();
|
344
|
+
assert_eq!(
|
345
|
+
html,
|
346
|
+
"<p><a href=\"https://nara.jp/foo/bar\" target=\"_blank\" rel=\"noopener noreferrer\">some title</a></p>\n"
|
347
|
+
);
|
348
|
+
}
|
349
|
+
}
|
350
|
+
{
|
351
|
+
// without sheme part
|
352
|
+
{
|
353
|
+
// domain part is the same as base_url
|
354
|
+
let src = "[some title](kyoto.dosue.jp/foo/bar)";
|
355
|
+
let html = md.parse(src).render();
|
356
|
+
assert_eq!(
|
357
|
+
html,
|
358
|
+
"<p><a href=\"kyoto.dosue.jp/foo/bar\">some title</a></p>\n"
|
359
|
+
);
|
360
|
+
}
|
361
|
+
{
|
362
|
+
// domain part is different from base_url
|
363
|
+
// NOTE: for simplicity, any url without scheme part is treated as "internal" link
|
364
|
+
let src = "[some title](dosue.jp/foo/bar)";
|
365
|
+
let html = md.parse(src).render();
|
366
|
+
assert_eq!(html, "<p><a href=\"dosue.jp/foo/bar\">some title</a></p>\n");
|
367
|
+
}
|
368
|
+
{
|
369
|
+
// without domain part
|
370
|
+
let src = "[some title](foo/bar)";
|
371
|
+
let html = md.parse(src).render();
|
372
|
+
assert_eq!(html, "<p><a href=\"foo/bar\">some title</a></p>\n");
|
373
|
+
}
|
374
|
+
{
|
375
|
+
// with filename
|
376
|
+
let src = "[some title](foo.jpg)";
|
377
|
+
let html = md.parse(src).render();
|
378
|
+
assert_eq!(html, "<p><a href=\"foo.jpg\">some title</a></p>\n");
|
379
|
+
}
|
380
|
+
}
|
381
|
+
}
|
382
|
+
{
|
383
|
+
// for autolink urls
|
384
|
+
{
|
385
|
+
// with base_url
|
386
|
+
let src = "<https://kyoto.dosue.jp>";
|
387
|
+
let html = md.parse(src).render();
|
388
|
+
assert_eq!(
|
389
|
+
html,
|
390
|
+
"<p><a href=\"https://kyoto.dosue.jp\">kyoto.dosue.jp</a></p>\n"
|
391
|
+
);
|
392
|
+
}
|
393
|
+
{
|
394
|
+
// with child domain of base_url
|
395
|
+
let src = "<https://pontocho.kyoto.dosue.jp>";
|
396
|
+
let html = md.parse(src).render();
|
397
|
+
assert_eq!(
|
398
|
+
html,
|
399
|
+
"<p><a href=\"https://pontocho.kyoto.dosue.jp\">pontocho.kyoto.dosue.jp</a></p>\n"
|
400
|
+
);
|
401
|
+
}
|
402
|
+
{
|
403
|
+
// with parent domain of base_url
|
404
|
+
let src = "<https://dosue.jp>";
|
405
|
+
let html = md.parse(src).render();
|
406
|
+
assert_eq!(
|
407
|
+
html,
|
408
|
+
"<p><a href=\"https://dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">dosue.jp</a></p>\n"
|
409
|
+
);
|
410
|
+
}
|
411
|
+
{
|
412
|
+
// with base_url, with http scheme
|
413
|
+
let src = "<http://kyoto.dosue.jp>";
|
414
|
+
let html = md.parse(src).render();
|
415
|
+
assert_eq!(
|
416
|
+
html,
|
417
|
+
"<p><a href=\"http://kyoto.dosue.jp\">kyoto.dosue.jp</a></p>\n"
|
418
|
+
);
|
419
|
+
}
|
420
|
+
{
|
421
|
+
// external links
|
422
|
+
let src = "<https://nara.jp>";
|
423
|
+
let html = md.parse(src).render();
|
424
|
+
assert_eq!(html, "<p><a href=\"https://nara.jp\" target=\"_blank\" rel=\"noopener noreferrer\">nara.jp</a></p>\n");
|
425
|
+
}
|
426
|
+
}
|
427
|
+
{
|
428
|
+
// for linkified urls
|
429
|
+
{
|
430
|
+
// with base_url
|
431
|
+
let src = "https://kyoto.dosue.jp";
|
432
|
+
let html = md.parse(src).render();
|
433
|
+
assert_eq!(
|
434
|
+
html,
|
435
|
+
"<p><a href=\"https://kyoto.dosue.jp\">kyoto.dosue.jp</a></p>\n"
|
436
|
+
);
|
437
|
+
}
|
438
|
+
{
|
439
|
+
// with child domain of base_url
|
440
|
+
let src = "https://pontocho.kyoto.dosue.jp";
|
441
|
+
let html = md.parse(src).render();
|
442
|
+
assert_eq!(
|
443
|
+
html,
|
444
|
+
"<p><a href=\"https://pontocho.kyoto.dosue.jp\">pontocho.kyoto.dosue.jp</a></p>\n"
|
445
|
+
);
|
446
|
+
}
|
447
|
+
{
|
448
|
+
// with parent domain of base_url
|
449
|
+
let src = "https://dosue.jp";
|
450
|
+
let html = md.parse(src).render();
|
451
|
+
assert_eq!(
|
452
|
+
html,
|
453
|
+
"<p><a href=\"https://dosue.jp\" target=\"_blank\" rel=\"noopener noreferrer\">dosue.jp</a></p>\n"
|
454
|
+
);
|
455
|
+
}
|
456
|
+
{
|
457
|
+
// with base_url, with http scheme
|
458
|
+
let src = "http://kyoto.dosue.jp";
|
459
|
+
let html = md.parse(src).render();
|
460
|
+
assert_eq!(
|
461
|
+
html,
|
462
|
+
"<p><a href=\"http://kyoto.dosue.jp\">kyoto.dosue.jp</a></p>\n"
|
463
|
+
);
|
464
|
+
}
|
465
|
+
{
|
466
|
+
// external links
|
467
|
+
let src = "https://nara.jp";
|
468
|
+
let html = md.parse(src).render();
|
469
|
+
assert_eq!(html, "<p><a href=\"https://nara.jp\" target=\"_blank\" rel=\"noopener noreferrer\">nara.jp</a></p>\n");
|
470
|
+
}
|
471
|
+
}
|
472
|
+
}
|
473
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
use crate::driver::MarkdonwItOptions;
|
2
|
+
use markdown_it::parser::core::CoreRule;
|
3
|
+
use markdown_it::plugins::extra::tables::Table;
|
4
|
+
use markdown_it::{MarkdownIt, Node, NodeValue, Renderer};
|
5
|
+
|
6
|
+
#[derive(Debug)]
|
7
|
+
pub struct TableDecoration {
|
8
|
+
pub table: Table,
|
9
|
+
pub class_name: String,
|
10
|
+
}
|
11
|
+
|
12
|
+
impl NodeValue for TableDecoration {
|
13
|
+
fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
|
14
|
+
let class_name = self.class_name.clone();
|
15
|
+
let div_attrs = vec![("class", class_name)];
|
16
|
+
fmt.open("div", &div_attrs);
|
17
|
+
fmt.cr();
|
18
|
+
self.table.render(node, fmt);
|
19
|
+
fmt.cr();
|
20
|
+
fmt.close("div");
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
struct TableDecorationRule;
|
25
|
+
|
26
|
+
impl CoreRule for TableDecorationRule {
|
27
|
+
// a custom function that will be invoked once per document.
|
28
|
+
fn run(root: &mut Node, md: &MarkdownIt) {
|
29
|
+
let options = md.ext.get::<MarkdonwItOptions>();
|
30
|
+
let table_class_name = match options {
|
31
|
+
Some(options) => options.get_option_or_default("table_class_name", "table"),
|
32
|
+
None => "table".to_string(),
|
33
|
+
};
|
34
|
+
root.walk_mut(|node, _| {
|
35
|
+
if let Some(table) = node.cast::<Table>() {
|
36
|
+
node.replace(TableDecoration {
|
37
|
+
table: Table {
|
38
|
+
alignments: table.alignments.clone(),
|
39
|
+
},
|
40
|
+
class_name: table_class_name.clone(),
|
41
|
+
})
|
42
|
+
}
|
43
|
+
});
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
pub fn add(md: &mut MarkdownIt) {
|
48
|
+
md.add_rule::<TableDecorationRule>();
|
49
|
+
}
|
50
|
+
|
51
|
+
#[test]
|
52
|
+
fn test_table_decoration() {
|
53
|
+
use std::collections::HashMap;
|
54
|
+
|
55
|
+
let mut md = MarkdownIt::new();
|
56
|
+
|
57
|
+
markdown_it::plugins::cmark::add(&mut md);
|
58
|
+
markdown_it::plugins::extra::add(&mut md);
|
59
|
+
add(&mut md);
|
60
|
+
|
61
|
+
{
|
62
|
+
// without options
|
63
|
+
let src = "| 左寄せタイトル | センタリング | 右寄せタイトル |\n |:------------|:------------:|-----------:|\n | column | column | column |\n";
|
64
|
+
let html = md.parse(src).render();
|
65
|
+
|
66
|
+
assert_eq!(
|
67
|
+
html,
|
68
|
+
"<div class=\"table\">\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">左寄せタイトル</th>\n<th style=\"text-align:center\">センタリング</th>\n<th style=\"text-align:right\">右寄せタイトル</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\">column</td>\n<td style=\"text-align:center\">column</td>\n<td style=\"text-align:right\">column</td>\n</tr>\n</tbody>\n</table>\n</div>"
|
69
|
+
)
|
70
|
+
}
|
71
|
+
|
72
|
+
{
|
73
|
+
// with options
|
74
|
+
let options = MarkdonwItOptions::new(HashMap::from([(
|
75
|
+
"table_class_name".to_string(),
|
76
|
+
"custom-table-class-name".to_string(),
|
77
|
+
)]));
|
78
|
+
options.add(&mut md);
|
79
|
+
|
80
|
+
let src = "| 左寄せタイトル | センタリング | 右寄せタイトル |\n |:------------|:------------:|-----------:|\n | column | column | column |\n";
|
81
|
+
let html = md.parse(src).render();
|
82
|
+
|
83
|
+
assert_eq!(
|
84
|
+
html,
|
85
|
+
"<div class=\"custom-table-class-name\">\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">左寄せタイトル</th>\n<th style=\"text-align:center\">センタリング</th>\n<th style=\"text-align:right\">右寄せタイトル</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\">column</td>\n<td style=\"text-align:center\">column</td>\n<td style=\"text-align:right\">column</td>\n</tr>\n</tbody>\n</table>\n</div>"
|
86
|
+
)
|
87
|
+
}
|
88
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
pub(super) mod heading_level_modification;
|
2
|
+
pub(super) mod link_with_target;
|
3
|
+
pub(super) mod table_decoration;
|
4
|
+
|
5
|
+
use crate::driver::MarkdonwItOptions;
|
6
|
+
use markdown_it::MarkdownIt;
|
7
|
+
|
8
|
+
pub(super) fn add(md: &mut MarkdownIt, option: &MarkdonwItOptions) {
|
9
|
+
if option.is_enabled("heading_level_offset", true) {
|
10
|
+
heading_level_modification::add(md);
|
11
|
+
}
|
12
|
+
if option.is_enabled("internal_domain_name", true) {
|
13
|
+
link_with_target::add(md);
|
14
|
+
}
|
15
|
+
if option.is_enabled("table_class_name", true) {
|
16
|
+
table_decoration::add(md);
|
17
|
+
}
|
18
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
mod driver;
|
2
|
+
mod extensions;
|
3
|
+
use magnus::{define_module, function, prelude::*, Error};
|
4
|
+
use std::collections::HashMap;
|
5
|
+
|
6
|
+
// macro for regex
|
7
|
+
//
|
8
|
+
// usage:
|
9
|
+
//
|
10
|
+
// use crate::regex;
|
11
|
+
// let domain_regex = regex!(r"^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+/");
|
12
|
+
// if domain_regex.is_match(url) { ...
|
13
|
+
#[macro_export]
|
14
|
+
macro_rules! regex {
|
15
|
+
($re:literal $(,)?) => {{
|
16
|
+
static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
|
17
|
+
RE.get_or_init(|| regex::Regex::new($re).unwrap())
|
18
|
+
}};
|
19
|
+
}
|
20
|
+
|
21
|
+
use driver::MarkdownDriver;
|
22
|
+
|
23
|
+
fn convert(contents: String, options: HashMap<String, String>) -> String {
|
24
|
+
let handler = MarkdownDriver::new(options);
|
25
|
+
handler.parse(contents);
|
26
|
+
handler.render()
|
27
|
+
}
|
28
|
+
|
29
|
+
#[magnus::init]
|
30
|
+
fn init() -> Result<(), Error> {
|
31
|
+
let module = define_module("MarkdownIt")?;
|
32
|
+
module.define_singleton_method("__convert", function!(convert, 2))?;
|
33
|
+
|
34
|
+
Ok(())
|
35
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "markdown_it_ruby/version"
|
4
|
+
require_relative "markdown_it_ruby/markdown_it_ruby"
|
5
|
+
|
6
|
+
module MarkdownIt
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
def self.convert(input, **options)
|
10
|
+
options.transform_keys!(&:to_s)
|
11
|
+
options.transform_values!(&:to_s)
|
12
|
+
|
13
|
+
__convert(input, **options)
|
14
|
+
end
|
15
|
+
end
|