diff --git a/main.go b/main.go index 7250eb7..49d89a2 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,42 @@ var template string //go:embed mermaid.min.js var mermaidJS string +// while I understand why the default arg parsing behavior of the flag package is to stop parsing +// at the first non-flag argument, I want to allow interspersed flags and arguments. +// This function creates a new FlagSet and parses the arguments, allowing for interspersed flags. +// 2026-09-29: yes, I used AI to generate this; but yes, I unerstand it +// I agree it contains "over-kill" (the "--" option isn't specified), +// but I want to contribute something, and I intend to re-use this for my own purposes later +// *What is my motivation?* +// I created a bash script that wound up looking like `mdview filename.md -o filename.html`, +// and it was frustrating that it doesn't work that way, when there's no reason it shouldn't. +func parseInterspersedArgs(flagSet *flag.FlagSet, args []string) ([]string, error) { + for i, arg := range args { + if arg == "--" { + positional, err := parseInterspersedArgs(flagSet, args[:i]) + if err != nil { + return nil, err + } + return append(positional, args[i+1:]...), nil + } + } + + var psositional []string + for len(args) > 0 { + if err := flagSet.Parse(args); err != nil { + return nil, err + } + remaining := flagSet.Args() + if len(remaining) == 0 { + break + } + psositional = append(psositional, remaining[0]) + args = remaining[1:] + } + + return psositional, nil +} + func main() { var outfilePtr = flag.String("o", "", "Output filename. (Optional)") var versionPtr = flag.Bool("version", false, "Prints mdview version.") @@ -52,7 +88,16 @@ func main() { flag.BoolVar(barePtr, "b", false, "Bare HTML with no style applied.") flag.Parse() - inputFilename := flag.Arg(0) + args, err := parseInterspersedArgs(flag.CommandLine, os.Args[1:]) + if err != nil { + log.Println("Error parsing command-line arguments:", err) + os.Exit(2) + } + + inputFilename := "" + if len(args) > 0 { + inputFilename = args[0] + } if *versionPtr { fmt.Println(appVersion) @@ -69,7 +114,6 @@ func main() { // paths are then resolved against the working directory, as there is no input // file to anchor them to. var dat []byte - var err error baseDir := "." if inputFilename == "-" { @@ -88,10 +132,10 @@ func main() { // Create Goldmark markdown processor with extensions md := goldmark.New( goldmark.WithExtensions( - extension.GFM, // GitHub Flavored Markdown (includes tables) + extension.GFM, // GitHub Flavored Markdown (includes tables) extension.Typographer, // Smart quotes, dashes, etc. &mermaid.Extender{ - NoScript: true, // Don't add CDN script tags - we'll add our own embedded version + NoScript: true, // Don't add CDN script tags - we'll add our own embedded version }, ), goldmark.WithParserOptions( @@ -104,17 +148,17 @@ func main() { // Parse markdown to AST doc := md.Parser().Parse(text.NewReader(processedBytes)) - + // Extract title from AST title := getTitleFromAST(doc, processedBytes) - + // Render to HTML var buf bytes.Buffer if err := md.Renderer().Render(&buf, processedBytes, doc); err != nil { log.Fatal(err) } htmlContent := buf.String() - + // Add embedded Mermaid.js and initialization script when diagrams are present htmlContent = embedMermaidScript(htmlContent) @@ -239,7 +283,7 @@ func processMarkdownImages(markdown string, baseDir string) string { } alt := parts[1] imgPath := parts[2] - + if isRelativePath(imgPath) { if dataURI := imageToDataURI(imgPath, baseDir); dataURI != "" { return fmt.Sprintf("![%s](%s)", alt, dataURI) @@ -247,10 +291,10 @@ func processMarkdownImages(markdown string, baseDir string) string { } return match }) - + // Process HTML img tags in markdown markdown = processHTMLImages(markdown, baseDir) - + return markdown } @@ -263,27 +307,27 @@ func processHTMLImages(html string, baseDir string) string { if len(parts) != 5 { return match } - - prefix := parts[1] // " maxSize { log.Printf("Warning: Image file %s is too large (%d bytes, max %d bytes)", cleanedPath, fileInfo.Size(), maxSize) return "" } - + // Read the image file data, err := os.ReadFile(cleanedPath) if err != nil { log.Printf("Warning: Unable to read image file %s: %v", cleanedPath, err) return "" } - + // Determine MIME type based on file extension mimeType := getMimeType(cleanedPath) - + // Encode to base64 encoded := base64.StdEncoding.EncodeToString(data) - + // Return data URI return fmt.Sprintf("data:%s;base64,%s", mimeType, encoded) } @@ -413,11 +457,11 @@ func embedMermaidScript(htmlContent string) string { if !strings.Contains(htmlContent, `class="mermaid"`) { return htmlContent // No mermaid diagrams, don't add the script } - - // 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 := ` @@ -434,6 +478,6 @@ func embedMermaidScript(htmlContent string) string { }); ` inlineScript := fmt.Sprintf("", escapedMermaidJS, initScript) - + return htmlContent + inlineScript }