From c56a4d3459a8eb7e50f06d714b370e8d7361c0c5 Mon Sep 17 00:00:00 2001 From: Julia Ogris Date: Mon, 26 Aug 2024 16:28:10 +1000 Subject: [PATCH] labsite: Add tooling for Next button Add markdown tooling for Next button generation from [Next] A `
...
` HTMLBlock generated from Markdown also stops when a `[Next]` button is found (in addition to thematic break and heading as end marker). As we were developing this we uncovered a bug around newlines inside code blocks after conversion to HTMLBlock used again as MD. I've snuck in a fix and test for that too. --- build-tools/labsite-gen/main.go | 49 +++++++++++++++-- build-tools/labsite-gen/main_test.go | 78 ++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 5 deletions(-) diff --git a/build-tools/labsite-gen/main.go b/build-tools/labsite-gen/main.go index e3288904..a8a9a31c 100644 --- a/build-tools/labsite-gen/main.go +++ b/build-tools/labsite-gen/main.go @@ -14,7 +14,12 @@ import ( "rsc.io/markdown" ) -const marker = "[>]" +const ( + detailsMarker = "[>]" + nextButtonMarker = "[Next]" +) + +var nextButton = newNextButton() func main() { if len(os.Args) != 3 { @@ -34,6 +39,7 @@ func run(mdFile, htmlfFile string) error { return err } doc := parse(string(md)) + replaceNextButton(doc) doc.Blocks = collapse(doc.Blocks) html := markdown.ToHTML(doc) @@ -45,6 +51,29 @@ func parse(md string) *markdown.Document { return p.Parse(md) } +func replaceNextButton(doc *markdown.Document) { + for i, block := range doc.Blocks { + if isNextButton(block) { + doc.Blocks[i] = nextButton + } + } +} + +func isNextButton(block markdown.Block) bool { + paragraph, ok := block.(*markdown.Paragraph) + if !ok { + return false + } + if len(paragraph.Text.Inline) != 1 { + return false + } + plain, ok := paragraph.Text.Inline[0].(*markdown.Plain) + if !ok { + return false + } + return plain.Text == nextButtonMarker +} + func collapse(blocks []markdown.Block) []markdown.Block { var result []markdown.Block for idx := 0; idx < len(blocks); { @@ -75,19 +104,22 @@ func isCollapsible(block markdown.Block) bool { return false } plain, ok := heading.Text.Inline[0].(*markdown.Plain) - return ok && strings.HasPrefix(plain.Text, marker) + return ok && strings.HasPrefix(plain.Text, detailsMarker) } func deleteCollapseMarker(heading *markdown.Heading) { // assumes isCollapsible returned true plain := heading.Text.Inline[0].(*markdown.Plain) - s := strings.TrimPrefix(plain.Text, marker) + s := strings.TrimPrefix(plain.Text, detailsMarker) s = strings.TrimLeftFunc(s, unicode.IsSpace) plain.Text = s } func findEndIdx(level, start int, blocks []markdown.Block) int { for i := start; i < len(blocks); i++ { + if blocks[i] == nextButton { + return i + } if _, ok := blocks[i].(*markdown.ThematicBreak); ok { return i } @@ -110,8 +142,15 @@ func toDetailsHTML(heading *markdown.Heading, blocks []markdown.Block) *markdown buf.WriteString(markdown.ToHTML(block)) } buf.WriteString("") - doc := parse(buf.String()) - return doc.Blocks[0].(*markdown.HTMLBlock) + htmlBlock := &markdown.HTMLBlock{ + Text: strings.Split(buf.String(), "\n"), + } + return htmlBlock +} + +func newNextButton() *markdown.Paragraph { + doc := parse(``) + return doc.Blocks[0].(*markdown.Paragraph) } func isThematicBreak(blocks []markdown.Block, idx int) bool { diff --git a/build-tools/labsite-gen/main_test.go b/build-tools/labsite-gen/main_test.go index 94b5ace0..cf1401a8 100644 --- a/build-tools/labsite-gen/main_test.go +++ b/build-tools/labsite-gen/main_test.go @@ -63,6 +63,26 @@ anybody here?`, goodbye +`[1:], + }, { + name: "codeblocks", + in: ` +# [>] hallo + +` + "```evy" + ` +print 1 + +print 2 +` + "```" + ` +`, + want: ` +
+hallo +
print 1
+
+print 2
+
+
`[1:], }, } @@ -78,3 +98,61 @@ func TestCollapse(t *testing.T) { }) } } + +var nextButtonTests = []struct { + name string + in string + wantMD string + wantHTML string +}{ + { + name: "no marker", + in: "hallo", + wantMD: "hallo\n", + wantHTML: "

hallo

\n", + }, + { + name: "single button", + in: `[Next]`, + wantMD: `` + "\n", + wantHTML: `

` + "\n", + }, + { + name: "multiple buttons", + in: `# Heading +[Next] + +paragraph + +[Next]`, + wantMD: ` +# Heading + + + +paragraph + + +`[1:], + wantHTML: ` +

Heading

+

+

paragraph

+

+`[1:], + }, +} + +func TestReplaceNextButton(t *testing.T) { + for _, tt := range nextButtonTests { + t.Run(tt.name, func(t *testing.T) { + p := markdown.Parser{} + doc := p.Parse(tt.in) + replaceNextButton(doc) + gotMD := markdown.Format(doc) + assert.Equal(t, tt.wantMD, gotMD) + gotHTML := markdown.ToHTML(doc) + assert.Equal(t, tt.wantHTML, gotHTML) + }) + } +}