From 5542459bd93d2d63b4c30948d19b891009818094 Mon Sep 17 00:00:00 2001 From: Mark Pitman Date: Tue, 13 Oct 2026 21:34:50 -0700 Subject: [PATCH] Clean up main.go and extend test coverage Format main.go with gofmt. Build the Snap temp path with filepath.Join. Print log warnings without a timestamp and with the mdview prefix, to match the messages from check(). The html parameter shadowing and the per-call regex compile from the issue were already fixed in the image embedding rewrite. Add tests for getTitleFromAST, isRelativePath and tempFileName. The traversal limits, size cap, HTML img handling, embedMermaidScript and getTempDir are covered by earlier changes. Closes #69 --- main.go | 16 ++++++++----- main_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index 4d359ab..f673f91 100644 --- a/main.go +++ b/main.go @@ -58,6 +58,10 @@ var template string var mermaidJS string func main() { + // Warnings go to stderr without a timestamp, like the messages from check() + log.SetFlags(0) + log.SetPrefix("mdview: ") + var outfilePtr = flag.String("o", "", "Output filename. (Optional)") var versionPtr = flag.Bool("version", false, "Prints mdview version.") var helpPtr = flag.Bool("help", false, "Prints mdview help message.") @@ -118,10 +122,10 @@ func main() { // Parse markdown to AST doc := md.Parser().Parse(text.NewReader(dat)) - + // Extract title from AST title := getTitleFromAST(doc, dat) - + // Render to HTML var buf bytes.Buffer check(md.Renderer().Render(&buf, dat, doc)) @@ -239,7 +243,7 @@ func getTempDir() string { } if isSnap() { - var tmpdir = os.Getenv("HOME") + "/mdview-temp" + var tmpdir = filepath.Join(os.Getenv("HOME"), "mdview-temp") check(os.MkdirAll(tmpdir, 0700)) return tmpdir } @@ -594,10 +598,10 @@ func hasMermaidBlock(doc ast.Node) bool { // embedMermaidScript adds the embedded Mermaid.js library to the HTML content // Since we set NoScript: true on the Mermaid extender, we need to manually add the script func embedMermaidScript(htmlContent string) string { - // Escape any tags (with closing >) inside the mermaid.js code + // Escape any tags (with closing >) inside the mermaid.js code // to prevent premature script closure. The standard way is to escape the forward slash. escapedMermaidJS := strings.ReplaceAll(mermaidJS, "", "<\\/script>") - + // Add the embedded Mermaid.js and initialization at the end of the content // Initialize mermaid with theme detection initScript := ` @@ -614,6 +618,6 @@ func embedMermaidScript(htmlContent string) string { }); ` inlineScript := fmt.Sprintf("", escapedMermaidJS, initScript) - + return htmlContent + inlineScript } diff --git a/main_test.go b/main_test.go index 2b5b8c8..a807292 100644 --- a/main_test.go +++ b/main_test.go @@ -388,3 +388,71 @@ func TestRemoveStaleTempFiles(t *testing.T) { } } } + +func TestGetTitleFromAST(t *testing.T) { + tests := []struct { + name string + markdown string + want string + }{ + {"simple", "# Hello\n", "Hello"}, + {"first H1 wins", "# First\n\n# Second\n", "First"}, + {"skips other levels", "## Sub\n\n# Main\n", "Main"}, + {"no H1", "## Sub\n\ntext\n", ""}, + {"empty document", "", ""}, + {"inline markup", "# Hello *big* **bold** `code` [link](http://x)\n", "Hello big bold code link"}, + {"setext heading", "Title\n=====\n", "Title"}, + {"surrounding space", "# Spaced \n", "Spaced"}, + {"H1 below a paragraph", "text\n\n# Later\n", "Later"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + source := []byte(tt.markdown) + doc := goldmark.New().Parser().Parse(text.NewReader(source)) + if got := getTitleFromAST(doc, source); got != tt.want { + t.Fatalf("getTitleFromAST = %q, want %q", got, tt.want) + } + }) + } +} + +func TestIsRelativePath(t *testing.T) { + tests := map[string]bool{ + "image.png": true, + "./image.png": true, + "../image.png": true, + "dir/image.png": true, + "my image.png": true, + "": false, + "/etc/image.png": false, + "//cdn.example.com/a.png": false, + "http://example.com/a.png": false, + "https://example.com/a": false, + "data:image/png;base64,AA": false, + "file:///tmp/a.png": false, + "ftp://example.com/a.png": false, + "blob:abc": false, + "mailto:a@b.c": false, + } + for path, want := range tests { + if got := isRelativePath(path); got != want { + t.Errorf("isRelativePath(%q) = %v, want %v", path, got, want) + } + } +} + +func TestTempFileName(t *testing.T) { + dir := t.TempDir() + first := tempFileName(dir, "mdview", ".html") + second := tempFileName(dir, "mdview", ".html") + + if first == second { + t.Fatal("two temp file names were identical") + } + if filepath.Dir(first) != dir { + t.Fatalf("temp file %q is not in %q", first, dir) + } + if !tempFileRegex.MatchString(filepath.Base(first)) { + t.Fatalf("temp file name %q does not match the cleanup pattern", filepath.Base(first)) + } +}