md-live-server/main.go
main.goRaw
1package main
2
3import (
4 "flag"
5 "fmt"
6 "html/template"
7 "log"
8 "net/http"
9 "strings"
10 "time"
11
12 "github.com/fatih/color"
13 "github.com/fsnotify/fsnotify"
14 "github.com/gorilla/mux"
15 "github.com/gorilla/websocket"
16)
17
18const version = "v0_20260706"
19
20var (
21 upgrader = websocket.Upgrader{
22 ReadBufferSize: 1024,
23 WriteBufferSize: 1024,
24 }
25)
26
27type PageModel struct {
28 Title string
29 Content template.HTML
30 LastMod time.Time
31}
32
33func main() {
34 port := flag.Int("port", 8080, "port to listen on")
35 flag.Parse()
36
37 fmt.Println("md-live-server version:", version)
38
39 // fill the CSS into the HTML templates
40 dirTemplate = fillCSSintoTemplate(dirTemplate)
41 htmlTemplate = fillCSSintoTemplate(htmlTemplate)
42
43 router := mux.NewRouter()
44 router.HandleFunc("/", getDir).Methods("GET")
45 router.HandleFunc("/{path}", getPage).Methods("GET")
46 router.HandleFunc("/ws/{path}", serveWs)
47
48 addr := fmt.Sprintf(":%d", *port)
49
50 log.Println("md-live-server web server running")
51 log.Printf("port: %d", *port)
52 log.Fatal(http.ListenAndServe(addr, router))
53}
54
55func getDir(w http.ResponseWriter, r *http.Request) {
56 elements := readDir("./")
57 var content string
58 content = `<ul>`
59 for _, elem := range elements {
60 content += `
61 <li><a href="` + elem + `">` + elem + `</a></li>
62 `
63 }
64 content += `</ul>`
65 var page PageModel
66 page.Title = "dir"
67 page.Content = template.HTML(content)
68
69 tmplPage := template.Must(template.New("t").Parse(dirTemplate))
70 tmplPage.Execute(w, page)
71}
72
73func getPage(w http.ResponseWriter, r *http.Request) {
74 vars := mux.Vars(r)
75 path := vars["path"]
76 path = strings.Replace(path, "%", "/", -1)
77 log.Println(path)
78
79 if len(strings.Split(path, ".")) < 2 {
80 fmt.Fprintf(w, errTemplate)
81 return
82 }
83
84 if strings.Split(path, ".")[1] != "md" {
85 http.ServeFile(w, r, path)
86 return
87 }
88
89 content, err := fileToHTML(path)
90 if err != nil {
91 color.Red(err.Error())
92 fmt.Fprintf(w, errTemplate)
93 return
94 }
95
96 var page PageModel
97 page.Title = path
98 page.Content = template.HTML(content)
99
100 tmplPage := template.Must(template.New("t").Parse(htmlTemplate))
101 tmplPage.Execute(w, page)
102}
103
104func serveWs(w http.ResponseWriter, r *http.Request) {
105 vars := mux.Vars(r)
106 path := vars["path"]
107 path = strings.Replace(path, "%", "/", -1)
108 log.Println("websocket", path)
109
110 ws, err := upgrader.Upgrade(w, r, nil)
111 if err != nil {
112 if _, ok := err.(websocket.HandshakeError); !ok {
113 log.Println(err)
114 }
115 return
116 }
117
118 // watch file
119 watcher, err := fsnotify.NewWatcher()
120 if err != nil {
121 log.Fatal(err)
122 }
123 defer watcher.Close()
124
125 done := make(chan bool)
126 go func() {
127 for {
128 select {
129 case event, ok := <-watcher.Events:
130 if !ok {
131 return
132 }
133 log.Println("event:", event)
134 if event.Op&fsnotify.Write == fsnotify.Write {
135 log.Println("modified file:", event.Name)
136 writer(ws, path)
137 }
138 case err, ok := <-watcher.Errors:
139 if !ok {
140 return
141 }
142 log.Println("error:", err)
143 }
144 }
145 }()
146
147 err = watcher.Add(path)
148 if err != nil {
149 log.Fatal(err)
150 }
151 <-done
152}
153
154func writer(ws *websocket.Conn, path string) {
155 content, err := fileToHTML(path)
156 check(err)
157 if err := ws.WriteMessage(websocket.TextMessage, []byte(content)); err != nil {
158 return
159 }
160}