main.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. package main
  2. import (
  3. "bytes"
  4. "flag"
  5. "fmt"
  6. "html/template"
  7. "io"
  8. "os"
  9. "os/exec"
  10. "runtime"
  11. "time"
  12. "github.com/microcosm-cc/bluemonday"
  13. "github.com/russross/blackfriday/v2"
  14. )
  15. const (
  16. defaultTemplate = `<!DOCTYPE html>
  17. <html>
  18. <head>
  19. <meta http-equiv="content-type" content="text/html; charset=utf-8">
  20. <title>{{ .Title }}</title>
  21. </head>
  22. <body>
  23. {{ .Body }}
  24. </body>
  25. </html>
  26. `
  27. )
  28. type content struct {
  29. Title string
  30. Body template.HTML
  31. }
  32. func main() {
  33. // Parse flags
  34. filename := flag.String("file", "", "Markdown file to preview")
  35. skipPreview := flag.Bool("s", false, "Skip auto-preview")
  36. tFname := flag.String("t", "", "Template file to use")
  37. flag.Parse()
  38. // If file is not provided, print usage and exit
  39. if *filename == "" {
  40. flag.Usage()
  41. os.Exit(1)
  42. }
  43. if err := run(*filename, *tFname, os.Stdout, *skipPreview); err != nil {
  44. fmt.Fprintf(os.Stderr, "error: %v\n", err)
  45. os.Exit(1)
  46. }
  47. }
  48. func run(filename string, tFname string, out io.Writer, skipPreview bool) error {
  49. // Read all the data from the input file and check for errors
  50. input, err := os.ReadFile(filename)
  51. if err != nil {
  52. return fmt.Errorf("could not read file: %w", err)
  53. }
  54. // Converting Markdown to HTML
  55. htmlData, err := parseContent(input, tFname)
  56. if err != nil {
  57. return err
  58. }
  59. // Create the output file name
  60. temp, err := os.CreateTemp("", "mdp*.html")
  61. if err != nil {
  62. return err
  63. }
  64. if err := temp.Close(); err != nil {
  65. return err
  66. }
  67. outName := temp.Name()
  68. fmt.Fprintln(out, outName)
  69. if err := saveHTML(outName, htmlData); err != nil {
  70. return err
  71. }
  72. if skipPreview {
  73. return nil
  74. }
  75. defer os.Remove(outName)
  76. return preview(outName)
  77. }
  78. func parseContent(input []byte, tFname string) ([]byte, error) {
  79. // Parse the markdown file through blackfriday and bluemonday
  80. // to generate a valid and safe html
  81. output := blackfriday.Run(input)
  82. body := bluemonday.UGCPolicy().SanitizeBytes(output)
  83. // Parse the contents of the defaultTemplate const into a new template
  84. t, err := template.New("mdp").Parse(defaultTemplate)
  85. if err != nil {
  86. return nil, err
  87. }
  88. // If user provided alternate template file, replace template
  89. if tFname != "" {
  90. t, err = template.ParseFiles(tFname)
  91. if err != nil {
  92. return nil, err
  93. }
  94. }
  95. // Initialize the content struct with the title and body
  96. c := content{
  97. Title: "Markdown Preview Tool",
  98. Body: template.HTML(body),
  99. }
  100. var buffer bytes.Buffer
  101. // Execute the template with the content struct and write the output to the buffer
  102. if err := t.Execute(&buffer, c); err != nil {
  103. return nil, err
  104. }
  105. return buffer.Bytes(), err
  106. }
  107. func saveHTML(filename string, data []byte) error {
  108. return os.WriteFile(filename, data, 0644)
  109. }
  110. func preview(fname string) error {
  111. cName := ""
  112. cParams := []string{}
  113. // Define executable based on OS environment
  114. switch runtime.GOOS {
  115. case "windows":
  116. cName = "cmd.exe"
  117. cParams = []string{"/c", "start"}
  118. case "linux":
  119. cName = "xdg-open"
  120. case "darwin":
  121. cName = "open"
  122. default:
  123. return fmt.Errorf("unsupported platform")
  124. }
  125. // Append the filename to the command parameters
  126. cParams = append(cParams, fname)
  127. cPath, err := exec.LookPath(cName)
  128. if err != nil {
  129. return err
  130. }
  131. // Open the file using the default program
  132. err = exec.Command(cPath, cParams...).Run()
  133. time.Sleep(2 * time.Second)
  134. return err
  135. }