From f9fbc5590fdace209e926d7ec62ce4b913cd38e0 Mon Sep 17 00:00:00 2001 From: Stephen Searles Date: Wed, 16 Aug 2017 17:36:27 -0700 Subject: [PATCH 1/3] wip: drafts done with hugolib --- client.go | 27 ++++++++++++------- deltas.go | 5 ---- http.go | 47 +++++++++++++++++++++++++++++++-- hugo.go | 78 ++++++++++++++++++++++++++++++++----------------------- 4 files changed, 107 insertions(+), 50 deletions(-) diff --git a/client.go b/client.go index 5c3f536..8963aa8 100644 --- a/client.go +++ b/client.go @@ -1,14 +1,14 @@ package caddyhugo import ( - "encoding/base64" "fmt" "io/ioutil" "net/http" - "os" "path" "sync" + "github.com/spf13/afero" + "git.stephensearles.com/stephen/acedoc" "git.stephensearles.com/stephen/idleshut" ) @@ -18,7 +18,7 @@ type editSession struct { docname string filename string doc *acedoc.Document - tmpdir string + tmpfs afero.Fs mtx sync.Mutex } @@ -38,16 +38,16 @@ func (ch *CaddyHugo) newEditSession(docName string) (*editSession, error) { return nil, err } - draftPrefix := fmt.Sprintf("draft-%s", base64.RawURLEncoding.EncodeToString([]byte(docName))) - tmpdir := path.Join(os.TempDir(), draftPrefix) - es := &editSession{ docname: docName, filename: filename, doc: acedoc.NewString(string(contents)), - tmpdir: tmpdir, + // tmpfs: afero.NewCopyOnWriteFs(afero.NewBasePathFs(afero.NewOsFs(), ch.Dir+"/"), afero.NewMemMapFs()), + tmpfs: afero.NewCopyOnWriteFs(afero.NewReadOnlyFs(afero.NewBasePathFs(afero.NewOsFs(), ch.Dir)), afero.NewMemMapFs()), } + printTree(es.tmpfs) + err = es.doc.LogToFile(path.Join(ch.Dir, "logs", docName)) if err != nil { fmt.Println(err) @@ -56,7 +56,10 @@ func (ch *CaddyHugo) newEditSession(docName string) (*editSession, error) { ch.docs[filename] = es - ch.renderDraft(es) + err = ch.renderDraft(es) + if err != nil { + return nil, err + } return es, nil } @@ -70,9 +73,13 @@ func (ch *CaddyHugo) renderDraft(es *editSession) error { } } - proc = idleshut.New(HugoCmdProcessConfig(ch, es, f)) + cfg, err := HugoCmdProcessConfig(ch, es, f) + if err != nil { + return fmt.Errorf("rendering draft: %v", err) + } - return nil + proc = idleshut.New(cfg) + return proc.Start() } func (ch *CaddyHugo) hasEditSession(docName string) (*editSession, bool) { diff --git a/deltas.go b/deltas.go index 7ab91fd..5955bfb 100644 --- a/deltas.go +++ b/deltas.go @@ -16,11 +16,6 @@ type DeltaConn interface { WriteJSON(v interface{}) error } -const ( - IdleWebsocketTimeout = 10 * time.Minute - WebsocketFileTicker = 1 * time.Second -) - func (ch *CaddyHugo) ObserveLTime(ltime uint64) uint64 { ch.mtx.Lock() diff --git a/http.go b/http.go index 2a17c96..352c53c 100644 --- a/http.go +++ b/http.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "os" "path" "path/filepath" "strings" @@ -12,6 +13,7 @@ import ( "git.stephensearles.com/stephen/caddy-hugo2/assets" "github.com/mholt/caddy" "github.com/mholt/caddy/caddyhttp/httpserver" + "github.com/spf13/afero" ) func (ch *CaddyHugo) ServeHTTPWithNext(next httpserver.Handler, c *caddy.Controller, w http.ResponseWriter, r *http.Request) (int, error) { @@ -199,7 +201,7 @@ func (ch *CaddyHugo) serveDraft(w http.ResponseWriter, r *http.Request) (int, er r.URL.Path = strings.ToLower(r.URL.Path) prefix := "/hugo/draft/" + encoded - r.URL.Path = r.URL.Path[len(prefix):] + r.URL.Path = path.Join("public", r.URL.Path[len(prefix):]) page := ch.HugoSites.GetContentPage(ch.docFilename(name)) if page == nil { @@ -208,7 +210,48 @@ func (ch *CaddyHugo) serveDraft(w http.ResponseWriter, r *http.Request) (int, er } r.URL.Path = page.RelPermalink() - http.FileServer(http.Dir(docref.tmpdir)).ServeHTTP(w, r) + http.FileServer(aferoHTTP{afero.NewBasePathFs(docref.tmpfs, "public")}).ServeHTTP(w, r) return 200, nil } + +func printTree(fs afero.Fs) { + const tab = " " + + afero.Walk(fs, "/", filepath.WalkFunc(func(path string, info os.FileInfo, err error) error { + if path == "/" { + return nil + } + if filepath.Base(path) == ".git" { + return filepath.SkipDir + } + + if err != nil && !os.IsNotExist(err) { + fmt.Println(err) + return err + } else if err != nil { + return nil + } + + nIndent := len(strings.Split(path, string(filepath.Separator))) - 1 + indent := strings.Repeat(tab, nIndent) + + if info.IsDir() { + fmt.Printf("%s├ %s\n", indent, info.Name()) + nIndent++ + } else { + fmt.Printf("%s| %s\t%d\n", indent, info.Name(), info.Size()) + } + + return nil + })) +} + +type aferoHTTP struct { + afero.Fs +} + +func (a aferoHTTP) Open(name string) (http.File, error) { + af, err := a.Fs.Open(name) + return af, err +} diff --git a/hugo.go b/hugo.go index 36c06cd..46f253a 100644 --- a/hugo.go +++ b/hugo.go @@ -2,13 +2,21 @@ package caddyhugo import ( "fmt" - "io/ioutil" - "os" - "os/exec" + "time" + + "github.com/gohugoio/hugo/deps" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugolib" + "github.com/spf13/afero" "git.stephensearles.com/stephen/idleshut" ) +const ( + IdleWebsocketTimeout = 10 * time.Minute + WebsocketFileTicker = 1 * time.Second +) + type HugoInteractor interface { Render(srcdir, workdir string) HugoRenderer } @@ -19,48 +27,52 @@ type HugoRenderer interface { Stop() error } -func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) idleshut.Config { - cmd := exec.Command("hugo", "--watch", "-D", "-d", es.tmpdir) - cmd.Dir = es.tmpdir +func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) (idleshut.Config, error) { + + printTree(es.tmpfs) + var err error + hugoCfg := &deps.DepsCfg{Fs: hugofs.NewFrom(es.tmpfs, ch.HugoCfg.Cfg)} + hugoCfg.Cfg, err = hugolib.LoadConfig(es.tmpfs, "/", "config.toml") + if err != nil { + return idleshut.Config{}, fmt.Errorf("caddy-hugo: loading site configuration: %v", err) + } + + hugoCfg.Cfg.Set("workingdir", "/") + hugoSites, err := hugolib.NewHugoSites(*hugoCfg) + if err != nil { + return idleshut.Config{}, fmt.Errorf("caddy-hugo: initializing site: %v", err) + } + + err = hugoSites.Build(hugolib.BuildCfg{ResetState: true}) + if err != nil { + // TODO better + return idleshut.Config{}, fmt.Errorf("caddy-hugo: building site: %v", err) + } return idleshut.Config{ TickDuration: WebsocketFileTicker, MaxIdleTicks: uint(IdleWebsocketTimeout/WebsocketFileTicker) + 1, - Start: func() error { - - err := cmd.Start() - if err != nil { - return fmt.Errorf("error starting hugo: %v", err) - } - - return nil - }, Stop: func() error { - if cmd == nil { - return nil - } - - err := cmd.Process.Signal(os.Interrupt) - if err != nil { - return fmt.Errorf("error signalling hugo to stop: %v", err) - } - err = cmd.Wait() - if err != nil { - return fmt.Errorf("error waiting for hugo to stop: %v", err) - return err - } - es.doc.Close() - os.RemoveAll(es.tmpdir) delete(ch.docs, es.filename) - return nil }, TickError: func(err error) { fmt.Println("error processing draft:", err) }, Tick: func() error { - return ioutil.WriteFile(es.filename, []byte(es.doc.Contents()), 0644) + fmt.Println("TICK") + err := afero.WriteFile(es.tmpfs, es.filename, []byte(es.doc.Contents()), 0644) + if err != nil { + return err + } + + err = hugoSites.Build(hugolib.BuildCfg{ResetState: true}) + if err != nil { + return err + } + + return nil }, - } + }, nil } From 95f3e41f1e3afa1488d1350eaf5068338d3c3c70 Mon Sep 17 00:00:00 2001 From: Stephen Searles Date: Sun, 27 Aug 2017 16:44:00 -0600 Subject: [PATCH 2/3] drafts seem to be working again --- caddyhugo.go | 6 ++++++ client.go | 12 +++++++++--- http.go | 11 +++++++---- hugo.go | 14 ++++++++++---- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/caddyhugo.go b/caddyhugo.go index d7bd9ac..91446f7 100644 --- a/caddyhugo.go +++ b/caddyhugo.go @@ -75,6 +75,12 @@ func (ch *CaddyHugo) docFilename(orig string) string { // Publish really renders new content into the public directory func (ch *CaddyHugo) Publish() error { + ch.mtx.Lock() + for _, es := range ch.docs { + ch.persistEdits(es) + } + ch.mtx.Unlock() + cmd := exec.Command("hugo") cmd.Dir = ch.Dir _, err := cmd.CombinedOutput() diff --git a/client.go b/client.go index 8963aa8..f8527ba 100644 --- a/client.go +++ b/client.go @@ -43,11 +43,9 @@ func (ch *CaddyHugo) newEditSession(docName string) (*editSession, error) { filename: filename, doc: acedoc.NewString(string(contents)), // tmpfs: afero.NewCopyOnWriteFs(afero.NewBasePathFs(afero.NewOsFs(), ch.Dir+"/"), afero.NewMemMapFs()), - tmpfs: afero.NewCopyOnWriteFs(afero.NewReadOnlyFs(afero.NewBasePathFs(afero.NewOsFs(), ch.Dir)), afero.NewMemMapFs()), + tmpfs: afero.NewCopyOnWriteFs(afero.NewOsFs(), afero.NewMemMapFs()), } - printTree(es.tmpfs) - err = es.doc.LogToFile(path.Join(ch.Dir, "logs", docName)) if err != nil { fmt.Println(err) @@ -82,6 +80,14 @@ func (ch *CaddyHugo) renderDraft(es *editSession) error { return proc.Start() } +func (ch *CaddyHugo) persistEdits(es *editSession) error { + err := afero.WriteFile(afero.NewOsFs(), es.filename, []byte(es.doc.Contents()), 0644) + if err != nil { + return err + } + return nil +} + func (ch *CaddyHugo) hasEditSession(docName string) (*editSession, bool) { dr, ok := ch.docs[ch.docFilename(docName)] return dr, ok diff --git a/http.go b/http.go index 352c53c..4bb140f 100644 --- a/http.go +++ b/http.go @@ -210,7 +210,7 @@ func (ch *CaddyHugo) serveDraft(w http.ResponseWriter, r *http.Request) (int, er } r.URL.Path = page.RelPermalink() - http.FileServer(aferoHTTP{afero.NewBasePathFs(docref.tmpfs, "public")}).ServeHTTP(w, r) + http.FileServer(aferoHTTP{afero.NewBasePathFs(docref.tmpfs, path.Join(ch.Dir, "public"))}).ServeHTTP(w, r) return 200, nil } @@ -219,9 +219,6 @@ func printTree(fs afero.Fs) { const tab = " " afero.Walk(fs, "/", filepath.WalkFunc(func(path string, info os.FileInfo, err error) error { - if path == "/" { - return nil - } if filepath.Base(path) == ".git" { return filepath.SkipDir } @@ -253,5 +250,11 @@ type aferoHTTP struct { func (a aferoHTTP) Open(name string) (http.File, error) { af, err := a.Fs.Open(name) + if os.IsExist(err) { + err = os.ErrExist + } + if os.IsNotExist(err) { + err = os.ErrNotExist + } return af, err } diff --git a/hugo.go b/hugo.go index 46f253a..980d2cb 100644 --- a/hugo.go +++ b/hugo.go @@ -2,6 +2,7 @@ package caddyhugo import ( "fmt" + "path" "time" "github.com/gohugoio/hugo/deps" @@ -29,15 +30,15 @@ type HugoRenderer interface { func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) (idleshut.Config, error) { - printTree(es.tmpfs) var err error hugoCfg := &deps.DepsCfg{Fs: hugofs.NewFrom(es.tmpfs, ch.HugoCfg.Cfg)} - hugoCfg.Cfg, err = hugolib.LoadConfig(es.tmpfs, "/", "config.toml") + fmt.Println(ch.Dir) + hugoCfg.Cfg, err = hugolib.LoadConfig(es.tmpfs, "", path.Join(ch.Dir, "config.toml")) if err != nil { return idleshut.Config{}, fmt.Errorf("caddy-hugo: loading site configuration: %v", err) } - hugoCfg.Cfg.Set("workingdir", "/") + hugoCfg.Cfg.Set("workingDir", ch.Dir) hugoSites, err := hugolib.NewHugoSites(*hugoCfg) if err != nil { return idleshut.Config{}, fmt.Errorf("caddy-hugo: initializing site: %v", err) @@ -53,15 +54,20 @@ func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) (idles TickDuration: WebsocketFileTicker, MaxIdleTicks: uint(IdleWebsocketTimeout/WebsocketFileTicker) + 1, Stop: func() error { + ch.persistEdits(es) + es.doc.Close() + + ch.mtx.Lock() + defer ch.mtx.Unlock() delete(ch.docs, es.filename) + return nil }, TickError: func(err error) { fmt.Println("error processing draft:", err) }, Tick: func() error { - fmt.Println("TICK") err := afero.WriteFile(es.tmpfs, es.filename, []byte(es.doc.Contents()), 0644) if err != nil { return err From c609179dfa1676536ce3f6536ec1fcb63618d34c Mon Sep 17 00:00:00 2001 From: Stephen Searles Date: Wed, 30 Aug 2017 16:38:24 -0600 Subject: [PATCH 3/3] fixes from looking at PR --- caddyhugo.go | 10 ++++------ client.go | 19 +++++++++++++++---- http.go | 29 ----------------------------- hugo.go | 5 ++--- 4 files changed, 21 insertions(+), 42 deletions(-) diff --git a/caddyhugo.go b/caddyhugo.go index 91446f7..36730c4 100644 --- a/caddyhugo.go +++ b/caddyhugo.go @@ -75,15 +75,13 @@ func (ch *CaddyHugo) docFilename(orig string) string { // Publish really renders new content into the public directory func (ch *CaddyHugo) Publish() error { - ch.mtx.Lock() - for _, es := range ch.docs { - ch.persistEdits(es) + err := ch.persistAllEdits() + if err != nil { + return err } - ch.mtx.Unlock() - cmd := exec.Command("hugo") cmd.Dir = ch.Dir - _, err := cmd.CombinedOutput() + _, err = cmd.CombinedOutput() if err != nil { return err } diff --git a/client.go b/client.go index f8527ba..65ee767 100644 --- a/client.go +++ b/client.go @@ -42,8 +42,7 @@ func (ch *CaddyHugo) newEditSession(docName string) (*editSession, error) { docname: docName, filename: filename, doc: acedoc.NewString(string(contents)), - // tmpfs: afero.NewCopyOnWriteFs(afero.NewBasePathFs(afero.NewOsFs(), ch.Dir+"/"), afero.NewMemMapFs()), - tmpfs: afero.NewCopyOnWriteFs(afero.NewOsFs(), afero.NewMemMapFs()), + tmpfs: afero.NewCopyOnWriteFs(afero.NewOsFs(), afero.NewMemMapFs()), } err = es.doc.LogToFile(path.Join(ch.Dir, "logs", docName)) @@ -71,7 +70,7 @@ func (ch *CaddyHugo) renderDraft(es *editSession) error { } } - cfg, err := HugoCmdProcessConfig(ch, es, f) + cfg, err := HugoInternalProcessConfig(ch, es, f) if err != nil { return fmt.Errorf("rendering draft: %v", err) } @@ -80,7 +79,19 @@ func (ch *CaddyHugo) renderDraft(es *editSession) error { return proc.Start() } -func (ch *CaddyHugo) persistEdits(es *editSession) error { +func (ch *CaddyHugo) persistAllEdits() error { + ch.mtx.Lock() + defer ch.mtx.Unlock() + for _, es := range ch.docs { + err := ch.persistEditsForSession(es) + if err != nil { + return err + } + } + return nil +} + +func (ch *CaddyHugo) persistEditsForSession(es *editSession) error { err := afero.WriteFile(afero.NewOsFs(), es.filename, []byte(es.doc.Contents()), 0644) if err != nil { return err diff --git a/http.go b/http.go index 4bb140f..ca46bba 100644 --- a/http.go +++ b/http.go @@ -215,35 +215,6 @@ func (ch *CaddyHugo) serveDraft(w http.ResponseWriter, r *http.Request) (int, er return 200, nil } -func printTree(fs afero.Fs) { - const tab = " " - - afero.Walk(fs, "/", filepath.WalkFunc(func(path string, info os.FileInfo, err error) error { - if filepath.Base(path) == ".git" { - return filepath.SkipDir - } - - if err != nil && !os.IsNotExist(err) { - fmt.Println(err) - return err - } else if err != nil { - return nil - } - - nIndent := len(strings.Split(path, string(filepath.Separator))) - 1 - indent := strings.Repeat(tab, nIndent) - - if info.IsDir() { - fmt.Printf("%s├ %s\n", indent, info.Name()) - nIndent++ - } else { - fmt.Printf("%s| %s\t%d\n", indent, info.Name(), info.Size()) - } - - return nil - })) -} - type aferoHTTP struct { afero.Fs } diff --git a/hugo.go b/hugo.go index 980d2cb..f05d8b3 100644 --- a/hugo.go +++ b/hugo.go @@ -28,7 +28,7 @@ type HugoRenderer interface { Stop() error } -func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) (idleshut.Config, error) { +func HugoInternalProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) (idleshut.Config, error) { var err error hugoCfg := &deps.DepsCfg{Fs: hugofs.NewFrom(es.tmpfs, ch.HugoCfg.Cfg)} @@ -46,7 +46,6 @@ func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) (idles err = hugoSites.Build(hugolib.BuildCfg{ResetState: true}) if err != nil { - // TODO better return idleshut.Config{}, fmt.Errorf("caddy-hugo: building site: %v", err) } @@ -54,7 +53,7 @@ func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) (idles TickDuration: WebsocketFileTicker, MaxIdleTicks: uint(IdleWebsocketTimeout/WebsocketFileTicker) + 1, Stop: func() error { - ch.persistEdits(es) + ch.persistEditsForSession(es) es.doc.Close()