server: watch for changes in templates and stylesheets
Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
parent
038695d123
commit
c5b1e0363e
3 changed files with 93 additions and 21 deletions
2
go.mod
2
go.mod
|
@ -4,10 +4,12 @@ go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aymerick/raymond v2.0.2+incompatible
|
github.com/aymerick/raymond v2.0.2+incompatible
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.1
|
github.com/mitchellh/mapstructure v1.4.1
|
||||||
github.com/wellington/go-libsass v0.9.2
|
github.com/wellington/go-libsass v0.9.2
|
||||||
github.com/yuin/goldmark v1.3.1
|
github.com/yuin/goldmark v1.3.1
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
||||||
github.com/yuin/goldmark-meta v1.0.0
|
github.com/yuin/goldmark-meta v1.0.0
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20210219172841-57ea560cfca1 // indirect
|
||||||
)
|
)
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -20,6 +20,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
@ -71,7 +73,11 @@ gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638/go.mod h1:EGRJaqe2eO9X
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210219172841-57ea560cfca1 h1:mDSj8NPponP6fRpRDblAGl5bpSHjPulHtk5lGl0gLSY=
|
||||||
|
golang.org/x/sys v0.0.0-20210219172841-57ea560cfca1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
|
106
server.go
106
server.go
|
@ -7,19 +7,19 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/aymerick/raymond"
|
"github.com/aymerick/raymond"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/wellington/go-libsass"
|
"github.com/wellington/go-libsass"
|
||||||
)
|
)
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
pages []*Page
|
pages []*Page
|
||||||
staticHandler http.Handler
|
staticHandler http.Handler
|
||||||
pageTpl *raymond.Template
|
|
||||||
fullPostTpl *raymond.Template
|
tplMutex sync.RWMutex
|
||||||
summaryTpl *raymond.Template
|
templates map[string]*raymond.Template
|
||||||
errorTpl *raymond.Template
|
|
||||||
notFoundTpl *raymond.Template
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer() (*server, error) {
|
func newServer() (*server, error) {
|
||||||
|
@ -42,6 +42,28 @@ func newServer() (*server, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go listenForChanges("templates/", func(file string) error {
|
||||||
|
s.tplMutex.Lock()
|
||||||
|
defer s.tplMutex.Unlock()
|
||||||
|
tplName := strings.TrimSuffix(file, ".html")
|
||||||
|
newTpl, err := loadTemplate(tplName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.templates[tplName] = newTpl
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
go listenForChanges("styles/", func(file string) error {
|
||||||
|
var err error
|
||||||
|
if strings.HasSuffix(file, ".scss") {
|
||||||
|
err = loadSassStylesheet(file)
|
||||||
|
} else if strings.HasSuffix(file, ".css") {
|
||||||
|
err = loadRegularStylesheet(file)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,33 +90,40 @@ func (s *server) refreshPages() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadTemplate(file string) (*raymond.Template, error) {
|
||||||
|
tpl, err := raymond.ParseFile("templates/" + file + ".html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not parse %s template: %w", file, err)
|
||||||
|
}
|
||||||
|
log.Printf("Loaded template: %s", file)
|
||||||
|
return tpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
// loadTemplates, for each f in files, loads `templates/$f.html`
|
// loadTemplates, for each f in files, loads `templates/$f.html`
|
||||||
// as a handlebars HTML template. If any single template fails to
|
// as a handlebars HTML template. If any single template fails to
|
||||||
// load, only an error is returned. Conversely, if there is no error,
|
// load, only an error is returned. Conversely, if there is no error,
|
||||||
// every template name passed is guaranteed to have loaded successfully.
|
// every template name passed is guaranteed to have loaded successfully.
|
||||||
func loadTemplates(files []string) ([]*raymond.Template, error) {
|
func loadTemplates(files []string) (map[string]*raymond.Template, error) {
|
||||||
templates := make([]*raymond.Template, 0, len(files))
|
templates := make(map[string]*raymond.Template)
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
tpl, err := raymond.ParseFile("templates/" + f + ".html")
|
tpl, err := loadTemplate(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not parse %s template: %w", f, err)
|
return nil, err
|
||||||
}
|
}
|
||||||
templates = append(templates, tpl)
|
templates[f] = tpl
|
||||||
}
|
}
|
||||||
log.Printf("Loaded templates: %s", files)
|
log.Printf("Loaded templates: %s", files)
|
||||||
return templates, nil
|
return templates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) refreshTemplates() error {
|
func (s *server) refreshTemplates() error {
|
||||||
templates, err := loadTemplates([]string{"page", "fullpost", "summary", "notfound", "error"})
|
s.tplMutex.Lock()
|
||||||
|
defer s.tplMutex.Unlock()
|
||||||
|
tpls, err := loadTemplates([]string{"page", "fullpost", "summary", "notfound", "error"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.pageTpl = templates[0]
|
s.templates = tpls
|
||||||
s.fullPostTpl = templates[1]
|
|
||||||
s.summaryTpl = templates[2]
|
|
||||||
s.notFoundTpl = templates[3]
|
|
||||||
s.errorTpl = templates[4]
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,17 +190,52 @@ func (s *server) refreshStyles() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listenForChanges(folder string, action func(string) error) {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not start fsnotify watcher for folder %s", folder)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-watcher.Events:
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
log.Printf("Modified file: %s", event.Name)
|
||||||
|
err := action(strings.TrimPrefix(event.Name, folder))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("watcher action on %s failed: %v", event.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
log.Printf("Watcher error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = watcher.Add(folder)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
func (s *server) logRequest(req *http.Request) {
|
func (s *server) logRequest(req *http.Request) {
|
||||||
log.Printf("%s %s from %s", req.Method, req.URL.Path, req.RemoteAddr)
|
log.Printf("%s %s from %s", req.Method, req.URL.Path, req.RemoteAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) router(res http.ResponseWriter, req *http.Request) {
|
func (s *server) router(res http.ResponseWriter, req *http.Request) {
|
||||||
|
s.tplMutex.RLock()
|
||||||
|
defer s.tplMutex.RUnlock()
|
||||||
s.logRequest(req)
|
s.logRequest(req)
|
||||||
res = &errorCatcher{
|
res = &errorCatcher{
|
||||||
res: res,
|
res: res,
|
||||||
req: req,
|
req: req,
|
||||||
errorTpl: s.errorTpl,
|
errorTpl: s.templates["error"],
|
||||||
notFoundTpl: s.notFoundTpl,
|
notFoundTpl: s.templates["notfound"],
|
||||||
handledError: false,
|
handledError: false,
|
||||||
}
|
}
|
||||||
slug := req.URL.Path[1:]
|
slug := req.URL.Path[1:]
|
||||||
|
@ -202,12 +266,12 @@ func (s *server) createPage(title, contents string) (string, error) {
|
||||||
"title": title,
|
"title": title,
|
||||||
"contents": contents,
|
"contents": contents,
|
||||||
}
|
}
|
||||||
return s.pageTpl.Exec(ctx)
|
return s.templates["page"].Exec(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) renderPage(p *Page, res http.ResponseWriter, req *http.Request) {
|
func (s *server) renderPage(p *Page, res http.ResponseWriter, req *http.Request) {
|
||||||
res.Header().Add("content-type", "text/html")
|
res.Header().Add("content-type", "text/html")
|
||||||
contents, err := p.render(s.fullPostTpl)
|
contents, err := p.render(s.templates["fullpost"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.errorInRequest(res, req, err)
|
s.errorInRequest(res, req, err)
|
||||||
}
|
}
|
||||||
|
@ -224,7 +288,7 @@ func (s *server) homePage(res http.ResponseWriter, req *http.Request) {
|
||||||
var posts string
|
var posts string
|
||||||
|
|
||||||
for _, p := range s.pages {
|
for _, p := range s.pages {
|
||||||
summary, err := p.render(s.summaryTpl)
|
summary, err := p.render(s.templates["summary"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("could not render page summary for %s", p.Slug)
|
log.Printf("could not render page summary for %s", p.Slug)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue