From e9fd60170fe07f17cbe21e50822456e6cd88122d Mon Sep 17 00:00:00 2001 From: JT Archie Date: Sat, 2 Sep 2023 11:41:25 -0600 Subject: [PATCH] add watcher struct for testing --- cli.go | 29 ++++++++---------- go.mod | 1 + go.sum | 2 ++ watcher.go | 41 +++++++++++++++++++++++++ watcher_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 watcher.go create mode 100644 watcher_test.go diff --git a/cli.go b/cli.go index 14c9ec7..30eef5a 100644 --- a/cli.go +++ b/cli.go @@ -6,7 +6,6 @@ import ( "path/filepath" "github.com/bmatcuk/doublestar/v4" - "github.com/fsnotify/fsnotify" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) @@ -31,28 +30,24 @@ func (c *CLI) Run() error { } if c.Serve { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return fmt.Errorf("could not create file watcher: %w", err) - } - defer watcher.Close() + watcher := NewWatcher(c.SourcePath) - go func() { - for event := range watcher.Events { - matched, _ := doublestar.Match(filepath.Join(c.SourcePath, "**", "*.md"), event.Name) + //nolint:errcheck,unparam + go watcher.Execute(func(filename string) error { + glob := filepath.Join(c.SourcePath, "**", "*.md") + matched, _ := doublestar.Match(glob, filename) - if matched { - slog.Info("rebuilding markdown files") + if matched { + slog.Info("rebuilding markdown files") - _ = renderer.Execute() + err := renderer.Execute() + if err != nil { + slog.Error("could not rebuild markdown files", slog.String("error", err.Error())) } } - }() - err = watcher.Add(c.SourcePath) - if err != nil { - return fmt.Errorf("could add watching path: %w", err) - } + return nil + }) e := echo.New() e.Use(middleware.Logger()) diff --git a/go.mod b/go.mod index 1efd247..743cc7e 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/stretchr/testify v1.8.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.12.0 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/net v0.14.0 // indirect diff --git a/go.sum b/go.sum index 3992c0d..59b23be 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZ github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= go.abhg.dev/goldmark/mermaid v0.4.0 h1:yqwaYvNFbKwh9JByKzN0eDpxrUoYe8NUJvtSKY94S5M= go.abhg.dev/goldmark/mermaid v0.4.0/go.mod h1:L5SiQ7PedPuZY0+zaPoJ5ZnDitIYS0Obi5w7Pf02tPY= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= diff --git a/watcher.go b/watcher.go new file mode 100644 index 0000000..31e698e --- /dev/null +++ b/watcher.go @@ -0,0 +1,41 @@ +package builder + +import ( + "fmt" + + "github.com/fsnotify/fsnotify" +) + +type Watcher struct { + sourceDir string +} + +func NewWatcher( + sourceDir string, +) *Watcher { + return &Watcher{ + sourceDir: sourceDir, + } +} + +func (w *Watcher) Execute(fn func(string) error) error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return fmt.Errorf("could not create file watcher: %w", err) + } + defer watcher.Close() + + err = watcher.Add(w.sourceDir) + if err != nil { + return fmt.Errorf("could add watching path: %w", err) + } + + for event := range watcher.Events { + err := fn(event.Name) + if err != nil { + return fmt.Errorf("could not execute fn in watcher: %w", err) + } + } + + return nil +} diff --git a/watcher_test.go b/watcher_test.go new file mode 100644 index 0000000..f508f3d --- /dev/null +++ b/watcher_test.go @@ -0,0 +1,81 @@ +package builder_test + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/jtarchie/builder" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.uber.org/atomic" +) + +var _ = Describe("Watcher", func() { + When("a file gets updated", func() { + It("executes the callback with the affected filename", func() { + var foundFilename atomic.String + + sourceDir, err := os.MkdirTemp("", "") + Expect(err).NotTo(HaveOccurred()) + + watcher := builder.NewWatcher(sourceDir) + + //nolint:errcheck,unparam + go watcher.Execute(func(filename string) error { + foundFilename.Store(filename) + + return nil + }) + + Consistently(foundFilename.Load).Should(Equal("")) + + expectedFilename := filepath.Join(sourceDir, "file") + + err = os.WriteFile(expectedFilename, []byte(""), os.ModePerm) + Expect(err).NotTo(HaveOccurred()) + + Eventually(foundFilename.Load).Should(Equal(expectedFilename)) + }) + }) + + When("the source path does not exists", func() { + It("returns an error", func() { + watcher := builder.NewWatcher("asdf") + + err := watcher.Execute(func(s string) error { + return nil + }) + Expect(err).To(HaveOccurred()) + }) + }) + + When("the callback returns an error", func() { + It("stops watching altogether", func() { + var callbackCount atomic.Int32 + + sourceDir, err := os.MkdirTemp("", "") + Expect(err).NotTo(HaveOccurred()) + + watcher := builder.NewWatcher(sourceDir) + + //nolint:errcheck,unparam + go watcher.Execute(func(filename string) error { + callbackCount.Add(1) + + return fmt.Errorf("some error") + }) + + Consistently(callbackCount.Load).Should(BeEquivalentTo(0)) + + for i := 0; i < 10; i++ { + expectedFilename := filepath.Join(sourceDir, "file") + + err = os.WriteFile(expectedFilename, []byte(fmt.Sprintf("%d", i)), os.ModePerm) + Expect(err).NotTo(HaveOccurred()) + } + + Consistently(callbackCount.Load).Should(BeEquivalentTo(1)) + }) + }) +})