main.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package main
  2. import (
  3. "fmt"
  4. "html/template"
  5. "io/ioutil"
  6. "log"
  7. "net"
  8. "net/http"
  9. "os"
  10. "path/filepath"
  11. "strings"
  12. "github.com/codegangsta/cli"
  13. "github.com/jaschaephraim/lrserver"
  14. "github.com/russross/blackfriday"
  15. "github.com/skratchdot/open-golang/open"
  16. "gopkg.in/fsnotify.v1"
  17. )
  18. var watchDir = "."
  19. // template with list of files found in watchDir (with livereload)
  20. var mdList = template.Must(template.New("mdList").Parse(`<!doctype html>
  21. <html>
  22. <head>
  23. <title>List of Markdown files</title>
  24. <script src="http://localhost:35729/livereload.js"></script>
  25. <body>
  26. <ul>
  27. {{range .}}
  28. <li><a href="/md?file={{.}}">{{.}}</a></li>
  29. {{end}}
  30. </ul>
  31. </body>
  32. </html>`))
  33. // template for rendering markdown content (with livereload)
  34. var md = template.Must(template.New("md").Parse(`<!doctype html>
  35. <html>
  36. <head>
  37. <!-- <link href="http://kevinburke.bitbucket.org/markdowncss/markdown.css" rel="stylesheet"></link>-->
  38. <script src="http://localhost:35729/livereload.js"></script>
  39. <body>
  40. {{.}}
  41. </body>
  42. </html>`))
  43. func main() {
  44. app := cli.NewApp()
  45. app.Name = "livefriday"
  46. app.Usage = "see your markdown grow as you save it"
  47. app.Flags = []cli.Flag{
  48. cli.StringFlag{Name: "dir,d", Value: ".", Usage: "The directory to watch and compile"},
  49. cli.StringFlag{Name: "host", Value: "localhost", Usage: "The http host to listen on"},
  50. cli.IntFlag{Name: "port,p", Value: 0, Usage: "The http port to listen on"},
  51. }
  52. app.Action = run
  53. app.Run(os.Args)
  54. }
  55. func run(c *cli.Context) {
  56. // Create file watcher
  57. watcher, err := fsnotify.NewWatcher()
  58. check(err)
  59. defer watcher.Close()
  60. watchDir = c.String("dir")
  61. // Add dir to watcher
  62. err = watcher.Add(watchDir)
  63. check(err)
  64. // Start LiveReload server
  65. lr := lrserver.New(lrserver.DefaultName, lrserver.DefaultPort)
  66. go func() {
  67. check(lr.ListenAndServe())
  68. }()
  69. // Start goroutine that requests reload upon watcher event
  70. go func() {
  71. for {
  72. event := <-watcher.Events
  73. if strings.HasSuffix(event.Name, ".md") || strings.HasSuffix(event.Name, ".mdown") {
  74. lr.Reload(event.Name)
  75. }
  76. }
  77. }()
  78. // Start serving html
  79. http.HandleFunc("/", indexHandler)
  80. http.HandleFunc("/md", mdHandler)
  81. http.Handle("/assets/", http.StripPrefix("/assets", http.FileServer(http.Dir(watchDir))))
  82. listenAddr := fmt.Sprintf("%s:%d", c.String("host"), c.Int("port"))
  83. // Start listening first.
  84. listener, err := net.Listen("tcp", listenAddr)
  85. check(err)
  86. listenAddr = listener.Addr().String()
  87. // Open a browser tab and navigate to the main page.
  88. go open.Run("http://" + listenAddr)
  89. log.Println("livefriday server is running at http://" + listenAddr)
  90. err = http.Serve(listener, nil)
  91. check(err)
  92. }
  93. // indexHandler builds a list with links to all .md files in the watchDir
  94. func indexHandler(rw http.ResponseWriter, req *http.Request) {
  95. dir, err := os.Open(watchDir)
  96. if err != nil {
  97. http.Error(rw, err.Error(), http.StatusInternalServerError)
  98. return
  99. }
  100. dirNames, err := dir.Readdirnames(-1)
  101. if err != nil {
  102. http.Error(rw, err.Error(), http.StatusInternalServerError)
  103. return
  104. }
  105. mdFiles := make([]string, len(dirNames))
  106. i := 0
  107. for _, n := range dirNames {
  108. if strings.HasSuffix(n, ".md") || strings.HasSuffix(n, ".mdown") {
  109. mdFiles[i] = n
  110. i++
  111. }
  112. }
  113. rw.WriteHeader(http.StatusOK)
  114. mdList.Execute(rw, mdFiles[:i])
  115. }
  116. // mdHandler ReadFile's the passed file argument and puts it through blackfriday
  117. func mdHandler(rw http.ResponseWriter, req *http.Request) {
  118. fname := req.URL.Query().Get("file")
  119. if fname == "" {
  120. http.Error(rw, "no fname", http.StatusBadRequest)
  121. return
  122. }
  123. input, err := ioutil.ReadFile(filepath.Join(watchDir, filepath.Base(fname)))
  124. if err != nil {
  125. http.Error(rw, err.Error(), http.StatusInternalServerError)
  126. return
  127. }
  128. rw.WriteHeader(http.StatusOK)
  129. md.Execute(rw, template.HTML(blackfriday.MarkdownCommon(input)))
  130. }
  131. func check(err error) {
  132. if err != nil {
  133. log.Fatalln(err)
  134. }
  135. }