Support regular expressions with `--match` (#222)

pull/221/head
Mickaël Menu 2 years ago committed by GitHub
parent 1a05a04432
commit 4b76fbadf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
## Unreleased ## Unreleased
### Added
* Use regular expressions when searching for notes with `--match`.
```sh
# Find notes containing emails.
$ zk list --match-strategy re --match ".+@.+"
$ zk list -Mr -m ".+@.+"
```
### Changed
* The flags `--exact-match`/`-e` are deprecated in favor of `--match-strategy exact`/`-Me`.
### Deprecated ### Deprecated
* The LSP server does not support resolving a wiki link to a note title anymore. * The LSP server does not support resolving a wiki link to a note title anymore.

@ -45,11 +45,35 @@ $ zk list --linked-by "`zk inline journal`"
Use `--match <query>` (or `-m`) to search through the title and body of notes. Use `--match <query>` (or `-m`) to search through the title and body of notes.
The search is powered by a [full-text search](https://en.wikipedia.org/wiki/Full-text_search) database enabling near-instant results. Queries are not case-sensitive and terms are tokenized, which means that searching for `create` will also match `created` and `creating`. The search is powered by different strategies to answer various use cases:
* `fts` (default) uses a [full-text search](https://en.wikipedia.org/wiki/Full-text_search) database to offer near-instant results and advanced search operators.
* `exact` is useful if you need to find patterns containing special characters.
* `re` enables regular expression for advanced use cases.
Change the currently used strategy with `--match-strategy <strategy>` (or `-M`). To set the default strategy, you can declare a [custom alias](config-alias.md):
```toml
[alias]
list = "zk list --match-strategy re $@"
```
### Full-text search (`fts`)
The default match strategy is powered by a [full-text search](https://en.wikipedia.org/wiki/Full-text_search) database enabling near-instant results. Queries are not case-sensitive and terms are tokenized, which means that searching for `create` will also match `created` and `creating`.
A syntax similar to Google Search is available for advanced search queries. A syntax similar to Google Search is available for advanced search queries.
### Combining terms ```sh
# FTS is the default match strategy
$ zk list --match "tesla OR edison"
# ...but you can enable it explicitly.
$ zk list --match-strategy fts --match "tesla OR edison"
$ zk list -Mf -m "tesla OR edison"
```
#### Combining terms
By default, the search engine will find the notes containing all the terms in the query, in any order. By default, the search engine will find the notes containing all the terms in the query, in any order.
@ -83,7 +107,7 @@ Finally, you can filter out results by excluding a term with `NOT` (all caps) or
"tesla -car" "tesla -car"
``` ```
### Search in specific fields #### Search in specific fields
If you want to search only in the title or body of notes, prefix a query with `title:` or `body:`. If you want to search only in the title or body of notes, prefix a query with `title:` or `body:`.
@ -92,7 +116,7 @@ If you want to search only in the title or body of notes, prefix a query with `t
"body: (tesla OR edison)" "body: (tesla OR edison)"
``` ```
### Prefix terms #### Prefix terms
Match any term beginning with the given prefix with a wildcard `*`. Match any term beginning with the given prefix with a wildcard `*`.
@ -106,13 +130,25 @@ Prefixing a query with `^` will match notes whose title or body start with the f
"title: ^journal" "title: ^journal"
``` ```
### Search for special characters ### Exact matches (`exact`)
If you need to find patterns containing special characters, such as an `email@addre.ss` or a `[[wiki-link]]`, use the `--exact-match` / `-e` option. The search will be case-insensitive. If you need to find patterns containing special characters, such as an `email@addre.ss` or a `[[wiki-link]]`, use the `exact` match strategy. The search will be case-insensitive.
```sh
$ zk list --match-strategy exact --match "[[link]]"
$ zk list -Me -m "[[link]]"
``` ```
$ zk list --exact-match --match "[[link]]"
$ zk list -em "[[link]]" ### Regular expressions (`re`)
For advanced use cases, you can use the `re` match strategy to search the notebook using regular expressions. The supported syntax is similar to the one used by Python or Perl. [See the full reference](https://golang.org/s/re2syntax).
:warning: Make sure to use quotes to prevent your shell from expanding wildcards.
```sh
# Find notes containing emails.
$ zk list --match-strategy re --match ".+@.+"
$ zk list -Mr -m ".+@.+"
``` ```
## Filter by tags ## Filter by tags

@ -380,8 +380,8 @@ func (d *NoteDAO) expandMentionsIntoMatch(opts core.NoteFindOpts) (core.NoteFind
if opts.Mention == nil { if opts.Mention == nil {
return opts, nil return opts, nil
} }
if opts.ExactMatch { if opts.MatchStrategy != core.MatchStrategyFts {
return opts, fmt.Errorf("--exact-match and --mention cannot be used together") return opts, fmt.Errorf("--mention can only be used with --match-strategy=fts")
} }
// Find the IDs for the mentioned paths. // Find the IDs for the mentioned paths.
@ -518,15 +518,20 @@ func (d *NoteDAO) findRows(opts core.NoteFindOpts, selection noteSelection) (*sq
} }
if !opts.Match.IsNull() { if !opts.Match.IsNull() {
if opts.ExactMatch { switch opts.MatchStrategy {
case core.MatchStrategyExact:
whereExprs = append(whereExprs, `n.raw_content LIKE '%' || ? || '%' ESCAPE '\'`) whereExprs = append(whereExprs, `n.raw_content LIKE '%' || ? || '%' ESCAPE '\'`)
args = append(args, escapeLikeTerm(opts.Match.String(), '\\')) args = append(args, escapeLikeTerm(opts.Match.String(), '\\'))
} else { case core.MatchStrategyFts:
snippetCol = `snippet(fts_match.notes_fts, 2, '<zk:match>', '</zk:match>', '…', 20)` snippetCol = `snippet(fts_match.notes_fts, 2, '<zk:match>', '</zk:match>', '…', 20)`
joinClauses = append(joinClauses, "JOIN notes_fts fts_match ON n.id = fts_match.rowid") joinClauses = append(joinClauses, "JOIN notes_fts fts_match ON n.id = fts_match.rowid")
additionalOrderTerms = append(additionalOrderTerms, `bm25(fts_match.notes_fts, 1000.0, 500.0, 1.0)`) additionalOrderTerms = append(additionalOrderTerms, `bm25(fts_match.notes_fts, 1000.0, 500.0, 1.0)`)
whereExprs = append(whereExprs, "fts_match.notes_fts MATCH ?") whereExprs = append(whereExprs, "fts_match.notes_fts MATCH ?")
args = append(args, fts5.ConvertQuery(opts.Match.String())) args = append(args, fts5.ConvertQuery(opts.Match.String()))
case core.MatchStrategyRe:
whereExprs = append(whereExprs, "n.raw_content REGEXP ?")
args = append(args, opts.Match.String())
break
} }
} }

@ -312,9 +312,10 @@ func TestNoteDAOFindMinimalAll(t *testing.T) {
func TestNoteDAOFindMinimalWithFilter(t *testing.T) { func TestNoteDAOFindMinimalWithFilter(t *testing.T) {
testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { testNoteDAO(t, func(tx Transaction, dao *NoteDAO) {
notes, err := dao.FindMinimal(core.NoteFindOpts{ notes, err := dao.FindMinimal(core.NoteFindOpts{
Match: opt.NewString("daily | index"), Match: opt.NewString("daily | index"),
Sorters: []core.NoteSorter{{Field: core.NoteSortWordCount, Ascending: true}}, MatchStrategy: core.MatchStrategyFts,
Limit: 3, Sorters: []core.NoteSorter{{Field: core.NoteSortWordCount, Ascending: true}},
Limit: 3,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -366,7 +367,10 @@ func TestNoteDAOFindTag(t *testing.T) {
func TestNoteDAOFindMatch(t *testing.T) { func TestNoteDAOFindMatch(t *testing.T) {
testNoteDAOFind(t, testNoteDAOFind(t,
core.NoteFindOpts{Match: opt.NewString("daily | index")}, core.NoteFindOpts{
Match: opt.NewString("daily | index"),
MatchStrategy: core.MatchStrategyFts,
},
[]core.ContextualNote{ []core.ContextualNote{
{ {
Note: core.Note{ Note: core.Note{
@ -451,7 +455,8 @@ func TestNoteDAOFindMatch(t *testing.T) {
func TestNoteDAOFindMatchWithSort(t *testing.T) { func TestNoteDAOFindMatchWithSort(t *testing.T) {
testNoteDAOFindPaths(t, testNoteDAOFindPaths(t,
core.NoteFindOpts{ core.NoteFindOpts{
Match: opt.NewString("daily | index"), Match: opt.NewString("daily | index"),
MatchStrategy: core.MatchStrategyFts,
Sorters: []core.NoteSorter{ Sorters: []core.NoteSorter{
{Field: core.NoteSortPath, Ascending: false}, {Field: core.NoteSortPath, Ascending: false},
}, },
@ -469,8 +474,8 @@ func TestNoteDAOFindExactMatch(t *testing.T) {
test := func(match string, expected []string) { test := func(match string, expected []string) {
testNoteDAOFindPaths(t, testNoteDAOFindPaths(t,
core.NoteFindOpts{ core.NoteFindOpts{
Match: opt.NewString(match), Match: opt.NewString(match),
ExactMatch: true, MatchStrategy: core.MatchStrategyExact,
}, },
expected, expected,
) )
@ -482,13 +487,27 @@ func TestNoteDAOFindExactMatch(t *testing.T) {
test(`[exact% ch\ar_acters]`, []string{"ref/test/a.md"}) test(`[exact% ch\ar_acters]`, []string{"ref/test/a.md"})
} }
func TestNoteDAOFindExactMatchCannotBeUsedWithMention(t *testing.T) { func TestNoteDAOFindMentionRequiresFtsMatchStrategy(t *testing.T) {
testNoteDAO(t, func(tx Transaction, dao *NoteDAO) {
_, err := dao.Find(core.NoteFindOpts{
MatchStrategy: core.MatchStrategyExact,
Mention: []string{"mention"},
})
assert.Err(t, err, "--mention can only be used with --match-strategy=fts")
})
testNoteDAO(t, func(tx Transaction, dao *NoteDAO) {
_, err := dao.Find(core.NoteFindOpts{
MatchStrategy: core.MatchStrategyRe,
Mention: []string{"mention"},
})
assert.Err(t, err, "--mention can only be used with --match-strategy=fts")
})
testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { testNoteDAO(t, func(tx Transaction, dao *NoteDAO) {
_, err := dao.Find(core.NoteFindOpts{ _, err := dao.Find(core.NoteFindOpts{
ExactMatch: true, MatchStrategy: core.MatchStrategyFts,
Mention: []string{"mention"}, Mention: []string{"mention"},
}) })
assert.Err(t, err, "--exact-match and --mention cannot be used together") assert.Err(t, err, "could not find notes at: mention")
}) })
} }
@ -559,7 +578,10 @@ func TestNoteDAOFindExcludingMultiplePaths(t *testing.T) {
func TestNoteDAOFindMentions(t *testing.T) { func TestNoteDAOFindMentions(t *testing.T) {
testNoteDAOFind(t, testNoteDAOFind(t,
core.NoteFindOpts{Mention: []string{"log/2021-01-03.md", "index.md"}}, core.NoteFindOpts{
MatchStrategy: core.MatchStrategyFts,
Mention: []string{"log/2021-01-03.md", "index.md"},
},
[]core.ContextualNote{ []core.ContextualNote{
{ {
Note: core.Note{ Note: core.Note{
@ -623,7 +645,8 @@ func TestNoteDAOFindMentions(t *testing.T) {
func TestNoteDAOFindUnlinkedMentions(t *testing.T) { func TestNoteDAOFindUnlinkedMentions(t *testing.T) {
testNoteDAOFindPaths(t, testNoteDAOFindPaths(t,
core.NoteFindOpts{ core.NoteFindOpts{
Mention: []string{"log/2021-01-03.md", "index.md"}, MatchStrategy: core.MatchStrategyFts,
Mention: []string{"log/2021-01-03.md", "index.md"},
LinkTo: &core.LinkFilter{ LinkTo: &core.LinkFilter{
Hrefs: []string{"log/2021-01-03.md", "index.md"}, Hrefs: []string{"log/2021-01-03.md", "index.md"},
Negate: true, Negate: true,
@ -636,7 +659,8 @@ func TestNoteDAOFindUnlinkedMentions(t *testing.T) {
func TestNoteDAOFindMentionUnknown(t *testing.T) { func TestNoteDAOFindMentionUnknown(t *testing.T) {
testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { testNoteDAO(t, func(tx Transaction, dao *NoteDAO) {
opts := core.NoteFindOpts{ opts := core.NoteFindOpts{
Mention: []string{"will-not-be-found"}, MatchStrategy: core.MatchStrategyFts,
Mention: []string{"will-not-be-found"},
} }
_, err := dao.Find(opts) _, err := dao.Find(opts)
assert.Err(t, err, "could not find notes at: will-not-be-found") assert.Err(t, err, "could not find notes at: will-not-be-found")
@ -645,7 +669,10 @@ func TestNoteDAOFindMentionUnknown(t *testing.T) {
func TestNoteDAOFindMentionedBy(t *testing.T) { func TestNoteDAOFindMentionedBy(t *testing.T) {
testNoteDAOFind(t, testNoteDAOFind(t,
core.NoteFindOpts{MentionedBy: []string{"ref/test/b.md", "log/2021-01-04.md"}}, core.NoteFindOpts{
MatchStrategy: core.MatchStrategyFts,
MentionedBy: []string{"ref/test/b.md", "log/2021-01-04.md"},
},
[]core.ContextualNote{ []core.ContextualNote{
{ {
Note: core.Note{ Note: core.Note{
@ -697,7 +724,8 @@ func TestNoteDAOFindMentionedBy(t *testing.T) {
func TestNoteDAOFindUnlinkedMentionedBy(t *testing.T) { func TestNoteDAOFindUnlinkedMentionedBy(t *testing.T) {
testNoteDAOFindPaths(t, testNoteDAOFindPaths(t,
core.NoteFindOpts{ core.NoteFindOpts{
MentionedBy: []string{"ref/test/b.md", "log/2021-01-04.md"}, MatchStrategy: core.MatchStrategyFts,
MentionedBy: []string{"ref/test/b.md", "log/2021-01-04.md"},
LinkedBy: &core.LinkFilter{ LinkedBy: &core.LinkFilter{
Hrefs: []string{"ref/test/b.md", "log/2021-01-04.md"}, Hrefs: []string{"ref/test/b.md", "log/2021-01-04.md"},
Negate: true, Negate: true,
@ -710,7 +738,8 @@ func TestNoteDAOFindUnlinkedMentionedBy(t *testing.T) {
func TestNoteDAOFindMentionedByUnknown(t *testing.T) { func TestNoteDAOFindMentionedByUnknown(t *testing.T) {
testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { testNoteDAO(t, func(tx Transaction, dao *NoteDAO) {
opts := core.NoteFindOpts{ opts := core.NoteFindOpts{
MentionedBy: []string{"will-not-be-found"}, MatchStrategy: core.MatchStrategyFts,
MentionedBy: []string{"will-not-be-found"},
} }
_, err := dao.Find(opts) _, err := dao.Find(opts)
assert.Err(t, err, "could not find notes at: will-not-be-found") assert.Err(t, err, "could not find notes at: will-not-be-found")

@ -139,6 +139,10 @@ func NewContainer(version string) (*Container, error) {
// XDG Base Directory specification // XDG Base Directory specification
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
func locateGlobalConfig() (string, error) { func locateGlobalConfig() (string, error) {
if _, ok := os.LookupEnv("RUNNING_TESH"); ok {
return "", nil
}
configPath := filepath.Join(globalConfigDir(), "config.toml") configPath := filepath.Join(globalConfigDir(), "config.toml")
exists, err := paths.Exists(configPath) exists, err := paths.Exists(configPath)
switch { switch {

@ -20,7 +20,7 @@ type Filtering struct {
Interactive bool `kong:"group='filter',short='i',help='Select notes interactively with fzf.'" json:"-"` Interactive bool `kong:"group='filter',short='i',help='Select notes interactively with fzf.'" json:"-"`
Limit int `kong:"group='filter',short='n',placeholder='COUNT',help='Limit the number of notes found.'" json:"limit"` Limit int `kong:"group='filter',short='n',placeholder='COUNT',help='Limit the number of notes found.'" json:"limit"`
Match string `kong:"group='filter',short='m',placeholder='QUERY',help='Terms to search for in the notes.'" json:"match"` Match string `kong:"group='filter',short='m',placeholder='QUERY',help='Terms to search for in the notes.'" json:"match"`
ExactMatch bool `kong:"group='filter',short='e',help='Search for exact occurrences of the --match argument (case insensitive).'" json:"exactMatch"` MatchStrategy string `kong:"group='filter',short='M',default='fts',placeholder='STRATEGY',help='Text matching strategy among: fts, re, exact.'" json:"matchStrategy"`
Exclude []string `kong:"group='filter',short='x',placeholder='PATH',help='Ignore notes matching the given path, including its descendants.'" json:"excludeHrefs"` Exclude []string `kong:"group='filter',short='x',placeholder='PATH',help='Ignore notes matching the given path, including its descendants.'" json:"excludeHrefs"`
Tag []string `kong:"group='filter',short='t',help='Find notes tagged with the given tags.'" json:"tags"` Tag []string `kong:"group='filter',short='t',help='Find notes tagged with the given tags.'" json:"tags"`
Mention []string `kong:"group='filter',placeholder='PATH',help='Find notes mentioning the title of the given ones.'" json:"mention"` Mention []string `kong:"group='filter',placeholder='PATH',help='Find notes mentioning the title of the given ones.'" json:"mention"`
@ -41,6 +41,9 @@ type Filtering struct {
ModifiedAfter string `kong:"group='filter',placeholder='DATE',help='Find notes modified after the given date.'" json:"modifiedAfter"` ModifiedAfter string `kong:"group='filter',placeholder='DATE',help='Find notes modified after the given date.'" json:"modifiedAfter"`
Sort []string `kong:"group='sort',short='s',placeholder='TERM',help='Order the notes by the given criterion.'" json:"sort"` Sort []string `kong:"group='sort',short='s',placeholder='TERM',help='Order the notes by the given criterion.'" json:"sort"`
// Deprecated
ExactMatch bool `kong:"hidden,short='e'" json:"exactMatch"`
} }
// ExpandNamedFilters expands recursively any named filter found in the Path field. // ExpandNamedFilters expands recursively any named filter found in the Path field.
@ -119,6 +122,9 @@ func (f Filtering) ExpandNamedFilters(filters map[string]string, expandedFilters
} else if parsedFilter.Match != "" { } else if parsedFilter.Match != "" {
f.Match = fmt.Sprintf("(%s) AND (%s)", f.Match, parsedFilter.Match) f.Match = fmt.Sprintf("(%s) AND (%s)", f.Match, parsedFilter.Match)
} }
if f.MatchStrategy == "" {
f.MatchStrategy = parsedFilter.MatchStrategy
}
} else { } else {
actualPaths = append(actualPaths, path) actualPaths = append(actualPaths, path)
@ -138,8 +144,15 @@ func (f Filtering) NewNoteFindOpts(notebook *core.Notebook) (core.NoteFindOpts,
return opts, err return opts, err
} }
if f.ExactMatch {
return opts, fmt.Errorf("the --exact-match (-e) option is deprecated, use --match-strategy=exact (-Me) instead")
}
opts.Match = opt.NewNotEmptyString(f.Match) opts.Match = opt.NewNotEmptyString(f.Match)
opts.ExactMatch = f.ExactMatch opts.MatchStrategy, err = core.MatchStrategyFromString(f.MatchStrategy)
if err != nil {
return opts, err
}
if paths, ok := relPaths(notebook, f.Path); ok { if paths, ok := relPaths(notebook, f.Path); ok {
opts.IncludeHrefs = paths opts.IncludeHrefs = paths

@ -11,10 +11,10 @@ import (
// NoteFindOpts holds a set of filtering options used to find notes. // NoteFindOpts holds a set of filtering options used to find notes.
type NoteFindOpts struct { type NoteFindOpts struct {
// Filter used to match the notes with FTS predicates. // Filter used to match the notes with the given MatchStrategy.
Match opt.String Match opt.String
// Search for exact occurrences of the Match string. // Text matching strategy used with Match.
ExactMatch bool MatchStrategy MatchStrategy
// Filter by note hrefs. // Filter by note hrefs.
IncludeHrefs []string IncludeHrefs []string
// Filter excluding notes at the given hrefs. // Filter excluding notes at the given hrefs.
@ -161,3 +161,29 @@ func NoteSorterFromString(str string) (NoteSorter, error) {
return sorter, nil return sorter, nil
} }
// MatchStrategy represents a text matching strategy used when filtering notes with `--match`.
type MatchStrategy int
const (
// Full text search.
MatchStrategyFts MatchStrategy = iota + 1
// Exact text matching.
MatchStrategyExact
// Regular expression.
MatchStrategyRe
)
// MatchStrategyFromString returns a MatchStrategy from its string representation.
func MatchStrategyFromString(str string) (MatchStrategy, error) {
switch str {
case "fts", "f", "":
return MatchStrategyFts, nil
case "re", "grep", "r":
return MatchStrategyRe, nil
case "exact", "e":
return MatchStrategyExact, nil
default:
return 0, fmt.Errorf("%s: unknown match strategy\ntry fts (full-text search), re (regular expression) or exact", str)
}
}

@ -67,3 +67,24 @@ func TestSortersFromStrings(t *testing.T) {
_, err := NoteSortersFromStrings([]string{"c", "foobar"}) _, err := NoteSortersFromStrings([]string{"c", "foobar"})
assert.Err(t, err, "foobar: unknown sorting term") assert.Err(t, err, "foobar: unknown sorting term")
} }
func TestMatchStrategyFromString(t *testing.T) {
test := func(str string, expected MatchStrategy) {
actual, err := MatchStrategyFromString(str)
assert.Nil(t, err)
assert.Equal(t, actual, expected)
}
test("f", MatchStrategyFts)
test("fts", MatchStrategyFts)
test("r", MatchStrategyRe)
test("re", MatchStrategyRe)
test("grep", MatchStrategyRe)
test("e", MatchStrategyExact)
test("exact", MatchStrategyExact)
_, err := MatchStrategyFromString("foobar")
assert.Err(t, err, "foobar: unknown match strategy\ntry fts (full-text search), re (regular expression) or exact")
}

@ -25,8 +25,7 @@ $ zk graph --help
> -i, --interactive Select notes interactively with fzf. > -i, --interactive Select notes interactively with fzf.
> -n, --limit=COUNT Limit the number of notes found. > -n, --limit=COUNT Limit the number of notes found.
> -m, --match=QUERY Terms to search for in the notes. > -m, --match=QUERY Terms to search for in the notes.
> -e, --exact-match Search for exact occurrences of the --match > -M, --match-strategy="fts" Text matching strategy among: fts, re, exact.
> argument (case insensitive).
> -x, --exclude=PATH,... Ignore notes matching the given path, including > -x, --exclude=PATH,... Ignore notes matching the given path, including
> its descendants. > its descendants.
> -t, --tag=TAG,... Find notes tagged with the given tags. > -t, --tag=TAG,... Find notes tagged with the given tags.

@ -0,0 +1,17 @@
# Exact search match strategy.
$ cd full-sample
# Long flag.
$ zk list -q --debug-style --match-strategy exact --match '["न", "म", "स्", "ते"]'
><title>Strings are a complicated data structure</title> <path>oumc.md</path> (just now)
>
> - Given the Hindi word "नमस्ते":
>
# Short flag.
$ zk list -q --debug-style -Me --match '["न", "म", "स्", "ते"]'
><title>Strings are a complicated data structure</title> <path>oumc.md</path> (just now)
>
> - Given the Hindi word "नमस्ते":
>

@ -0,0 +1,113 @@
# Full-text search match strategy.
$ cd full-sample
# Search for a multi-word term.
$ zk list -q --debug-style --match '"green thread"'
><title>Green threads</title> <path>inbox/my59.md</path> (just now)
>
> - …Programming language-provided threads are known as <term>green threads</term>, and languages that use these <term>green threads</term> will execute them in…
>
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …so it doesn't support [<term>green threads</term>](inbox/my59).
> * Crates exist to add support for <term>green threads</term> if needed.
> * Instead, Rust relies…
>
# Search for two terms (defaults to AND).
$ zk list -q --debug-style --match 'green channel'
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …runtime, so it doesn't support [<term>green</term> threads](inbox/my59).
> * Crates exist to add support for <term>green</term> threads if needed.
> * Instead, Rust…
>
# Search for two terms with explicit AND.
$ zk list -q --debug-style --match 'green AND channel'
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …runtime, so it doesn't support [<term>green</term> threads](inbox/my59).
> * Crates exist to add support for <term>green</term> threads if needed.
> * Instead, Rust…
>
# Search for two terms with OR.
$ zk list -q --debug-style --match 'green OR channel'
><title>Green threads</title> <path>inbox/my59.md</path> (just now)
>
> - …Programming language-provided threads are known as <term>green</term> threads, and languages that use these <term>green</term> threads will execute them in…
>
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …runtime, so it doesn't support [<term>green</term> threads](inbox/my59).
> * Crates exist to add support for <term>green</term> threads if needed.
> * Instead, Rust…
>
><title>Channel</title> <path>fwsj.md</path> (just now)
>
> - * <term>Channels</term> are a great approach for safe concurrency.
> * It's an implementation of the [message passing](4oma) pattern.
>
> :programming:
>
><title>Message passing</title> <path>4oma.md</path> (just now)
>
> - * A popular approach for safe concurrency is to use *message passing* instead of shared state.
> * <term>Channels</term> are an example of…
>
><title>Mutex</title> <path>inbox/er4k.md</path> (just now)
>
> - …with a *locking system*.
> * Managing mutexes is tricky, using [<term>channels</term>](../fwsj) is an easier alternative.
> * The main risk is to…
>
# Exclude a term.
$ zk list -q --debug-style --match 'green -channel'
><title>Green threads</title> <path>inbox/my59.md</path> (just now)
>
> - …Programming language-provided threads are known as <term>green</term> threads, and languages that use these <term>green</term> threads will execute them in…
>
# Search in the `title` field.
$ zk list -q --debug-style --match 'title:(green thread)'
><title>Green threads</title> <path>inbox/my59.md</path> (just now)
>
> - > Many operating systems provide an API for creating new threads. This model where a language calls the operating system APIs…
>
# Search with a prefix in `title`.
$ zk list -q --debug-style --match 'title:^do*'
><title>Do not communicate by sharing memory; instead, share memory by communicating</title> <path>ref/7fto.md</path> (just now)
>
> - * Advocates for the use of [message passing](4oma) instead of shared state.
> * A slogan initially coined by Rob Pike ([Effective…
>
><title>Don't speculate</title> <path>pywo.md</path> (just now)
>
> - You have more to loose by doing market timing (jumping in and out of the stocks market). Remember, [the less…
>
# Search with a prefix.
$ zk list -q --debug-style --match 'mut*'
><title>Mutex</title> <path>inbox/er4k.md</path> (just now)
>
> - * Abbreviation of *<term>mutual</term> exclusion*.
> * An approach to manage safely shared state by allowing only a single thread to access a…
>
><title>Data race error</title> <path>3403.md</path> (just now)
>
> - …Rust prevents *data races* by allowing only a single <term>mutable</term> reference of a value per scope.
>
> :programming:
>
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …data between threads:
> * [Channel](fwsj) for a safe [message passing](4oma) approach.
> * [<term>Mutex</term>](inbox/er4k) for managing shared state.
>
> :rust:programming:
>

@ -0,0 +1,17 @@
# Regular expression match strategy.
$ cd full-sample
# Long flag.
$ zk list -q --debug-style --match-strategy re --match 'न.*ते'
><title>Strings are a complicated data structure</title> <path>oumc.md</path> (just now)
>
> - Given the Hindi word "नमस्ते":
>
# Short flag.
$ zk list -q --debug-style -Mr --match 'न.*ते'
><title>Strings are a complicated data structure</title> <path>oumc.md</path> (just now)
>
> - Given the Hindi word "नमस्ते":
>

@ -1,126 +1,9 @@
$ cd full-sample $ cd blank
# Search for a multi-word term. # --exact-match is deprecated.
$ zk list -q --debug-style --match '"green thread"' 1$ zk list -q --exact-match
><title>Green threads</title> <path>inbox/my59.md</path> (just now) 2>zk: error: incorrect criteria: the --exact-match (-e) option is deprecated, use --match-strategy=exact (-Me) instead
>
> - …Programming language-provided threads are known as <term>green threads</term>, and languages that use these <term>green threads</term> will execute them in…
>
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …so it doesn't support [<term>green threads</term>](inbox/my59).
> * Crates exist to add support for <term>green threads</term> if needed.
> * Instead, Rust relies…
>
# Search for two terms (defaults to AND).
$ zk list -q --debug-style --match 'green channel'
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …runtime, so it doesn't support [<term>green</term> threads](inbox/my59).
> * Crates exist to add support for <term>green</term> threads if needed.
> * Instead, Rust…
>
# Search for two terms with explicit AND.
$ zk list -q --debug-style --match 'green AND channel'
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …runtime, so it doesn't support [<term>green</term> threads](inbox/my59).
> * Crates exist to add support for <term>green</term> threads if needed.
> * Instead, Rust…
>
# Search for two terms with OR.
$ zk list -q --debug-style --match 'green OR channel'
><title>Green threads</title> <path>inbox/my59.md</path> (just now)
>
> - …Programming language-provided threads are known as <term>green</term> threads, and languages that use these <term>green</term> threads will execute them in…
>
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …runtime, so it doesn't support [<term>green</term> threads](inbox/my59).
> * Crates exist to add support for <term>green</term> threads if needed.
> * Instead, Rust…
>
><title>Channel</title> <path>fwsj.md</path> (just now)
>
> - * <term>Channels</term> are a great approach for safe concurrency.
> * It's an implementation of the [message passing](4oma) pattern.
>
> :programming:
>
><title>Message passing</title> <path>4oma.md</path> (just now)
>
> - * A popular approach for safe concurrency is to use *message passing* instead of shared state.
> * <term>Channels</term> are an example of…
>
><title>Mutex</title> <path>inbox/er4k.md</path> (just now)
>
> - …with a *locking system*.
> * Managing mutexes is tricky, using [<term>channels</term>](../fwsj) is an easier alternative.
> * The main risk is to…
>
# Exclude a term.
$ zk list -q --debug-style --match 'green -channel'
><title>Green threads</title> <path>inbox/my59.md</path> (just now)
>
> - …Programming language-provided threads are known as <term>green</term> threads, and languages that use these <term>green</term> threads will execute them in…
>
# Search in the `title` field.
$ zk list -q --debug-style --match 'title:(green thread)'
><title>Green threads</title> <path>inbox/my59.md</path> (just now)
>
> - > Many operating systems provide an API for creating new threads. This model where a language calls the operating system APIs…
>
# Search with a prefix in `title`.
$ zk list -q --debug-style --match 'title:^do*'
><title>Do not communicate by sharing memory; instead, share memory by communicating</title> <path>ref/7fto.md</path> (just now)
>
> - * Advocates for the use of [message passing](4oma) instead of shared state.
> * A slogan initially coined by Rob Pike ([Effective…
>
><title>Don't speculate</title> <path>pywo.md</path> (just now)
>
> - You have more to loose by doing market timing (jumping in and out of the stocks market). Remember, [the less…
>
# Search with a prefix.
$ zk list -q --debug-style --match 'mut*'
><title>Mutex</title> <path>inbox/er4k.md</path> (just now)
>
> - * Abbreviation of *<term>mutual</term> exclusion*.
> * An approach to manage safely shared state by allowing only a single thread to access a…
>
><title>Data race error</title> <path>3403.md</path> (just now)
>
> - …Rust prevents *data races* by allowing only a single <term>mutable</term> reference of a value per scope.
>
> :programming:
>
><title>Concurrency in Rust</title> <path>g7qa.md</path> (just now)
>
> - …data between threads:
> * [Channel](fwsj) for a safe [message passing](4oma) approach.
> * [<term>Mutex</term>](inbox/er4k) for managing shared state.
>
> :rust:programming:
>
# Search for an exact match.
$ zk list -q --debug-style --exact-match --match '["न", "म", "स्", "ते"]'
><title>Strings are a complicated data structure</title> <path>oumc.md</path> (just now)
>
> - Given the Hindi word "नमस्ते":
>
# Search for an exact match (short flag).
$ zk list -q --debug-style -em '["न", "म", "स्", "ते"]'
><title>Strings are a complicated data structure</title> <path>oumc.md</path> (just now)
>
> - Given the Hindi word "नमस्ते":
>
# --exact-match is deprecated (short flag).
1$ zk list -q -e
2>zk: error: incorrect criteria: the --exact-match (-e) option is deprecated, use --match-strategy=exact (-Me) instead

@ -33,8 +33,7 @@ $ zk list --help
> -i, --interactive Select notes interactively with fzf. > -i, --interactive Select notes interactively with fzf.
> -n, --limit=COUNT Limit the number of notes found. > -n, --limit=COUNT Limit the number of notes found.
> -m, --match=QUERY Terms to search for in the notes. > -m, --match=QUERY Terms to search for in the notes.
> -e, --exact-match Search for exact occurrences of the --match > -M, --match-strategy="fts" Text matching strategy among: fts, re, exact.
> argument (case insensitive).
> -x, --exclude=PATH,... Ignore notes matching the given path, including > -x, --exclude=PATH,... Ignore notes matching the given path, including
> its descendants. > its descendants.
> -t, --tag=TAG,... Find notes tagged with the given tags. > -t, --tag=TAG,... Find notes tagged with the given tags.

Loading…
Cancel
Save