From b4f2c841eb860a505366ecdfcac9e63ba7ef7df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Sun, 3 Jan 2021 12:28:06 +0100 Subject: [PATCH] Move Diff to zk package and add Dir walking --- core/note/note.go | 17 ----- core/{note => zk}/diff.go | 49 +++++++------ core/{note => zk}/diff_test.go | 54 ++++++++++++--- core/zk/dir.go | 82 ++++++++++++++++++++++ core/zk/dir_test.go | 87 ++++++++++++++++++++++++ core/zk/fixtures/walk/a.md | 0 core/zk/fixtures/walk/b.md | 0 core/zk/fixtures/walk/dir1/.ignored.md | 0 core/zk/fixtures/walk/dir1/.ignored/a.md | 0 core/zk/fixtures/walk/dir1/a.md | 0 core/zk/fixtures/walk/dir1/b.md | 0 core/zk/fixtures/walk/dir1/dir1/a.md | 0 core/zk/fixtures/walk/dir1/ignored.txt | 0 core/zk/fixtures/walk/dir2/a.md | 0 core/zk/zk.go | 12 ++-- util/assert/assert.go | 5 +- 16 files changed, 243 insertions(+), 63 deletions(-) rename core/{note => zk}/diff.go (77%) rename core/{note => zk}/diff_test.go (80%) create mode 100644 core/zk/dir.go create mode 100644 core/zk/dir_test.go create mode 100644 core/zk/fixtures/walk/a.md create mode 100644 core/zk/fixtures/walk/b.md create mode 100644 core/zk/fixtures/walk/dir1/.ignored.md create mode 100644 core/zk/fixtures/walk/dir1/.ignored/a.md create mode 100644 core/zk/fixtures/walk/dir1/a.md create mode 100644 core/zk/fixtures/walk/dir1/b.md create mode 100644 core/zk/fixtures/walk/dir1/dir1/a.md create mode 100644 core/zk/fixtures/walk/dir1/ignored.txt create mode 100644 core/zk/fixtures/walk/dir2/a.md diff --git a/core/note/note.go b/core/note/note.go index 75c75bf..0e5b0c9 100644 --- a/core/note/note.go +++ b/core/note/note.go @@ -1,22 +1,5 @@ package note -import ( - "time" -) - -// Path holds a note path relative to its slip box. -type Path struct { - Dir string - Filename string -} - -// FileMetadata holds information about a note file. -type FileMetadata struct { - Path Path - Created time.Time - Modified time.Time -} - // Metadata holds information about a particular note. type Metadata struct { Title string diff --git a/core/note/diff.go b/core/zk/diff.go similarity index 77% rename from core/note/diff.go rename to core/zk/diff.go index 38427eb..3189d1e 100644 --- a/core/note/diff.go +++ b/core/zk/diff.go @@ -1,4 +1,4 @@ -package note +package zk // DiffChange represents a note change made in a slip box directory. type DiffChange struct { @@ -20,35 +20,32 @@ const ( // // Warning: The FileMetadata have to be sorted by their Path for the diffing to // work properly. -func Diff(source, target <-chan FileMetadata) <-chan DiffChange { - c := make(chan DiffChange) - go func() { - defer close(c) +func Diff(source, target <-chan FileMetadata, callback func(DiffChange) error) error { + var err error + var sourceFile, targetFile FileMetadata + var sourceOpened, targetOpened bool = true, true + pair := diffPair{} - pair := diffPair{} - var sourceFile, targetFile FileMetadata - var sourceOpened, targetOpened bool = true, true - - for sourceOpened || targetOpened { - if pair.source == nil { - sourceFile, sourceOpened = <-source - if sourceOpened { - pair.source = &sourceFile - } - } - if pair.target == nil { - targetFile, targetOpened = <-target - if targetOpened { - pair.target = &targetFile - } + for err == nil && (sourceOpened || targetOpened) { + if pair.source == nil { + sourceFile, sourceOpened = <-source + if sourceOpened { + pair.source = &sourceFile } - change := pair.diff() - if change != nil { - c <- *change + } + if pair.target == nil { + targetFile, targetOpened = <-target + if targetOpened { + pair.target = &targetFile } } - }() - return c + change := pair.diff() + if change != nil { + err = callback(*change) + } + } + + return err } // diffPair holds the current two files to be diffed. diff --git a/core/note/diff_test.go b/core/zk/diff_test.go similarity index 80% rename from core/note/diff_test.go rename to core/zk/diff_test.go index f857856..f5b2db0 100644 --- a/core/note/diff_test.go +++ b/core/zk/diff_test.go @@ -1,6 +1,7 @@ -package note +package zk import ( + "errors" "testing" "time" @@ -194,9 +195,48 @@ func TestDiffEmptyTarget(t *testing.T) { }) } +func TestDiffCancellation(t *testing.T) { + source := []FileMetadata{ + { + Path: Path{Dir: "a", Filename: "1"}, + Modified: date1, + }, + { + Path: Path{Dir: "a", Filename: "2"}, + Modified: date2, + }, + } + + target := []FileMetadata{} + + received := make([]DiffChange, 0) + err := Diff(toChannel(source), toChannel(target), func(change DiffChange) error { + received = append(received, change) + + if len(received) == 1 { + return errors.New("cancelled") + } else { + return nil + } + }) + + assert.Equal(t, received, []DiffChange{ + { + Path: Path{Dir: "a", Filename: "1"}, + Kind: DiffAdded, + }, + }) + assert.Err(t, err, "cancelled") +} + func test(t *testing.T, source, target []FileMetadata, expected []DiffChange) { - actual := toSlice(Diff(toChannel(source), toChannel(target))) - assert.Equal(t, actual, expected) + received := make([]DiffChange, 0) + err := Diff(toChannel(source), toChannel(target), func(change DiffChange) error { + received = append(received, change) + return nil + }) + assert.Nil(t, err) + assert.Equal(t, received, expected) } func toChannel(fm []FileMetadata) <-chan FileMetadata { @@ -209,11 +249,3 @@ func toChannel(fm []FileMetadata) <-chan FileMetadata { }() return c } - -func toSlice(c <-chan DiffChange) []DiffChange { - s := make([]DiffChange, 0) - for i := range c { - s = append(s, i) - } - return s -} diff --git a/core/zk/dir.go b/core/zk/dir.go new file mode 100644 index 0000000..3379f95 --- /dev/null +++ b/core/zk/dir.go @@ -0,0 +1,82 @@ +package zk + +import ( + "os" + "path/filepath" + "strings" + "time" + + "github.com/mickael-menu/zk/util" +) + +// Dir represents a directory inside a slip box. +type Dir struct { + // Name of the directory, which is the path relative to the slip box's root. + Name string + // Absolute path to the directory. + Path string + // User configuration for this directory. + Config DirConfig +} + +// FileMetadata holds information about a note file. +type FileMetadata struct { + Path Path + Modified time.Time +} + +// Walk emits the metadata of each note stored in the directory. +func (d Dir) Walk(logger util.Logger) <-chan FileMetadata { + c := make(chan FileMetadata, 50) + go func() { + defer close(c) + + err := filepath.Walk(d.Path, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + filename := info.Name() + isHidden := strings.HasPrefix(filename, ".") + + if info.IsDir() { + if isHidden { + return filepath.SkipDir + } + + } else { + // FIXME: Customize extension in config + if isHidden || filepath.Ext(filename) != ".md" { + return nil + } + + path, err := filepath.Rel(d.Path, path) + if err != nil { + logger.Println(err) + return nil + } + + curDir := filepath.Dir(path) + if curDir == "." { + curDir = "" + } + + c <- FileMetadata{ + Path: Path{ + Dir: filepath.Join(d.Name, curDir), + Filename: filename, + }, + Modified: info.ModTime(), + } + } + + return nil + }) + + if err != nil { + logger.Println(err) + } + }() + + return c +} diff --git a/core/zk/dir_test.go b/core/zk/dir_test.go new file mode 100644 index 0000000..e745ee5 --- /dev/null +++ b/core/zk/dir_test.go @@ -0,0 +1,87 @@ +package zk + +import ( + "path/filepath" + "testing" + "time" + + "github.com/mickael-menu/zk/util" + "github.com/mickael-menu/zk/util/assert" + "github.com/mickael-menu/zk/util/fixtures" +) + +var root = fixtures.Path("walk") + +func TestWalkRootDir(t *testing.T) { + dir := Dir{Name: "", Path: root} + res := toSlice(dir.Walk(&util.NullLogger)) + assert.Equal(t, res, []FileMetadata{ + { + Path: Path{Dir: "", Filename: "a.md"}, + Modified: date("2021-01-03T11:30:26.069257899+01:00"), + }, + { + Path: Path{Dir: "", Filename: "b.md"}, + Modified: date("2021-01-03T11:30:27.545667767+01:00"), + }, + { + Path: Path{Dir: "dir1", Filename: "a.md"}, + Modified: date("2021-01-03T11:31:18.961628888+01:00"), + }, + { + Path: Path{Dir: "dir1", Filename: "b.md"}, + Modified: date("2021-01-03T11:31:24.692881103+01:00"), + }, + { + Path: Path{Dir: "dir1/dir1", Filename: "a.md"}, + Modified: date("2021-01-03T11:31:27.900472856+01:00"), + }, + { + Path: Path{Dir: "dir2", Filename: "a.md"}, + Modified: date("2021-01-03T11:31:51.001827456+01:00"), + }, + }) +} + +func TestWalkSubDir(t *testing.T) { + dir := Dir{Name: "dir1", Path: filepath.Join(root, "dir1")} + res := toSlice(dir.Walk(&util.NullLogger)) + assert.Equal(t, res, []FileMetadata{ + { + Path: Path{Dir: "dir1", Filename: "a.md"}, + Modified: date("2021-01-03T11:31:18.961628888+01:00"), + }, + { + Path: Path{Dir: "dir1", Filename: "b.md"}, + Modified: date("2021-01-03T11:31:24.692881103+01:00"), + }, + { + Path: Path{Dir: "dir1/dir1", Filename: "a.md"}, + Modified: date("2021-01-03T11:31:27.900472856+01:00"), + }, + }) +} + +func TestWalkSubSubDir(t *testing.T) { + dir := Dir{Name: "dir1/dir1", Path: filepath.Join(root, "dir1/dir1")} + res := toSlice(dir.Walk(&util.NullLogger)) + assert.Equal(t, res, []FileMetadata{ + { + Path: Path{Dir: "dir1/dir1", Filename: "a.md"}, + Modified: date("2021-01-03T11:31:27.900472856+01:00"), + }, + }) +} + +func date(s string) time.Time { + date, _ := time.Parse(time.RFC3339, s) + return date +} + +func toSlice(c <-chan FileMetadata) []FileMetadata { + s := make([]FileMetadata, 0) + for fm := range c { + s = append(s, fm) + } + return s +} diff --git a/core/zk/fixtures/walk/a.md b/core/zk/fixtures/walk/a.md new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/fixtures/walk/b.md b/core/zk/fixtures/walk/b.md new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/fixtures/walk/dir1/.ignored.md b/core/zk/fixtures/walk/dir1/.ignored.md new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/fixtures/walk/dir1/.ignored/a.md b/core/zk/fixtures/walk/dir1/.ignored/a.md new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/fixtures/walk/dir1/a.md b/core/zk/fixtures/walk/dir1/a.md new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/fixtures/walk/dir1/b.md b/core/zk/fixtures/walk/dir1/b.md new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/fixtures/walk/dir1/dir1/a.md b/core/zk/fixtures/walk/dir1/dir1/a.md new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/fixtures/walk/dir1/ignored.txt b/core/zk/fixtures/walk/dir1/ignored.txt new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/fixtures/walk/dir2/a.md b/core/zk/fixtures/walk/dir2/a.md new file mode 100644 index 0000000..e69de29 diff --git a/core/zk/zk.go b/core/zk/zk.go index a333035..b3431c9 100644 --- a/core/zk/zk.go +++ b/core/zk/zk.go @@ -24,14 +24,10 @@ type Zk struct { Config Config } -// Dir represents a directory inside a slip box. -type Dir struct { - // Name of the directory, which is the path relative to the slip box's root. - Name string - // Absolute path to the directory. - Path string - // User configuration for this directory. - Config DirConfig +// Path holds a file path relative to a slip box. +type Path struct { + Dir string + Filename string } // Open locates a slip box at the given path and parses its configuration. diff --git a/util/assert/assert.go b/util/assert/assert.go index 6aeb486..8fd87df 100644 --- a/util/assert/assert.go +++ b/util/assert/assert.go @@ -38,7 +38,10 @@ func toJSON(t *testing.T, obj interface{}) string { } func Err(t *testing.T, err error, expected string) { - if !strings.Contains(err.Error(), expected) { + switch { + case err == nil: + t.Errorf("Expected error `%v`, received nil", expected) + case !strings.Contains(err.Error(), expected): t.Errorf("Expected error `%v`, received `%v`", expected, err.Error()) } }