package main import ( "bytes" "flag" "fmt" "html/template" "io" "os" "os/exec" "runtime" "time" "github.com/microcosm-cc/bluemonday" "github.com/russross/blackfriday/v2" ) const ( defaultTemplate = ` {{ .Title }} {{ .Body }} ` ) type content struct { Title string Body template.HTML } func main() { // Parse flags filename := flag.String("file", "", "Markdown file to preview") skipPreview := flag.Bool("s", false, "Skip auto-preview") tFname := flag.String("t", "", "Template file to use") flag.Parse() // If file is not provided, print usage and exit if *filename == "" { flag.Usage() os.Exit(1) } if err := run(*filename, *tFname, os.Stdout, *skipPreview); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } } func run(filename string, tFname string, out io.Writer, skipPreview bool) error { // Read all the data from the input file and check for errors input, err := os.ReadFile(filename) if err != nil { return fmt.Errorf("could not read file: %w", err) } // Converting Markdown to HTML htmlData, err := parseContent(input, tFname) if err != nil { return err } // Create the output file name temp, err := os.CreateTemp("", "mdp*.html") if err != nil { return err } if err := temp.Close(); err != nil { return err } outName := temp.Name() fmt.Fprintln(out, outName) if err := saveHTML(outName, htmlData); err != nil { return err } if skipPreview { return nil } defer os.Remove(outName) return preview(outName) } func parseContent(input []byte, tFname string) ([]byte, error) { // Parse the markdown file through blackfriday and bluemonday // to generate a valid and safe html output := blackfriday.Run(input) body := bluemonday.UGCPolicy().SanitizeBytes(output) // Parse the contents of the defaultTemplate const into a new template t, err := template.New("mdp").Parse(defaultTemplate) if err != nil { return nil, err } // If user provided alternate template file, replace template if tFname != "" { t, err = template.ParseFiles(tFname) if err != nil { return nil, err } } // Initialize the content struct with the title and body c := content{ Title: "Markdown Preview Tool", Body: template.HTML(body), } var buffer bytes.Buffer // Execute the template with the content struct and write the output to the buffer if err := t.Execute(&buffer, c); err != nil { return nil, err } return buffer.Bytes(), err } func saveHTML(filename string, data []byte) error { return os.WriteFile(filename, data, 0644) } func preview(fname string) error { cName := "" cParams := []string{} // Define executable based on OS environment switch runtime.GOOS { case "windows": cName = "cmd.exe" cParams = []string{"/c", "start"} case "linux": cName = "xdg-open" case "darwin": cName = "open" default: return fmt.Errorf("unsupported platform") } // Append the filename to the command parameters cParams = append(cParams, fname) cPath, err := exec.LookPath(cName) if err != nil { return err } // Open the file using the default program err = exec.Command(cPath, cParams...).Run() time.Sleep(2 * time.Second) return err }