From 925473eeb1e4e7826018aa812f156cda96791714 Mon Sep 17 00:00:00 2001 From: Jun Nishimura Date: Thu, 7 Sep 2023 19:56:44 +0900 Subject: [PATCH] Add `--all` flag to `alias delete` command (#7900) --- pkg/cmd/alias/delete/delete.go | 56 ++++++--- pkg/cmd/alias/delete/delete_test.go | 186 +++++++++++++++++++++------- 2 files changed, 182 insertions(+), 60 deletions(-) diff --git a/pkg/cmd/alias/delete/delete.go b/pkg/cmd/alias/delete/delete.go index daaaa2435..16e943044 100644 --- a/pkg/cmd/alias/delete/delete.go +++ b/pkg/cmd/alias/delete/delete.go @@ -2,6 +2,7 @@ package delete import ( "fmt" + "sort" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/pkg/cmdutil" @@ -14,6 +15,7 @@ type DeleteOptions struct { IO *iostreams.IOStreams Name string + All bool } func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command { @@ -23,12 +25,19 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co } cmd := &cobra.Command{ - Use: "delete ", - Short: "Delete an alias", - Args: cobra.ExactArgs(1), + Use: "delete { | --all}", + Short: "Delete set aliases", + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.Name = args[0] - + if len(args) == 0 && !opts.All { + return cmdutil.FlagErrorf("specify an alias to delete or `--all`") + } + if len(args) > 0 && opts.All { + return cmdutil.FlagErrorf("cannot use `--all` with alias name") + } + if len(args) > 0 { + opts.Name = args[0] + } if runF != nil { return runF(opts) } @@ -36,6 +45,8 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co }, } + cmd.Flags().BoolVar(&opts.All, "all", false, "Delete all aliases") + return cmd } @@ -47,25 +58,40 @@ func deleteRun(opts *DeleteOptions) error { aliasCfg := cfg.Aliases() - expansion, err := aliasCfg.Get(opts.Name) - if err != nil { - return fmt.Errorf("no such alias %s", opts.Name) - + aliases := make(map[string]string) + if opts.All { + aliases = aliasCfg.All() + if len(aliases) == 0 { + return cmdutil.NewNoResultsError("no aliases configured") + } + } else { + expansion, err := aliasCfg.Get(opts.Name) + if err != nil { + return fmt.Errorf("no such alias %s", opts.Name) + } + aliases[opts.Name] = expansion } - err = aliasCfg.Delete(opts.Name) - if err != nil { - return fmt.Errorf("failed to delete alias %s: %w", opts.Name, err) + for name := range aliases { + if err := aliasCfg.Delete(name); err != nil { + return fmt.Errorf("failed to delete alias %s: %w", name, err) + } } - err = cfg.Write() - if err != nil { + if err := cfg.Write(); err != nil { return err } if opts.IO.IsStdoutTTY() { cs := opts.IO.ColorScheme() - fmt.Fprintf(opts.IO.ErrOut, "%s Deleted alias %s; was %s\n", cs.SuccessIconWithColor(cs.Red), opts.Name, expansion) + keys := make([]string, 0, len(aliases)) + for k := range aliases { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fmt.Fprintf(opts.IO.ErrOut, "%s Deleted alias %s; was %s\n", cs.SuccessIconWithColor(cs.Red), k, aliases[k]) + } } return nil diff --git a/pkg/cmd/alias/delete/delete_test.go b/pkg/cmd/alias/delete/delete_test.go index d6c7098b1..9990abdfc 100644 --- a/pkg/cmd/alias/delete/delete_test.go +++ b/pkg/cmd/alias/delete/delete_test.go @@ -11,78 +11,174 @@ import ( "github.com/cli/cli/v2/pkg/iostreams" "github.com/google/shlex" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func TestAliasDelete(t *testing.T) { - _ = config.StubWriteConfig(t) - +func TestNewCmdDelete(t *testing.T) { tests := []struct { - name string - config string - cli string - isTTY bool - wantStdout string - wantStderr string - wantErr string + name string + input string + output DeleteOptions + wantErr bool + errMsg string }{ { - name: "no aliases", - config: "", - cli: "co", - isTTY: true, - wantStdout: "", - wantStderr: "", - wantErr: "no such alias co", + name: "no arguments", + input: "", + wantErr: true, + errMsg: "specify an alias to delete or `--all`", }, { - name: "delete one", + name: "specified alias", + input: "co", + output: DeleteOptions{ + Name: "co", + }, + }, + { + name: "all flag", + input: "--all", + output: DeleteOptions{ + All: true, + }, + }, + { + name: "specified alias and all flag", + input: "co --all", + wantErr: true, + errMsg: "cannot use `--all` with alias name", + }, + { + name: "too many arguments", + input: "il co", + wantErr: true, + errMsg: "accepts at most 1 arg(s), received 2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ios, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + IOStreams: ios, + } + argv, err := shlex.Split(tt.input) + assert.NoError(t, err) + var gotOpts *DeleteOptions + cmd := NewCmdDelete(f, func(opts *DeleteOptions) error { + gotOpts = opts + return nil + }) + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + _, err = cmd.ExecuteC() + if tt.wantErr { + assert.EqualError(t, err, tt.errMsg) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.output.Name, gotOpts.Name) + assert.Equal(t, tt.output.All, gotOpts.All) + }) + } +} + +func TestDeleteRun(t *testing.T) { + tests := []struct { + name string + config string + isTTY bool + opts *DeleteOptions + wantAliases map[string]string + wantStdout string + wantStderr string + wantErrMsg string + }{ + { + name: "delete alias", config: heredoc.Doc(` aliases: il: issue list co: pr checkout `), - cli: "co", - isTTY: true, - wantStdout: "", + isTTY: true, + opts: &DeleteOptions{ + Name: "co", + All: false, + }, + wantAliases: map[string]string{ + "il": "issue list", + }, wantStderr: "āœ“ Deleted alias co; was pr checkout\n", }, + { + name: "delete all aliases", + config: heredoc.Doc(` + aliases: + il: issue list + co: pr checkout + `), + isTTY: true, + opts: &DeleteOptions{ + All: true, + }, + wantAliases: map[string]string{}, + wantStderr: "āœ“ Deleted alias co; was pr checkout\nāœ“ Deleted alias il; was issue list\n", + }, + { + name: "delete alias that does not exist", + config: heredoc.Doc(` + aliases: + il: issue list + co: pr checkout + `), + isTTY: true, + opts: &DeleteOptions{ + Name: "unknown", + }, + wantAliases: map[string]string{ + "il": "issue list", + "co": "pr checkout", + }, + wantErrMsg: "no such alias unknown", + }, + { + name: "delete all aliases when none exist", + isTTY: true, + opts: &DeleteOptions{ + All: true, + }, + wantAliases: map[string]string{}, + wantErrMsg: "no aliases configured", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cfg := config.NewFromString(tt.config) - ios, _, stdout, stderr := iostreams.Test() - ios.SetStdoutTTY(tt.isTTY) ios.SetStdinTTY(tt.isTTY) + ios.SetStdoutTTY(tt.isTTY) ios.SetStderrTTY(tt.isTTY) + tt.opts.IO = ios - factory := &cmdutil.Factory{ - IOStreams: ios, - Config: func() (config.Config, error) { - return cfg, nil - }, + cfg := config.NewFromString(tt.config) + tt.opts.Config = func() (config.Config, error) { + return cfg, nil } - cmd := NewCmdDelete(factory, nil) - - argv, err := shlex.Split(tt.cli) - require.NoError(t, err) - cmd.SetArgs(argv) - - cmd.SetIn(&bytes.Buffer{}) - cmd.SetOut(io.Discard) - cmd.SetErr(io.Discard) - - _, err = cmd.ExecuteC() - if tt.wantErr != "" { - assert.EqualError(t, err, tt.wantErr) - return + err := deleteRun(tt.opts) + if tt.wantErrMsg != "" { + assert.EqualError(t, err, tt.wantErrMsg) + writeCalls := cfg.WriteCalls() + assert.Equal(t, 0, len(writeCalls)) + } else { + assert.NoError(t, err) + writeCalls := cfg.WriteCalls() + assert.Equal(t, 1, len(writeCalls)) } - require.NoError(t, err) assert.Equal(t, tt.wantStdout, stdout.String()) assert.Equal(t, tt.wantStderr, stderr.String()) + assert.Equal(t, tt.wantAliases, cfg.Aliases().All()) }) } }