• R/O
  • HTTP
  • SSH
  • HTTPS

提交

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

A Markdown shard for the Crystal programming language


Commit MetaInfo

修訂f00309f94962bce86a760ecd7c103afa79ed9010 (tree)
時間2023-12-28 12:19:51
作者supercell <stigma@disr...>
Commitersupercell

Log Message

Add support for AlertBlockSyntaxes

Change Summary

差異

--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ All notable changes to Luce will be documented in this file.
55 ## [Unreleased]
66
77 * Support for [footnotes](https://pandoc.org/MANUAL.html#footnotes).
8+* Support for [Github Alerts][gh-alerts]
89 * Tables are now able to interrupt other blocks.
910 * Allow Element attributes to have no value ([#6][issue-6] - thanks [solvin]!).
1011 * Fix an infinite loop occurring when using a link in table headers.
@@ -16,6 +17,7 @@ All notable changes to Luce will be documented in this file.
1617 ([#3][issue-3] - thanks [solvin]!)
1718 * Fix crash in Crystal >=1.8 caused by exceeding the Regex Match Limit.
1819
20+[gh-alerts]: https://docs.github.com/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
1921 [issue-3]: https://codeberg.org/supercell/luce/pulls/3
2022 [issue-6]: https://codeberg.org/supercell/luce/pulls/6
2123 [solvin]: https://codeberg.org/solvin
--- /dev/null
+++ b/spec/extensions/alert_extension.unit
@@ -0,0 +1,94 @@
1+>>> type note
2+> [!NoTe]
3+> Test note alert.
4+<<<
5+<div class="markdown-alert markdown-alert-note">
6+<p class="markdown-alert-title">Note</p>
7+<p>Test note alert.</p>
8+</div>
9+>>> type tip
10+> [!TiP]
11+> Test tip alert.
12+<<<
13+<div class="markdown-alert markdown-alert-tip">
14+<p class="markdown-alert-title">Tip</p>
15+<p>Test tip alert.</p>
16+</div>
17+>>> type important
18+> [!ImpoRtanT]
19+> Test important alert.
20+<<<
21+<div class="markdown-alert markdown-alert-important">
22+<p class="markdown-alert-title">Important</p>
23+<p>Test important alert.</p>
24+</div>
25+>>> type warning
26+> [!WarNinG]
27+> Test warning alert.
28+<<<
29+<div class="markdown-alert markdown-alert-warning">
30+<p class="markdown-alert-title">Warning</p>
31+<p>Test warning alert.</p>
32+</div>
33+>>> type caution
34+> [!CauTioN]
35+> Test caution alert.
36+<<<
37+<div class="markdown-alert markdown-alert-caution">
38+<p class="markdown-alert-title">Caution</p>
39+<p>Test caution alert.</p>
40+</div>
41+>>> invalid type
42+> [!foo]
43+> Test foo alert.
44+<<<
45+<blockquote>
46+<p>[!foo]
47+Test foo alert.</p>
48+</blockquote>
49+>>> contents can both contain/not contain starting quote
50+> [!NOTE]
51+Test note alert.
52+>Test note alert x2.
53+<<<
54+<div class="markdown-alert markdown-alert-note">
55+<p class="markdown-alert-title">Note</p>
56+<p>Test note alert.
57+Test note alert x2.</p>
58+</div>
59+>>> spaces everywhere
60+ > [!NOTE]
61+> Test note alert.
62+ > Test note alert x2.
63+<<<
64+<div class="markdown-alert markdown-alert-note">
65+<p class="markdown-alert-title">Note</p>
66+<p>Test note alert.
67+Test note alert x2.</p>
68+</div>
69+>>> title has 3 more spaces then fallback to blockquote
70+> [!NOTE]
71+> Test blockquote.
72+<<<
73+<blockquote>
74+<p>[!NOTE]
75+Test blockquote.</p>
76+</blockquote>
77+>>>nested blockquote
78+> [!NOTE]
79+>> Test nested blockquote.
80+<<<
81+<div class="markdown-alert markdown-alert-note">
82+<p class="markdown-alert-title">Note</p>
83+<blockquote>
84+<p>Test nested blockquote.</p>
85+</blockquote>
86+</div>
87+>>>escape brackets
88+> \[!note\]
89+> Test escape brackets.
90+<<<
91+<div class="markdown-alert markdown-alert-note">
92+<p class="markdown-alert-title">Note</p>
93+<p>Test escape brackets.</p>
94+</div>
--- a/spec/luce_spec.cr
+++ b/spec/luce_spec.cr
@@ -97,6 +97,8 @@ test_file "extensions/unordered_list_with_checkboxes.unit",
9797 block_syntaxes: [Luce::UnorderedListWithCheckboxSyntax.new]
9898 test_file "extensions/footnote_block.unit",
9999 block_syntaxes: [Luce::FootnoteDefSyntax.new]
100+test_file "extensions/alert_extension.unit",
101+ block_syntaxes: [Luce::AlertBlockSyntax.new]
100102
101103 # Inline syntax extensions
102104 test_file "extensions/autolink_extension.unit",
--- a/src/luce.cr
+++ b/src/luce.cr
@@ -5,6 +5,7 @@
55 #
66 require "./luce/ast"
77 require "./luce/block_parser"
8+require "./luce/block_syntaxes/alert_block_syntax"
89 require "./luce/block_syntaxes/block_syntax"
910 require "./luce/block_syntaxes/blockquote_syntax"
1011 require "./luce/block_syntaxes/code_block_syntax"
--- /dev/null
+++ b/src/luce/block_syntaxes/alert_block_syntax.cr
@@ -0,0 +1,94 @@
1+#
2+# Copyright (c) 2023 supercell
3+#
4+# SPDX-License-Identifier: BSD-3-Clause
5+#
6+require "./block_syntax"
7+
8+# Parses GitHub Alert blocks
9+#
10+# See also: https://docs.github.com/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
11+module Luce
12+ class AlertBlockSyntax < BlockSyntax
13+ # Whether this alert ends with a lazy continuation line.
14+ #
15+ # The definition of lazy continuation lines:
16+ # https://spec.commonmark.org/0.30/#lazy-continuation-line
17+ @lazy_continuation = false
18+ @content_line_regexp = Regex.new(%q{>?\s?(.*)*})
19+
20+ def pattern : Regex
21+ Luce.alert_pattern
22+ end
23+
24+ def can_parse?(parser : BlockParser) : Bool
25+ pattern.matches?(parser.current.content) && parser.lines.any?(&.content.matches?(@content_line_regexp))
26+ end
27+
28+ def parse_child_lines(parser : BlockParser) : Array(Line)
29+ # Grab all of the lines that form the alert, stripping off the ">".
30+ child_lines = [] of Line
31+ @lazy_continuation = false
32+
33+ until parser.done?
34+ stripped_content = parser.current.content.sub(/^\s*>?\s*/, "")
35+ match = @content_line_regexp.match(stripped_content)
36+ unless match.nil?
37+ child_lines << Line.new(stripped_content)
38+ parser.advance
39+ @lazy_continuation = false
40+ next
41+ end
42+
43+ last_line = child_lines.last
44+
45+ # A paragraph continuation is OK. This is content that cannot be parsed
46+ # as any other syntax except Paragraph, and it doesn't match the bar in
47+ # a Setext Header.
48+ # Because indented code blocks cannot interrupt paragraphs, a line
49+ # matched CodeBlockSyntax is also paragraph continuation text.
50+ other_matched = parser.block_syntaxes.find(&.can_parse?(parser))
51+ if (other_matched.is_a?(ParagraphSyntax) && !last_line.is_blank_line? && !Luce.code_fence_pattern.matches?(last_line.content)) ||
52+ (other_matched.is_a?(CodeBlockSyntax) && !Luce.indent_pattern.matches?(last_line.content))
53+ child_lines << parser.current
54+ @lazy_continuation = true
55+ parser.advance
56+ else
57+ break
58+ end
59+ end
60+
61+ child_lines
62+ end
63+
64+ def parse(parser : BlockParser) : Node?
65+ # Parse the alert type from the first line.
66+ type = pattern.match(parser.current.content).not_nil![1].not_nil!.downcase
67+ parser.advance
68+ child_lines = parse_child_lines(parser)
69+ # Recursively parse the contents of the alert.
70+ children = BlockParser.new(child_lines, parser.document).parse_lines(
71+ # The setext heading underline cannot be a lazy continuation line in a
72+ # block quote.
73+ # https://spec.commonmark.org/0.30/#example-93
74+ disable_setext_heading: @lazy_continuation,
75+ parent_syntax: self)
76+
77+ # Mapping the alert title text.
78+ type_text_map = {
79+ "note" => "Note",
80+ "tip" => "Tip",
81+ "important" => "Important",
82+ "caution" => "Caution",
83+ "warning" => "Warning",
84+ }
85+ title_text = type_text_map[type]
86+ title_element = Element.new("p", [Text.new(title_text)] of Node)
87+ title_element.attributes["class"] = "markdown-alert-title"
88+ element_class = "markdown-alert markdown-alert-#{type.downcase}"
89+ element = Element.new("div", [title_element] + children)
90+ element.attributes["class"] = element_class
91+ element
92+ end
93+ end
94+end
--- a/src/luce/extension_set.cr
+++ b/src/luce/extension_set.cr
@@ -44,6 +44,7 @@ module Luce
4444 UnorderedListWithCheckboxSyntax.new,
4545 OrderedListWithCheckboxSyntax.new,
4646 FootnoteDefSyntax.new,
47+ AlertBlockSyntax.new,
4748 ],
4849 [
4950 InlineHTMLSyntax.new,
--- a/src/luce/patterns.cr
+++ b/src/luce/patterns.cr
@@ -85,8 +85,13 @@ module Luce
8585 @@html_characters_pattern = Regex.new("&(?:([a-z0-9]+)|#([0-9]{1,7})|#x([a-f0-9]{1,6}));",
8686 Regex::Options::IGNORE_CASE)
8787
88+ # :nodoc:
8889 @@link_reference_definition_pattern = Regex.new(%q{^[ ]{0,3}\[})
8990
91+ # :nodoc:
92+ @@alert_pattern = Regex.new(%q{^\s{0,3}>\s{0,3}\\?\[!(note|tip|important|caution|warning)\\?\]\s*$},
93+ Regex::Options::IGNORE_CASE)
94+
9095 # The line contains only whitespace or is empty
9196 def self.empty_pattern : Regex
9297 @@empty_pattern
@@ -197,4 +202,12 @@ module Luce
197202 def self.link_reference_definition_pattern : Regex
198203 @@link_reference_definition_pattern
199204 end
205+
206+ # Alert type patterns.
207+ # A alert block is similar to a blockquote,
208+ # starts with `> [!TYPE]`, and only 5 types are supported
209+ # with case-insensitive.
210+ def self.alert_pattern : Regex
211+ @@alert_pattern
212+ end
200213 end