2017-06-17 16:51:32 +02:00
|
|
|
package gojekyll
|
2017-06-10 21:38:09 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-06-17 02:49:44 +02:00
|
|
|
"log"
|
2017-06-17 06:03:36 +02:00
|
|
|
"mime"
|
2017-06-10 21:38:09 +02:00
|
|
|
"net/http"
|
2017-06-17 06:03:36 +02:00
|
|
|
"path"
|
2017-06-17 18:31:18 +02:00
|
|
|
"sync"
|
|
|
|
"time"
|
2017-06-17 02:49:44 +02:00
|
|
|
|
|
|
|
"github.com/fsnotify/fsnotify"
|
2017-06-10 21:38:09 +02:00
|
|
|
)
|
|
|
|
|
2017-06-17 05:50:30 +02:00
|
|
|
// Server serves the site on HTTP.
|
2017-06-17 18:31:18 +02:00
|
|
|
type Server struct {
|
|
|
|
Site *Site
|
|
|
|
mu sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
type emptyType struct{}
|
|
|
|
|
|
|
|
var void emptyType
|
2017-06-17 02:49:44 +02:00
|
|
|
|
2017-06-17 05:50:30 +02:00
|
|
|
// Run runs the server.
|
2017-06-17 16:51:32 +02:00
|
|
|
func (s *Server) Run(logger func(label, value string)) error {
|
2017-06-10 21:38:09 +02:00
|
|
|
address := "localhost:4000"
|
2017-06-17 05:50:30 +02:00
|
|
|
if err := s.watchFiles(); err != nil {
|
2017-06-17 02:49:44 +02:00
|
|
|
return err
|
|
|
|
}
|
2017-06-17 16:51:32 +02:00
|
|
|
logger("Server address:", "http://"+address+"/")
|
|
|
|
logger("Server running...", "press ctrl-c to stop.")
|
2017-06-17 05:50:30 +02:00
|
|
|
http.HandleFunc("/", s.handler)
|
2017-06-12 02:05:17 +02:00
|
|
|
return http.ListenAndServe(address, nil)
|
2017-06-10 21:38:09 +02:00
|
|
|
}
|
|
|
|
|
2017-06-17 06:03:36 +02:00
|
|
|
func (s *Server) handler(rw http.ResponseWriter, r *http.Request) {
|
2017-06-17 18:31:18 +02:00
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
2017-06-17 04:09:25 +02:00
|
|
|
site := s.Site
|
2017-06-17 02:06:55 +02:00
|
|
|
urlpath := r.URL.Path
|
2017-06-17 06:03:36 +02:00
|
|
|
mimeType := mime.TypeByExtension(path.Ext(urlpath))
|
|
|
|
if mimeType != "" {
|
|
|
|
rw.Header().Set("Content-Type", mimeType)
|
|
|
|
}
|
2017-06-10 21:38:09 +02:00
|
|
|
|
2017-06-17 05:50:30 +02:00
|
|
|
p, found := site.PageForURL(urlpath)
|
2017-06-10 21:38:09 +02:00
|
|
|
if !found {
|
2017-06-17 06:03:36 +02:00
|
|
|
rw.WriteHeader(http.StatusNotFound)
|
2017-06-13 17:00:24 +02:00
|
|
|
p, found = site.Paths["404.html"]
|
2017-06-10 21:38:09 +02:00
|
|
|
}
|
|
|
|
if !found {
|
2017-06-17 06:03:36 +02:00
|
|
|
fmt.Fprintf(rw, "404 page not found: %s", urlpath)
|
2017-06-10 21:38:09 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-17 06:03:36 +02:00
|
|
|
err := p.Write(rw)
|
2017-06-10 21:38:09 +02:00
|
|
|
if err != nil {
|
2017-06-17 02:06:55 +02:00
|
|
|
fmt.Printf("Error rendering %s: %s", urlpath, err)
|
2017-06-12 02:05:17 +02:00
|
|
|
}
|
2017-06-10 21:38:09 +02:00
|
|
|
}
|
2017-06-17 02:49:44 +02:00
|
|
|
|
2017-06-17 18:31:18 +02:00
|
|
|
func (s *Server) syncReloadSite() {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
start := time.Now()
|
|
|
|
fmt.Printf("%s Reloading site...", start.Format(time.Stamp))
|
|
|
|
if err := s.Site.Reload(); err != nil {
|
|
|
|
fmt.Println()
|
|
|
|
fmt.Printf(err.Error())
|
|
|
|
}
|
|
|
|
fmt.Printf("reloaded in %.2fs\n", time.Since(start).Seconds())
|
|
|
|
}
|
|
|
|
|
2017-06-17 02:49:44 +02:00
|
|
|
func (s *Server) watchFiles() error {
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-06-17 18:31:18 +02:00
|
|
|
var (
|
|
|
|
events = make(chan emptyType)
|
|
|
|
debounced = debounce(time.Second, events)
|
|
|
|
)
|
|
|
|
|
2017-06-17 02:49:44 +02:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
2017-06-17 18:31:18 +02:00
|
|
|
case <-watcher.Events:
|
|
|
|
events <- void
|
2017-06-17 02:49:44 +02:00
|
|
|
case err := <-watcher.Errors:
|
|
|
|
log.Println("error:", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-06-17 18:31:18 +02:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
<-debounced
|
|
|
|
s.syncReloadSite()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-06-17 02:49:44 +02:00
|
|
|
return watcher.Add(s.Site.Source)
|
|
|
|
}
|
2017-06-17 18:31:18 +02:00
|
|
|
|
|
|
|
// debounce relays values from input to output, merging successive values within interval
|
|
|
|
// TODO consider https://github.com/ReactiveX/RxGo
|
|
|
|
func debounce(interval time.Duration, input chan emptyType) (output chan emptyType) {
|
|
|
|
output = make(chan emptyType)
|
|
|
|
var (
|
|
|
|
pending = false
|
|
|
|
ticker = time.Tick(interval)
|
|
|
|
)
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-input:
|
|
|
|
pending = true
|
|
|
|
case <-ticker:
|
|
|
|
if pending {
|
|
|
|
output <- void
|
|
|
|
pending = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return
|
|
|
|
}
|