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