Sort tags by given criteria

pull/85/head
Mickaël Menu 3 years ago
parent 6b8d3cab9e
commit d21b34198b
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -749,7 +749,7 @@ func (s *Server) refreshDiagnosticsOfDocument(doc *document, notify glsp.NotifyF
}
func (s *Server) buildTagCompletionList(notebook *core.Notebook, triggerChar string) ([]protocol.CompletionItem, error) {
tags, err := notebook.FindCollections(core.CollectionKindTag)
tags, err := notebook.FindCollections(core.CollectionKindTag, nil)
if err != nil {
return nil, err
}

@ -3,6 +3,7 @@ package sqlite
import (
"database/sql"
"fmt"
"strings"
"github.com/mickael-menu/zk/internal/core"
"github.com/mickael-menu/zk/internal/util"
@ -17,7 +18,6 @@ type CollectionDAO struct {
// Prepared SQL statements
createCollectionStmt *LazyStmt
findCollectionStmt *LazyStmt
findAllCollectionsStmt *LazyStmt
findAssociationStmt *LazyStmt
createAssociationStmt *LazyStmt
removeAssociationsStmt *LazyStmt
@ -42,16 +42,6 @@ func NewCollectionDAO(tx Transaction, logger util.Logger) *CollectionDAO {
WHERE kind = ? AND name = ?
`),
// Find all collections.
findAllCollectionsStmt: tx.PrepareLazy(`
SELECT c.name, COUNT(nc.id) as count
FROM collections c
INNER JOIN notes_collections nc ON nc.collection_id = c.id
WHERE kind = ?
GROUP BY c.id
ORDER BY c.name
`),
// Returns whether a note and a collection are associated.
findAssociationStmt: tx.PrepareLazy(`
SELECT id FROM notes_collections
@ -87,8 +77,25 @@ func (d *CollectionDAO) FindOrCreate(kind core.CollectionKind, name string) (cor
}
}
func (d *CollectionDAO) FindAll(kind core.CollectionKind) ([]core.Collection, error) {
rows, err := d.findAllCollectionsStmt.Query(kind)
func (d *CollectionDAO) FindAll(kind core.CollectionKind, sorters []core.CollectionSorter) ([]core.Collection, error) {
query := `
SELECT c.name, COUNT(nc.id) as count
FROM collections c
INNER JOIN notes_collections nc ON nc.collection_id = c.id
WHERE kind = ?
GROUP BY c.id
`
orderTerms := []string{}
if sorters != nil {
for _, sorter := range sorters {
orderTerms = append(orderTerms, collectionOrderTerm(sorter))
}
}
orderTerms = append(orderTerms, `c.name ASC`)
query += "ORDER BY " + strings.Join(orderTerms, ", ") + "\n"
rows, err := d.tx.Query(query, kind)
if err != nil {
return []core.Collection{}, err
}
@ -114,6 +121,22 @@ func (d *CollectionDAO) FindAll(kind core.CollectionKind) ([]core.Collection, er
return collections, nil
}
func collectionOrderTerm(sorter core.CollectionSorter) string {
order := " ASC"
if !sorter.Ascending {
order = " DESC"
}
switch sorter.Field {
case core.CollectionSortName:
return "c.name COLLATE NOCASE" + order
case core.CollectionSortNoteCount:
return "count" + order
default:
panic(fmt.Sprintf("%v: unknown core.CollectionSortField", sorter.Field))
}
}
func (d *CollectionDAO) findCollection(kind core.CollectionKind, name string) (core.CollectionID, error) {
wrap := errors.Wrapperf("failed to get %s named %s", kind, name)

@ -35,18 +35,51 @@ func TestCollectionDAOFindOrCreate(t *testing.T) {
func TestCollectionDaoFindAll(t *testing.T) {
testCollectionDAO(t, func(tx Transaction, dao *CollectionDAO) {
// Finds none
cs, err := dao.FindAll("missing")
cs, err := dao.FindAll("missing", nil)
assert.Nil(t, err)
assert.Equal(t, len(cs), 0)
// Finds existing
cs, err = dao.FindAll("tag")
cs, err = dao.FindAll("tag", nil)
assert.Nil(t, err)
assert.Equal(t, cs, []core.Collection{
{Kind: "tag", Name: "adventure", NoteCount: 2},
{Kind: "tag", Name: "fantasy", NoteCount: 1},
{Kind: "tag", Name: "fiction", NoteCount: 1},
{Kind: "tag", Name: "history", NoteCount: 1},
{Kind: "tag", Name: "science", NoteCount: 3},
})
})
}
func TestCollectionDaoFindAllSortedByName(t *testing.T) {
testCollectionDAO(t, func(tx Transaction, dao *CollectionDAO) {
cs, err := dao.FindAll("tag", []core.CollectionSorter{
{Field: core.CollectionSortName, Ascending: false},
})
assert.Nil(t, err)
assert.Equal(t, cs, []core.Collection{
{Kind: "tag", Name: "science", NoteCount: 3},
{Kind: "tag", Name: "history", NoteCount: 1},
{Kind: "tag", Name: "fiction", NoteCount: 1},
{Kind: "tag", Name: "fantasy", NoteCount: 1},
{Kind: "tag", Name: "adventure", NoteCount: 2},
})
})
}
func TestCollectionDaoFindAllSortedByNoteCount(t *testing.T) {
testCollectionDAO(t, func(tx Transaction, dao *CollectionDAO) {
cs, err := dao.FindAll("tag", []core.CollectionSorter{
{Field: core.CollectionSortNoteCount, Ascending: false},
})
assert.Nil(t, err)
assert.Equal(t, cs, []core.Collection{
{Kind: "tag", Name: "science", NoteCount: 3},
{Kind: "tag", Name: "adventure", NoteCount: 2},
{Kind: "tag", Name: "fantasy", NoteCount: 1},
{Kind: "tag", Name: "fiction", NoteCount: 1},
{Kind: "tag", Name: "history", NoteCount: 1},
})
})
}

@ -673,7 +673,7 @@ func TestNoteDAOFindMentions(t *testing.T) {
RawContent: "# A nested note\nThis one is in a sub sub directory",
WordCount: 8,
Links: []core.Link{},
Tags: []string{"adventure", "history"},
Tags: []string{"adventure", "history", "science"},
Metadata: map[string]interface{}{},
Created: time.Date(2019, 11, 20, 20, 32, 56, 0, time.UTC),
Modified: time.Date(2019, 11, 20, 20, 34, 6, 0, time.UTC),

@ -47,9 +47,9 @@ func (ni *NoteIndex) FindMinimal(opts core.NoteFindOpts) (notes []core.MinimalNo
}
// FindCollections implements core.NoteIndex.
func (ni *NoteIndex) FindCollections(kind core.CollectionKind) (collections []core.Collection, err error) {
func (ni *NoteIndex) FindCollections(kind core.CollectionKind, sorters []core.CollectionSorter) (collections []core.Collection, err error) {
err = ni.commit(func(dao *dao) error {
collections, err = dao.collections.FindAll(kind)
collections, err = dao.collections.FindAll(kind, sorters)
return err
})
return

@ -16,3 +16,6 @@
- id: 6
kind: "tag"
name: "empty"
- id: 7
kind: "tag"
name: "science"

@ -16,3 +16,12 @@
- id: 6
note_id: 5 # ref/test/b.md
collection_id: 5 # tag:adventure
- id: 7
note_id: 5 # ref/test/b.md
collection_id: 7 # tag:science
- id: 8
note_id: 4 # f39c8.md
collection_id: 7 # tag:science
- id: 9
note_id: 5 # ref/test/b.md
collection_id: 7 # tag:science

@ -84,7 +84,12 @@ func (cmd *TagList) Run(container *cli.Container) error {
return err
}
tags, err := notebook.FindCollections(core.CollectionKindTag)
sorters, err := core.CollectionSortersFromStrings(cmd.Sort)
if err != nil {
return err
}
tags, err := notebook.FindCollections(core.CollectionKindTag, sorters)
if err != nil {
return err
}

@ -1,5 +1,11 @@
package core
import (
"fmt"
"strings"
"unicode/utf8"
)
// Collection represents a collection, such as a tag.
type Collection struct {
// Unique ID of this collection in the Notebook.
@ -43,8 +49,8 @@ type CollectionRepository interface {
FindOrCreateCollection(name string, kind CollectionKind) (CollectionID, error)
// FindCollections returns the list of all collections in the repository
// for the given kind.
FindCollections(kind CollectionKind) ([]Collection, error)
// for the given kind, ordered with the given sorters.
FindCollections(kind CollectionKind, sorters []CollectionSorter) ([]Collection, error)
// AssociateNoteCollection creates a new association between a note and a
// collection, if it does not already exist.
@ -54,3 +60,65 @@ type CollectionRepository interface {
// note.
RemoveNoteAssociations(noteId NoteID) error
}
// CollectionSorter represents an order term used to sort a list of collections.
type CollectionSorter struct {
Field CollectionSortField
Ascending bool
}
// CollectionSortField represents a collection field used to sort a list of collections.
type CollectionSortField int
const (
// Sort by the collection names.
CollectionSortName CollectionSortField = iota + 1
// Sort by the number of notes part of the collection.
CollectionSortNoteCount
)
// CollectionSortersFromStrings returns a list of CollectionSorter from their string
// representation.
func CollectionSortersFromStrings(strs []string) ([]CollectionSorter, error) {
sorters := make([]CollectionSorter, 0)
// Iterates in reverse order to be able to override sort criteria set in a
// config alias with a `--sort` flag.
for i := len(strs) - 1; i >= 0; i-- {
sorter, err := CollectionSorterFromString(strs[i])
if err != nil {
return sorters, err
}
sorters = append(sorters, sorter)
}
return sorters, nil
}
// CollectionSorterFromString returns a CollectionSorter from its string representation.
//
// If the input str has for suffix `+`, then the order will be ascending, while
// descending for `-`. If no suffix is given, then the default order for the
// sorting field will be used.
func CollectionSorterFromString(str string) (CollectionSorter, error) {
orderSymbol, _ := utf8.DecodeLastRuneInString(str)
str = strings.TrimRight(str, "+-")
var sorter CollectionSorter
switch str {
case "name", "n":
sorter = CollectionSorter{Field: CollectionSortName, Ascending: true}
case "note-count", "nc":
sorter = CollectionSorter{Field: CollectionSortNoteCount, Ascending: false}
default:
return sorter, fmt.Errorf("%s: unknown sorting term\ntry name or note-count", str)
}
switch orderSymbol {
case '+':
sorter.Ascending = true
case '-':
sorter.Ascending = false
}
return sorter, nil
}

@ -20,7 +20,7 @@ type NoteIndex interface {
FindMinimal(opts NoteFindOpts) ([]MinimalNote, error)
// FindCollections retrieves all the collections of the given kind.
FindCollections(kind CollectionKind) ([]Collection, error)
FindCollections(kind CollectionKind, sorters []CollectionSorter) ([]Collection, error)
// Indexed returns the list of indexed note file metadata.
IndexedPaths() (<-chan paths.Metadata, error)

@ -504,7 +504,7 @@ type noteIndexAddMock struct {
func (m *noteIndexAddMock) Find(opts NoteFindOpts) ([]ContextualNote, error) { return nil, nil }
func (m *noteIndexAddMock) FindMinimal(opts NoteFindOpts) ([]MinimalNote, error) { return nil, nil }
func (m *noteIndexAddMock) FindCollections(kind CollectionKind) ([]Collection, error) {
func (m *noteIndexAddMock) FindCollections(kind CollectionKind, sorters []CollectionSorter) ([]Collection, error) {
return nil, nil
}
func (m *noteIndexAddMock) IndexedPaths() (<-chan paths.Metadata, error) { return nil, nil }

@ -247,8 +247,8 @@ func (n *Notebook) FindMatching(terms string) (*MinimalNote, error) {
}
// FindCollections retrieves all the collections of the given kind.
func (n *Notebook) FindCollections(kind CollectionKind) ([]Collection, error) {
return n.index.FindCollections(kind)
func (n *Notebook) FindCollections(kind CollectionKind, sorters []CollectionSorter) ([]Collection, error) {
return n.index.FindCollections(kind, sorters)
}
// RelPath returns the path relative to the notebook root to the given path.

Loading…
Cancel
Save