diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-02 10:58:44 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-02 10:58:44 +0200 |
| commit | 99bb2e39263dd477c8e438ff38ac1c158e4097a9 (patch) | |
| tree | 34ef8d85af64112df6eba4efe0f69ebe112e7466 | |
| parent | b636b12566ea6c2862bf3adc6686c57e0d785ca3 (diff) | |
store/cli: move search output to CLI layer (task 400)
| -rw-r--r-- | internal/cli/cli.go | 12 | ||||
| -rw-r--r-- | internal/store/store.go | 14 | ||||
| -rw-r--r-- | internal/store/store_test.go | 32 |
3 files changed, 44 insertions, 14 deletions
diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 7b18c34..11f1f7e 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -267,7 +267,7 @@ func (c *CLI) dispatch(ctx context.Context, argv []string) int { func (c *CLI) dispatchSimple(ctx context.Context, argv []string, cmd string) (int, string, bool) { switch cmd { case "ls": - indexes, err := c.st.Search(ctx, ".", store.ActionNone, nil) + indexes, err := c.st.Search(ctx, ".", store.ActionNone, nil, printIndex) if err != nil { warn(err.Error()) return 1, "", true @@ -393,7 +393,7 @@ func (c *CLI) dispatchSearch(ctx context.Context, argv []string, cmd string) (in default: // Unknown command: treat as a search term, mirroring Ruby's else branch. // This allows bare search terms to be typed without prefixing "search". - indexes, err := c.st.Search(ctx, cmd, store.ActionNone, nil) + indexes, err := c.st.Search(ctx, cmd, store.ActionNone, nil, printIndex) if err != nil { warn(err.Error()) return 1, "" @@ -513,7 +513,7 @@ func (c *CLI) cmdFullCommit(ctx context.Context) int { // cmdSearchOnly runs a search without any action and returns the result. func (c *CLI) cmdSearchOnly(ctx context.Context, term string) (int, string) { - indexes, err := c.st.Search(ctx, term, store.ActionNone, nil) + indexes, err := c.st.Search(ctx, term, store.ActionNone, nil, printIndex) if err != nil { warn(err.Error()) return 1, "" @@ -526,7 +526,7 @@ func (c *CLI) cmdSearchOnly(ctx context.Context, term string) (int, string) { // cmdSearchAction runs a search with the given action and optional callback. func (c *CLI) cmdSearchAction(ctx context.Context, term string, action store.Action, actionFn func(context.Context, *store.Index, *store.Data) error) (int, string) { - indexes, err := c.st.Search(ctx, term, action, actionFn) + indexes, err := c.st.Search(ctx, term, action, actionFn, printIndex) if err != nil { warn(err.Error()) return 1, "" @@ -537,6 +537,10 @@ func (c *CLI) cmdSearchAction(ctx context.Context, term string, action store.Act return 0, "" } +func printIndex(idx *store.Index) { + fmt.Print(idx.String()) +} + // makeActionFn returns the appropriate callback function for actions that // require external tools (paste, open, edit). For actions handled internally // by the store (cat, export, pathexport), nil is returned. diff --git a/internal/store/store.go b/internal/store/store.go index 8d251b3..e1fb4e8 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -155,8 +155,16 @@ func (s *Store) processIndexFile(ctx context.Context, path, searchTerm string, r // printed; for ActionExport/ActionPathExport the content is written to ExportDir. // Actions requiring external tools (paste, open, edit) are delegated to the // optional actionFn callback — pass nil if those actions are not needed. +// The optional onMatch callback lets callers handle presentation concerns +// (for example, printing idx.String()) outside the store layer. // Returns the sorted list of matching indexes for the caller's use. -func (s *Store) Search(ctx context.Context, searchTerm string, action Action, actionFn func(context.Context, *Index, *Data) error) ([]*Index, error) { +func (s *Store) Search( + ctx context.Context, + searchTerm string, + action Action, + actionFn func(context.Context, *Index, *Data) error, + onMatch func(*Index), +) ([]*Index, error) { var indexes IndexSlice if err := s.WalkIndexes(ctx, searchTerm, func(idx *Index) error { indexes = append(indexes, idx) @@ -168,7 +176,9 @@ func (s *Store) Search(ctx context.Context, searchTerm string, action Action, ac sort.Sort(indexes) for _, idx := range indexes { - fmt.Print(idx.String()) + if onMatch != nil { + onMatch(idx) + } if err := s.applyAction(ctx, idx, action, actionFn); err != nil { return indexes, err } diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 86b7d0d..bae61bb 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -375,7 +375,7 @@ func TestRemoveEntry(t *testing.T) { // --- TestSearch -------------------------------------------------------------- // TestSearch adds two entries, then calls Search with ActionNone and verifies -// both descriptions are returned sorted and printed to stdout. +// both descriptions are returned sorted without store-layer stdout side effects. func TestSearch(t *testing.T) { ctx, store, cfg, _, _ := testSetup(t) initGitRepo(t, cfg.DataDir) @@ -386,7 +386,20 @@ func TestSearch(t *testing.T) { } } - results, err := store.Search(ctx, "", ActionNone, nil) + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("creating pipe: %v", err) + } + oldStdout := os.Stdout + os.Stdout = w + + results, err := store.Search(ctx, "", ActionNone, nil, nil) + + w.Close() + os.Stdout = oldStdout + var out strings.Builder + io.Copy(&out, r) + if err != nil { t.Fatalf("Search: %v", err) } @@ -397,6 +410,9 @@ func TestSearch(t *testing.T) { if results[0].Description != "apple/entry" || results[1].Description != "zebra/entry" { t.Errorf("unexpected sort order: %v, %v", results[0].Description, results[1].Description) } + if out.String() != "" { + t.Errorf("Search(ActionNone) wrote unexpected stdout: %q", out.String()) + } } // --- TestSearchActionCat ----------------------------------------------------- @@ -419,7 +435,7 @@ func TestSearchActionCat(t *testing.T) { } os.Stdout = w - results, err := store.Search(ctx, "note.txt", ActionCat, nil) + results, err := store.Search(ctx, "note.txt", ActionCat, nil, nil) w.Close() os.Stdout = oldStdout @@ -459,7 +475,7 @@ func TestSearchActionCatBinarySkip(t *testing.T) { oldStdout := os.Stdout os.Stdout = w - results, searchErr := store.Search(ctx, "photo.jpg", ActionCat, nil) + results, searchErr := store.Search(ctx, "photo.jpg", ActionCat, nil, nil) w.Close() os.Stdout = oldStdout @@ -519,7 +535,7 @@ func TestSearchActionExport(t *testing.T) { t.Fatalf("Add: %v", err) } - results, err := store.Search(ctx, "report.txt", ActionExport, nil) + results, err := store.Search(ctx, "report.txt", ActionExport, nil, nil) if err != nil { t.Fatalf("Search ActionExport: %v", err) } @@ -759,7 +775,7 @@ func TestSearchActionPathExport(t *testing.T) { t.Fatalf("Add: %v", err) } - results, err := store.Search(ctx, "report.txt", ActionPathExport, nil) + results, err := store.Search(ctx, "report.txt", ActionPathExport, nil, nil) if err != nil { t.Fatalf("Search ActionPathExport: %v", err) } @@ -800,7 +816,7 @@ func TestSearchActionWithCallback(t *testing.T) { } // ActionPaste falls through to the default/actionFn branch of applyAction. - results, err := store.Search(ctx, "cb/entry.txt", ActionPaste, actionFn) + results, err := store.Search(ctx, "cb/entry.txt", ActionPaste, actionFn, nil) if err != nil { t.Fatalf("Search with callback: %v", err) } @@ -828,7 +844,7 @@ func TestSearchActionNilCallback(t *testing.T) { } // nil actionFn: applyAction must return nil without calling anything. - _, err := store.Search(ctx, "nil/cb.txt", ActionPaste, nil) + _, err := store.Search(ctx, "nil/cb.txt", ActionPaste, nil, nil) if err != nil { t.Fatalf("Search with nil callback: %v", err) } |
