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
}