Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit0b2ba96

Browse files
feat(cli): add shell completions (#14341)
1 parent6f9b3c1 commit0b2ba96

File tree

80 files changed

+511
-419
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+511
-419
lines changed

‎cli/cliui/output.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ func (f *OutputFormatter) AttachOptions(opts *serpent.OptionSet) {
6565
Flag:"output",
6666
FlagShorthand:"o",
6767
Default:f.formats[0].ID(),
68-
Value:serpent.StringOf(&f.formatID),
69-
Description:"Output format. Available formats: "+strings.Join(formatNames,", ")+".",
68+
Value:serpent.EnumOf(&f.formatID,formatNames...),
69+
Description:"Output format.",
7070
},
7171
)
7272
}
@@ -136,8 +136,8 @@ func (f *tableFormat) AttachOptions(opts *serpent.OptionSet) {
136136
Flag:"column",
137137
FlagShorthand:"c",
138138
Default:strings.Join(f.defaultColumns,","),
139-
Value:serpent.StringArrayOf(&f.columns),
140-
Description:"Columns to display in table output. Available columns: "+strings.Join(f.allColumns,", ")+".",
139+
Value:serpent.EnumArrayOf(&f.columns,f.allColumns...),
140+
Description:"Columns to display in table output.",
141141
},
142142
)
143143
}

‎cli/cliui/output_test.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ func Test_OutputFormatter(t *testing.T) {
106106

107107
fs:=cmd.Options.FlagSet()
108108

109-
selected,err:=fs.GetString("output")
110-
require.NoError(t,err)
111-
require.Equal(t,"json",selected)
109+
selected:=cmd.Options.ByFlag("output")
110+
require.NotNil(t,selected)
111+
require.Equal(t,"json",selected.Value.String())
112112
usage:=fs.FlagUsages()
113-
require.Contains(t,usage,"Available formats: json, foo")
113+
require.Contains(t,usage,"Output format.")
114114
require.Contains(t,usage,"foo flag 1234")
115115

116116
ctx:=context.Background()
@@ -129,11 +129,10 @@ func Test_OutputFormatter(t *testing.T) {
129129
require.Equal(t,"foo",out)
130130
require.EqualValues(t,1,atomic.LoadInt64(&called))
131131

132-
require.NoError(t,fs.Set("output","bar"))
132+
require.Error(t,fs.Set("output","bar"))
133133
out,err=f.Format(ctx,data)
134-
require.Error(t,err)
135-
require.ErrorContains(t,err,"bar")
136-
require.Equal(t,"",out)
137-
require.EqualValues(t,1,atomic.LoadInt64(&called))
134+
require.NoError(t,err)
135+
require.Equal(t,"foo",out)
136+
require.EqualValues(t,2,atomic.LoadInt64(&called))
138137
})
139138
}

‎cli/completion.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/coder/coder/v2/cli/cliui"
7+
"github.com/coder/serpent"
8+
"github.com/coder/serpent/completion"
9+
)
10+
11+
func (*RootCmd)completion()*serpent.Command {
12+
varshellNamestring
13+
varprintOutputbool
14+
shellOptions:=completion.ShellOptions(&shellName)
15+
return&serpent.Command{
16+
Use:"completion",
17+
Short:"Install or update shell completion scripts for the detected or chosen shell.",
18+
Options: []serpent.Option{
19+
{
20+
Flag:"shell",
21+
FlagShorthand:"s",
22+
Description:"The shell to install completion for.",
23+
Value:shellOptions,
24+
},
25+
{
26+
Flag:"print",
27+
Description:"Print the completion script instead of installing it.",
28+
FlagShorthand:"p",
29+
30+
Value:serpent.BoolOf(&printOutput),
31+
},
32+
},
33+
Handler:func(inv*serpent.Invocation)error {
34+
ifshellName!="" {
35+
shell,err:=completion.ShellByName(shellName,inv.Command.Parent.Name())
36+
iferr!=nil {
37+
returnerr
38+
}
39+
ifprintOutput {
40+
returnshell.WriteCompletion(inv.Stdout)
41+
}
42+
returninstallCompletion(inv,shell)
43+
}
44+
shell,err:=completion.DetectUserShell(inv.Command.Parent.Name())
45+
iferr==nil {
46+
returninstallCompletion(inv,shell)
47+
}
48+
// Silently continue to the shell selection if detecting failed.
49+
choice,err:=cliui.Select(inv, cliui.SelectOptions{
50+
Message:"Select a shell to install completion for:",
51+
Options:shellOptions.Choices,
52+
})
53+
iferr!=nil {
54+
returnerr
55+
}
56+
shellChoice,err:=completion.ShellByName(choice,inv.Command.Parent.Name())
57+
iferr!=nil {
58+
returnerr
59+
}
60+
ifprintOutput {
61+
returnshellChoice.WriteCompletion(inv.Stdout)
62+
}
63+
returninstallCompletion(inv,shellChoice)
64+
},
65+
}
66+
}
67+
68+
funcinstallCompletion(inv*serpent.Invocation,shell completion.Shell)error {
69+
path,err:=shell.InstallPath()
70+
iferr!=nil {
71+
cliui.Error(inv.Stderr,fmt.Sprintf("Failed to determine completion path %v",err))
72+
returnshell.WriteCompletion(inv.Stdout)
73+
}
74+
choice,err:=cliui.Select(inv, cliui.SelectOptions{
75+
Options: []string{
76+
"Confirm",
77+
"Print to terminal",
78+
},
79+
Message:fmt.Sprintf("Install completion for %s at %s?",shell.Name(),path),
80+
HideSearch:true,
81+
})
82+
iferr!=nil {
83+
returnerr
84+
}
85+
ifchoice=="Print to terminal" {
86+
returnshell.WriteCompletion(inv.Stdout)
87+
}
88+
returncompletion.InstallShellCompletion(shell)
89+
}

‎cli/configssh.go

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"strings"
1818

1919
"github.com/cli/safeexec"
20+
"github.com/natefinch/atomic"
2021
"github.com/pkg/diff"
2122
"github.com/pkg/diff/write"
2223
"golang.org/x/exp/constraints"
@@ -524,7 +525,7 @@ func (r *RootCmd) configSSH() *serpent.Command {
524525
}
525526

526527
if!bytes.Equal(configRaw,configModified) {
527-
err=writeWithTempFileAndMove(sshConfigFile,bytes.NewReader(configModified))
528+
err=atomic.WriteFile(sshConfigFile,bytes.NewReader(configModified))
528529
iferr!=nil {
529530
returnxerrors.Errorf("write ssh config failed: %w",err)
530531
}
@@ -758,50 +759,6 @@ func sshConfigSplitOnCoderSection(data []byte) (before, section []byte, after []
758759
returndata,nil,nil,nil
759760
}
760761

761-
// writeWithTempFileAndMove writes to a temporary file in the same
762-
// directory as path and renames the temp file to the file provided in
763-
// path. This ensure we avoid trashing the file we are writing due to
764-
// unforeseen circumstance like filesystem full, command killed, etc.
765-
funcwriteWithTempFileAndMove(pathstring,r io.Reader) (errerror) {
766-
dir:=filepath.Dir(path)
767-
name:=filepath.Base(path)
768-
769-
// Ensure that e.g. the ~/.ssh directory exists.
770-
iferr=os.MkdirAll(dir,0o700);err!=nil {
771-
returnxerrors.Errorf("create directory: %w",err)
772-
}
773-
774-
// Create a tempfile in the same directory for ensuring write
775-
// operation does not fail.
776-
f,err:=os.CreateTemp(dir,fmt.Sprintf(".%s.",name))
777-
iferr!=nil {
778-
returnxerrors.Errorf("create temp file failed: %w",err)
779-
}
780-
deferfunc() {
781-
iferr!=nil {
782-
_=os.Remove(f.Name())// Cleanup in case a step failed.
783-
}
784-
}()
785-
786-
_,err=io.Copy(f,r)
787-
iferr!=nil {
788-
_=f.Close()
789-
returnxerrors.Errorf("write temp file failed: %w",err)
790-
}
791-
792-
err=f.Close()
793-
iferr!=nil {
794-
returnxerrors.Errorf("close temp file failed: %w",err)
795-
}
796-
797-
err=os.Rename(f.Name(),path)
798-
iferr!=nil {
799-
returnxerrors.Errorf("rename temp file failed: %w",err)
800-
}
801-
802-
returnnil
803-
}
804-
805762
// sshConfigExecEscape quotes the string if it contains spaces, as per
806763
// `man 5 ssh_config`. However, OpenSSH uses exec in the users shell to
807764
// run the command, and as such the formatting/escape requirements

‎cli/help.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ var usageTemplate = func() *template.Template {
8181
switchv:=opt.Value.(type) {
8282
case*serpent.Enum:
8383
returnstrings.Join(v.Choices,"|")
84+
case*serpent.EnumArray:
85+
returnfmt.Sprintf("[%s]",strings.Join(v.Choices,"|"))
8486
default:
8587
returnv.Type()
8688
}

‎cli/organizationmembers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func (r *RootCmd) assignOrganizationRoles(orgContext *OrganizationContext) *serp
137137

138138
func (r*RootCmd)listOrganizationMembers(orgContext*OrganizationContext)*serpent.Command {
139139
formatter:=cliui.NewOutputFormatter(
140-
cliui.TableFormat([]codersdk.OrganizationMemberWithUserData{}, []string{"username","organization_roles"}),
140+
cliui.TableFormat([]codersdk.OrganizationMemberWithUserData{}, []string{"username","organization roles"}),
141141
cliui.JSONFormat(),
142142
)
143143

‎cli/organizationmembers_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestListOrganizationMembers(t *testing.T) {
2323
client,user:=coderdtest.CreateAnotherUser(t,ownerClient,owner.OrganizationID,rbac.RoleUserAdmin())
2424

2525
ctx:=testutil.Context(t,testutil.WaitMedium)
26-
inv,root:=clitest.New(t,"organization","members","list","-c","user_id,username,roles")
26+
inv,root:=clitest.New(t,"organization","members","list","-c","user id,username,organizationroles")
2727
clitest.SetupConfig(t,client,root)
2828

2929
buf:=new(bytes.Buffer)

‎cli/organizationroles.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func (r *RootCmd) organizationRoles(orgContext *OrganizationContext) *serpent.Co
3636
func (r*RootCmd)showOrganizationRoles(orgContext*OrganizationContext)*serpent.Command {
3737
formatter:=cliui.NewOutputFormatter(
3838
cliui.ChangeFormatterData(
39-
cliui.TableFormat([]roleTableRow{}, []string{"name","display_name","site_permissions","organization_permissions","user_permissions"}),
39+
cliui.TableFormat([]roleTableRow{}, []string{"name","display name","site permissions","organization permissions","user permissions"}),
4040
func(dataany) (any,error) {
4141
inputs,ok:=data.([]codersdk.AssignableRoles)
4242
if!ok {
@@ -103,7 +103,7 @@ func (r *RootCmd) showOrganizationRoles(orgContext *OrganizationContext) *serpen
103103
func (r*RootCmd)editOrganizationRole(orgContext*OrganizationContext)*serpent.Command {
104104
formatter:=cliui.NewOutputFormatter(
105105
cliui.ChangeFormatterData(
106-
cliui.TableFormat([]roleTableRow{}, []string{"name","display_name","site_permissions","organization_permissions","user_permissions"}),
106+
cliui.TableFormat([]roleTableRow{}, []string{"name","display name","site permissions","organization permissions","user permissions"}),
107107
func(dataany) (any,error) {
108108
typed,_:=data.(codersdk.Role)
109109
return []roleTableRow{roleToTableView(typed)},nil
@@ -408,10 +408,10 @@ func roleToTableView(role codersdk.Role) roleTableRow {
408408

409409
typeroleTableRowstruct {
410410
Namestring`table:"name,default_sort"`
411-
DisplayNamestring`table:"display_name"`
412-
OrganizationIDstring`table:"organization_id"`
413-
SitePermissionsstring` table:"site_permissions"`
411+
DisplayNamestring`table:"display name"`
412+
OrganizationIDstring`table:"organization id"`
413+
SitePermissionsstring` table:"site permissions"`
414414
// map[<org_id>] -> Permissions
415-
OrganizationPermissionsstring`table:"organization_permissions"`
416-
UserPermissionsstring`table:"user_permissions"`
415+
OrganizationPermissionsstring`table:"organization permissions"`
416+
UserPermissionsstring`table:"user permissions"`
417417
}

‎cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const (
8282
func (r*RootCmd)CoreSubcommands() []*serpent.Command {
8383
// Please re-sort this list alphabetically if you change it!
8484
return []*serpent.Command{
85+
r.completion(),
8586
r.dotfiles(),
8687
r.externalAuth(),
8788
r.login(),

‎cli/stat.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ func (r *RootCmd) stat() *serpent.Command {
3232
fs=afero.NewReadOnlyFs(afero.NewOsFs())
3333
formatter=cliui.NewOutputFormatter(
3434
cliui.TableFormat([]statsRow{}, []string{
35-
"host_cpu",
36-
"host_memory",
37-
"home_disk",
38-
"container_cpu",
39-
"container_memory",
35+
"host cpu",
36+
"host memory",
37+
"home disk",
38+
"container cpu",
39+
"container memory",
4040
}),
4141
cliui.JSONFormat(),
4242
)
@@ -284,9 +284,9 @@ func (*RootCmd) statDisk(fs afero.Fs) *serpent.Command {
284284
}
285285

286286
typestatsRowstruct {
287-
HostCPU*clistat.Result`json:"host_cpu" table:"host_cpu,default_sort"`
288-
HostMemory*clistat.Result`json:"host_memory" table:"host_memory"`
289-
Disk*clistat.Result`json:"home_disk" table:"home_disk"`
290-
ContainerCPU*clistat.Result`json:"container_cpu" table:"container_cpu"`
291-
ContainerMemory*clistat.Result`json:"container_memory" table:"container_memory"`
287+
HostCPU*clistat.Result`json:"host_cpu" table:"host cpu,default_sort"`
288+
HostMemory*clistat.Result`json:"host_memory" table:"host memory"`
289+
Disk*clistat.Result`json:"home_disk" table:"home disk"`
290+
ContainerCPU*clistat.Result`json:"container_cpu" table:"container cpu"`
291+
ContainerMemory*clistat.Result`json:"container_memory" table:"container memory"`
292292
}

‎cli/templateedit.go

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cli
33
import (
44
"fmt"
55
"net/http"
6-
"strings"
76
"time"
87

98
"golang.org/x/xerrors"
@@ -239,35 +238,14 @@ func (r *RootCmd) templateEdit() *serpent.Command {
239238
Value:serpent.DurationOf(&activityBump),
240239
},
241240
{
242-
Flag:"autostart-requirement-weekdays",
243-
// workspaces created from this template must be restarted on the given weekdays. To unset this value for the template (and disable the autostop requirement for the template), pass 'none'.
241+
Flag:"autostart-requirement-weekdays",
244242
Description:"Edit the template autostart requirement weekdays - workspaces created from this template can only autostart on the given weekdays. To unset this value for the template (and allow autostart on all days), pass 'all'.",
245-
Value:serpent.Validate(serpent.StringArrayOf(&autostartRequirementDaysOfWeek),func(value*serpent.StringArray)error {
246-
v:=value.GetSlice()
247-
iflen(v)==1&&v[0]=="all" {
248-
returnnil
249-
}
250-
_,err:=codersdk.WeekdaysToBitmap(v)
251-
iferr!=nil {
252-
returnxerrors.Errorf("invalid autostart requirement days of week %q: %w",strings.Join(v,","),err)
253-
}
254-
returnnil
255-
}),
243+
Value:serpent.EnumArrayOf(&autostartRequirementDaysOfWeek,append(codersdk.AllDaysOfWeek,"all")...),
256244
},
257245
{
258246
Flag:"autostop-requirement-weekdays",
259247
Description:"Edit the template autostop requirement weekdays - workspaces created from this template must be restarted on the given weekdays. To unset this value for the template (and disable the autostop requirement for the template), pass 'none'.",
260-
Value:serpent.Validate(serpent.StringArrayOf(&autostopRequirementDaysOfWeek),func(value*serpent.StringArray)error {
261-
v:=value.GetSlice()
262-
iflen(v)==1&&v[0]=="none" {
263-
returnnil
264-
}
265-
_,err:=codersdk.WeekdaysToBitmap(v)
266-
iferr!=nil {
267-
returnxerrors.Errorf("invalid autostop requirement days of week %q: %w",strings.Join(v,","),err)
268-
}
269-
returnnil
270-
}),
248+
Value:serpent.EnumArrayOf(&autostopRequirementDaysOfWeek,append(codersdk.AllDaysOfWeek,"none")...),
271249
},
272250
{
273251
Flag:"autostop-requirement-weeks",

‎cli/templateversions.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ func (r *RootCmd) templateVersions() *serpent.Command {
4040

4141
func (r*RootCmd)templateVersionsList()*serpent.Command {
4242
defaultColumns:= []string{
43-
"Name",
44-
"Created At",
45-
"Created By",
46-
"Status",
47-
"Active",
43+
"name",
44+
"created at",
45+
"created by",
46+
"status",
47+
"active",
4848
}
4949
formatter:=cliui.NewOutputFormatter(
5050
cliui.TableFormat([]templateVersionRow{},defaultColumns),
@@ -70,10 +70,10 @@ func (r *RootCmd) templateVersionsList() *serpent.Command {
7070
for_,opt:=rangei.Command.Options {
7171
ifopt.Flag=="column" {
7272
ifopt.ValueSource==serpent.ValueSourceDefault {
73-
v,ok:=opt.Value.(*serpent.StringArray)
73+
v,ok:=opt.Value.(*serpent.EnumArray)
7474
ifok {
7575
// Add the extra new default column.
76-
*v=append(*v,"Archived")
76+
_=v.Append("Archived")
7777
}
7878
}
7979
break

‎cli/testdata/coder_--help.golden

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ USAGE:
1515

1616
SUBCOMMANDS:
1717
autoupdate Toggle auto-update policy for a workspace
18+
completion Install or update shell completion scripts for the
19+
detected or chosen shell.
1820
config-ssh Add an SSH Host entry for your workspaces "ssh
1921
coder.workspace"
2022
create Create a workspace

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp