diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cc0ab0..10fed8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ All notable changes to this project will be documented in this file. ### Added * Support for tags. - * Many tag flavors are supported: `#hashtags`, `:colon:separated:tags:` and even Bear's [`#multi-word tags#`](https://blog.bear.app/2017/11/bear-tips-how-to-create-multi-word-tags/). If you prefer to use a YAML frontmatter, list your tags with the keys `tags` or `keywords`. + * Filter notes by their tags using `--tag "history, europe"`. To match notes associated with either tags, use a pipe `|` or `OR` (all caps), e.g. `--tag "inbox OR todo"`. + * Many tag flavors are supported: `#hashtags`, `:colon:separated:tags:` and even Bear's [`#multi-word tags#`](https://blog.bear.app/2017/11/bear-tips-how-to-create-multi-word-tags/). If you prefer to use a YAML frontmatter, list your tags with the key `tags` or `keywords`. ### Changed * Multiple `--extra` variables are now separated by `,` instead of `;`. - diff --git a/README.md b/README.md index cd5cf1e..08af72e 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,7 @@ What `zk` is not: * a note editor * a tool to serve your notes on the web – for this, you may be interested in [Neuron](docs/neuron.md) or [Gollum](https://github.com/gollum/gollum). -## Roadmap - -* [ ] Tags -* [ ] Link relations -* [ ] Extended YAML front matter support +[See the changelog](CHANGELOG.md) for the list of upcoming features waiting to be released. ## Install diff --git a/adapter/handlebars/handlebars.go b/adapter/handlebars/handlebars.go index e565041..80863fc 100644 --- a/adapter/handlebars/handlebars.go +++ b/adapter/handlebars/handlebars.go @@ -15,6 +15,7 @@ import ( func Init(lang string, supportsUTF8 bool, logger util.Logger, styler style.Styler) { helpers.RegisterConcat() helpers.RegisterDate(logger) + helpers.RegisterJoin() helpers.RegisterList(supportsUTF8) helpers.RegisterPrepend(logger) helpers.RegisterShell(logger) diff --git a/adapter/handlebars/handlebars_test.go b/adapter/handlebars/handlebars_test.go index 57e3393..6300f44 100644 --- a/adapter/handlebars/handlebars_test.go +++ b/adapter/handlebars/handlebars_test.go @@ -94,6 +94,18 @@ func TestConcatHelper(t *testing.T) { testString(t, "{{concat '> ' 'A quote'}}", nil, "> A quote") } +func TestJoinHelper(t *testing.T) { + test := func(items []string, expected string) { + context := map[string]interface{}{"items": items} + testString(t, "{{join items '-'}}", context, expected) + } + + test([]string{}, "") + test([]string{"Item 1"}, "Item 1") + test([]string{"Item 1", "Item 2"}, "Item 1-Item 2") + test([]string{"Item 1", "Item 2", "Item 3"}, "Item 1-Item 2-Item 3") +} + func TestPrependHelper(t *testing.T) { // inline testString(t, "{{prepend '> ' 'A quote'}}", nil, "> A quote") diff --git a/adapter/handlebars/helpers/join.go b/adapter/handlebars/helpers/join.go new file mode 100644 index 0000000..2427591 --- /dev/null +++ b/adapter/handlebars/helpers/join.go @@ -0,0 +1,17 @@ +package helpers + +import ( + "strings" + + "github.com/aymerick/raymond" +) + +// RegisterJoin registers a {{join}} template helper which concatenates list +// items with the given separator. +// +// {{join list ', '}} -> item1, item2, item3 +func RegisterJoin() { + raymond.RegisterHelper("join", func(list []string, delimiter string) string { + return strings.Join(list, delimiter) + }) +} diff --git a/adapter/markdown/extensions/tag.go b/adapter/markdown/extensions/tag.go index 366eb09..d33ecb7 100644 --- a/adapter/markdown/extensions/tag.go +++ b/adapter/markdown/extensions/tag.go @@ -156,6 +156,7 @@ func (p *hashtagParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont } } + tag = strings.TrimSpace(tag) if len(tag) == 0 || !isValidHashTag(tag) { return nil } @@ -220,6 +221,7 @@ func (p *colontagParser) Parse(parent ast.Node, block text.Reader, pc parser.Con escaping = true } else if char == ':' { + tag = strings.TrimSpace(tag) if len(tag) == 0 { break } diff --git a/adapter/markdown/markdown.go b/adapter/markdown/markdown.go index 0369dc7..c5917ab 100644 --- a/adapter/markdown/markdown.go +++ b/adapter/markdown/markdown.go @@ -166,8 +166,18 @@ func parseTags(frontmatter frontmatter, root ast.Node, source []byte) ([]string, findFMTags := func(key string) []string { if tags, ok := frontmatter.getStrings(key); ok { return tags + } else if tags := frontmatter.getString(key); !tags.IsNull() { - return strings.Fields(tags.Unwrap()) + // Parse a space-separated string list + res := []string{} + for _, s := range strings.Fields(tags.Unwrap()) { + s = strings.TrimSpace(s) + if len(s) > 0 { + res = append(res, s) + } + } + return res + } else { return []string{} } @@ -304,11 +314,14 @@ func (m frontmatter) getStrings(keys ...string) ([]string, bool) { key = strings.ToLower(key) if val, ok := m.values[key]; ok { if val, ok := val.([]interface{}); ok { - strings := []string{} + strs := []string{} for _, v := range val { - strings = append(strings, fmt.Sprint(v)) + s := strings.TrimSpace(fmt.Sprint(v)) + if len(s) > 0 { + strs = append(strs, s) + } } - return strings, true + return strs, true } } } diff --git a/adapter/markdown/markdown_test.go b/adapter/markdown/markdown_test.go index 5d022cc..ca876ae 100644 --- a/adapter/markdown/markdown_test.go +++ b/adapter/markdown/markdown_test.go @@ -217,6 +217,8 @@ func TestParseWordtags(t *testing.T) { test("#a/@'~-_$%&+=: end", []string{"a/@'~-_$%&+=:"}) // Escape punctuation and space test(`#an\ \\espaced\ tag\!`, []string{`an \espaced tag!`}) + // Leading and trailing spaces are trimmed + test(`#\ \ tag\ \ end`, []string{`tag`}) // Hashtags containing only numbers and dots are invalid test("#123, #1.2.3", []string{}) // Must not be preceded by a hash or any other valid hashtag character @@ -265,6 +267,8 @@ func TestParseColontags(t *testing.T) { test(":#a/@'~-_$%&+=: end", []string{"#a/@'~-_$%&+="}) // Escape punctuation and space test(`:an\ \\espaced\ tag\!:`, []string{`an \espaced tag!`}) + // Leading and trailing spaces are trimmed + test(`:\ \ tag\ \ :`, []string{`tag`}) // A colontag containing only numbers is valid test(":123:1.2.3:", []string{"123"}) // Must not be preceded by a : or any other valid colontag character @@ -308,9 +312,9 @@ Body `, []string{"keyword1", "keyword 2"}) test(`--- -tags: [tag1, tag 2] +tags: [tag1, " tag 2 "] keywords: - - keyword1 + - keyword1 - keyword 2 --- @@ -319,7 +323,7 @@ Body // When a string, parse space-separated tags. test(`--- -Tags: "tag1 #tag-2" +Tags: "tag1 #tag-2" Keywords: kw1 kw2 kw3 --- diff --git a/adapter/sqlite/collection_dao_test.go b/adapter/sqlite/collection_dao_test.go index 3108559..22e3a32 100644 --- a/adapter/sqlite/collection_dao_test.go +++ b/adapter/sqlite/collection_dao_test.go @@ -26,7 +26,7 @@ func TestCollectionDAOFindOrCreate(t *testing.T) { // Creates when not found sql := "SELECT id FROM collections WHERE kind = ? AND name = ?" assertNotExist(t, tx, sql, "unknown", "created") - id, err = dao.FindOrCreate("unknown", "created") + _, err = dao.FindOrCreate("unknown", "created") assert.Nil(t, err) assertExist(t, tx, sql, "unknown", "created") }) diff --git a/adapter/sqlite/db.go b/adapter/sqlite/db.go index 0a111dc..cf947eb 100644 --- a/adapter/sqlite/db.go +++ b/adapter/sqlite/db.go @@ -4,6 +4,7 @@ import ( "database/sql" _ "github.com/mattn/go-sqlite3" + "github.com/mickael-menu/zk/core/note" "github.com/mickael-menu/zk/util/errors" ) @@ -137,6 +138,14 @@ func (db *DB) Migrate() error { )`, `CREATE INDEX IF NOT EXISTS index_notes_collections ON notes_collections (note_id, collection_id)`, + // View of notes with their associated metadata (e.g. tags), for simpler queries. + `CREATE VIEW notes_with_metadata AS + SELECT n.*, GROUP_CONCAT(c.name, '` + "\x01" + `') AS tags + FROM notes n + LEFT JOIN notes_collections nc ON nc.note_id = n.id + LEFT JOIN collections c ON nc.collection_id = c.id AND c.kind = '` + string(note.CollectionKindTag) + `' + GROUP BY n.id`, + `PRAGMA user_version = 2`, }) diff --git a/adapter/sqlite/fixtures/default/collections.yml b/adapter/sqlite/fixtures/default/collections.yml index 5fdc128..9051d1f 100644 --- a/adapter/sqlite/fixtures/default/collections.yml +++ b/adapter/sqlite/fixtures/default/collections.yml @@ -10,3 +10,6 @@ - id: 4 kind: "tag" name: "fantasy" +- id: 5 + kind: "tag" + name: "history" diff --git a/adapter/sqlite/fixtures/default/notes_collections.yml b/adapter/sqlite/fixtures/default/notes_collections.yml index 34315b7..15fd887 100644 --- a/adapter/sqlite/fixtures/default/notes_collections.yml +++ b/adapter/sqlite/fixtures/default/notes_collections.yml @@ -1,9 +1,18 @@ - id: 1 - note_id: 1 - collection_id: 1 + note_id: 1 # log/2021-01-03.md + collection_id: 1 # tag:fiction - id: 2 - note_id: 1 - collection_id: 2 + note_id: 1 # log/2021-01-03.md + collection_id: 2 # tag:adventure - id: 3 - note_id: 2 - collection_id: 3 + note_id: 2 # log/2021-01-04.md + collection_id: 3 # genre:fiction +- id: 4 + note_id: 5 # ref/test/b.md + collection_id: 2 # tag:adventure +- id: 5 + note_id: 4 # f39c8.md + collection_id: 4 # tag:fantasy +- id: 6 + note_id: 5 # ref/test/b.md + collection_id: 5 # tag:adventure diff --git a/adapter/sqlite/note_dao.go b/adapter/sqlite/note_dao.go index fa0592f..0304397 100644 --- a/adapter/sqlite/note_dao.go +++ b/adapter/sqlite/note_dao.go @@ -3,6 +3,7 @@ package sqlite import ( "database/sql" "fmt" + "regexp" "strconv" "strings" "time" @@ -293,25 +294,19 @@ func (d *NoteDAO) Find(opts note.FinderOpts) ([]note.Match, error) { var ( id, wordCount int title, lead, body, rawContent string - nullableSnippets sql.NullString + snippets, tags sql.NullString path, checksum string created, modified time.Time ) - err := rows.Scan(&id, &path, &title, &lead, &body, &rawContent, &wordCount, &created, &modified, &checksum, &nullableSnippets) + err := rows.Scan(&id, &path, &title, &lead, &body, &rawContent, &wordCount, &created, &modified, &checksum, &tags, &snippets) if err != nil { d.logger.Err(err) continue } - snippets := make([]string, 0) - if nullableSnippets.Valid && nullableSnippets.String != "" { - snippets = strings.Split(nullableSnippets.String, "\x01") - snippets = strutil.RemoveDuplicates(snippets) - } - matches = append(matches, note.Match{ - Snippets: snippets, + Snippets: parseListFromNullString(snippets), Metadata: note.Metadata{ Path: path, Title: title, @@ -319,6 +314,8 @@ func (d *NoteDAO) Find(opts note.FinderOpts) ([]note.Match, error) { Body: body, RawContent: rawContent, WordCount: wordCount, + Links: []note.Link{}, + Tags: parseListFromNullString(tags), Created: created, Modified: modified, Checksum: checksum, @@ -329,12 +326,22 @@ func (d *NoteDAO) Find(opts note.FinderOpts) ([]note.Match, error) { return matches, nil } +// parseListFromNullString splits a 0-separated string. +func parseListFromNullString(str sql.NullString) []string { + list := []string{} + if str.Valid && str.String != "" { + list = strings.Split(str.String, "\x01") + list = strutil.RemoveDuplicates(list) + } + return list +} + func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { snippetCol := `n.lead` - joinClauses := make([]string, 0) - whereExprs := make([]string, 0) - additionalOrderTerms := make([]string, 0) - args := make([]interface{}, 0) + joinClauses := []string{} + whereExprs := []string{} + additionalOrderTerms := []string{} + args := []interface{}{} groupBy := "" transitiveClosure := false @@ -359,7 +366,7 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { if !negate { if direction != 0 { - snippetCol = "GROUP_CONCAT(REPLACE(l.snippet, l.title, '' || l.title || ''), '\x01') AS snippet" + snippetCol = "GROUP_CONCAT(REPLACE(l.snippet, l.title, '' || l.title || ''), '\x01')" } joinOns := make([]string, 0) @@ -413,7 +420,7 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { switch filter := filter.(type) { case note.MatchFilter: - snippetCol = `snippet(notes_fts, 2, '', '', '…', 20) as snippet` + snippetCol = `snippet(notes_fts, 2, '', '', '…', 20)` joinClauses = append(joinClauses, "JOIN notes_fts ON n.id = notes_fts.rowid") additionalOrderTerms = append(additionalOrderTerms, `bm25(notes_fts, 1000.0, 500.0, 1.0)`) whereExprs = append(whereExprs, "notes_fts MATCH ?") @@ -430,6 +437,33 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { } whereExprs = append(whereExprs, strings.Join(regexes, " OR ")) + case note.TagFilter: + if len(filter) == 0 { + break + } + separatorRegex := regexp.MustCompile(`(\ OR\ )|\|`) + for _, tags := range filter { + tags := separatorRegex.Split(tags, -1) + + globs := make([]string, 0) + for _, tag := range tags { + tag = strings.TrimSpace(tag) + if len(tag) == 0 { + continue + } + globs = append(globs, "t.name GLOB ?") + args = append(args, tag) + } + + whereExprs = append(whereExprs, fmt.Sprintf(`n.id IN ( +SELECT note_id FROM notes_collections + WHERE collection_id IN (SELECT id FROM collections t WHERE kind = '%s' AND (%s)) + )`, + note.CollectionKindTag, + strings.Join(globs, " OR "), + )) + } + case note.ExcludePathFilter: if len(filter) == 0 { break @@ -504,14 +538,14 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { query += `WITH RECURSIVE transitive_closure(source_id, target_id, title, snippet, distance, path) AS ( SELECT source_id, target_id, title, snippet, - 1 AS distance, + 1 AS distance, '.' || source_id || '.' || target_id || '.' AS path FROM links UNION ALL SELECT tc.source_id, l.target_id, l.title, l.snippet, - tc.distance + 1, + tc.distance + 1, tc.path || l.target_id || '.' AS path FROM links AS l JOIN transitive_closure AS tc @@ -528,9 +562,9 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { query += "\n)\n" } - query += "SELECT n.id, n.path, n.title, n.lead, n.body, n.raw_content, n.word_count, n.created, n.modified, n.checksum, " + snippetCol + "\n" + query += fmt.Sprintf("SELECT n.id, n.path, n.title, n.lead, n.body, n.raw_content, n.word_count, n.created, n.modified, n.checksum, n.tags, %s AS snippet\n", snippetCol) - query += "FROM notes n\n" + query += "FROM notes_with_metadata n\n" for _, clause := range joinClauses { query += clause + "\n" diff --git a/adapter/sqlite/note_dao_test.go b/adapter/sqlite/note_dao_test.go index 48938df..a2b9508 100644 --- a/adapter/sqlite/note_dao_test.go +++ b/adapter/sqlite/note_dao_test.go @@ -402,6 +402,24 @@ func TestNoteDAOFindLimit(t *testing.T) { }) } +func TestNoteDAOFindTag(t *testing.T) { + test := func(tags []string, expectedPaths []string) { + testNoteDAOFindPaths(t, note.FinderOpts{ + Filters: []note.Filter{note.TagFilter(tags)}, + }, expectedPaths) + } + + test([]string{"fiction"}, []string{"log/2021-01-03.md"}) + test([]string{" adventure "}, []string{"ref/test/b.md", "log/2021-01-03.md"}) + test([]string{"fiction", "adventure"}, []string{"log/2021-01-03.md"}) + test([]string{"fiction|fantasy"}, []string{"f39c8.md", "log/2021-01-03.md"}) + test([]string{"fiction | fantasy"}, []string{"f39c8.md", "log/2021-01-03.md"}) + test([]string{"fiction OR fantasy"}, []string{"f39c8.md", "log/2021-01-03.md"}) + test([]string{"fiction | adventure | fantasy"}, []string{"ref/test/b.md", "f39c8.md", "log/2021-01-03.md"}) + test([]string{"fiction | history", "adventure"}, []string{"ref/test/b.md", "log/2021-01-03.md"}) + test([]string{"fiction", "unknown"}, []string{}) +} + func TestNoteDAOFindMatch(t *testing.T) { testNoteDAOFind(t, note.FinderOpts{ @@ -416,6 +434,8 @@ func TestNoteDAOFindMatch(t *testing.T) { Body: "Index of the Zettelkasten", RawContent: "# Index\nIndex of the Zettelkasten", WordCount: 4, + Links: []note.Link{}, + Tags: []string{}, Created: time.Date(2019, 12, 4, 11, 59, 11, 0, time.UTC), Modified: time.Date(2019, 12, 4, 12, 17, 21, 0, time.UTC), Checksum: "iaefhv", @@ -430,6 +450,8 @@ func TestNoteDAOFindMatch(t *testing.T) { Body: "A third daily note", RawContent: "# A third daily note", WordCount: 4, + Links: []note.Link{}, + Tags: []string{}, Created: time.Date(2020, 11, 29, 8, 20, 18, 0, time.UTC), Modified: time.Date(2020, 11, 10, 8, 20, 18, 0, time.UTC), Checksum: "earkte", @@ -444,6 +466,8 @@ func TestNoteDAOFindMatch(t *testing.T) { Body: "A second daily note", RawContent: "# A second daily note", WordCount: 4, + Links: []note.Link{}, + Tags: []string{}, Created: time.Date(2020, 11, 29, 8, 20, 18, 0, time.UTC), Modified: time.Date(2020, 11, 29, 8, 20, 18, 0, time.UTC), Checksum: "arstde", @@ -458,6 +482,8 @@ func TestNoteDAOFindMatch(t *testing.T) { Body: "A daily note\n\nWith lot of content", RawContent: "# A daily note\nA daily note\n\nWith lot of content", WordCount: 3, + Links: []note.Link{}, + Tags: []string{"fiction", "adventure"}, Created: time.Date(2020, 11, 22, 16, 27, 45, 0, time.UTC), Modified: time.Date(2020, 11, 22, 16, 27, 45, 0, time.UTC), Checksum: "qwfpgj", @@ -602,7 +628,8 @@ func TestNoteDAOFindLinkedByWithSnippets(t *testing.T) { Body: "It shall appear before b.md", RawContent: "#Another nested note\nIt shall appear before b.md", WordCount: 5, - Links: nil, + Links: []note.Link{}, + Tags: []string{}, Created: time.Date(2019, 11, 20, 20, 32, 56, 0, time.UTC), Modified: time.Date(2019, 11, 20, 20, 34, 6, 0, time.UTC), Checksum: "iecywst", @@ -620,7 +647,8 @@ func TestNoteDAOFindLinkedByWithSnippets(t *testing.T) { Body: "A daily note\n\nWith lot of content", RawContent: "# A daily note\nA daily note\n\nWith lot of content", WordCount: 3, - Links: nil, + Links: []note.Link{}, + Tags: []string{"fiction", "adventure"}, Created: time.Date(2020, 11, 22, 16, 27, 45, 0, time.UTC), Modified: time.Date(2020, 11, 22, 16, 27, 45, 0, time.UTC), Checksum: "qwfpgj", diff --git a/cmd/finder_opts.go b/cmd/finder_opts.go index 9c168dd..eb44aad 100644 --- a/cmd/finder_opts.go +++ b/cmd/finder_opts.go @@ -17,6 +17,7 @@ type Filtering struct { Limit int `group:filter short:n placeholder:COUNT help:"Limit the number of notes found."` Match string `group:filter short:m placeholder:QUERY help:"Terms to search for in the notes."` Exclude []string `group:filter short:x placeholder:PATH help:"Ignore notes matching the given path, including its descendants."` + Tag []string `group:filter short:t help:"Find notes tagged with the given tags."` Orphan bool `group:filter help:"Find notes which are not linked by any other note." xor:link` LinkedBy []string `group:filter short:l placeholder:PATH help:"Find notes which are linked by the given ones." xor:link` LinkingTo []string `group:filter short:L placeholder:PATH help:"Find notes which are linking to the given ones." xor:link` @@ -53,6 +54,10 @@ func NewFinderOpts(zk *zk.Zk, filtering Filtering, sorting Sorting) (*note.Finde filters = append(filters, note.ExcludePathFilter(excludePaths)) } + if len(filtering.Tag) > 0 { + filters = append(filters, note.TagFilter(filtering.Tag)) + } + if filtering.Match != "" { filters = append(filters, note.MatchFilter(filtering.Match)) } diff --git a/core/note/find.go b/core/note/find.go index c00c73d..e6b5271 100644 --- a/core/note/find.go +++ b/core/note/find.go @@ -44,6 +44,9 @@ type PathFilter []string // ExcludePathFilter is a note filter using path globs to exclude notes from the list. type ExcludePathFilter []string +// TagFilter is a note filter using tag globs found in the notes. +type TagFilter []string + // LinkedByFilter is a note filter used to select notes being linked by another one. type LinkedByFilter struct { Paths []string @@ -80,6 +83,7 @@ type InteractiveFilter bool func (f MatchFilter) sealed() {} func (f PathFilter) sealed() {} func (f ExcludePathFilter) sealed() {} +func (f TagFilter) sealed() {} func (f LinkedByFilter) sealed() {} func (f LinkingToFilter) sealed() {} func (f RelatedFilter) sealed() {} diff --git a/core/note/format.go b/core/note/format.go index a6ea64a..723a56f 100644 --- a/core/note/format.go +++ b/core/note/format.go @@ -79,6 +79,7 @@ Modified: {{date created "short"}} "full": `{{style "title" title}} {{style "path" path}} Created: {{date created "short"}} Modified: {{date created "short"}} +Tags: {{join tags ", "}} {{prepend " " body}} `, @@ -104,6 +105,7 @@ func (f *Formatter) Format(match Match) (string, error) { Lead: match.Lead, Body: match.Body, Snippets: snippets, + Tags: match.Tags, RawContent: match.RawContent, WordCount: match.WordCount, Created: match.Created, @@ -120,6 +122,7 @@ type formatRenderContext struct { Snippets []string RawContent string `handlebars:"raw-content"` WordCount int `handlebars:"word-count"` + Tags []string Created time.Time Modified time.Time Checksum string diff --git a/core/note/format_test.go b/core/note/format_test.go index f2eec7f..ce69fbf 100644 --- a/core/note/format_test.go +++ b/core/note/format_test.go @@ -55,6 +55,7 @@ Modified: {{date created "short"}} test("full", `{{style "title" title}} {{style "path" path}} Created: {{date created "short"}} Modified: {{date created "short"}} +Tags: {{join tags ", "}} {{prepend " " body}} `) diff --git a/core/zk/config.go b/core/zk/config.go index d3a4c93..257e54f 100644 --- a/core/zk/config.go +++ b/core/zk/config.go @@ -56,7 +56,7 @@ type GroupConfig struct { Extra map[string]string } -// ConfigOverrides holds user configuration overriden values, for example fed +// ConfigOverrides holds user configuration overridden values, for example fed // from CLI flags. type ConfigOverrides struct { Group opt.String @@ -79,7 +79,7 @@ func (c GroupConfig) Clone() GroupConfig { } // Override modifies the GroupConfig receiver by updating the properties -// overriden in ConfigOverrides. +// overridden in ConfigOverrides. func (c *GroupConfig) Override(overrides ConfigOverrides) { if !overrides.BodyTemplatePath.IsNull() { c.Note.BodyTemplatePath = overrides.BodyTemplatePath diff --git a/core/zk/config_test.go b/core/zk/config_test.go index 3415f47..d54160c 100644 --- a/core/zk/config_test.go +++ b/core/zk/config_test.go @@ -445,9 +445,9 @@ func TestGroupConfigOverride(t *testing.T) { // Some overrides sut.Override(ConfigOverrides{ - BodyTemplatePath: opt.NewString("overriden-template"), + BodyTemplatePath: opt.NewString("overridden-template"), Extra: map[string]string{ - "hello": "overriden", + "hello": "overridden", "additional": "value", }, }) @@ -455,7 +455,7 @@ func TestGroupConfigOverride(t *testing.T) { Paths: []string{"path"}, Note: NoteConfig{ FilenameTemplate: "filename", - BodyTemplatePath: opt.NewString("overriden-template"), + BodyTemplatePath: opt.NewString("overridden-template"), IDOptions: IDOptions{ Length: 4, Charset: CharsetLetters, @@ -463,7 +463,7 @@ func TestGroupConfigOverride(t *testing.T) { }, }, Extra: map[string]string{ - "hello": "overriden", + "hello": "overridden", "salut": "le monde", "additional": "value", }, diff --git a/core/zk/zk.go b/core/zk/zk.go index 5efe7c1..c5c040f 100644 --- a/core/zk/zk.go +++ b/core/zk/zk.go @@ -70,7 +70,7 @@ template = "default.md" # Specify the list of directories which will automatically belong to the group # with the optional ` + "`" + `paths` + "`" + ` property. # -# Omiting ` + "`" + `paths` + "`" + ` is equivalent to providing a single path equal to the name of +# Omitting ` + "`" + `paths` + "`" + ` is equivalent to providing a single path equal to the name of # the group. This can be useful to quickly declare a group by the name of the # directory it applies to. @@ -315,18 +315,18 @@ func (zk *Zk) DirAt(path string, overrides ...ConfigOverrides) (*Dir, error) { func (zk *Zk) findConfigForDirNamed(name string, overrides []ConfigOverrides) (GroupConfig, error) { // If there's a Group overrides, attempt to find a matching group. - overridenGroup := "" + overriddenGroup := "" for _, o := range overrides { if !o.Group.IsNull() { - overridenGroup = o.Group.Unwrap() - if group, ok := zk.Config.Groups[overridenGroup]; ok { + overriddenGroup = o.Group.Unwrap() + if group, ok := zk.Config.Groups[overriddenGroup]; ok { return group, nil } } } - if overridenGroup != "" { - return GroupConfig{}, fmt.Errorf("%s: group not find in the config file", overridenGroup) + if overriddenGroup != "" { + return GroupConfig{}, fmt.Errorf("%s: group not find in the config file", overriddenGroup) } for groupName, group := range zk.Config.Groups { diff --git a/core/zk/zk_test.go b/core/zk/zk_test.go index da5654e..d0b8898 100644 --- a/core/zk/zk_test.go +++ b/core/zk/zk_test.go @@ -253,9 +253,9 @@ func TestDirAtWithOverrides(t *testing.T) { dir, err := zk.DirAt(".", ConfigOverrides{ - BodyTemplatePath: opt.NewString("overriden-template"), + BodyTemplatePath: opt.NewString("overridden-template"), Extra: map[string]string{ - "hello": "overriden", + "hello": "overridden", "additional": "value", }, }, @@ -272,7 +272,7 @@ func TestDirAtWithOverrides(t *testing.T) { Paths: []string{}, Note: NoteConfig{ FilenameTemplate: "{{id}}.note", - BodyTemplatePath: opt.NewString("overriden-template"), + BodyTemplatePath: opt.NewString("overridden-template"), IDOptions: IDOptions{ Length: 4, Charset: CharsetLetters, @@ -280,7 +280,7 @@ func TestDirAtWithOverrides(t *testing.T) { }, }, Extra: map[string]string{ - "hello": "overriden", + "hello": "overridden", "additional": "value2", "additional2": "value3", }, diff --git a/docs/template-format.md b/docs/template-format.md index 2313d27..763c0a5 100644 --- a/docs/template-format.md +++ b/docs/template-format.md @@ -11,6 +11,7 @@ The following variables are available in the templates used when formatting note | `snippets` | [string] | List of context-sensitive relevant excerpts from the note | | `raw-content` | string | The full raw content of the note file | | `word-count` | int | Number of words in the note | +| `tags` | [string] | List of tags found in the note | | `created` | date | Date of creation of the note | | `modified` | date | Last date of modification of the note | | `checksum` | string | SHA-256 checksum of the note file | diff --git a/util/fts5/fts5.go b/util/fts5/fts5.go index 56f6918..515b6dc 100644 --- a/util/fts5/fts5.go +++ b/util/fts5/fts5.go @@ -22,7 +22,7 @@ func ConvertQuery(query string) string { ')': true, } - // Indicates whether the current term was explicitely quoted in the query. + // Indicates whether the current term was explicitly quoted in the query. inQuote := false // Current term being read. term := "" diff --git a/util/paths/walk.go b/util/paths/walk.go index e01c201..51462c8 100644 --- a/util/paths/walk.go +++ b/util/paths/walk.go @@ -41,11 +41,6 @@ func Walk(basePath string, extension string, logger util.Logger) <-chan Metadata return nil } - curDir := filepath.Dir(path) - if curDir == "." { - curDir = "" - } - c <- Metadata{ Path: path, Modified: info.ModTime().UTC(),