Skip to content

Commit dedea0f

Browse files
committed
Add interactive mode for del command
1 parent 76b9f7e commit dedea0f

4 files changed

Lines changed: 197 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ Displays a list of available profiles
5454
git profile list
5555
```
5656

57+
Delete a profile or a specific key from a profile
58+
59+
```bash
60+
git profile del
61+
git profile del home
62+
git profile del home user.email
63+
```
64+
65+
When called without arguments, `del` opens an interactive profile selector and
66+
removes the selected profile entirely.
67+
5768
Applies the selected profile entries to the current git repository
5869

5970
```bash

‎cmd/add.go‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"os"
77
"strings"
88

9-
"charm.land/bubbles/v2/key"
109
"charm.land/huh/v2"
1110
"github.com/spf13/cobra"
1211

@@ -143,15 +142,11 @@ func storeInteractiveEntries(
143142
return nil
144143
}
145144

146-
//nolint:funlen
147145
func runAddPrompt(cfg storage, in io.Reader, out io.Writer) (addPromptResult, error) {
148146
result := addPromptResult{
149147
Values: make(map[string]string),
150148
}
151149

152-
keymap := huh.NewDefaultKeyMap()
153-
keymap.Quit = key.NewBinding(key.WithKeys("ctrl+c", "esc"))
154-
155150
err := huh.NewForm(
156151
huh.NewGroup(huh.
157152
NewInput().
@@ -165,7 +160,7 @@ func runAddPrompt(cfg storage, in io.Reader, out io.Writer) (addPromptResult, er
165160
return nil
166161
}),
167162
),
168-
).WithKeyMap(keymap).WithInput(in).WithOutput(out).Run()
163+
).WithKeyMap(huh.NewDefaultKeyMap()).WithInput(in).WithOutput(out).Run()
169164
if err != nil {
170165
return addPromptResult{}, err
171166
}
@@ -197,7 +192,7 @@ func runAddPrompt(cfg storage, in io.Reader, out io.Writer) (addPromptResult, er
197192
Title("Value for user.signingkey").
198193
Value(&signingKey),
199194
),
200-
).WithKeyMap(keymap).WithInput(in).WithOutput(out).Run()
195+
).WithKeyMap(huh.NewDefaultKeyMap()).WithInput(in).WithOutput(out).Run()
201196
if err != nil {
202197
return addPromptResult{}, err
203198
}

‎cmd/del.go‎

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package cmd
22

33
import (
4+
"fmt"
5+
"io"
46
"os"
7+
"sort"
58

9+
"charm.land/huh/v2"
610
"github.com/spf13/cobra"
711
)
812

913
// Del returns `del` command
1014
func Del(cfg storage) *cobra.Command {
15+
return delCommand(cfg, promptDeleteProfile)
16+
}
17+
18+
//nolint:funlen
19+
func delCommand(cfg storage, prompt func(storage, io.Writer) (string, error)) *cobra.Command {
1120
return &cobra.Command{
1221
Use: "del [profile] [key]",
1322
Aliases: []string{"rm"},
@@ -17,27 +26,49 @@ func Del(cfg storage) *cobra.Command {
1726
`Enter the "key" argument to remove the exact key from the profile.`,
1827
),
1928
Example: multiline(
29+
`git-profile del`,
2030
`git-profile del my-profile (delete the entire profile)`,
2131
`git-profile del my-profile user.name (deleting only a certain key)`,
2232
),
23-
Args: cobra.RangeArgs(1, 2),
33+
Args: func(_ *cobra.Command, args []string) error {
34+
if len(args) <= 2 { //nolint:mnd
35+
return nil
36+
}
37+
38+
return fmt.Errorf("accepts at most 2 arguments")
39+
},
2440
Run: func(cmd *cobra.Command, args []string) {
25-
profile := args[0]
2641
filename, _ := cmd.Flags().GetString("config")
2742

28-
if len(args) == 1 { //nolint
29-
if ok := cfg.DeleteProfile(profile); !ok {
30-
cmd.PrintErrln("There is no profile with given name")
43+
if len(args) == 0 {
44+
profile, err := prompt(cfg, cmd.OutOrStdout())
45+
if err != nil {
46+
if err == huh.ErrUserAborted {
47+
cmd.Println("Interactive delete cancelled.")
48+
return
49+
}
50+
51+
cmd.PrintErrln(err)
3152
os.Exit(1)
3253
}
3354

34-
err := cfg.Save(filename)
55+
err = deleteProfile(cmd, cfg, filename, profile)
3556
if err != nil {
3657
cmd.PrintErrln("Unable to save config file:", err)
3758
os.Exit(1)
3859
}
3960

40-
cmd.Printf("Successfully removed `%s` profile.\n", profile)
61+
return
62+
}
63+
64+
profile := args[0]
65+
66+
if len(args) == 1 {
67+
err := deleteProfile(cmd, cfg, filename, profile)
68+
if err != nil {
69+
cmd.PrintErrln("Unable to save config file:", err)
70+
os.Exit(1)
71+
}
4172
} else {
4273
if ok := cfg.Delete(profile, args[1]); !ok {
4374
cmd.PrintErrln("There is no profile with given name")
@@ -55,3 +86,42 @@ func Del(cfg storage) *cobra.Command {
5586
},
5687
}
5788
}
89+
90+
func promptDeleteProfile(cfg storage, _ io.Writer) (string, error) {
91+
if cfg.Len() == 0 {
92+
return "", fmt.Errorf("There are no available profiles")
93+
}
94+
95+
names := cfg.Names()
96+
sort.Strings(names)
97+
98+
var profile string
99+
100+
err := huh.NewSelect[string]().
101+
Title("Select a profile to delete").
102+
Options(huh.NewOptions(names...)...).
103+
Value(&profile).
104+
WithKeyMap(huh.NewDefaultKeyMap()).
105+
Run()
106+
if err != nil {
107+
return "", err
108+
}
109+
110+
return profile, nil
111+
}
112+
113+
func deleteProfile(cmd *cobra.Command, cfg storage, filename string, profile string) error {
114+
if ok := cfg.DeleteProfile(profile); !ok {
115+
cmd.PrintErrln("There is no profile with given name")
116+
os.Exit(1)
117+
}
118+
119+
err := cfg.Save(filename)
120+
if err != nil {
121+
return err
122+
}
123+
124+
cmd.Printf("Successfully removed `%s` profile.\n", profile)
125+
126+
return nil
127+
}

‎cmd/del_test.go‎

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package cmd
22

33
import (
44
"bytes"
5+
"io"
56
"strings"
67
"testing"
78

9+
"charm.land/huh/v2"
810
"github.com/matryer/is"
911
)
1012

@@ -31,3 +33,108 @@ func TestDel(t *testing.T) {
3133
is.NoErr(err)
3234
is.Equal(strings.TrimSpace(b.String()), "Successfully removed `profile` profile.")
3335
}
36+
37+
func TestDelKey(t *testing.T) {
38+
is := is.New(t)
39+
40+
cfg := &storageMock{
41+
DeleteFunc: func(profile string, value string) bool {
42+
return true
43+
},
44+
SaveFunc: func(filename string) error {
45+
return nil
46+
},
47+
}
48+
49+
var b bytes.Buffer
50+
51+
cmd := Del(cfg)
52+
53+
cmd.SetOut(&b)
54+
cmd.SetArgs([]string{"profile", "user.name"})
55+
err := cmd.Execute()
56+
57+
is.NoErr(err)
58+
is.Equal(strings.TrimSpace(b.String()), "Successfully removed `user.name` from `profile` profile.")
59+
}
60+
61+
func TestDelInteractive(t *testing.T) {
62+
is := is.New(t)
63+
64+
cfg := &storageMock{
65+
DeleteProfileFunc: func(profile string) bool {
66+
return true
67+
},
68+
SaveFunc: func(filename string) error {
69+
return nil
70+
},
71+
}
72+
73+
var b bytes.Buffer
74+
75+
cmd := delCommand(cfg, func(cfg storage, _ io.Writer) (string, error) {
76+
return "work", nil
77+
})
78+
79+
cmd.SetOut(&b)
80+
cmd.SetArgs(nil)
81+
err := cmd.Execute()
82+
83+
is.NoErr(err)
84+
is.Equal(len(cfg.DeleteProfileCalls()), 1)
85+
is.Equal(cfg.DeleteProfileCalls()[0].Profile, "work")
86+
is.Equal(len(cfg.SaveCalls()), 1)
87+
is.Equal(strings.TrimSpace(b.String()), "Successfully removed `work` profile.")
88+
}
89+
90+
func TestDelInteractiveCancelled(t *testing.T) {
91+
is := is.New(t)
92+
93+
cfg := &storageMock{
94+
DeleteProfileFunc: func(profile string) bool {
95+
t.Fatalf("DeleteProfile should not be called")
96+
return false
97+
},
98+
SaveFunc: func(filename string) error {
99+
t.Fatalf("Save should not be called")
100+
return nil
101+
},
102+
}
103+
104+
var (
105+
out bytes.Buffer
106+
errOut bytes.Buffer
107+
)
108+
109+
cmd := delCommand(cfg, func(cfg storage, _ io.Writer) (string, error) {
110+
return "", huh.ErrUserAborted
111+
})
112+
113+
cmd.SetOut(&out)
114+
cmd.SetErr(&errOut)
115+
cmd.SetArgs(nil)
116+
err := cmd.Execute()
117+
118+
is.NoErr(err)
119+
is.Equal(strings.TrimSpace(out.String()), "Interactive delete cancelled.")
120+
is.Equal(errOut.String(), "")
121+
}
122+
123+
func TestDelInteractiveNoProfiles(t *testing.T) {
124+
is := is.New(t)
125+
126+
cfg := &storageMock{
127+
LenFunc: func() int {
128+
return 0
129+
},
130+
NamesFunc: func() []string {
131+
t.Fatalf("Names should not be called")
132+
return nil
133+
},
134+
}
135+
136+
_, err := promptDeleteProfile(cfg, &bytes.Buffer{})
137+
138+
is.True(err != nil)
139+
is.Equal(err.Error(), "There are no available profiles")
140+
}

0 commit comments

Comments
 (0)