diff --git a/caddyhugo.go b/caddyhugo.go index fbb3879..8d35bfe 100644 --- a/caddyhugo.go +++ b/caddyhugo.go @@ -6,6 +6,7 @@ import ( "net/http" _ "net/http/pprof" "os/exec" + "path/filepath" "strings" "sync" @@ -61,10 +62,15 @@ func (ch CaddyHugo) BasePath() string { return "/hugo" } -func (ch *CaddyHugo) docname(orig string) string { +func docname(orig string) string { + orig = strings.Replace(orig, " ", "-", -1) return strings.ToLower(orig) } +func (ch *CaddyHugo) docFilename(orig string) string { + return filepath.Join(ch.Dir, "content", docname(orig)) +} + func (ch *CaddyHugo) Publish() error { cmd := exec.Command("hugo") cmd.Dir = ch.Dir diff --git a/client.go b/client.go index 2d88240..bc00621 100644 --- a/client.go +++ b/client.go @@ -8,8 +8,6 @@ import ( "os" "os/exec" "path" - "path/filepath" - "strings" "time" "git.stephensearles.com/stephen/acedoc" @@ -22,93 +20,109 @@ type docref struct { tmpdir string } -func (ch *CaddyHugo) doc(r *http.Request) (*docref, error) { - ch.mtx.Lock() - defer ch.mtx.Unlock() - - name := r.URL.Path[len("/hugo/edit/"):] - name = filepath.Join(ch.Dir, name) - name = strings.ToLower(name) +func (ch *CaddyHugo) newClient(docName string) (*docref, error) { + filename := ch.docFilename(docName) - _, ok := ch.docs[ch.docname(name)] - if !ok { - fmt.Println("opening", name) - contents, err := ioutil.ReadFile(name) - if err != nil { - return nil, err - } + fmt.Println("opening", filename) + contents, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } - draftPrefix := fmt.Sprintf("draft-%s", base64.RawURLEncoding.EncodeToString([]byte(name))) - tmpdir := path.Join(os.TempDir(), draftPrefix) + draftPrefix := fmt.Sprintf("draft-%s", base64.RawURLEncoding.EncodeToString([]byte(docName))) + tmpdir := path.Join(os.TempDir(), draftPrefix) - ref := &docref{ - name: name, - doc: acedoc.NewString(string(contents)), - tmpdir: tmpdir, - } + ref := &docref{ + name: docName, + doc: acedoc.NewString(string(contents)), + tmpdir: tmpdir, + } - err = ref.doc.LogToFile(path.Join(ch.Dir, "logs", r.URL.Path[len("/hugo/edit/"):])) - if err != nil { - fmt.Println(err) - return nil, err - } + err = ref.doc.LogToFile(path.Join(ch.Dir, "logs", docName)) + if err != nil { + fmt.Println(err) + return nil, err + } - ch.docs[ch.docname(name)] = ref + ch.docs[filename] = ref - hugoCmd := exec.Command("hugo", "--watch", "-D", "-d", ref.tmpdir) - hugoCmd.Dir = ch.Dir - err = hugoCmd.Start() - if err != nil { - return nil, fmt.Errorf("error starting hugo: %v", err) - } + hugoCmd := exec.Command("hugo", "--watch", "-D", "-d", ref.tmpdir) + hugoCmd.Dir = ch.Dir + err = hugoCmd.Start() + if err != nil { + return nil, fmt.Errorf("error starting hugo: %v", err) + } - go func() { - ticker := time.NewTicker(WebsocketFileTicker) - idleTicks := 0 + go func() { + ticker := time.NewTicker(WebsocketFileTicker) + idleTicks := 0 - defer func() { - err := hugoCmd.Process.Signal(os.Interrupt) - if err != nil { - fmt.Println("error signaling to hugo:", err) - } - err = hugoCmd.Wait() - if err != nil { - fmt.Println("error waiting for hugo:", err) - } - }() + defer func() { + err := hugoCmd.Process.Signal(os.Interrupt) + if err != nil { + fmt.Println("error signaling to hugo:", err) + } + err = hugoCmd.Wait() + if err != nil { + fmt.Println("error waiting for hugo:", err) + } + }() - for { - <-ticker.C - ch.mtx.Lock() + for { + <-ticker.C + ch.mtx.Lock() - err := ioutil.WriteFile(name, []byte(ref.doc.Contents()), 0644) - if err != nil { - fmt.Println("error saving document contents:", err) - } + err := ioutil.WriteFile(filename, []byte(ref.doc.Contents()), 0644) + if err != nil { + fmt.Println("error saving document contents:", err) + } - if ref.clients == 0 { - idleTicks++ - idleTime := time.Duration(idleTicks) * WebsocketFileTicker - if idleTime >= IdleWebsocketTimeout { - err := ch.Publish() - fmt.Printf("idle for %v, quitting\n", idleTime) - if err != nil { - fmt.Printf(", error publishing: %v\n", err) - } - - ref.doc.Close() - os.RemoveAll(tmpdir) - delete(ch.docs, ch.docname(name)) - ch.mtx.Unlock() - return + if ref.clients == 0 { + idleTicks++ + idleTime := time.Duration(idleTicks) * WebsocketFileTicker + if idleTime >= IdleWebsocketTimeout { + err := ch.Publish() + fmt.Printf("idle for %v, quitting\n", idleTime) + if err != nil { + fmt.Printf(", error publishing: %v\n", err) } - } else { - idleTicks = 0 + + ref.doc.Close() + os.RemoveAll(tmpdir) + delete(ch.docs, filename) + ch.mtx.Unlock() + return } - ch.mtx.Unlock() + } else { + idleTicks = 0 } - }() + ch.mtx.Unlock() + } + }() + + return ref, nil +} + +func (ch *CaddyHugo) hasdocref(docName string) (*docref, bool) { + dr, ok := ch.docs[ch.docFilename(docName)] + return dr, ok +} + +func (ch *CaddyHugo) client(docName string) (*docref, error) { + ch.mtx.Lock() + defer ch.mtx.Unlock() + + var err error + + dr, ok := ch.hasdocref(docName) + if !ok { + dr, err = ch.newClient(docName) } - return ch.docs[ch.docname(name)], nil + return dr, err +} + +func (ch *CaddyHugo) doc(r *http.Request) (*docref, error) { + name := r.URL.Path[len("/hugo/edit/"):] + return ch.client(name) } diff --git a/content.go b/content.go index f93bc28..3b7a985 100644 --- a/content.go +++ b/content.go @@ -7,7 +7,6 @@ import ( "path" "path/filepath" "sort" - "strings" "time" "github.com/gohugoio/hugo/hugolib" @@ -25,10 +24,8 @@ type Metadata struct { Date, Lastmod time.Time } -func ListContent() ([]Content, error) { - return nil, nil -} - +// GetContent fetches the list of available content from the site directory. If +// possible, the return value will be enriched with metadata from Hugo. func GetContent(siteRoot string, sites *hugolib.HugoSites) ([]Content, error) { var files = []Content{} @@ -83,6 +80,10 @@ func GetContent(siteRoot string, sites *hugolib.HugoSites) ([]Content, error) { return files, nil } +// NewContent initializes new content with the name (title) and content type. +// If ctype is empty string, "default" is used. The return value is the filename, +// which may be modified from the title and includes the content type if other than +// default, but does not include the full directory relative to the site. func (ch CaddyHugo) NewContent(name, ctype string) (string, error) { if filepath.Ext(name) != ".md" { name += ".md" @@ -92,9 +93,10 @@ func (ch CaddyHugo) NewContent(name, ctype string) (string, error) { ctype = "default" } - filename := path.Join(ctype, strings.ToLower(name)) + name = docname(name) + filename := path.Join(ctype, name) if ctype == "default" { - filename = strings.ToLower(name) + filename = name } _, err := os.Stat(path.Join(ch.Dir, "content", filename)) diff --git a/doc_test.go b/doc_test.go index 8d7b974..0e762eb 100644 --- a/doc_test.go +++ b/doc_test.go @@ -4,7 +4,12 @@ import ( "io/ioutil" "os" "os/exec" + "path" + "sync" "testing" + "time" + + "git.stephensearles.com/stephen/acedoc" ) type World struct { @@ -38,10 +43,53 @@ func NewWorld(t *testing.T) World { return w } -func TestDoc(t *testing.T) { +func TestEdits(t *testing.T) { w := NewWorld(t) defer w.Clean() + const title = "sometitle" + w.CH.NewContent(title, "") + + send := []acedoc.Delta{ + acedoc.Insert(0, 0, "hello"), + acedoc.Insert(0, 5, " world"), + acedoc.Insert(0, 11, " world"), + } + var mtx sync.Mutex + received := []acedoc.Delta{} + + doc, err := w.CH.client(title + ".md") + if err != nil { + t.Fatal("error creating document client:", err) + } + + doc.doc.Client(acedoc.DeltaHandlerFunc(func(ds []acedoc.Delta) error { + // receive some deltas... + mtx.Lock() + defer mtx.Unlock() + received = append(received, ds...) + return nil + })) + + _, ok := w.CH.hasdocref(title + ".md") + if !ok { + t.Fatal("expected there to be an established client") + } + + doc.doc.Apply(send...) + + <-time.After(5 * time.Second) + if len(received) != len(send) { + t.Errorf("expected %d deltas, received %d; expected: %v, received: %v", len(send), len(received), send, received) + } + +} + +func TestPagesInPagesOut(t *testing.T) { + w := NewWorld(t) + defer w.Clean() + + // check there's no content at first c, err := GetContent(w.BlogFolder, w.CH.HugoSites) if err != nil { t.Fatalf("couldn't get content from a blank test environment: %v", err) @@ -50,21 +98,46 @@ func TestDoc(t *testing.T) { t.Fatalf("expected a blank test environment, but saw %d pages", len(c)) } - w.CH.NewContent("test1", "") - c, err = GetContent(w.BlogFolder, w.CH.HugoSites) - if err != nil { - t.Fatalf("couldn't get content from the test environment: %v", err) + titles := []string{ + "test1", + "TEST 2!!", } - if len(c) != 1 { - t.Fatalf("expected 1 page, but saw %d pages", len(c)) + + found := map[string]bool{} + + // create some known content + for i, title := range titles { + w.CH.NewContent(title, "") + c, err = GetContent(w.BlogFolder, w.CH.HugoSites) + if err != nil { + t.Fatalf("couldn't get content from the test environment: %v", err) + } + if len(c)-1 != i { + t.Fatalf("expected %d page, but saw %d pages", i-1, len(c)) + } + } - w.CH.NewContent("TEST 2!!", "") + // make sure we get the content out that we just created c, err = GetContent(w.BlogFolder, w.CH.HugoSites) if err != nil { t.Fatalf("couldn't get content from the test environment: %v", err) } - if len(c) != 2 { - t.Fatalf("expected 2 pages, but saw %d pages", len(c)) + + for _, content := range c { + found[content.Filename] = true + } + + var missingSomething bool + for _, title := range titles { + adjusted := path.Join("content", docname(title)+".md") + if !found[adjusted] { + missingSomething = true + t.Errorf("expected to find title %q, but didn't see it", adjusted) + } + } + + if missingSomething { + t.Logf("found titles: %v", found) } } diff --git a/http.go b/http.go index 4996fcd..5865f3d 100644 --- a/http.go +++ b/http.go @@ -179,7 +179,7 @@ func (ch *CaddyHugo) serveDraft(w http.ResponseWriter, r *http.Request) (int, er ch.mtx.Lock() defer ch.mtx.Unlock() - docref, ok := ch.docs[ch.docname(name)] + docref, ok := ch.docs[docname(name)] if !ok { return http.StatusNotFound, fmt.Errorf("draft not found") }