arnaucube
arnaucube/md-live-serverPublic Self-hosted repository
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_20240128"
 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}