From 4b76fbadf171dbb18a3154701fe3b4d1cba31a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Menu?= Date: Sat, 28 May 2022 20:03:06 +0200 Subject: [PATCH] Support regular expressions with `--match` (#222) --- CHANGELOG.md | 13 +++ docs/note-filtering.md | 52 +++++++-- internal/adapter/sqlite/note_dao.go | 13 ++- internal/adapter/sqlite/note_dao_test.go | 63 ++++++++--- internal/cli/container.go | 4 + internal/cli/filtering.go | 17 ++- internal/core/note_find.go | 32 +++++- internal/core/note_find_test.go | 21 ++++ tests/cmd-graph.tesh | 3 +- tests/cmd-list-filter-match-exact.tesh | 17 +++ tests/cmd-list-filter-match-fts.tesh | 113 +++++++++++++++++++ tests/cmd-list-filter-match-re.tesh | 17 +++ tests/cmd-list-filter-match.tesh | 131 ++--------------------- tests/cmd-list.tesh | 3 +- 14 files changed, 337 insertions(+), 162 deletions(-) create mode 100644 tests/cmd-list-filter-match-exact.tesh create mode 100644 tests/cmd-list-filter-match-fts.tesh create mode 100644 tests/cmd-list-filter-match-re.tesh diff --git a/CHANGELOG.md b/CHANGELOG.md index 4718b81..5dfd8a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. ## 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 * The LSP server does not support resolving a wiki link to a note title anymore. diff --git a/docs/note-filtering.md b/docs/note-filtering.md index e6d5e44..a3d711e 100644 --- a/docs/note-filtering.md +++ b/docs/note-filtering.md @@ -45,11 +45,35 @@ $ zk list --linked-by "`zk inline journal`" Use `--match ` (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 ` (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. -### 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. @@ -83,7 +107,7 @@ Finally, you can filter out results by excluding a term with `NOT` (all caps) or "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:`. @@ -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)" ``` -### Prefix terms +#### Prefix terms 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" ``` -### 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 diff --git a/internal/adapter/sqlite/note_dao.go b/internal/adapter/sqlite/note_dao.go index bb7687c..81f3d40 100644 --- a/internal/adapter/sqlite/note_dao.go +++ b/internal/adapter/sqlite/note_dao.go @@ -380,8 +380,8 @@ func (d *NoteDAO) expandMentionsIntoMatch(opts core.NoteFindOpts) (core.NoteFind if opts.Mention == nil { return opts, nil } - if opts.ExactMatch { - return opts, fmt.Errorf("--exact-match and --mention cannot be used together") + if opts.MatchStrategy != core.MatchStrategyFts { + return opts, fmt.Errorf("--mention can only be used with --match-strategy=fts") } // 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.ExactMatch { + switch opts.MatchStrategy { + case core.MatchStrategyExact: whereExprs = append(whereExprs, `n.raw_content LIKE '%' || ? || '%' ESCAPE '\'`) args = append(args, escapeLikeTerm(opts.Match.String(), '\\')) - } else { + case core.MatchStrategyFts: snippetCol = `snippet(fts_match.notes_fts, 2, '', '', '…', 20)` 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)`) whereExprs = append(whereExprs, "fts_match.notes_fts MATCH ?") 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 } } diff --git a/internal/adapter/sqlite/note_dao_test.go b/internal/adapter/sqlite/note_dao_test.go index 4be1a6a..83ba16e 100644 --- a/internal/adapter/sqlite/note_dao_test.go +++ b/internal/adapter/sqlite/note_dao_test.go @@ -312,9 +312,10 @@ func TestNoteDAOFindMinimalAll(t *testing.T) { func TestNoteDAOFindMinimalWithFilter(t *testing.T) { testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { notes, err := dao.FindMinimal(core.NoteFindOpts{ - Match: opt.NewString("daily | index"), - Sorters: []core.NoteSorter{{Field: core.NoteSortWordCount, Ascending: true}}, - Limit: 3, + Match: opt.NewString("daily | index"), + MatchStrategy: core.MatchStrategyFts, + Sorters: []core.NoteSorter{{Field: core.NoteSortWordCount, Ascending: true}}, + Limit: 3, }) assert.Nil(t, err) @@ -366,7 +367,10 @@ func TestNoteDAOFindTag(t *testing.T) { func TestNoteDAOFindMatch(t *testing.T) { testNoteDAOFind(t, - core.NoteFindOpts{Match: opt.NewString("daily | index")}, + core.NoteFindOpts{ + Match: opt.NewString("daily | index"), + MatchStrategy: core.MatchStrategyFts, + }, []core.ContextualNote{ { Note: core.Note{ @@ -451,7 +455,8 @@ func TestNoteDAOFindMatch(t *testing.T) { func TestNoteDAOFindMatchWithSort(t *testing.T) { testNoteDAOFindPaths(t, core.NoteFindOpts{ - Match: opt.NewString("daily | index"), + Match: opt.NewString("daily | index"), + MatchStrategy: core.MatchStrategyFts, Sorters: []core.NoteSorter{ {Field: core.NoteSortPath, Ascending: false}, }, @@ -469,8 +474,8 @@ func TestNoteDAOFindExactMatch(t *testing.T) { test := func(match string, expected []string) { testNoteDAOFindPaths(t, core.NoteFindOpts{ - Match: opt.NewString(match), - ExactMatch: true, + Match: opt.NewString(match), + MatchStrategy: core.MatchStrategyExact, }, expected, ) @@ -482,13 +487,27 @@ func TestNoteDAOFindExactMatch(t *testing.T) { 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) { _, err := dao.Find(core.NoteFindOpts{ - ExactMatch: true, - Mention: []string{"mention"}, + MatchStrategy: core.MatchStrategyFts, + 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) { 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{ { Note: core.Note{ @@ -623,7 +645,8 @@ func TestNoteDAOFindMentions(t *testing.T) { func TestNoteDAOFindUnlinkedMentions(t *testing.T) { testNoteDAOFindPaths(t, 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{ Hrefs: []string{"log/2021-01-03.md", "index.md"}, Negate: true, @@ -636,7 +659,8 @@ func TestNoteDAOFindUnlinkedMentions(t *testing.T) { func TestNoteDAOFindMentionUnknown(t *testing.T) { testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { opts := core.NoteFindOpts{ - Mention: []string{"will-not-be-found"}, + MatchStrategy: core.MatchStrategyFts, + Mention: []string{"will-not-be-found"}, } _, err := dao.Find(opts) 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) { 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{ { Note: core.Note{ @@ -697,7 +724,8 @@ func TestNoteDAOFindMentionedBy(t *testing.T) { func TestNoteDAOFindUnlinkedMentionedBy(t *testing.T) { testNoteDAOFindPaths(t, 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{ Hrefs: []string{"ref/test/b.md", "log/2021-01-04.md"}, Negate: true, @@ -710,7 +738,8 @@ func TestNoteDAOFindUnlinkedMentionedBy(t *testing.T) { func TestNoteDAOFindMentionedByUnknown(t *testing.T) { testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { opts := core.NoteFindOpts{ - MentionedBy: []string{"will-not-be-found"}, + MatchStrategy: core.MatchStrategyFts, + MentionedBy: []string{"will-not-be-found"}, } _, err := dao.Find(opts) assert.Err(t, err, "could not find notes at: will-not-be-found") diff --git a/internal/cli/container.go b/internal/cli/container.go index 26e41e4..56c26ce 100644 --- a/internal/cli/container.go +++ b/internal/cli/container.go @@ -139,6 +139,10 @@ func NewContainer(version string) (*Container, error) { // XDG Base Directory specification // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html func locateGlobalConfig() (string, error) { + if _, ok := os.LookupEnv("RUNNING_TESH"); ok { + return "", nil + } + configPath := filepath.Join(globalConfigDir(), "config.toml") exists, err := paths.Exists(configPath) switch { diff --git a/internal/cli/filtering.go b/internal/cli/filtering.go index 82eac95..e39cd79 100644 --- a/internal/cli/filtering.go +++ b/internal/cli/filtering.go @@ -20,7 +20,7 @@ type Filtering struct { 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"` 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"` 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"` @@ -41,6 +41,9 @@ type Filtering struct { 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"` + + // Deprecated + ExactMatch bool `kong:"hidden,short='e'" json:"exactMatch"` } // 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 != "" { f.Match = fmt.Sprintf("(%s) AND (%s)", f.Match, parsedFilter.Match) } + if f.MatchStrategy == "" { + f.MatchStrategy = parsedFilter.MatchStrategy + } } else { actualPaths = append(actualPaths, path) @@ -138,8 +144,15 @@ func (f Filtering) NewNoteFindOpts(notebook *core.Notebook) (core.NoteFindOpts, 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.ExactMatch = f.ExactMatch + opts.MatchStrategy, err = core.MatchStrategyFromString(f.MatchStrategy) + if err != nil { + return opts, err + } if paths, ok := relPaths(notebook, f.Path); ok { opts.IncludeHrefs = paths diff --git a/internal/core/note_find.go b/internal/core/note_find.go index 832425b..59d994e 100644 --- a/internal/core/note_find.go +++ b/internal/core/note_find.go @@ -11,10 +11,10 @@ import ( // NoteFindOpts holds a set of filtering options used to find notes. 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 - // Search for exact occurrences of the Match string. - ExactMatch bool + // Text matching strategy used with Match. + MatchStrategy MatchStrategy // Filter by note hrefs. IncludeHrefs []string // Filter excluding notes at the given hrefs. @@ -161,3 +161,29 @@ func NoteSorterFromString(str string) (NoteSorter, error) { 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) + } +} diff --git a/internal/core/note_find_test.go b/internal/core/note_find_test.go index cc349f2..4eaed19 100644 --- a/internal/core/note_find_test.go +++ b/internal/core/note_find_test.go @@ -67,3 +67,24 @@ func TestSortersFromStrings(t *testing.T) { _, err := NoteSortersFromStrings([]string{"c", "foobar"}) 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") +} diff --git a/tests/cmd-graph.tesh b/tests/cmd-graph.tesh index 04ed869..781f887 100644 --- a/tests/cmd-graph.tesh +++ b/tests/cmd-graph.tesh @@ -25,8 +25,7 @@ $ zk graph --help > -i, --interactive Select notes interactively with fzf. > -n, --limit=COUNT Limit the number of notes found. > -m, --match=QUERY Terms to search for in the notes. -> -e, --exact-match Search for exact occurrences of the --match -> argument (case insensitive). +> -M, --match-strategy="fts" Text matching strategy among: fts, re, exact. > -x, --exclude=PATH,... Ignore notes matching the given path, including > its descendants. > -t, --tag=TAG,... Find notes tagged with the given tags. diff --git a/tests/cmd-list-filter-match-exact.tesh b/tests/cmd-list-filter-match-exact.tesh new file mode 100644 index 0000000..f4fe252 --- /dev/null +++ b/tests/cmd-list-filter-match-exact.tesh @@ -0,0 +1,17 @@ +# Exact search match strategy. + +$ cd full-sample + +# Long flag. +$ zk list -q --debug-style --match-strategy exact --match '["न", "म", "स्", "ते"]' +>Strings are a complicated data structure oumc.md (just now) +> +> - Given the Hindi word "नमस्ते": +> + +# Short flag. +$ zk list -q --debug-style -Me --match '["न", "म", "स्", "ते"]' +>Strings are a complicated data structure oumc.md (just now) +> +> - Given the Hindi word "नमस्ते": +> diff --git a/tests/cmd-list-filter-match-fts.tesh b/tests/cmd-list-filter-match-fts.tesh new file mode 100644 index 0000000..8b1f08f --- /dev/null +++ b/tests/cmd-list-filter-match-fts.tesh @@ -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"' +>Green threads inbox/my59.md (just now) +> +> - …Programming language-provided threads are known as green threads, and languages that use these green threads will execute them in… +> +>Concurrency in Rust g7qa.md (just now) +> +> - …so it doesn't support [green threads](inbox/my59). +> * Crates exist to add support for green threads if needed. +> * Instead, Rust relies… +> + +# Search for two terms (defaults to AND). +$ zk list -q --debug-style --match 'green channel' +>Concurrency in Rust g7qa.md (just now) +> +> - …runtime, so it doesn't support [green threads](inbox/my59). +> * Crates exist to add support for green threads if needed. +> * Instead, Rust… +> + +# Search for two terms with explicit AND. +$ zk list -q --debug-style --match 'green AND channel' +>Concurrency in Rust g7qa.md (just now) +> +> - …runtime, so it doesn't support [green threads](inbox/my59). +> * Crates exist to add support for green threads if needed. +> * Instead, Rust… +> + +# Search for two terms with OR. +$ zk list -q --debug-style --match 'green OR channel' +>Green threads inbox/my59.md (just now) +> +> - …Programming language-provided threads are known as green threads, and languages that use these green threads will execute them in… +> +>Concurrency in Rust g7qa.md (just now) +> +> - …runtime, so it doesn't support [green threads](inbox/my59). +> * Crates exist to add support for green threads if needed. +> * Instead, Rust… +> +>Channel fwsj.md (just now) +> +> - * Channels are a great approach for safe concurrency. +> * It's an implementation of the [message passing](4oma) pattern. +> +> :programming: +> +>Message passing 4oma.md (just now) +> +> - * A popular approach for safe concurrency is to use *message passing* instead of shared state. +> * Channels are an example of… +> +>Mutex inbox/er4k.md (just now) +> +> - …with a *locking system*. +> * Managing mutexes is tricky, using [channels](../fwsj) is an easier alternative. +> * The main risk is to… +> + +# Exclude a term. +$ zk list -q --debug-style --match 'green -channel' +>Green threads inbox/my59.md (just now) +> +> - …Programming language-provided threads are known as green threads, and languages that use these green threads will execute them in… +> + +# Search in the `title` field. +$ zk list -q --debug-style --match 'title:(green thread)' +>Green threads inbox/my59.md (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*' +>Do not communicate by sharing memory; instead, share memory by communicating ref/7fto.md (just now) +> +> - * Advocates for the use of [message passing](4oma) instead of shared state. +> * A slogan initially coined by Rob Pike ([Effective… +> +>Don't speculate pywo.md (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*' +>Mutex inbox/er4k.md (just now) +> +> - * Abbreviation of *mutual exclusion*. +> * An approach to manage safely shared state by allowing only a single thread to access a… +> +>Data race error 3403.md (just now) +> +> - …Rust prevents *data races* by allowing only a single mutable reference of a value per scope. +> +> :programming: +> +>Concurrency in Rust g7qa.md (just now) +> +> - …data between threads: +> * [Channel](fwsj) for a safe [message passing](4oma) approach. +> * [Mutex](inbox/er4k) for managing shared state. +> +> :rust:programming: +> diff --git a/tests/cmd-list-filter-match-re.tesh b/tests/cmd-list-filter-match-re.tesh new file mode 100644 index 0000000..eac734e --- /dev/null +++ b/tests/cmd-list-filter-match-re.tesh @@ -0,0 +1,17 @@ +# Regular expression match strategy. + +$ cd full-sample + +# Long flag. +$ zk list -q --debug-style --match-strategy re --match 'न.*ते' +>Strings are a complicated data structure oumc.md (just now) +> +> - Given the Hindi word "नमस्ते": +> + +# Short flag. +$ zk list -q --debug-style -Mr --match 'न.*ते' +>Strings are a complicated data structure oumc.md (just now) +> +> - Given the Hindi word "नमस्ते": +> diff --git a/tests/cmd-list-filter-match.tesh b/tests/cmd-list-filter-match.tesh index 921b89a..a63590a 100644 --- a/tests/cmd-list-filter-match.tesh +++ b/tests/cmd-list-filter-match.tesh @@ -1,126 +1,9 @@ -$ cd full-sample +$ cd blank -# Search for a multi-word term. -$ zk list -q --debug-style --match '"green thread"' ->Green threads inbox/my59.md (just now) -> -> - …Programming language-provided threads are known as green threads, and languages that use these green threads will execute them in… -> ->Concurrency in Rust g7qa.md (just now) -> -> - …so it doesn't support [green threads](inbox/my59). -> * Crates exist to add support for green threads if needed. -> * Instead, Rust relies… -> - -# Search for two terms (defaults to AND). -$ zk list -q --debug-style --match 'green channel' ->Concurrency in Rust g7qa.md (just now) -> -> - …runtime, so it doesn't support [green threads](inbox/my59). -> * Crates exist to add support for green threads if needed. -> * Instead, Rust… -> - -# Search for two terms with explicit AND. -$ zk list -q --debug-style --match 'green AND channel' ->Concurrency in Rust g7qa.md (just now) -> -> - …runtime, so it doesn't support [green threads](inbox/my59). -> * Crates exist to add support for green threads if needed. -> * Instead, Rust… -> - -# Search for two terms with OR. -$ zk list -q --debug-style --match 'green OR channel' ->Green threads inbox/my59.md (just now) -> -> - …Programming language-provided threads are known as green threads, and languages that use these green threads will execute them in… -> ->Concurrency in Rust g7qa.md (just now) -> -> - …runtime, so it doesn't support [green threads](inbox/my59). -> * Crates exist to add support for green threads if needed. -> * Instead, Rust… -> ->Channel fwsj.md (just now) -> -> - * Channels are a great approach for safe concurrency. -> * It's an implementation of the [message passing](4oma) pattern. -> -> :programming: -> ->Message passing 4oma.md (just now) -> -> - * A popular approach for safe concurrency is to use *message passing* instead of shared state. -> * Channels are an example of… -> ->Mutex inbox/er4k.md (just now) -> -> - …with a *locking system*. -> * Managing mutexes is tricky, using [channels](../fwsj) is an easier alternative. -> * The main risk is to… -> - -# Exclude a term. -$ zk list -q --debug-style --match 'green -channel' ->Green threads inbox/my59.md (just now) -> -> - …Programming language-provided threads are known as green threads, and languages that use these green threads will execute them in… -> - -# Search in the `title` field. -$ zk list -q --debug-style --match 'title:(green thread)' ->Green threads inbox/my59.md (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*' ->Do not communicate by sharing memory; instead, share memory by communicating ref/7fto.md (just now) -> -> - * Advocates for the use of [message passing](4oma) instead of shared state. -> * A slogan initially coined by Rob Pike ([Effective… -> ->Don't speculate pywo.md (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*' ->Mutex inbox/er4k.md (just now) -> -> - * Abbreviation of *mutual exclusion*. -> * An approach to manage safely shared state by allowing only a single thread to access a… -> ->Data race error 3403.md (just now) -> -> - …Rust prevents *data races* by allowing only a single mutable reference of a value per scope. -> -> :programming: -> ->Concurrency in Rust g7qa.md (just now) -> -> - …data between threads: -> * [Channel](fwsj) for a safe [message passing](4oma) approach. -> * [Mutex](inbox/er4k) for managing shared state. -> -> :rust:programming: -> - -# Search for an exact match. -$ zk list -q --debug-style --exact-match --match '["न", "म", "स्", "ते"]' ->Strings are a complicated data structure oumc.md (just now) -> -> - Given the Hindi word "नमस्ते": -> - -# Search for an exact match (short flag). -$ zk list -q --debug-style -em '["न", "म", "स्", "ते"]' ->Strings are a complicated data structure oumc.md (just now) -> -> - Given the Hindi word "नमस्ते": -> +# --exact-match is deprecated. +1$ zk list -q --exact-match +2>zk: error: incorrect criteria: the --exact-match (-e) option is deprecated, use --match-strategy=exact (-Me) instead +# --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 diff --git a/tests/cmd-list.tesh b/tests/cmd-list.tesh index e71b6f3..6ea377f 100644 --- a/tests/cmd-list.tesh +++ b/tests/cmd-list.tesh @@ -33,8 +33,7 @@ $ zk list --help > -i, --interactive Select notes interactively with fzf. > -n, --limit=COUNT Limit the number of notes found. > -m, --match=QUERY Terms to search for in the notes. -> -e, --exact-match Search for exact occurrences of the --match -> argument (case insensitive). +> -M, --match-strategy="fts" Text matching strategy among: fts, re, exact. > -x, --exclude=PATH,... Ignore notes matching the given path, including > its descendants. > -t, --tag=TAG,... Find notes tagged with the given tags.