From c3a5e669c1a85d3b0e1b0d598b19e7abbdebac06 Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Thu, 22 Feb 2024 17:31:01 +0100 Subject: [PATCH] Implement `AlertBlockSyntax` using `BlockquoteSyntax` As I understand the alert-block syntax, it is an extension on the orignal blockquote support in markdown. Basically, it's a blockquote that is rendered differently. That means that if alert-block syntax isn't supported a markdown parser will render it as a blockquote. This provides a nice graceful fallback. It also means that if you're implementing it, you really should implement it the same way you implement blockquote parsing. --- lib/markdown.dart | 1 - .../block_syntaxes/alert_block_syntax.dart | 110 ------------------ lib/src/block_syntaxes/blockquote_syntax.dart | 62 ++++++++++ lib/src/extension_set.dart | 2 +- test/extensions/alert_extension.unit | 9 ++ 5 files changed, 72 insertions(+), 112 deletions(-) delete mode 100644 lib/src/block_syntaxes/alert_block_syntax.dart diff --git a/lib/markdown.dart b/lib/markdown.dart index a09e763a..409baaf6 100644 --- a/lib/markdown.dart +++ b/lib/markdown.dart @@ -42,7 +42,6 @@ import 'src/version.dart'; export 'src/ast.dart'; export 'src/block_parser.dart'; -export 'src/block_syntaxes/alert_block_syntax.dart'; export 'src/block_syntaxes/block_syntax.dart'; export 'src/block_syntaxes/blockquote_syntax.dart'; export 'src/block_syntaxes/code_block_syntax.dart'; diff --git a/lib/src/block_syntaxes/alert_block_syntax.dart b/lib/src/block_syntaxes/alert_block_syntax.dart deleted file mode 100644 index 54286040..00000000 --- a/lib/src/block_syntaxes/alert_block_syntax.dart +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import '../ast.dart'; -import '../block_parser.dart'; -import '../line.dart'; -import '../patterns.dart'; -import 'block_syntax.dart'; -import 'code_block_syntax.dart'; -import 'paragraph_syntax.dart'; - -/// Parses GitHub Alerts blocks. -/// -/// 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 -class AlertBlockSyntax extends BlockSyntax { - const AlertBlockSyntax(); - - @override - RegExp get pattern => alertPattern; - - @override - bool canParse(BlockParser parser) { - return pattern.hasMatch(parser.current.content) && - parser.lines.any((line) => _contentLineRegExp.hasMatch(line.content)); - } - - /// Whether this alert ends with a lazy continuation line. - /// - /// The definition of lazy continuation lines: - /// https://spec.commonmark.org/0.30/#lazy-continuation-line - static bool _lazyContinuation = false; - static final _contentLineRegExp = RegExp(r'>?\s?(.*)*'); - - @override - List parseChildLines(BlockParser parser) { - // Grab all of the lines that form the alert, stripping off the ">". - final childLines = []; - _lazyContinuation = false; - - while (!parser.isDone) { - final strippedContent = - parser.current.content.replaceFirst(RegExp(r'^\s*>?\s*'), ''); - final match = strippedContent.isEmpty - ? null - : _contentLineRegExp.firstMatch(strippedContent); - if (match != null) { - childLines.add(Line(strippedContent)); - parser.advance(); - _lazyContinuation = false; - continue; - } - - final lastLine = childLines.last; - - // A paragraph continuation is OK. This is content that cannot be parsed - // as any other syntax except Paragraph, and it doesn't match the bar in - // a Setext header. - // Because indented code blocks cannot interrupt paragraphs, a line - // matched CodeBlockSyntax is also paragraph continuation text. - final otherMatched = - parser.blockSyntaxes.firstWhere((s) => s.canParse(parser)); - if ((otherMatched is ParagraphSyntax && - !lastLine.isBlankLine && - !codeFencePattern.hasMatch(lastLine.content)) || - (otherMatched is CodeBlockSyntax && - !indentPattern.hasMatch(lastLine.content))) { - childLines.add(parser.current); - _lazyContinuation = true; - parser.advance(); - } else { - break; - } - } - - return childLines; - } - - @override - Node parse(BlockParser parser) { - // Parse the alert type from the first line. - final type = - pattern.firstMatch(parser.current.content)!.group(1)!.toLowerCase(); - parser.advance(); - final childLines = parseChildLines(parser); - // Recursively parse the contents of the alert. - final children = BlockParser(childLines, parser.document).parseLines( - // The setext heading underline cannot be a lazy continuation line in a - // block quote. - // https://spec.commonmark.org/0.30/#example-93 - disabledSetextHeading: _lazyContinuation, - parentSyntax: this, - ); - - // Mapping the alert title text. - const typeTextMap = { - 'note': 'Note', - 'tip': 'Tip', - 'important': 'Important', - 'caution': 'Caution', - 'warning': 'Warning', - }; - final titleText = typeTextMap[type]!; - final titleElement = Element('p', [Text(titleText)]) - ..attributes['class'] = 'markdown-alert-title'; - final elementClass = 'markdown-alert markdown-alert-$type'; - return Element('div', [titleElement, ...children]) - ..attributes['class'] = elementClass; - } -} diff --git a/lib/src/block_syntaxes/blockquote_syntax.dart b/lib/src/block_syntaxes/blockquote_syntax.dart index afbe23a0..a4f55377 100644 --- a/lib/src/block_syntaxes/blockquote_syntax.dart +++ b/lib/src/block_syntaxes/blockquote_syntax.dart @@ -97,3 +97,65 @@ class BlockquoteSyntax extends BlockSyntax { return Element('blockquote', children); } } + +/// Parses GitHub Alerts blocks. +/// +/// 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 +class AlertBlockSyntax extends BlockSyntax { + @override + RegExp get pattern => alertPattern; + + const AlertBlockSyntax(); + + @override + List parseChildLines(BlockParser parser) { + return const BlockquoteSyntax().parseChildLines(parser); + } + + @override + Node parse(BlockParser parser) { + // Parse the alert type from the first line. + final type = + pattern.firstMatch(parser.current.content)!.group(1)!.toLowerCase(); + + final childLines = parseChildLines(parser); + // Until we've parse all the child lines, we can't actually know if this is + // a blockquote containing `[!note]` or if this is an alert-block. + // + // This is because `> [!note]` is not a valid alert-block! + final isBlockquote = childLines.length <= 1; + + if (!isBlockquote) { + // Always remove the first line, this is the line that contained the type. + childLines.removeAt(0); + } + + // Recursively parse the contents of the blockquote. + final children = BlockParser(childLines, parser.document).parseLines( + // The setext heading underline cannot be a lazy continuation line in a + // block quote. + // https://spec.commonmark.org/0.30/#example-93 + disabledSetextHeading: BlockquoteSyntax._lazyContinuation, + parentSyntax: this, + ); + + if (isBlockquote) { + return Element('blockquote', children); + } + + // Mapping the alert title text. + const typeTextMap = { + 'note': 'Note', + 'tip': 'Tip', + 'important': 'Important', + 'caution': 'Caution', + 'warning': 'Warning', + }; + final titleText = typeTextMap[type]!; + final titleElement = Element('p', [Text(titleText)]) + ..attributes['class'] = 'markdown-alert-title'; + final elementClass = 'markdown-alert markdown-alert-$type'; + return Element('div', [titleElement, ...children]) + ..attributes['class'] = elementClass; + } +} diff --git a/lib/src/extension_set.dart b/lib/src/extension_set.dart index 58a25d86..2463b462 100644 --- a/lib/src/extension_set.dart +++ b/lib/src/extension_set.dart @@ -1,5 +1,5 @@ -import 'block_syntaxes/alert_block_syntax.dart'; import 'block_syntaxes/block_syntax.dart'; +import 'block_syntaxes/blockquote_syntax.dart'; import 'block_syntaxes/fenced_code_block_syntax.dart'; import 'block_syntaxes/footnote_def_syntax.dart'; import 'block_syntaxes/header_with_id_syntax.dart'; diff --git a/test/extensions/alert_extension.unit b/test/extensions/alert_extension.unit index 0f422b5c..0d7f3385 100644 --- a/test/extensions/alert_extension.unit +++ b/test/extensions/alert_extension.unit @@ -129,3 +129,12 @@ Additional markdown text. with two lines.

Additional markdown text.

+>>> #584 +> [!CAUTION] +> +> some warning +<<< +
+

Caution

+

some warning

+