From 7cac5201c146b4957a1dca3bd944744939e79a4d Mon Sep 17 00:00:00 2001 From: Lorris Saint-Genez Date: Wed, 17 Jun 2026 11:18:25 -0700 Subject: [PATCH] feat: add output formatting flags to more commands Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/cmd/apikeys/create/create.go | 13 ++++++ pkg/cmd/dictionary/entries/clear/clear.go | 17 +++++++- .../dictionary/entries/clear/clear_test.go | 41 +++++++++++++++++++ pkg/cmd/dictionary/settings/set/set.go | 13 ++++++ pkg/cmd/indices/clear/clear.go | 13 ++++++ pkg/cmd/indices/copy/copy.go | 13 ++++++ pkg/cmd/indices/move/move.go | 13 ++++++ pkg/cmd/objects/import/import.go | 14 +++++++ pkg/cmd/rules/delete/delete.go | 26 ++++++++---- pkg/cmd/rules/delete/delete_test.go | 29 +++++++++++++ pkg/cmd/settings/import/import.go | 13 ++++++ pkg/cmd/settings/set/set.go | 13 ++++++ pkg/cmd/synonyms/delete/delete.go | 26 ++++++++---- pkg/cmd/synonyms/delete/delete_test.go | 32 +++++++++++++++ pkg/cmd/synonyms/save/save.go | 13 ++++++ 15 files changed, 272 insertions(+), 17 deletions(-) diff --git a/pkg/cmd/apikeys/create/create.go b/pkg/cmd/apikeys/create/create.go index 1a56eda3..0576b64e 100644 --- a/pkg/cmd/apikeys/create/create.go +++ b/pkg/cmd/apikeys/create/create.go @@ -27,6 +27,8 @@ type CreateOptions struct { Indices []string Referers []string Validity time.Duration + + PrintFlags *cmdutil.PrintFlags } // NewCreateCmd returns a new instance of CreateCmd @@ -35,6 +37,7 @@ func NewCreateCmd(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co IO: f.IOStreams, config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } cmd := &cobra.Command{ Use: "create", @@ -138,6 +141,8 @@ func NewCreateCmd(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co "seeUnretrievableAttributes": "retrieve unretrievableAttributes for all operations that return records", }, "can")) + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -165,6 +170,14 @@ func runCreateCmd(opts *CreateOptions) error { return err } + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, res) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.Out, "%s API key created: %s\n", cs.SuccessIcon(), res.Key) diff --git a/pkg/cmd/dictionary/entries/clear/clear.go b/pkg/cmd/dictionary/entries/clear/clear.go index 2144482b..38832a18 100644 --- a/pkg/cmd/dictionary/entries/clear/clear.go +++ b/pkg/cmd/dictionary/entries/clear/clear.go @@ -27,6 +27,8 @@ type ClearOptions struct { All bool DoConfirm bool + + PrintFlags *cmdutil.PrintFlags } // NewClearCmd creates and returns a clear command for dictionaries' entries. @@ -38,6 +40,7 @@ func NewClearCmd(f *cmdutil.Factory, runF func(*ClearOptions) error) *cobra.Comm IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } cmd := &cobra.Command{ Use: "clear {... | --all} [--confirm]", @@ -100,6 +103,8 @@ func NewClearCmd(f *cmdutil.Factory, runF func(*ClearOptions) error) *cobra.Comm BoolVarP(&confirm, "confirm", "y", false, "Skip the clear dictionary entry confirmation prompt") cmd.Flags().BoolVarP(&opts.All, "all", "a", false, "Clear all dictionaries") + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -153,17 +158,27 @@ func runClearCmd(opts *ClearOptions) error { } } + responses := make([]search.UpdatedAtResponse, 0, len(dictionaries)) for _, dict := range dictionaries { batchParams := search.NewBatchDictionaryEntriesParams( []search.BatchDictionaryEntriesRequest{}, search.WithBatchDictionaryEntriesParamsClearExistingDictionaryEntries(true), ) - _, err := client.BatchDictionaryEntries( + res, err := client.BatchDictionaryEntries( client.NewApiBatchDictionaryEntriesRequest(dict, batchParams), ) if err != nil { return err } + responses = append(responses, *res) + } + + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, responses) } if opts.IO.IsStdoutTTY() { diff --git a/pkg/cmd/dictionary/entries/clear/clear_test.go b/pkg/cmd/dictionary/entries/clear/clear_test.go index 9dbf461f..d2cb6c2d 100644 --- a/pkg/cmd/dictionary/entries/clear/clear_test.go +++ b/pkg/cmd/dictionary/entries/clear/clear_test.go @@ -1,6 +1,7 @@ package clear import ( + "encoding/json" "fmt" "testing" @@ -207,3 +208,43 @@ func Test_runDeleteCmd(t *testing.T) { }) } } + +// Test_runClearCmd_outputJSON checks that the --output json path emits one +// response per cleared dictionary, in order — i.e. the collection loop. +func Test_runClearCmd_outputJSON(t *testing.T) { + r := httpmock.Registry{} + dicts := []search.DictionaryType{ + search.DICTIONARY_TYPE_PLURALS, + search.DICTIONARY_TYPE_COMPOUNDS, + } + for i, d := range dicts { + r.Register( + httpmock.REST("POST", fmt.Sprintf("1/dictionaries/%s/search", d)), + httpmock.JSONResponse(search.SearchDictionaryEntriesResponse{ + Hits: []search.DictionaryEntry{ + { + ObjectID: "1", + Language: search.SUPPORTED_LANGUAGE_EN.Ptr(), + Words: []string{"foo"}, + Type: search.DICTIONARY_ENTRY_TYPE_CUSTOM.Ptr(), + }, + }, + }), + ) + r.Register( + httpmock.REST("POST", fmt.Sprintf("1/dictionaries/%s/batch", d)), + httpmock.JSONResponse(search.UpdatedAtResponse{TaskID: int64((i + 1) * 100)}), + ) + } + + f, out := test.NewFactory(false, &r, nil, "") + cmd := NewClearCmd(f, nil) + out, err := test.Execute(cmd, "plurals compounds --confirm --output json", out) + require.NoError(t, err) + + var got []search.UpdatedAtResponse + require.NoError(t, json.Unmarshal([]byte(out.String()), &got)) + assert.Len(t, got, 2) + assert.Equal(t, int64(100), got[0].TaskID) + assert.Equal(t, int64(200), got[1].TaskID) +} diff --git a/pkg/cmd/dictionary/settings/set/set.go b/pkg/cmd/dictionary/settings/set/set.go index ed29d3cc..9ecc96d5 100644 --- a/pkg/cmd/dictionary/settings/set/set.go +++ b/pkg/cmd/dictionary/settings/set/set.go @@ -21,6 +21,8 @@ type SetOptions struct { DisableStandardEntries []string EnableStandardEntries []string ResetStandardEntries bool + + PrintFlags *cmdutil.PrintFlags } // NewSetCmd creates and returns a set command for dictionaries' settings. @@ -29,6 +31,7 @@ func NewSetCmd(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } cmd := &cobra.Command{ Use: "set --disable-standard-entries --enable-standard-entries [--reset-standard-entries]", @@ -112,6 +115,8 @@ func NewSetCmd(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command cmdutil.StringCompletionFunc(supportedLanguages), ) + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -159,6 +164,14 @@ func runSetCmd(opts *SetOptions) error { opts.IO.StopProgressIndicator() + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, res) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.Out, "%s Dictionary settings successfully updated\n", cs.SuccessIcon()) diff --git a/pkg/cmd/indices/clear/clear.go b/pkg/cmd/indices/clear/clear.go index 8cf7fddc..cd695d6d 100644 --- a/pkg/cmd/indices/clear/clear.go +++ b/pkg/cmd/indices/clear/clear.go @@ -23,6 +23,8 @@ type ClearOptions struct { Index string DoConfirm bool Wait bool + + PrintFlags *cmdutil.PrintFlags } // NewClearCmd creates and returns a clear command for indices @@ -31,6 +33,7 @@ func NewClearCmd(f *cmdutil.Factory, runF func(*ClearOptions) error) *cobra.Comm IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } var confirm bool @@ -73,6 +76,8 @@ func NewClearCmd(f *cmdutil.Factory, runF func(*ClearOptions) error) *cobra.Comm cmd.Flags().BoolVarP(&confirm, "confirm", "y", false, "Skip confirmation prompt") cmd.Flags().BoolVarP(&opts.Wait, "wait", "w", false, "Wait for the operation to complete") + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -116,6 +121,14 @@ func runClearCmd(opts *ClearOptions) error { opts.IO.StopProgressIndicator() + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, res) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.Out, "%s Cleared index %s\n", cs.SuccessIcon(), opts.Index) diff --git a/pkg/cmd/indices/copy/copy.go b/pkg/cmd/indices/copy/copy.go index 3df1a9cd..ea240a28 100644 --- a/pkg/cmd/indices/copy/copy.go +++ b/pkg/cmd/indices/copy/copy.go @@ -29,6 +29,8 @@ type CopyOptions struct { Wait bool DoConfirm bool + + PrintFlags *cmdutil.PrintFlags } // NewCopyCmd creates and returns a copy command for indices @@ -37,6 +39,7 @@ func NewCopyCmd(f *cmdutil.Factory, runF func(*CopyOptions) error) *cobra.Comman IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } var confirm bool @@ -101,6 +104,8 @@ func NewCopyCmd(f *cmdutil.Factory, runF func(*CopyOptions) error) *cobra.Comman "rules": "rules", }, "copy")) + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -174,6 +179,14 @@ func runCopyCmd(opts *CopyOptions) error { opts.IO.StopProgressIndicator() + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, res) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf( diff --git a/pkg/cmd/indices/move/move.go b/pkg/cmd/indices/move/move.go index beb01f53..a6a7757c 100644 --- a/pkg/cmd/indices/move/move.go +++ b/pkg/cmd/indices/move/move.go @@ -27,6 +27,8 @@ type MoveOptions struct { Wait bool DoConfirm bool + + PrintFlags *cmdutil.PrintFlags } // NewMoveCmd creates and returns a move command for indices @@ -35,6 +37,7 @@ func NewMoveCmd(f *cmdutil.Factory, runF func(*MoveOptions) error) *cobra.Comman IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } var confirm bool @@ -78,6 +81,8 @@ func NewMoveCmd(f *cmdutil.Factory, runF func(*MoveOptions) error) *cobra.Comman cmd.Flags().BoolVarP(&confirm, "confirm", "y", false, "Skip the move index confirmation prompt") cmd.Flags().BoolVarP(&opts.Wait, "wait", "w", false, "Wait for the operation to complete") + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -136,6 +141,14 @@ func runMoveCmd(opts *MoveOptions) error { opts.IO.StopProgressIndicator() + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, res) + } + if opts.IO.IsStdoutTTY() { fmt.Fprintf( opts.IO.Out, diff --git a/pkg/cmd/objects/import/import.go b/pkg/cmd/objects/import/import.go index 31aadf91..3ef5a576 100644 --- a/pkg/cmd/objects/import/import.go +++ b/pkg/cmd/objects/import/import.go @@ -26,6 +26,8 @@ type ImportOptions struct { BatchSize int AutoObjectIDs bool Wait bool + + PrintFlags *cmdutil.PrintFlags } // NewImportCmd creates and returns an import command for records @@ -34,6 +36,7 @@ func NewImportCmd(f *cmdutil.Factory) *cobra.Command { IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } var file string @@ -79,6 +82,9 @@ func NewImportCmd(f *cmdutil.Factory) *cobra.Command { cmd.Flags(). BoolVarP(&opts.AutoObjectIDs, "auto-generate-object-id-if-not-exist", "a", false, "Auto-generate object IDs if they don't exist") cmd.Flags().BoolVarP(&opts.Wait, "wait", "w", false, "wait for the operation to complete") + + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -146,6 +152,14 @@ func runImportCmd(opts *ImportOptions) error { return err } + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, responses) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf( diff --git a/pkg/cmd/rules/delete/delete.go b/pkg/cmd/rules/delete/delete.go index c49bd739..2fb37dc0 100644 --- a/pkg/cmd/rules/delete/delete.go +++ b/pkg/cmd/rules/delete/delete.go @@ -28,6 +28,8 @@ type DeleteOptions struct { Wait bool DoConfirm bool + + PrintFlags *cmdutil.PrintFlags } // NewDeleteCmd creates and returns a delete command for index rules @@ -38,6 +40,7 @@ func NewDeleteCmd(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } cmd := &cobra.Command{ @@ -85,6 +88,8 @@ func NewDeleteCmd(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co cmd.Flags().BoolVarP(&confirm, "confirm", "y", false, "Skip confirmation prompt") cmd.Flags().BoolVarP(&opts.Wait, "wait", "w", false, "Wait for the operation to complete") + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -123,7 +128,7 @@ func runDeleteCmd(opts *DeleteOptions) error { } } - var taskIDs []int64 + responses := make([]search.UpdatedAtResponse, 0, len(opts.RuleIDs)) for _, ruleID := range opts.RuleIDs { res, err := client.DeleteRule( @@ -133,20 +138,25 @@ func runDeleteCmd(opts *DeleteOptions) error { if err != nil { return fmt.Errorf("failed to delete rule %s: %w", ruleID, err) } - if opts.Wait { - taskIDs = append(taskIDs, res.TaskID) - } + responses = append(responses, *res) } - if len(taskIDs) > 0 { - for _, taskID := range taskIDs { - _, err := client.WaitForTask(opts.Index, taskID) - if err != nil { + if opts.Wait { + for _, res := range responses { + if _, err := client.WaitForTask(opts.Index, res.TaskID); err != nil { return err } } } + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, responses) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf( diff --git a/pkg/cmd/rules/delete/delete_test.go b/pkg/cmd/rules/delete/delete_test.go index 2a153c92..f3600590 100644 --- a/pkg/cmd/rules/delete/delete_test.go +++ b/pkg/cmd/rules/delete/delete_test.go @@ -1,6 +1,7 @@ package delete import ( + "encoding/json" "fmt" "testing" @@ -196,3 +197,31 @@ func Test_runDeleteCmd(t *testing.T) { }) } } + +// Test_runDeleteCmd_outputJSON checks that the --output json path emits every +// delete response, in order — i.e. the multi-id collection loop, not the printer. +func Test_runDeleteCmd_outputJSON(t *testing.T) { + r := httpmock.Registry{} + ids := []string{"1", "2"} + for i, id := range ids { + r.Register( + httpmock.REST("GET", fmt.Sprintf("1/indexes/foo/rules/%s", id)), + httpmock.JSONResponse(search.SearchRulesResponse{}), + ) + r.Register( + httpmock.REST("DELETE", fmt.Sprintf("1/indexes/foo/rules/%s", id)), + httpmock.JSONResponse(search.UpdatedAtResponse{TaskID: int64((i + 1) * 100)}), + ) + } + + f, out := test.NewFactory(false, &r, nil, "") + cmd := NewDeleteCmd(f, nil) + out, err := test.Execute(cmd, "foo --rule-ids 1,2 --confirm --output json", out) + require.NoError(t, err) + + var got []search.UpdatedAtResponse + require.NoError(t, json.Unmarshal([]byte(out.String()), &got)) + assert.Len(t, got, 2) + assert.Equal(t, int64(100), got[0].TaskID) + assert.Equal(t, int64(200), got[1].TaskID) +} diff --git a/pkg/cmd/settings/import/import.go b/pkg/cmd/settings/import/import.go index cb1fd159..7479b911 100644 --- a/pkg/cmd/settings/import/import.go +++ b/pkg/cmd/settings/import/import.go @@ -24,6 +24,8 @@ type ImportOptions struct { Settings search.IndexSettings ForwardToReplicas bool Wait bool + + PrintFlags *cmdutil.PrintFlags } // NewImportCmd creates and returns an import command for settings @@ -32,6 +34,7 @@ func NewImportCmd(f *cmdutil.Factory) *cobra.Command { IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } var settingsFile string @@ -68,6 +71,8 @@ func NewImportCmd(f *cmdutil.Factory) *cobra.Command { BoolVarP(&opts.ForwardToReplicas, "forward-to-replicas", "f", false, "Forward the settings to the replicas") cmd.Flags().BoolVarP(&opts.Wait, "wait", "w", false, "wait for the operation to complete") + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -98,6 +103,14 @@ func runImportCmd(opts *ImportOptions) error { opts.IO.StopProgressIndicator() + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, res) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.Out, "%s Imported settings on %v\n", cs.SuccessIcon(), opts.Index) diff --git a/pkg/cmd/settings/set/set.go b/pkg/cmd/settings/set/set.go index 1133c1fa..fb919708 100644 --- a/pkg/cmd/settings/set/set.go +++ b/pkg/cmd/settings/set/set.go @@ -25,6 +25,8 @@ type SetOptions struct { Wait bool Index string + + PrintFlags *cmdutil.PrintFlags } // NewSetCmd creates and returns a set command for settings @@ -33,6 +35,7 @@ func NewSetCmd(f *cmdutil.Factory) *cobra.Command { IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } cmd := &cobra.Command{ Use: "set ", @@ -74,6 +77,8 @@ func NewSetCmd(f *cmdutil.Factory) *cobra.Command { cmdutil.AddIndexSettingsFlags(cmd) + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -107,6 +112,14 @@ func runSetCmd(opts *SetOptions) error { opts.IO.StopProgressIndicator() + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, res) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.Out, "%s Set settings on %v\n", cs.SuccessIcon(), opts.Index) diff --git a/pkg/cmd/synonyms/delete/delete.go b/pkg/cmd/synonyms/delete/delete.go index b61ed1b7..ccdd9eec 100644 --- a/pkg/cmd/synonyms/delete/delete.go +++ b/pkg/cmd/synonyms/delete/delete.go @@ -28,6 +28,8 @@ type DeleteOptions struct { Wait bool DoConfirm bool + + PrintFlags *cmdutil.PrintFlags } // NewDeleteCmd creates and returns a delete command for index synonyms @@ -38,6 +40,7 @@ func NewDeleteCmd(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } cmd := &cobra.Command{ @@ -85,6 +88,8 @@ func NewDeleteCmd(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co cmd.Flags().BoolVarP(&confirm, "confirm", "y", false, "Skip confirmation prompt") cmd.Flags().BoolVarP(&opts.Wait, "wait", "w", false, "Wait for the operation to complete") + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -124,7 +129,7 @@ func runDeleteCmd(opts *DeleteOptions) error { } } - var taskIDs []int64 + responses := make([]search.DeletedAtResponse, 0, len(opts.SynonymIDs)) for _, synonymID := range opts.SynonymIDs { res, err := client.DeleteSynonym( @@ -134,20 +139,25 @@ func runDeleteCmd(opts *DeleteOptions) error { if err != nil { return fmt.Errorf("failed to delete synonym %s: %w", synonymID, err) } - if opts.Wait { - taskIDs = append(taskIDs, res.TaskID) - } + responses = append(responses, *res) } - if len(taskIDs) > 0 { - for _, taskID := range taskIDs { - _, err := client.WaitForTask(opts.Index, taskID) - if err != nil { + if opts.Wait { + for _, res := range responses { + if _, err := client.WaitForTask(opts.Index, res.TaskID); err != nil { return err } } } + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, responses) + } + cs := opts.IO.ColorScheme() if opts.IO.IsStdoutTTY() { fmt.Fprintf( diff --git a/pkg/cmd/synonyms/delete/delete_test.go b/pkg/cmd/synonyms/delete/delete_test.go index 8133fd84..587b9c20 100644 --- a/pkg/cmd/synonyms/delete/delete_test.go +++ b/pkg/cmd/synonyms/delete/delete_test.go @@ -1,6 +1,7 @@ package delete import ( + "encoding/json" "fmt" "testing" @@ -199,3 +200,34 @@ func Test_runDeleteCmd(t *testing.T) { }) } } + +// Test_runDeleteCmd_outputJSON checks that the --output json path emits every +// delete response, in order — i.e. the multi-id collection loop, not the printer. +func Test_runDeleteCmd_outputJSON(t *testing.T) { + r := httpmock.Registry{} + ids := []string{"1", "2"} + for i, id := range ids { + r.Register( + httpmock.REST("GET", fmt.Sprintf("1/indexes/foo/synonyms/%s", id)), + httpmock.JSONResponse(search.SynonymHit{ + ObjectID: id, + Type: search.SYNONYM_TYPE_ONEWAYSYNONYM, + }), + ) + r.Register( + httpmock.REST("DELETE", fmt.Sprintf("1/indexes/foo/synonyms/%s", id)), + httpmock.JSONResponse(search.DeletedAtResponse{TaskID: int64((i + 1) * 100)}), + ) + } + + f, out := test.NewFactory(false, &r, nil, "") + cmd := NewDeleteCmd(f, nil) + out, err := test.Execute(cmd, "foo --synonym-ids 1,2 --confirm --output json", out) + require.NoError(t, err) + + var got []search.DeletedAtResponse + require.NoError(t, json.Unmarshal([]byte(out.String()), &got)) + assert.Len(t, got, 2) + assert.Equal(t, int64(100), got[0].TaskID) + assert.Equal(t, int64(200), got[1].TaskID) +} diff --git a/pkg/cmd/synonyms/save/save.go b/pkg/cmd/synonyms/save/save.go index 05645be5..a1d13b3a 100644 --- a/pkg/cmd/synonyms/save/save.go +++ b/pkg/cmd/synonyms/save/save.go @@ -26,6 +26,8 @@ type SaveOptions struct { Synonym search.SynonymHit SuccessMessage string Wait bool + + PrintFlags *cmdutil.PrintFlags } // NewSaveCmd creates and returns a save command for index synonyms @@ -34,6 +36,7 @@ func NewSaveCmd(f *cmdutil.Factory, runF func(*SaveOptions) error) *cobra.Comman IO: f.IOStreams, Config: f.Config, SearchClient: f.SearchClient, + PrintFlags: cmdutil.NewPrintFlags(), } flags := &shared.SynonymFlags{} @@ -123,6 +126,8 @@ func NewSaveCmd(f *cmdutil.Factory, runF func(*SaveOptions) error) *cobra.Comman StringSliceVarP(&flags.SynonymCorrections, "corrections", "c", nil, "A list of corrections of the word (alt correction synonyms only)") cmd.Flags().BoolVarP(&opts.Wait, "wait", "", false, "Wait for the operation to complete") + opts.PrintFlags.AddFlags(cmd) + return cmd } @@ -145,6 +150,14 @@ func runSaveCmd(opts *SaveOptions) error { } } + if opts.PrintFlags.OutputFlagSpecified() && opts.PrintFlags.OutputFormat != nil { + p, err := opts.PrintFlags.ToPrinter() + if err != nil { + return err + } + return p.Print(opts.IO, res) + } + if opts.IO.IsStdoutTTY() { fmt.Fprint(opts.IO.Out, opts.SuccessMessage) }