qiita_marker 0.23.2.0 → 0.23.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +19 -17
- data/README.md +29 -1
- data/ext/qiita_marker/autolink.c +57 -10
- data/ext/qiita_marker/core-extensions.c +2 -0
- data/ext/qiita_marker/html.c +13 -0
- data/ext/qiita_marker/inlines.c +17 -5
- data/ext/qiita_marker/qfm.h +22 -0
- data/ext/qiita_marker/qfm_custom_block.c +265 -0
- data/ext/qiita_marker/qfm_custom_block.h +10 -0
- data/ext/qiita_marker/qfm_mention_no_emphasis.c +37 -0
- data/ext/qiita_marker/qfm_mention_no_emphasis.h +8 -0
- data/ext/qiita_marker/qfm_scanners.c +291 -0
- data/ext/qiita_marker/qfm_scanners.h +20 -0
- data/ext/qiita_marker/qfm_scanners.re +50 -0
- data/lib/qiita_marker/config.rb +7 -2
- data/lib/qiita_marker/renderer/html_renderer.rb +4 -0
- data/lib/qiita_marker/version.rb +1 -1
- data/qiita_marker.gemspec +1 -1
- data/test/test_qfm_autolink_class_name.rb +62 -0
- data/test/test_qfm_code_data_metadata.rb +41 -0
- data/test/test_qfm_custom_block.rb +39 -0
- data/test/test_qfm_mention_no_emphasis.rb +60 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad4193d920fb95f2c24307d15220150746187c0d74a3d470690e318b2d99d0d6
|
4
|
+
data.tar.gz: b5d1fb774bfafe83550bc0beba25fdc9d8cf67612bb3f4818e3d8e83be58cce8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 690f8870c83b4bf02a799dfd6206ef1c893f22acb22c91110b5523dbe0c35c8f5b3f2276926828697f3cc90497ac9a0302fe09c5fd2b33aca21213c586e94c42
|
7
|
+
data.tar.gz: 674aa47fc8e97954c20a98648307cd0c96dfda31e29f975333c3fdd4d8baf2ad059c759f4db7e7bc3dbe652d2085c224b5baa6359065524d64c5092c4c38f00d
|
data/LICENSE.txt
CHANGED
@@ -1,21 +1,23 @@
|
|
1
|
-
|
1
|
+
Copyright (c) 2015 Garen J. Torikian
|
2
|
+
Copyright (c) 2021 Qiita Inc.
|
2
3
|
|
3
|
-
|
4
|
+
MIT License
|
4
5
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
of this software and associated documentation files (the
|
7
|
-
in the Software without restriction, including
|
8
|
-
to use, copy, modify, merge, publish,
|
9
|
-
copies of the Software, and to
|
10
|
-
furnished to do so, subject to
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
11
13
|
|
12
|
-
The above copyright notice and this permission notice shall be
|
13
|
-
copies or substantial portions of the Software.
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
14
16
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
SOFTWARE.
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Qiita Marker
|
2
2
|
|
3
|
-
[![Build Status](https://github.com/increments/qiita-marker/actions/workflows/test.yml/badge.svg)](https://github.com/increments/qiita-marker/actions/workflows/test.yml)
|
3
|
+
[![Build Status](https://github.com/increments/qiita-marker/actions/workflows/test.yml/badge.svg)](https://github.com/increments/qiita-marker/actions/workflows/test.yml) [![Gem Version](https://badge.fury.io/rb/qiita_marker.svg)](https://badge.fury.io/rb/qiita_marker)
|
4
4
|
|
5
5
|
:warning: This library is still in the testing phase. As such, development may be halted.
|
6
6
|
|
@@ -11,6 +11,29 @@ It will be a core module of [Qiita Markdown](https://github.com/increments/qiita
|
|
11
11
|
|
12
12
|
Please see [CommonMarker's Usage](https://github.com/gjtorikian/commonmarker#usage).
|
13
13
|
|
14
|
+
In addition to CommonMarker's options and extensions, the following are available in Qiita Marker.
|
15
|
+
|
16
|
+
### Original options
|
17
|
+
|
18
|
+
#### Parse options
|
19
|
+
|
20
|
+
| Name | Description |
|
21
|
+
| --- | --- |
|
22
|
+
| `:MENTION_NO_EMPHASIS` | Prevent parsing mentions as emphasis. |
|
23
|
+
| `:AUTOLINK_CLASS_NAME` | Append `class="autolink"` to extension's autolinks. |
|
24
|
+
|
25
|
+
#### Render options
|
26
|
+
|
27
|
+
| Name | Description |
|
28
|
+
| --- | --- |
|
29
|
+
| `:CODE_DATA_METADATA` | Use `<code data-metadata>` for fenced code blocks. |
|
30
|
+
| `:MENTION_NO_EMPHASIS` | Prevent parsing mentions as emphasis. |
|
31
|
+
| `:AUTOLINK_CLASS_NAME` | Append `class="autolink"` to extension's autolinks. |
|
32
|
+
|
33
|
+
### Original extensions
|
34
|
+
|
35
|
+
- `:custom_block` - This provides support for customizable blocks.
|
36
|
+
|
14
37
|
## Contributing
|
15
38
|
|
16
39
|
If you have suggestion or modification to this repository, please create an Issue or Pull Request.
|
@@ -45,6 +68,11 @@ $ docker compose run --rm app ./script/bootstrap
|
|
45
68
|
$ docker compose run --rm rake test
|
46
69
|
```
|
47
70
|
|
71
|
+
### Versioning policy
|
72
|
+
|
73
|
+
Qiita Marker follows CommonMarker's updates by merging the upstream changes.
|
74
|
+
The version format is `MAJOR.MINOR.PATCH.FORK`. `MAJOR.MINOR.PATCH` is the same as the version of CommonMarker that Qiita Marker is based on. `FORK` is incremented on each release of Qiita Marker itself and reset to zero when any of `MAJOR.MINOR.PATCH` is bumped.
|
75
|
+
|
48
76
|
## License
|
49
77
|
|
50
78
|
Please see [LICENSE.txt](/LICENSE.txt).
|
data/ext/qiita_marker/autolink.c
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
#include "autolink.h"
|
2
|
+
#include "houdini.h"
|
3
|
+
#include "qfm.h"
|
4
|
+
#include "scanners.h"
|
2
5
|
#include <parser.h>
|
6
|
+
#include <render.h>
|
3
7
|
#include <string.h>
|
4
8
|
#include <utf8.h>
|
5
9
|
|
@@ -9,6 +13,11 @@
|
|
9
13
|
#include <strings.h>
|
10
14
|
#endif
|
11
15
|
|
16
|
+
static void escape_html(cmark_strbuf *dest, const unsigned char *source,
|
17
|
+
bufsize_t length) {
|
18
|
+
houdini_escape_html0(dest, source, length, 0);
|
19
|
+
}
|
20
|
+
|
12
21
|
static int is_valid_hostchar(const uint8_t *link, size_t link_len) {
|
13
22
|
int32_t ch;
|
14
23
|
int r = cmark_utf8proc_iterate(link, (bufsize_t)link_len, &ch);
|
@@ -144,7 +153,8 @@ static size_t check_domain(uint8_t *data, size_t size, int allow_short) {
|
|
144
153
|
}
|
145
154
|
|
146
155
|
static cmark_node *www_match(cmark_parser *parser, cmark_node *parent,
|
147
|
-
cmark_inline_parser *inline_parser
|
156
|
+
cmark_inline_parser *inline_parser,
|
157
|
+
cmark_syntax_extension *ext) {
|
148
158
|
cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser);
|
149
159
|
size_t max_rewind = cmark_inline_parser_get_offset(inline_parser);
|
150
160
|
uint8_t *data = chunk->data + max_rewind;
|
@@ -176,6 +186,9 @@ static cmark_node *www_match(cmark_parser *parser, cmark_node *parent,
|
|
176
186
|
cmark_inline_parser_set_offset(inline_parser, (int)(max_rewind + link_end));
|
177
187
|
|
178
188
|
cmark_node *node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem);
|
189
|
+
if (parser->options & CMARK_OPT_AUTOLINK_CLASS_NAME) {
|
190
|
+
cmark_node_set_syntax_extension(node, ext);
|
191
|
+
}
|
179
192
|
|
180
193
|
cmark_strbuf buf;
|
181
194
|
cmark_strbuf_init(parser->mem, &buf, 10);
|
@@ -199,7 +212,8 @@ static cmark_node *www_match(cmark_parser *parser, cmark_node *parent,
|
|
199
212
|
}
|
200
213
|
|
201
214
|
static cmark_node *url_match(cmark_parser *parser, cmark_node *parent,
|
202
|
-
cmark_inline_parser *inline_parser
|
215
|
+
cmark_inline_parser *inline_parser,
|
216
|
+
cmark_syntax_extension *ext) {
|
203
217
|
size_t link_end, domain_len;
|
204
218
|
int rewind = 0;
|
205
219
|
|
@@ -237,6 +251,9 @@ static cmark_node *url_match(cmark_parser *parser, cmark_node *parent,
|
|
237
251
|
cmark_node_unput(parent, rewind);
|
238
252
|
|
239
253
|
cmark_node *node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem);
|
254
|
+
if (parser->options & CMARK_OPT_AUTOLINK_CLASS_NAME) {
|
255
|
+
cmark_node_set_syntax_extension(node, ext);
|
256
|
+
}
|
240
257
|
|
241
258
|
cmark_chunk url = cmark_chunk_dup(chunk, max_rewind - rewind,
|
242
259
|
(bufsize_t)(link_end + rewind));
|
@@ -257,10 +274,10 @@ static cmark_node *match(cmark_syntax_extension *ext, cmark_parser *parser,
|
|
257
274
|
return NULL;
|
258
275
|
|
259
276
|
if (c == ':')
|
260
|
-
return url_match(parser, parent, inline_parser);
|
277
|
+
return url_match(parser, parent, inline_parser, ext);
|
261
278
|
|
262
279
|
if (c == 'w')
|
263
|
-
return www_match(parser, parent, inline_parser);
|
280
|
+
return www_match(parser, parent, inline_parser, ext);
|
264
281
|
|
265
282
|
return NULL;
|
266
283
|
|
@@ -269,7 +286,8 @@ static cmark_node *match(cmark_syntax_extension *ext, cmark_parser *parser,
|
|
269
286
|
// inline was finished in inlines.c.
|
270
287
|
}
|
271
288
|
|
272
|
-
static void postprocess_text(cmark_parser *parser, cmark_node *text, int offset,
|
289
|
+
static void postprocess_text(cmark_parser *parser, cmark_node *text, int offset,
|
290
|
+
int depth, cmark_syntax_extension *ext) {
|
273
291
|
// postprocess_text can recurse very deeply if there is a very long line of
|
274
292
|
// '@' only. Stop at a reasonable depth to ensure it cannot crash.
|
275
293
|
if (depth > 1000) return;
|
@@ -311,7 +329,7 @@ static void postprocess_text(cmark_parser *parser, cmark_node *text, int offset,
|
|
311
329
|
}
|
312
330
|
|
313
331
|
if (rewind == 0 || ns > 0) {
|
314
|
-
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1);
|
332
|
+
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1, ext);
|
315
333
|
return;
|
316
334
|
}
|
317
335
|
|
@@ -331,20 +349,23 @@ static void postprocess_text(cmark_parser *parser, cmark_node *text, int offset,
|
|
331
349
|
|
332
350
|
if (link_end < 2 || nb != 1 || np == 0 ||
|
333
351
|
(!cmark_isalpha(data[link_end - 1]) && data[link_end - 1] != '.')) {
|
334
|
-
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1);
|
352
|
+
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1, ext);
|
335
353
|
return;
|
336
354
|
}
|
337
355
|
|
338
356
|
link_end = autolink_delim(data, link_end);
|
339
357
|
|
340
358
|
if (link_end == 0) {
|
341
|
-
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1);
|
359
|
+
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1, ext);
|
342
360
|
return;
|
343
361
|
}
|
344
362
|
|
345
363
|
cmark_chunk_to_cstr(parser->mem, &text->as.literal);
|
346
364
|
|
347
365
|
cmark_node *link_node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem);
|
366
|
+
if (parser->options & CMARK_OPT_AUTOLINK_CLASS_NAME) {
|
367
|
+
cmark_node_set_syntax_extension(link_node, ext);
|
368
|
+
}
|
348
369
|
cmark_strbuf buf;
|
349
370
|
cmark_strbuf_init(parser->mem, &buf, 10);
|
350
371
|
cmark_strbuf_puts(&buf, "mailto:");
|
@@ -373,7 +394,7 @@ static void postprocess_text(cmark_parser *parser, cmark_node *text, int offset,
|
|
373
394
|
text->as.literal.len = offset + max_rewind - rewind;
|
374
395
|
text->as.literal.data[text->as.literal.len] = 0;
|
375
396
|
|
376
|
-
postprocess_text(parser, post, 0, depth + 1);
|
397
|
+
postprocess_text(parser, post, 0, depth + 1, ext);
|
377
398
|
}
|
378
399
|
|
379
400
|
static cmark_node *postprocess(cmark_syntax_extension *ext, cmark_parser *parser, cmark_node *root) {
|
@@ -400,7 +421,7 @@ static cmark_node *postprocess(cmark_syntax_extension *ext, cmark_parser *parser
|
|
400
421
|
}
|
401
422
|
|
402
423
|
if (ev == CMARK_EVENT_ENTER && node->type == CMARK_NODE_TEXT) {
|
403
|
-
postprocess_text(parser, node, 0, /*depth*/0);
|
424
|
+
postprocess_text(parser, node, 0, /*depth*/ 0, ext);
|
404
425
|
}
|
405
426
|
}
|
406
427
|
|
@@ -409,12 +430,38 @@ static cmark_node *postprocess(cmark_syntax_extension *ext, cmark_parser *parser
|
|
409
430
|
return root;
|
410
431
|
}
|
411
432
|
|
433
|
+
static void html_render(cmark_syntax_extension *extension,
|
434
|
+
cmark_html_renderer *renderer, cmark_node *node,
|
435
|
+
cmark_event_type ev_type, int options) {
|
436
|
+
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
437
|
+
cmark_strbuf *html = renderer->html;
|
438
|
+
|
439
|
+
if (entering) {
|
440
|
+
cmark_strbuf_puts(html, "<a href=\"");
|
441
|
+
if ((options & CMARK_OPT_UNSAFE) ||
|
442
|
+
!(scan_dangerous_url(&node->as.link.url, 0))) {
|
443
|
+
houdini_escape_href(html, node->as.link.url.data, node->as.link.url.len);
|
444
|
+
}
|
445
|
+
if (node->as.link.title.len) {
|
446
|
+
cmark_strbuf_puts(html, "\" title=\"");
|
447
|
+
escape_html(html, node->as.link.title.data, node->as.link.title.len);
|
448
|
+
}
|
449
|
+
if (options & CMARK_OPT_AUTOLINK_CLASS_NAME) {
|
450
|
+
cmark_strbuf_puts(html, "\" class=\"autolink");
|
451
|
+
}
|
452
|
+
cmark_strbuf_puts(html, "\">");
|
453
|
+
} else {
|
454
|
+
cmark_strbuf_puts(html, "</a>");
|
455
|
+
}
|
456
|
+
}
|
457
|
+
|
412
458
|
cmark_syntax_extension *create_autolink_extension(void) {
|
413
459
|
cmark_syntax_extension *ext = cmark_syntax_extension_new("autolink");
|
414
460
|
cmark_llist *special_chars = NULL;
|
415
461
|
|
416
462
|
cmark_syntax_extension_set_match_inline_func(ext, match);
|
417
463
|
cmark_syntax_extension_set_postprocess_func(ext, postprocess);
|
464
|
+
cmark_syntax_extension_set_html_render_func(ext, html_render);
|
418
465
|
|
419
466
|
cmark_mem *mem = cmark_get_default_mem_allocator();
|
420
467
|
special_chars = cmark_llist_append(mem, special_chars, (void *)':');
|
@@ -6,6 +6,7 @@
|
|
6
6
|
#include "tasklist.h"
|
7
7
|
#include "registry.h"
|
8
8
|
#include "plugin.h"
|
9
|
+
#include "qfm_custom_block.h"
|
9
10
|
|
10
11
|
static int core_extensions_registration(cmark_plugin *plugin) {
|
11
12
|
cmark_plugin_register_syntax_extension(plugin, create_table_extension());
|
@@ -14,6 +15,7 @@ static int core_extensions_registration(cmark_plugin *plugin) {
|
|
14
15
|
cmark_plugin_register_syntax_extension(plugin, create_autolink_extension());
|
15
16
|
cmark_plugin_register_syntax_extension(plugin, create_tagfilter_extension());
|
16
17
|
cmark_plugin_register_syntax_extension(plugin, create_tasklist_extension());
|
18
|
+
cmark_plugin_register_syntax_extension(plugin, create_qfm_custom_block_extension());
|
17
19
|
return 1;
|
18
20
|
}
|
19
21
|
|
data/ext/qiita_marker/html.c
CHANGED
@@ -10,6 +10,7 @@
|
|
10
10
|
#include "syntax_extension.h"
|
11
11
|
#include "html.h"
|
12
12
|
#include "render.h"
|
13
|
+
#include "qfm.h"
|
13
14
|
|
14
15
|
// Functions to convert cmark_nodes to HTML strings.
|
15
16
|
|
@@ -222,6 +223,18 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
|
|
222
223
|
escape_html(html, node->as.code.info.data + first_tag + 1, node->as.code.info.len - first_tag - 1);
|
223
224
|
}
|
224
225
|
cmark_strbuf_puts(html, "\"><code>");
|
226
|
+
} else if (options & CMARK_OPT_CODE_DATA_METADATA) {
|
227
|
+
cmark_strbuf_puts(html, "<pre");
|
228
|
+
cmark_html_render_sourcepos(node, html, options);
|
229
|
+
cmark_strbuf_puts(html, "><code data-metadata=\"");
|
230
|
+
escape_html(html, node->as.code.info.data, node->as.code.info.len);
|
231
|
+
if (first_tag < node->as.code.info.len &&
|
232
|
+
(options & CMARK_OPT_FULL_INFO_STRING)) {
|
233
|
+
cmark_strbuf_puts(html, "\" data-meta=\"");
|
234
|
+
escape_html(html, node->as.code.info.data + first_tag + 1,
|
235
|
+
node->as.code.info.len - first_tag - 1);
|
236
|
+
}
|
237
|
+
cmark_strbuf_puts(html, "\">");
|
225
238
|
} else {
|
226
239
|
cmark_strbuf_puts(html, "<pre");
|
227
240
|
cmark_html_render_sourcepos(node, html, options);
|
data/ext/qiita_marker/inlines.c
CHANGED
@@ -13,6 +13,8 @@
|
|
13
13
|
#include "scanners.h"
|
14
14
|
#include "inlines.h"
|
15
15
|
#include "syntax_extension.h"
|
16
|
+
#include "qfm.h"
|
17
|
+
#include "qfm_mention_no_emphasis.h"
|
16
18
|
|
17
19
|
static const char *EMDASH = "\xE2\x80\x94";
|
18
20
|
static const char *ENDASH = "\xE2\x80\x93";
|
@@ -381,11 +383,10 @@ static cmark_node *handle_backticks(subject *subj, int options) {
|
|
381
383
|
}
|
382
384
|
}
|
383
385
|
|
384
|
-
|
385
386
|
// Scan ***, **, or * and return number scanned, or 0.
|
386
387
|
// Advances position.
|
387
388
|
static int scan_delims(subject *subj, unsigned char c, bool *can_open,
|
388
|
-
bool *can_close) {
|
389
|
+
bool *can_close, bool mention_no_emphasis) {
|
389
390
|
int numdelims = 0;
|
390
391
|
bufsize_t before_char_pos, after_char_pos;
|
391
392
|
int32_t after_char = 0;
|
@@ -393,6 +394,15 @@ static int scan_delims(subject *subj, unsigned char c, bool *can_open,
|
|
393
394
|
int len;
|
394
395
|
bool left_flanking, right_flanking;
|
395
396
|
|
397
|
+
if (mention_no_emphasis &&
|
398
|
+
is_part_of_mention(subj->input.data + subj->pos, subj->pos)) {
|
399
|
+
numdelims++;
|
400
|
+
advance(subj);
|
401
|
+
*can_open = false;
|
402
|
+
*can_close = false;
|
403
|
+
return numdelims;
|
404
|
+
}
|
405
|
+
|
396
406
|
if (subj->pos == 0) {
|
397
407
|
before_char = 10;
|
398
408
|
} else {
|
@@ -528,13 +538,14 @@ static void push_bracket(subject *subj, bool image, cmark_node *inl_text) {
|
|
528
538
|
}
|
529
539
|
|
530
540
|
// Assumes the subject has a c at the current position.
|
531
|
-
static cmark_node *handle_delim(subject *subj, unsigned char c, bool smart
|
541
|
+
static cmark_node *handle_delim(subject *subj, unsigned char c, bool smart,
|
542
|
+
bool mention_no_emphasis) {
|
532
543
|
bufsize_t numdelims;
|
533
544
|
cmark_node *inl_text;
|
534
545
|
bool can_open, can_close;
|
535
546
|
cmark_chunk contents;
|
536
547
|
|
537
|
-
numdelims = scan_delims(subj, c, &can_open, &can_close);
|
548
|
+
numdelims = scan_delims(subj, c, &can_open, &can_close, mention_no_emphasis);
|
538
549
|
|
539
550
|
if (c == '\'' && smart) {
|
540
551
|
contents = cmark_chunk_literal(RIGHTSINGLEQUOTE);
|
@@ -1387,7 +1398,8 @@ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent,
|
|
1387
1398
|
case '_':
|
1388
1399
|
case '\'':
|
1389
1400
|
case '"':
|
1390
|
-
new_inl = handle_delim(subj, c, (options & CMARK_OPT_SMART) != 0
|
1401
|
+
new_inl = handle_delim(subj, c, (options & CMARK_OPT_SMART) != 0,
|
1402
|
+
(options & CMARK_OPT_MENTION_NO_EMPHASIS) != 0);
|
1391
1403
|
break;
|
1392
1404
|
case '-':
|
1393
1405
|
new_inl = handle_hyphen(subj, (options & CMARK_OPT_SMART) != 0);
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#ifndef QFM_H
|
2
|
+
#define QFM_H
|
3
|
+
|
4
|
+
#ifdef __cplusplus
|
5
|
+
extern "C" {
|
6
|
+
#endif
|
7
|
+
|
8
|
+
/** Use <pre><code data-metadata="x"> tags for code blocks instead of <pre><code
|
9
|
+
* class="language-x">. **/
|
10
|
+
#define CMARK_OPT_CODE_DATA_METADATA (1 << 25)
|
11
|
+
|
12
|
+
/* Prevent parsing Qiita-style Mentions as emphasis. */
|
13
|
+
#define CMARK_OPT_MENTION_NO_EMPHASIS (1 << 26)
|
14
|
+
|
15
|
+
/* Render autolinks with class name */
|
16
|
+
#define CMARK_OPT_AUTOLINK_CLASS_NAME (1 << 27)
|
17
|
+
|
18
|
+
#ifdef __cplusplus
|
19
|
+
}
|
20
|
+
#endif
|
21
|
+
|
22
|
+
#endif
|
@@ -0,0 +1,265 @@
|
|
1
|
+
#include <html.h>
|
2
|
+
#include <parser.h>
|
3
|
+
#include <render.h>
|
4
|
+
|
5
|
+
#include "cmark-gfm-core-extensions.h"
|
6
|
+
#include "houdini.h"
|
7
|
+
#include "qfm_custom_block.h"
|
8
|
+
#include "qfm_scanners.h"
|
9
|
+
#include "strikethrough.h"
|
10
|
+
|
11
|
+
cmark_node_type CMARK_NODE_QFM_CUSTOM_BLOCK;
|
12
|
+
|
13
|
+
typedef struct {
|
14
|
+
cmark_chunk info;
|
15
|
+
bool opening;
|
16
|
+
cmark_strbuf *xml_attr_buff;
|
17
|
+
} node_qfm_custom_block;
|
18
|
+
|
19
|
+
static void escape_html(cmark_strbuf *dest, const unsigned char *source,
|
20
|
+
bufsize_t length) {
|
21
|
+
houdini_escape_html0(dest, source, length, 0);
|
22
|
+
}
|
23
|
+
|
24
|
+
static bool get_qfm_custom_block_opening(cmark_node *node) {
|
25
|
+
if (node == NULL) {
|
26
|
+
return false;
|
27
|
+
}
|
28
|
+
|
29
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
30
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
31
|
+
return ((node_qfm_custom_block *)node->as.opaque)->opening;
|
32
|
+
} else {
|
33
|
+
return false;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
static bool set_qfm_custom_block_opening(cmark_node *node, bool opening) {
|
38
|
+
if (node == NULL) {
|
39
|
+
return false;
|
40
|
+
}
|
41
|
+
|
42
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
43
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
44
|
+
((node_qfm_custom_block *)node->as.opaque)->opening = opening;
|
45
|
+
return true;
|
46
|
+
} else {
|
47
|
+
return false;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
static cmark_chunk *get_qfm_custom_block_info(cmark_node *node) {
|
52
|
+
if (node == NULL) {
|
53
|
+
return NULL;
|
54
|
+
}
|
55
|
+
|
56
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
57
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
58
|
+
return &((node_qfm_custom_block *)node->as.opaque)->info;
|
59
|
+
} else {
|
60
|
+
return NULL;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
static bool set_qfm_custom_block_info(cmark_node *node, const char *info) {
|
65
|
+
if (node == NULL) {
|
66
|
+
return false;
|
67
|
+
}
|
68
|
+
|
69
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
70
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
71
|
+
cmark_chunk_set_cstr(cmark_node_mem(node),
|
72
|
+
&((node_qfm_custom_block *)node->as.opaque)->info,
|
73
|
+
info);
|
74
|
+
return true;
|
75
|
+
} else {
|
76
|
+
return false;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
static void free_node_qfm_custom_block(cmark_mem *mem, void *ptr) {
|
81
|
+
node_qfm_custom_block *cb = (node_qfm_custom_block *)ptr;
|
82
|
+
|
83
|
+
cmark_chunk_free(mem, &cb->info);
|
84
|
+
cmark_strbuf_free(cb->xml_attr_buff);
|
85
|
+
mem->free(cb);
|
86
|
+
}
|
87
|
+
|
88
|
+
static int matches(cmark_syntax_extension *self, cmark_parser *parser,
|
89
|
+
unsigned char *input, int len,
|
90
|
+
cmark_node *parent_container) {
|
91
|
+
int res = 0;
|
92
|
+
|
93
|
+
if (get_qfm_custom_block_opening(parent_container)) {
|
94
|
+
bufsize_t matched = scan_close_qfm_custom_block_fence(
|
95
|
+
input, len, cmark_parser_get_first_nonspace(parser));
|
96
|
+
|
97
|
+
if (matched > 0) {
|
98
|
+
set_qfm_custom_block_opening(parent_container, false);
|
99
|
+
cmark_parser_advance_offset(parser, (char *)input,
|
100
|
+
len - cmark_parser_get_offset(parser), 0);
|
101
|
+
} else {
|
102
|
+
res = 1;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
return res;
|
107
|
+
}
|
108
|
+
|
109
|
+
static cmark_node *try_opening_qfm_custom_block_block(
|
110
|
+
cmark_syntax_extension *self, int indented, cmark_parser *parser,
|
111
|
+
cmark_node *parent_container, unsigned char *input, int len) {
|
112
|
+
cmark_node_type parent_type = cmark_node_get_type(parent_container);
|
113
|
+
|
114
|
+
if (!indented && parent_type != CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
115
|
+
bufsize_t matched = scan_open_qfm_custom_block_fence(
|
116
|
+
input, len, cmark_parser_get_first_nonspace(parser));
|
117
|
+
if (!matched) {
|
118
|
+
return NULL;
|
119
|
+
}
|
120
|
+
|
121
|
+
cmark_node *custom_block_node = cmark_parser_add_child(
|
122
|
+
parser, parent_container, CMARK_NODE_QFM_CUSTOM_BLOCK,
|
123
|
+
parser->first_nonspace_column);
|
124
|
+
custom_block_node->as.opaque = (node_qfm_custom_block *)parser->mem->calloc(
|
125
|
+
1, sizeof(node_qfm_custom_block));
|
126
|
+
|
127
|
+
cmark_strbuf *info = parser->mem->calloc(1, sizeof(cmark_strbuf));
|
128
|
+
cmark_strbuf_init(parser->mem, info, len - matched);
|
129
|
+
cmark_strbuf_put(info, input + matched, len - matched);
|
130
|
+
cmark_strbuf_trim(info);
|
131
|
+
|
132
|
+
set_qfm_custom_block_opening(custom_block_node, true);
|
133
|
+
set_qfm_custom_block_info(custom_block_node, (char *)info->ptr);
|
134
|
+
|
135
|
+
cmark_node_set_syntax_extension(custom_block_node, self);
|
136
|
+
cmark_parser_advance_offset(parser, (char *)input,
|
137
|
+
len - cmark_parser_get_offset(parser), 0);
|
138
|
+
|
139
|
+
return custom_block_node;
|
140
|
+
}
|
141
|
+
|
142
|
+
return NULL;
|
143
|
+
}
|
144
|
+
|
145
|
+
static const char *get_type_string(cmark_syntax_extension *self,
|
146
|
+
cmark_node *node) {
|
147
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
148
|
+
|
149
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
150
|
+
return "qfm_custom_block";
|
151
|
+
}
|
152
|
+
|
153
|
+
return "<unknown>";
|
154
|
+
}
|
155
|
+
|
156
|
+
static int can_contain(cmark_syntax_extension *self, cmark_node *node,
|
157
|
+
cmark_node_type child_type) {
|
158
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
159
|
+
|
160
|
+
return node_type == CMARK_NODE_QFM_CUSTOM_BLOCK;
|
161
|
+
}
|
162
|
+
|
163
|
+
static int contains_inlines(cmark_syntax_extension *self, cmark_node *node) {
|
164
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
165
|
+
|
166
|
+
return node_type == CMARK_NODE_QFM_CUSTOM_BLOCK;
|
167
|
+
}
|
168
|
+
|
169
|
+
static void plaintext_render(cmark_syntax_extension *self,
|
170
|
+
cmark_renderer *renderer, cmark_node *node,
|
171
|
+
cmark_event_type ev_type, int options) {
|
172
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
173
|
+
|
174
|
+
if (node_type != CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
175
|
+
assert(false);
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
static void html_render(cmark_syntax_extension *self,
|
180
|
+
cmark_html_renderer *renderer, cmark_node *node,
|
181
|
+
cmark_event_type ev_type, int options) {
|
182
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
183
|
+
|
184
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
185
|
+
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
186
|
+
cmark_strbuf *html = renderer->html;
|
187
|
+
|
188
|
+
if (entering) {
|
189
|
+
cmark_html_render_cr(html);
|
190
|
+
cmark_strbuf_puts(html,
|
191
|
+
"<div data-type=\"customblock\" data-metadata=\"");
|
192
|
+
cmark_chunk *info = get_qfm_custom_block_info(node);
|
193
|
+
escape_html(html, info->data, info->len);
|
194
|
+
cmark_strbuf_putc(html, '"');
|
195
|
+
cmark_html_render_sourcepos(node, html, options);
|
196
|
+
cmark_strbuf_putc(html, '>');
|
197
|
+
} else {
|
198
|
+
cmark_html_render_cr(html);
|
199
|
+
cmark_strbuf_puts(html, "</div>");
|
200
|
+
cmark_html_render_cr(html);
|
201
|
+
}
|
202
|
+
} else {
|
203
|
+
assert(false);
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
static const char *xml_attr(cmark_syntax_extension *self, cmark_node *node) {
|
208
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
209
|
+
|
210
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
211
|
+
cmark_chunk *info = get_qfm_custom_block_info(node);
|
212
|
+
cmark_mem *mem = node->content.mem;
|
213
|
+
|
214
|
+
cmark_strbuf *xml_attr_buff = mem->calloc(1, sizeof(cmark_strbuf));
|
215
|
+
((node_qfm_custom_block *)node->as.opaque)->xml_attr_buff = xml_attr_buff;
|
216
|
+
cmark_strbuf_init(
|
217
|
+
mem, xml_attr_buff,
|
218
|
+
17 + info->len); // `17` is length of ` data-metadata="` and `"`.
|
219
|
+
cmark_strbuf_puts(xml_attr_buff, " data-metadata=\"");
|
220
|
+
cmark_strbuf_puts(xml_attr_buff, (char *)info->data);
|
221
|
+
cmark_strbuf_putc(xml_attr_buff, '"');
|
222
|
+
|
223
|
+
return (char *)xml_attr_buff->ptr;
|
224
|
+
}
|
225
|
+
|
226
|
+
return NULL;
|
227
|
+
}
|
228
|
+
|
229
|
+
static void opaque_alloc(cmark_syntax_extension *self, cmark_mem *mem,
|
230
|
+
cmark_node *node) {
|
231
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
232
|
+
|
233
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
234
|
+
node->as.opaque = mem->calloc(1, sizeof(node_qfm_custom_block));
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
static void opaque_free(cmark_syntax_extension *self, cmark_mem *mem,
|
239
|
+
cmark_node *node) {
|
240
|
+
cmark_node_type node_type = cmark_node_get_type(node);
|
241
|
+
|
242
|
+
if (node_type == CMARK_NODE_QFM_CUSTOM_BLOCK) {
|
243
|
+
free_node_qfm_custom_block(mem, node->as.opaque);
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
cmark_syntax_extension *create_qfm_custom_block_extension(void) {
|
248
|
+
cmark_syntax_extension *self = cmark_syntax_extension_new("custom_block");
|
249
|
+
|
250
|
+
cmark_syntax_extension_set_match_block_func(self, matches);
|
251
|
+
cmark_syntax_extension_set_open_block_func(
|
252
|
+
self, try_opening_qfm_custom_block_block);
|
253
|
+
cmark_syntax_extension_set_get_type_string_func(self, get_type_string);
|
254
|
+
cmark_syntax_extension_set_can_contain_func(self, can_contain);
|
255
|
+
cmark_syntax_extension_set_contains_inlines_func(self, contains_inlines);
|
256
|
+
cmark_syntax_extension_set_commonmark_render_func(self, plaintext_render);
|
257
|
+
cmark_syntax_extension_set_plaintext_render_func(self, plaintext_render);
|
258
|
+
cmark_syntax_extension_set_xml_attr_func(self, xml_attr);
|
259
|
+
cmark_syntax_extension_set_html_render_func(self, html_render);
|
260
|
+
cmark_syntax_extension_set_opaque_alloc_func(self, opaque_alloc);
|
261
|
+
cmark_syntax_extension_set_opaque_free_func(self, opaque_free);
|
262
|
+
CMARK_NODE_QFM_CUSTOM_BLOCK = cmark_syntax_extension_add_node(0);
|
263
|
+
|
264
|
+
return self;
|
265
|
+
}
|