From a7b85eeb1e98fe7e9685470aaeb179013666ac62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Mon, 11 Jan 2021 22:22:23 +0100 Subject: [PATCH] Limit the number of results --- adapter/sqlite/note_dao.go | 19 ++- adapter/sqlite/note_dao_test.go | 264 ++++++++++++++++++-------------- cmd/list.go | 10 +- core/note/list.go | 15 +- 4 files changed, 179 insertions(+), 129 deletions(-) diff --git a/adapter/sqlite/note_dao.go b/adapter/sqlite/note_dao.go index 38780cf..db0ea22 100644 --- a/adapter/sqlite/note_dao.go +++ b/adapter/sqlite/note_dao.go @@ -146,14 +146,14 @@ func (d *NoteDAO) exists(path string) (bool, error) { return exists, nil } -func (d *NoteDAO) Find(callback func(note.Match) error, filters ...note.Filter) (int, error) { +func (d *NoteDAO) Find(opts note.FinderOpts, callback func(note.Match) error) (int, error) { rows, err := func() (*sql.Rows, error) { snippetCol := `""` orderTerm := `n.title ASC` whereExprs := make([]string, 0) args := make([]interface{}, 0) - for _, filter := range filters { + for _, filter := range opts.Filters { switch filter := filter.(type) { case note.MatchFilter: @@ -181,16 +181,19 @@ func (d *NoteDAO) Find(callback func(note.Match) error, filters ...note.Filter) query := "SELECT n.id, n.path, n.title, n.body, n.word_count, n.created, n.modified, n.checksum, " + snippetCol query += ` - FROM notes n - JOIN notes_fts - ON n.id = notes_fts.rowid - ` +FROM notes n +JOIN notes_fts +ON n.id = notes_fts.rowid` if len(whereExprs) > 0 { - query += " WHERE " + strings.Join(whereExprs, " AND ") + query += "\nWHERE " + strings.Join(whereExprs, "\nAND ") } - query += " ORDER BY " + orderTerm + query += "\nORDER BY " + orderTerm + + if opts.Limit > 0 { + query += fmt.Sprintf("\nLIMIT %d", opts.Limit) + } return d.tx.Query(query, args...) }() diff --git a/adapter/sqlite/note_dao_test.go b/adapter/sqlite/note_dao_test.go index 787dbbd..04a658b 100644 --- a/adapter/sqlite/note_dao_test.go +++ b/adapter/sqlite/note_dao_test.go @@ -142,7 +142,7 @@ func TestNoteDAORemoveUnknown(t *testing.T) { } func TestNoteDAOFindAll(t *testing.T) { - testNoteDAOFind(t, []note.Match{ + testNoteDAOFind(t, note.FinderOpts{}, []note.Match{ { Snippet: "", Metadata: note.Metadata{ @@ -230,94 +230,8 @@ func TestNoteDAOFindAll(t *testing.T) { }) } -func TestNoteDAOFindMatch(t *testing.T) { - expected := []note.Match{ - { - Snippet: "Index of the Zettelkasten", - Metadata: note.Metadata{ - Path: "index.md", - Title: "Index", - Body: "Index of the Zettelkasten", - WordCount: 4, - Created: time.Date(2019, 12, 4, 11, 59, 11, 0, time.Local), - Modified: time.Date(2019, 12, 4, 12, 17, 21, 0, time.Local), - Checksum: "iaefhv", - }, - }, - { - Snippet: "A daily note", - Metadata: note.Metadata{ - Path: "log/2021-01-03.md", - Title: "January 3, 2021", - Body: "A daily note", - WordCount: 3, - Created: time.Date(2020, 11, 22, 16, 27, 45, 0, time.Local), - Modified: time.Date(2020, 11, 22, 16, 27, 45, 0, time.Local), - Checksum: "qwfpgj", - }, - }, - { - Snippet: "A second daily note", - Metadata: note.Metadata{ - Path: "log/2021-01-04.md", - Title: "January 4, 2021", - Body: "A second daily note", - WordCount: 4, - Created: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), - Modified: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), - Checksum: "arstde", - }, - }, - { - Snippet: "A third daily note", - Metadata: note.Metadata{ - Path: "log/2021-02-04.md", - Title: "February 4, 2021", - Body: "A third daily note", - WordCount: 4, - Created: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), - Modified: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), - Checksum: "earkte", - }, - }, - } - - testNoteDAOFind(t, expected, note.MatchFilter("daily | index")) -} - -func TestNoteDAOFindInPath(t *testing.T) { - expected := []note.Match{ - { - Snippet: "", - Metadata: note.Metadata{ - Path: "log/2021-01-03.md", - Title: "January 3, 2021", - Body: "A daily note", - WordCount: 3, - Created: time.Date(2020, 11, 22, 16, 27, 45, 0, time.Local), - Modified: time.Date(2020, 11, 22, 16, 27, 45, 0, time.Local), - Checksum: "qwfpgj", - }, - }, - { - Snippet: "", - Metadata: note.Metadata{ - Path: "log/2021-01-04.md", - Title: "January 4, 2021", - Body: "A second daily note", - WordCount: 4, - Created: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), - Modified: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), - Checksum: "arstde", - }, - }, - } - - testNoteDAOFind(t, expected, note.PathFilter([]string{"log/2021-01-*"})) -} - -func TestNoteDAOFindInMultiplePath(t *testing.T) { - expected := []note.Match{ +func TestNoteDAOFindLimit(t *testing.T) { + testNoteDAOFind(t, note.FinderOpts{Limit: 2}, []note.Match{ { Snippet: "", Metadata: note.Metadata{ @@ -333,39 +247,163 @@ func TestNoteDAOFindInMultiplePath(t *testing.T) { { Snippet: "", Metadata: note.Metadata{ - Path: "ref/test/a.md", - Title: "Another nested note", - Body: "It shall appear before b.md", + Path: "f39c8.md", + Title: "An interesting note", + Body: "Its content will surprise you", WordCount: 5, - Created: time.Date(2019, 11, 20, 20, 32, 56, 0, time.Local), - Modified: time.Date(2019, 11, 20, 20, 34, 6, 0, time.Local), - Checksum: "iecywst", + Created: time.Date(2020, 1, 19, 10, 58, 41, 0, time.Local), + Modified: time.Date(2020, 1, 20, 8, 52, 42, 0, time.Local), + Checksum: "irkwyc", }, }, - { - Snippet: "", - Metadata: note.Metadata{ - Path: "index.md", - Title: "Index", - Body: "Index of the Zettelkasten", - WordCount: 4, - Created: time.Date(2019, 12, 4, 11, 59, 11, 0, time.Local), - Modified: time.Date(2019, 12, 4, 12, 17, 21, 0, time.Local), - Checksum: "iaefhv", - }, - }, - } - - testNoteDAOFind(t, expected, note.PathFilter([]string{"ref", "index.md"})) + }) } -func testNoteDAOFind(t *testing.T, expected []note.Match, filters ...note.Filter) { +func TestNoteDAOFindMatch(t *testing.T) { + testNoteDAOFind(t, + note.FinderOpts{ + Filters: []note.Filter{note.MatchFilter("daily | index")}, + }, + []note.Match{ + { + Snippet: "Index of the Zettelkasten", + Metadata: note.Metadata{ + Path: "index.md", + Title: "Index", + Body: "Index of the Zettelkasten", + WordCount: 4, + Created: time.Date(2019, 12, 4, 11, 59, 11, 0, time.Local), + Modified: time.Date(2019, 12, 4, 12, 17, 21, 0, time.Local), + Checksum: "iaefhv", + }, + }, + { + Snippet: "A daily note", + Metadata: note.Metadata{ + Path: "log/2021-01-03.md", + Title: "January 3, 2021", + Body: "A daily note", + WordCount: 3, + Created: time.Date(2020, 11, 22, 16, 27, 45, 0, time.Local), + Modified: time.Date(2020, 11, 22, 16, 27, 45, 0, time.Local), + Checksum: "qwfpgj", + }, + }, + { + Snippet: "A second daily note", + Metadata: note.Metadata{ + Path: "log/2021-01-04.md", + Title: "January 4, 2021", + Body: "A second daily note", + WordCount: 4, + Created: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), + Modified: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), + Checksum: "arstde", + }, + }, + { + Snippet: "A third daily note", + Metadata: note.Metadata{ + Path: "log/2021-02-04.md", + Title: "February 4, 2021", + Body: "A third daily note", + WordCount: 4, + Created: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), + Modified: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), + Checksum: "earkte", + }, + }, + }, + ) +} + +func TestNoteDAOFindInPath(t *testing.T) { + testNoteDAOFind(t, + note.FinderOpts{ + Filters: []note.Filter{note.PathFilter([]string{"log/2021-01-*"})}, + }, + []note.Match{ + { + Snippet: "", + Metadata: note.Metadata{ + Path: "log/2021-01-03.md", + Title: "January 3, 2021", + Body: "A daily note", + WordCount: 3, + Created: time.Date(2020, 11, 22, 16, 27, 45, 0, time.Local), + Modified: time.Date(2020, 11, 22, 16, 27, 45, 0, time.Local), + Checksum: "qwfpgj", + }, + }, + { + Snippet: "", + Metadata: note.Metadata{ + Path: "log/2021-01-04.md", + Title: "January 4, 2021", + Body: "A second daily note", + WordCount: 4, + Created: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), + Modified: time.Date(2020, 11, 29, 8, 20, 18, 0, time.Local), + Checksum: "arstde", + }, + }, + }, + ) +} + +func TestNoteDAOFindInMultiplePath(t *testing.T) { + testNoteDAOFind(t, + note.FinderOpts{ + Filters: []note.Filter{note.PathFilter([]string{"ref", "index.md"})}, + }, + []note.Match{ + { + Snippet: "", + Metadata: note.Metadata{ + Path: "ref/test/b.md", + Title: "A nested note", + Body: "This one is in a sub sub directory", + WordCount: 8, + Created: time.Date(2019, 11, 20, 20, 32, 56, 0, time.Local), + Modified: time.Date(2019, 11, 20, 20, 34, 6, 0, time.Local), + Checksum: "yvwbae", + }, + }, + { + Snippet: "", + Metadata: note.Metadata{ + Path: "ref/test/a.md", + Title: "Another nested note", + Body: "It shall appear before b.md", + WordCount: 5, + Created: time.Date(2019, 11, 20, 20, 32, 56, 0, time.Local), + Modified: time.Date(2019, 11, 20, 20, 34, 6, 0, time.Local), + Checksum: "iecywst", + }, + }, + { + Snippet: "", + Metadata: note.Metadata{ + Path: "index.md", + Title: "Index", + Body: "Index of the Zettelkasten", + WordCount: 4, + Created: time.Date(2019, 12, 4, 11, 59, 11, 0, time.Local), + Modified: time.Date(2019, 12, 4, 12, 17, 21, 0, time.Local), + Checksum: "iaefhv", + }, + }, + }, + ) +} + +func testNoteDAOFind(t *testing.T, opts note.FinderOpts, expected []note.Match) { testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { actual := make([]note.Match, 0) - count, err := dao.Find(func(m note.Match) error { + count, err := dao.Find(opts, func(m note.Match) error { actual = append(actual, m) return nil - }, filters...) + }) assert.Nil(t, err) assert.Equal(t, count, len(expected)) assert.Equal(t, actual, expected) diff --git a/cmd/list.go b/cmd/list.go index 26eff12..2da460c 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -12,8 +12,9 @@ import ( // List displays notes matching a set of criteria. type List struct { Paths []string `arg optional placeholder:"PATHS"` - Match string `help:"Terms to search for in the notes" placeholder:"TERMS"` Format string `help:"Pretty prints the list using the given format" placeholder:"TEMPLATE"` + Match string `help:"Terms to search for in the notes" placeholder:"TERMS"` + Limit int `help:"Limit the number of results" placeholder:"MAX"` } func (cmd *List) Run(container *Container) error { @@ -49,8 +50,11 @@ func (cmd *List) Run(container *Container) error { count, err := note.List( note.ListOpts{ - Format: opt.NewNotEmptyString(cmd.Format), - Filters: filters, + Format: opt.NewNotEmptyString(cmd.Format), + FinderOpts: note.FinderOpts{ + Filters: filters, + Limit: cmd.Limit, + }, }, note.ListDeps{ BasePath: zk.Path, diff --git a/core/note/list.go b/core/note/list.go index 19f6349..808ea04 100644 --- a/core/note/list.go +++ b/core/note/list.go @@ -35,12 +35,17 @@ func (m Match) String() string { // Finder retrieves notes matching the given Filter. // Returns the number of matches. type Finder interface { - Find(callback func(Match) error, filters ...Filter) (int, error) + Find(opts FinderOpts, callback func(Match) error) (int, error) +} + +type FinderOpts struct { + Filters []Filter + Limit int } type ListOpts struct { - Format opt.String - Filters []Filter + Format opt.String + FinderOpts } type ListDeps struct { @@ -58,7 +63,7 @@ func List(opts ListOpts, deps ListDeps, callback func(formattedNote string) erro return 0, err } - return deps.Finder.Find(func(note Match) error { + return deps.Finder.Find(opts.FinderOpts, func(note Match) error { ft, err := format(note, deps.BasePath, deps.Templates) if err != nil { return err @@ -68,7 +73,7 @@ func List(opts ListOpts, deps ListDeps, callback func(formattedNote string) erro return err } return callback(res) - }, opts.Filters...) + }) } var templates = map[string]string{