continuserv: Live serves updates to the spec

pull/977/head
Daniel Wagner-Hall 9 years ago
parent cf3475515e
commit c4acee3bcb

1
.gitignore vendored

@ -1,4 +1,5 @@
scripts/gen scripts/gen
scripts/continuserv/continuserv
templating/out templating/out
*.pyc *.pyc
supporting-docs/_site supporting-docs/_site

@ -0,0 +1,129 @@
// continuserv proactively re-generates the spec on filesystem changes, and serves it over HTTP.
// It will always serve the most recent version of the spec, and may block an HTTP request until regeneration is finished.
// It does not currently pre-empt stale generations, but will block until they are complete.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"sync"
"sync/atomic"
fsnotify "gopkg.in/fsnotify.v1"
)
var toServe atomic.Value // Always contains valid []byte to serve. May be stale unless wg is zero.
var wg sync.WaitGroup // Indicates how many updates are pending.
var mu sync.Mutex // Prevent multiple updates in parallel.
func main() {
w, err := fsnotify.NewWatcher()
if err != nil {
log.Fatalf("Error making watcher: %v", err)
}
dir, err := os.Getwd()
if err != nil {
log.Fatalf("Error getting wd: %v", err)
}
for ; !exists(path.Join(dir, ".git")); dir = path.Dir(dir) {
if dir == "/" {
log.Fatalf("Could not find git root")
}
}
filepath.Walk(dir, makeWalker(w))
wg.Add(1)
populateOnce(dir)
ch := make(chan struct{}, 100) // Buffered to ensure we can multiple-increment wg for pending writes
go doPopulate(ch, dir)
http.HandleFunc("/", serve)
go http.ListenAndServe(":8000", nil)
for {
select {
case e := <-w.Events:
if filter(e) {
wg.Add(1)
ch <- struct{}{}
}
}
}
}
func makeWalker(w *fsnotify.Watcher) filepath.WalkFunc {
return func(path string, _ os.FileInfo, err error) error {
if err != nil {
log.Fatalf("Error walking: %v", err)
}
w.Add(path)
return nil
}
}
// Return true if event should trigger re-population
func filter(e fsnotify.Event) bool {
// vim is *really* noisy about how it writes files
if e.Op != fsnotify.Write {
return false
}
// Avoid some temp files that vim writes
if strings.HasSuffix(e.Name, "~") || strings.HasSuffix(e.Name, ".swp") || strings.HasPrefix(e.Name, ".") {
return false
}
// Avoid infinite cycles being caused by writing actual output
if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") {
return false
}
return true
}
func serve(w http.ResponseWriter, req *http.Request) {
wg.Wait()
b := toServe.Load().([]byte)
w.Write(b)
}
func populateOnce(dir string) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
cmd := exec.Command("python", "gendoc.py")
cmd.Dir = path.Join(dir, "scripts")
var b bytes.Buffer
cmd.Stderr = &b
err := cmd.Run()
if err != nil {
toServe.Store([]byte(fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String()).Error()))
return
}
specBytes, err := ioutil.ReadFile(path.Join(dir, "scripts", "gen", "specification.html"))
if err != nil {
toServe.Store([]byte(fmt.Errorf("error reading spec: %v", err).Error()))
return
}
toServe.Store(specBytes)
}
func doPopulate(ch chan struct{}, dir string) {
for _ = range ch {
populateOnce(dir)
}
}
func exists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
Loading…
Cancel
Save