mopac-pdf v0: markdown to beautiful PDFs via digest-pinned typst
Go CLI that turns the markdown currency of this stack (Redmine notes, Discourse posts, briefing output) into typeset PDFs: two embedded typst templates (report with title page/TOC/headers, dense brief), front-matter (title/subtitle/author/date/classification/template), GFM tables, and bar charts rendered in pure Go (go-chart) from fenced chart data blocks. Engine is a prebuilt typst 0.15.1 container pinned by digest and run --network none; PDF bytes to stdout or -o. Exit codes 0/1/2. All dev in docker (dev.sh/Makefile); unit tests + golden typst fixtures plus a host-side smoke against the real engine container. Generated with Crush Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package markdown
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseKitchenSink(t *testing.T) {
|
||||
src := "# Title\n\nIntro with **bold**.\n\n- a\n- b\n\n1. one\n2. two\n\n> quoted\n\n---\n\n```go\nfmt.Println(1)\n```\n\n| H1 | H2 |\n|:--|--:|\n| a | b |\n\n```chart\ntype: bar\ntitle: T\nQ1: 1\nQ2: 2\n```\n"
|
||||
blocks := Parse(src)
|
||||
var kinds []Kind
|
||||
for _, b := range blocks {
|
||||
kinds = append(kinds, b.Kind)
|
||||
}
|
||||
want := []Kind{KindHeading, KindParagraph, KindList, KindList, KindQuote, KindRule, KindCode, KindTable, KindChart}
|
||||
if !reflect.DeepEqual(kinds, want) {
|
||||
t.Fatalf("kinds = %v want %v", kinds, want)
|
||||
}
|
||||
l := blocks[2]
|
||||
if l.Ordered || !reflect.DeepEqual(l.Items, []string{"a", "b"}) {
|
||||
t.Fatalf("ul: %+v", l)
|
||||
}
|
||||
ol := blocks[3]
|
||||
if !ol.Ordered || !reflect.DeepEqual(ol.Items, []string{"one", "two"}) {
|
||||
t.Fatalf("ol: %+v", ol)
|
||||
}
|
||||
code := blocks[6]
|
||||
if code.Language != "go" || !reflect.DeepEqual(code.Lines, []string{"fmt.Println(1)"}) {
|
||||
t.Fatalf("code: %+v", code)
|
||||
}
|
||||
tbl := blocks[7]
|
||||
if !reflect.DeepEqual(tbl.Header, []string{"H1", "H2"}) || !reflect.DeepEqual(tbl.Align, []string{"left", "right"}) {
|
||||
t.Fatalf("table: %+v", tbl)
|
||||
}
|
||||
if len(tbl.Rows) != 1 || !reflect.DeepEqual(tbl.Rows[0], []string{"a", "b"}) {
|
||||
t.Fatalf("rows: %+v", tbl.Rows)
|
||||
}
|
||||
ch := blocks[8].Chart
|
||||
if ch.Type != "bar" || ch.Title != "T" || !reflect.DeepEqual(ch.Labels, []string{"Q1", "Q2"}) {
|
||||
t.Fatalf("chart: %+v", ch)
|
||||
}
|
||||
if !reflect.DeepEqual(ch.Values, []float64{1, 2}) {
|
||||
t.Fatalf("values: %v", ch.Values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseParagraphGrouping(t *testing.T) {
|
||||
blocks := Parse("line one\nline two\n\nsecond para")
|
||||
if len(blocks) != 2 || blocks[0].Text != "line one line two" || blocks[1].Text != "second para" {
|
||||
t.Fatalf("%+v", blocks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseImage(t *testing.T) {
|
||||
blocks := Parse("")
|
||||
if len(blocks) != 1 || blocks[0].Kind != KindImage || blocks[0].Alt != "A caption" || blocks[0].Path != "pics/x.png" {
|
||||
t.Fatalf("%+v", blocks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseChartMissingData(t *testing.T) {
|
||||
blocks := Parse("```chart\ntitle: empty\n```")
|
||||
if len(blocks) != 1 || blocks[0].Kind != KindCode {
|
||||
t.Fatalf("chart without data must degrade to code block: %+v", blocks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHeadingLevels(t *testing.T) {
|
||||
blocks := Parse("## Two\n\n#### Four\n")
|
||||
if len(blocks) != 2 || blocks[0].Level != 2 || blocks[0].Text != "Two" || blocks[1].Level != 4 || blocks[1].Text != "Four" {
|
||||
t.Fatalf("%+v", blocks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNoHeadingWithoutSpace(t *testing.T) {
|
||||
blocks := Parse("#hashtag not heading")
|
||||
if len(blocks) != 1 || blocks[0].Kind != KindParagraph {
|
||||
t.Fatalf("%+v", blocks)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user