From 32490b2c903a579685e8f053f48a5f9671665d44 Mon Sep 17 00:00:00 2001 From: "Paul.Glesmann" Date: Tue, 18 Feb 2025 17:19:39 +0100 Subject: [PATCH] feat: add Support for converting Material for MkDocs Admonitions to Confluence Info Panels chore: add test files fix: add tests for stripnewline and droph1 chore: rename Admontion to MkDocsAdmonition, remove annoying comments fix: import parser instead of copying the file chore: rename mkDocs renderer function fix: fix bug and pipeline feat: add Support for converting Material for MkDocs Admonitions to Confluence Info Panels fix: add tests for stripnewline and droph1 chore: rename Admontion to MkDocsAdmonition, remove annoying comments fix: import parser instead of copying the file chore: rename mkDocs renderer function fix: fix bug and pipeline chore: remove test for droph1 and stripNewLines fix: add admonitions to StripNewLines and dropH1 tests feat: add Support for converting Material for MkDocs Admonitions to Confluence Info Panels fix: add tests for stripnewline and droph1 chore: rename Admontion to MkDocsAdmonition, remove annoying comments fix: import parser instead of copying the file chore: rename mkDocs renderer function fix: fix bug and pipeline feat: add Support for converting Material for MkDocs Admonitions to Confluence Info Panels chore: rename Admontion to MkDocsAdmonition, remove annoying comments fix: import parser instead of copying the file chore: rename mkDocs renderer function fix: fix bug and pipeline chore: remove test for droph1 and stripNewLines fix: add admonitions to StripNewLines and dropH1 tests feat: add mkdocsadmonition as opt-in renderer and parser fix: fix unit tests --- go.mod | 1 + go.sum | 4 + markdown/markdown.go | 14 +++ markdown/markdown_test.go | 12 +- renderer/mkDocsAdmonition.go | 150 ++++++++++++++++++++++++ testdata/admonitions-droph1.html | 83 +++++++++++++ testdata/admonitions-stripnewlines.html | 83 +++++++++++++ testdata/admonitions.html | 84 +++++++++++++ testdata/admonitions.md | 74 ++++++++++++ util/flags.go | 2 +- 10 files changed, 500 insertions(+), 7 deletions(-) create mode 100644 renderer/mkDocsAdmonition.go create mode 100644 testdata/admonitions-droph1.html create mode 100644 testdata/admonitions-stripnewlines.html create mode 100644 testdata/admonitions.html create mode 100644 testdata/admonitions.md diff --git a/go.mod b/go.mod index 5dead9c..6defd31 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/reconquest/karma-go v1.5.0 github.com/reconquest/pkg v1.3.1-0.20240901105413-68c2adbf2b64 github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4 + github.com/stefanfritsch/goldmark-admonitions v1.1.1 github.com/stretchr/testify v1.10.0 github.com/urfave/cli-altsrc/v3 v3.0.1 github.com/urfave/cli/v3 v3.3.8 diff --git a/go.sum b/go.sum index c8bdeb4..c4675df 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,10 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stefanfritsch/goldmark-admonitions v1.1.1 h1:SncsICdQrIYYaq02Ta+zyc9gNmMfYqQH2qwLSCJYxA4= +github.com/stefanfritsch/goldmark-admonitions v1.1.1/go.mod h1:cOZK5O0gE6eWfpxTdjGUmeONW2IL9j3Zujv3KlZWlLo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli-altsrc/v3 v3.0.1 h1:v+gHk59syLk8ao9rYybZs43+D5ut/gzj0omqQ1XYl8k= diff --git a/markdown/markdown.go b/markdown/markdown.go index 47635bc..2ae7edc 100644 --- a/markdown/markdown.go +++ b/markdown/markdown.go @@ -2,6 +2,7 @@ package mark import ( "bytes" + "slices" "github.com/kovetskiy/mark/attachment" cparser "github.com/kovetskiy/mark/parser" @@ -9,6 +10,7 @@ import ( "github.com/kovetskiy/mark/stdlib" "github.com/kovetskiy/mark/types" "github.com/reconquest/pkg/log" + mkDocsParser "github.com/stefanfritsch/goldmark-admonitions" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" @@ -56,6 +58,18 @@ func (c *ConfluenceExtension) Extend(m goldmark.Markdown) { util.Prioritized(crenderer.NewConfluenceLinkRenderer(), 100), )) + if slices.Contains(c.MarkConfig.Features, "mkdocsadmonitions") { + m.Parser().AddOptions( + parser.WithBlockParsers( + util.Prioritized(mkDocsParser.NewAdmonitionParser(), 100), + ), + ) + + m.Renderer().AddOptions(renderer.WithNodeRenderers( + util.Prioritized(crenderer.NewConfluenceMkDocsAdmonitionRenderer(), 100), + )) + } + m.Parser().AddOptions(parser.WithInlineParsers( // Must be registered with a higher priority than goldmark's linkParser to make sure goldmark doesn't parse // the tags. diff --git a/markdown/markdown_test.go b/markdown/markdown_test.go index 197f88e..12ea8f8 100644 --- a/markdown/markdown_test.go +++ b/markdown/markdown_test.go @@ -63,7 +63,7 @@ func TestCompileMarkdown(t *testing.T) { D2Scale: 1.0, DropFirstH1: false, StripNewlines: false, - Features: []string{}, + Features: []string{"mkdocsadmonitions"}, } actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg) @@ -93,7 +93,7 @@ func TestCompileMarkdownDropH1(t *testing.T) { } var variant string switch filename { - case "testdata/quotes.md", "testdata/header.md": + case "testdata/quotes.md", "testdata/header.md", "testdata/admonitions.md": variant = "-droph1" default: variant = "" @@ -103,10 +103,10 @@ func TestCompileMarkdownDropH1(t *testing.T) { cfg := types.MarkConfig{ MermaidProvider: "", MermaidScale: 1.0, - D2Scale: 1.0, + D2Scale: 1.0, DropFirstH1: true, StripNewlines: false, - Features: []string{}, + Features: []string{"mkdocsadmonitions"}, } actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg) @@ -137,7 +137,7 @@ func TestCompileMarkdownStripNewlines(t *testing.T) { } var variant string switch filename { - case "testdata/quotes.md", "testdata/codes.md", "testdata/newlines.md", "testdata/macro-include.md": + case "testdata/quotes.md", "testdata/codes.md", "testdata/newlines.md", "testdata/macro-include.md", "testdata/admonitions.md": variant = "-stripnewlines" default: variant = "" @@ -151,7 +151,7 @@ func TestCompileMarkdownStripNewlines(t *testing.T) { D2Scale: 1.0, DropFirstH1: false, StripNewlines: true, - Features: []string{}, + Features: []string{"mkdocsadmonitions"}, } actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg) diff --git a/renderer/mkDocsAdmonition.go b/renderer/mkDocsAdmonition.go new file mode 100644 index 0000000..262801d --- /dev/null +++ b/renderer/mkDocsAdmonition.go @@ -0,0 +1,150 @@ +package renderer + +import ( + "fmt" + + parser "github.com/stefanfritsch/goldmark-admonitions" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/renderer" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/util" +) + +// HeadingAttributeFilter defines attribute names which heading elements can have +var MkDocsAdmonitionAttributeFilter = html.GlobalAttributeFilter + +// A Renderer struct is an implementation of renderer.NodeRenderer that renders +// nodes as (X)HTML. +type ConfluenceMkDocsAdmonitionRenderer struct { + html.Config + LevelMap MkDocsAdmonitionLevelMap +} + +// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer +func NewConfluenceMkDocsAdmonitionRenderer(opts ...html.Option) renderer.NodeRenderer { + return &ConfluenceMkDocsAdmonitionRenderer{ + Config: html.NewConfig(), + LevelMap: nil, + } +} + +// RegisterFuncs implements NodeRenderer.RegisterFuncs. +func (r *ConfluenceMkDocsAdmonitionRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(parser.KindAdmonition, r.renderMkDocsAdmonition) +} + +// Define MkDocsAdmonitionType enum +type MkDocsAdmonitionType int + +const ( + AInfo MkDocsAdmonitionType = iota + ANote + AWarn + ATip + ANone +) + +func (t MkDocsAdmonitionType) String() string { + return []string{"info", "note", "warning", "tip", "none"}[t] +} + +type MkDocsAdmonitionLevelMap map[ast.Node]int + +func (m MkDocsAdmonitionLevelMap) Level(node ast.Node) int { + return m[node] +} + +func ParseMkDocsAdmonitionType(node ast.Node) MkDocsAdmonitionType { + n, ok := node.(*parser.Admonition) + if !ok { + return ANone + } + + switch string(n.AdmonitionClass) { + case "info": + return AInfo + case "note": + return ANote + case "warning": + return AWarn + case "tip": + return ATip + default: + return ANone + } +} + +// GenerateMkDocsAdmonitionLevel walks a given node and returns a map of blockquote levels +func GenerateMkDocsAdmonitionLevel(someNode ast.Node) MkDocsAdmonitionLevelMap { + + // We define state variable that tracks BlockQuote level while we walk the tree + admonitionLevel := 0 + AdmonitionLevelMap := make(map[ast.Node]int) + + rootNode := someNode + for rootNode.Parent() != nil { + rootNode = rootNode.Parent() + } + _ = ast.Walk(rootNode, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + if node.Kind() == ast.KindBlockquote && entering { + AdmonitionLevelMap[node] = admonitionLevel + admonitionLevel += 1 + } + if node.Kind() == ast.KindBlockquote && !entering { + admonitionLevel -= 1 + } + return ast.WalkContinue, nil + }) + return AdmonitionLevelMap +} + +// renderBlockQuote will render a BlockQuote +func (r *ConfluenceMkDocsAdmonitionRenderer) renderMkDocsAdmonition(writer util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + // Initialize BlockQuote level map + n := node.(*parser.Admonition) + if r.LevelMap == nil { + r.LevelMap = GenerateMkDocsAdmonitionLevel(node) + } + + admonitionType := ParseMkDocsAdmonitionType(node) + admonitionLevel := r.LevelMap.Level(node) + + if admonitionLevel == 0 && entering && admonitionType != ANone { + prefix := fmt.Sprintf("true\n", admonitionType) + if _, err := writer.Write([]byte(prefix)); err != nil { + return ast.WalkStop, err + } + if string(n.Title) != "" { + titleHTML := fmt.Sprintf("

%s

\n", string(n.Title)) + if _, err := writer.Write([]byte(titleHTML)); err != nil { + return ast.WalkStop, err + } + } + + return ast.WalkContinue, nil + } + if admonitionLevel == 0 && !entering && admonitionType != ANone { + suffix := "
\n" + if _, err := writer.Write([]byte(suffix)); err != nil { + return ast.WalkStop, err + } + return ast.WalkContinue, nil + } + return r.renderMkDocsAdmon(writer, source, node, entering) +} + +func (r *ConfluenceMkDocsAdmonitionRenderer) renderMkDocsAdmon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + n := node.(*parser.Admonition) + if entering { + if n.Attributes() != nil { + _, _ = w.WriteString("') + } else { + _, _ = w.WriteString("
\n") + } + } else { + _, _ = w.WriteString("
\n") + } + return ast.WalkContinue, nil +} diff --git a/testdata/admonitions-droph1.html b/testdata/admonitions-droph1.html new file mode 100644 index 0000000..e33097b --- /dev/null +++ b/testdata/admonitions-droph1.html @@ -0,0 +1,83 @@ +

First Heading

+true +

"NOTES:"

+
    +
  1. Note number one
  2. +
  3. Note number two
  4. +
+true +

a
+b

+
+

Warn (Should not be picked as blockquote type)

+
+

Second Heading

+true +

"Warn"

+
    +
  • Warn bullet 1
  • +
  • Warn bullet 2
  • +
+
+
    +
  • Regular list +that runs long
  • +
+

Third Heading

+true +

Test

+
+

Fourth Heading - Warn should not get picked as block quote

+true +

"TIP:"

+
    +
  1. Note number one
  2. +
  3. Note number two
  4. +
+true +

a
+b

+
+

Warn (Should not be picked as blockquote type)

+
+

Simple Blockquote

+
+

This paragraph is a simple blockquote

+
+

GH Alerts Heading

+

Note Type Alert Heading

+true +
    +
  • Note bullet 1
  • +
  • Note bullet 2
  • +
+
+

Tip Type Alert Heading

+true +
    +
  • Tip bullet 1
  • +
  • Tip bullet 2
  • +
+
+

Warning Type Alert Heading

+true +
    +
  • Warning bullet 1
  • +
  • Warning bullet 2
  • +
+
+

Important/Caution Type Alert Heading

+true +

"[!IMPORTANT]"

+
    +
  • Important bullet 1
  • +
  • Important bullet 2
  • +
+
+true +

"[!CAUTION]"

+
    +
  • Important bullet 1
  • +
  • Important bullet 2
  • +
+
diff --git a/testdata/admonitions-stripnewlines.html b/testdata/admonitions-stripnewlines.html new file mode 100644 index 0000000..78e7a27 --- /dev/null +++ b/testdata/admonitions-stripnewlines.html @@ -0,0 +1,83 @@ +

Main Heading

+

First Heading

+true +

"NOTES:"

+
    +
  1. Note number one
  2. +
  3. Note number two
  4. +
+true +

a
+b

+
+

Warn (Should not be picked as blockquote type)

+
+

Second Heading

+true +

"Warn"

+
    +
  • Warn bullet 1
  • +
  • Warn bullet 2
  • +
+
+
    +
  • Regular list that runs long
  • +
+

Third Heading

+true +

Test

+
+

Fourth Heading - Warn should not get picked as block quote

+true +

"TIP:"

+
    +
  1. Note number one
  2. +
  3. Note number two
  4. +
+true +

a
+b

+
+

Warn (Should not be picked as blockquote type)

+
+

Simple Blockquote

+
+

This paragraph is a simple blockquote

+
+

GH Alerts Heading

+

Note Type Alert Heading

+true +
    +
  • Note bullet 1
  • +
  • Note bullet 2
  • +
+
+

Tip Type Alert Heading

+true +
    +
  • Tip bullet 1
  • +
  • Tip bullet 2
  • +
+
+

Warning Type Alert Heading

+true +
    +
  • Warning bullet 1
  • +
  • Warning bullet 2
  • +
+
+

Important/Caution Type Alert Heading

+true +

"[!IMPORTANT]"

+
    +
  • Important bullet 1
  • +
  • Important bullet 2
  • +
+
+true +

"[!CAUTION]"

+
    +
  • Important bullet 1
  • +
  • Important bullet 2
  • +
+
diff --git a/testdata/admonitions.html b/testdata/admonitions.html new file mode 100644 index 0000000..10e4dfa --- /dev/null +++ b/testdata/admonitions.html @@ -0,0 +1,84 @@ +

Main Heading

+

First Heading

+true +

"NOTES:"

+
    +
  1. Note number one
  2. +
  3. Note number two
  4. +
+true +

a
+b

+
+

Warn (Should not be picked as blockquote type)

+
+

Second Heading

+true +

"Warn"

+
    +
  • Warn bullet 1
  • +
  • Warn bullet 2
  • +
+
+
    +
  • Regular list +that runs long
  • +
+

Third Heading

+true +

Test

+
+

Fourth Heading - Warn should not get picked as block quote

+true +

"TIP:"

+
    +
  1. Note number one
  2. +
  3. Note number two
  4. +
+true +

a
+b

+
+

Warn (Should not be picked as blockquote type)

+
+

Simple Blockquote

+
+

This paragraph is a simple blockquote

+
+

GH Alerts Heading

+

Note Type Alert Heading

+true +
    +
  • Note bullet 1
  • +
  • Note bullet 2
  • +
+
+

Tip Type Alert Heading

+true +
    +
  • Tip bullet 1
  • +
  • Tip bullet 2
  • +
+
+

Warning Type Alert Heading

+true +
    +
  • Warning bullet 1
  • +
  • Warning bullet 2
  • +
+
+

Important/Caution Type Alert Heading

+true +

"[!IMPORTANT]"

+
    +
  • Important bullet 1
  • +
  • Important bullet 2
  • +
+
+true +

"[!CAUTION]"

+
    +
  • Important bullet 1
  • +
  • Important bullet 2
  • +
+
diff --git a/testdata/admonitions.md b/testdata/admonitions.md new file mode 100644 index 0000000..189f7c0 --- /dev/null +++ b/testdata/admonitions.md @@ -0,0 +1,74 @@ +# Main Heading + +## First Heading + +!!! note "NOTES:" + 1. Note number one + 1. Note number two + + !!! note + a + b + + **Warn (Should not be picked as blockquote type)** + +## Second Heading + +!!! warning "Warn" + * Warn bullet 1 + * Warn bullet 2 + +* Regular list + that runs long + +## Third Heading + +!!! info + Test + +## Fourth Heading - Warn should not get picked as block quote + +!!! tip "TIP:" + 1. Note number one + 1. Note number two + + !!! tip + a + b + + **Warn (Should not be picked as blockquote type)** + +## Simple Blockquote + +> This paragraph is a simple blockquote + +## GH Alerts Heading + +### Note Type Alert Heading + +!!! note + * Note bullet 1 + * Note bullet 2 + +### Tip Type Alert Heading + +!!! tip + * Tip bullet 1 + * Tip bullet 2 + +### Warning Type Alert Heading + +!!! warning + * Warning bullet 1 + * Warning bullet 2 + +### Important/Caution Type Alert Heading + +!!! info "[!IMPORTANT]" + * Important bullet 1 + * Important bullet 2 + +!!! warning "[!CAUTION]" + * Important bullet 1 + * Important bullet 2 + diff --git a/util/flags.go b/util/flags.go index 0cf01c6..efd7d3c 100644 --- a/util/flags.go +++ b/util/flags.go @@ -190,7 +190,7 @@ var Flags = []cli.Flag{ &cli.StringSliceFlag{ Name: "features", Value: []string{"mermaid"}, - Usage: "Enables optional features. Current features: d2, mermaid", + Usage: "Enables optional features. Current features: d2, mermaid, mkdocsadmonitions", Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_FEATURES"), altsrctoml.TOML("features", altsrc.NewStringPtrSourcer(&filename))), }, }