Skip to content

Commit e8e918e

Browse files
committed
Add support for fish and zsh shell completions
1 parent 9a738ca commit e8e918e

7 files changed

Lines changed: 667 additions & 12 deletions

File tree

internal/cli/app.go

Lines changed: 131 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ import (
1515

1616
// App represents the full configuration for a fetch invocation.
1717
type App struct {
18-
URL *url.URL
18+
URL *url.URL
19+
ExtraArgs []string
1920

2021
Cfg config.Config
2122

2223
AWSSigv4 *aws.Config
2324
Basic *core.KeyVal
2425
Bearer string
2526
BuildInfo bool
27+
Complete string
2628
ConfigPath string
2729
Data io.Reader
2830
DryRun bool
@@ -44,12 +46,24 @@ func (a *App) PrintHelp(p *core.Printer) {
4446
}
4547

4648
func (a *App) CLI() *CLI {
49+
var extraArgs bool
4750
return &CLI{
4851
Description: "fetch is a modern HTTP(S) client for the command line",
4952
Args: []Arguments{
5053
{Name: "URL", Description: "The URL to make a request to"},
5154
},
5255
ArgFn: func(s string) error {
56+
// Append extra args, if necessary.
57+
if extraArgs {
58+
a.ExtraArgs = append(a.ExtraArgs, s)
59+
return nil
60+
}
61+
if s == "--" {
62+
extraArgs = true
63+
return nil
64+
}
65+
66+
// Otherwise, parse the provided URL.
5367
if a.URL != nil {
5468
return fmt.Errorf("unexpected argument: %q", s)
5569
}
@@ -186,14 +200,46 @@ func (a *App) CLI() *CLI {
186200
Description: "Enable/disable color",
187201
Default: "",
188202
Aliases: []string{"colour"},
189-
Values: []string{"auto", "off", "on"},
203+
Values: []core.KeyVal{
204+
{
205+
Key: "auto",
206+
Val: "Automatically determine color",
207+
},
208+
{
209+
Key: "off",
210+
Val: "Disable color output",
211+
},
212+
{
213+
Key: "on",
214+
Val: "Enable color output",
215+
},
216+
},
190217
IsSet: func() bool {
191218
return a.Cfg.Color != core.ColorUnknown
192219
},
193220
Fn: func(value string) error {
194221
return a.Cfg.ParseColor(value)
195222
},
196223
},
224+
{
225+
Short: "",
226+
Long: "complete",
227+
Args: "SHELL",
228+
Description: "Output shell completion",
229+
Default: "",
230+
Values: []core.KeyVal{
231+
{Key: "fish"},
232+
{Key: "zsh"},
233+
},
234+
HideValues: true,
235+
IsSet: func() bool {
236+
return a.Complete != ""
237+
},
238+
Fn: func(value string) error {
239+
a.Complete = value
240+
return nil
241+
},
242+
},
197243
{
198244
Short: "c",
199245
Long: "config",
@@ -288,7 +334,20 @@ func (a *App) CLI() *CLI {
288334
Args: "OPTION",
289335
Description: "Enable/disable formatting",
290336
Default: "",
291-
Values: []string{"auto", "off", "on"},
337+
Values: []core.KeyVal{
338+
{
339+
Key: "auto",
340+
Val: "Automatically determine whether to format",
341+
},
342+
{
343+
Key: "off",
344+
Val: "Disable output formatting",
345+
},
346+
{
347+
Key: "on",
348+
Val: "Enable output formatting",
349+
},
350+
},
292351
IsSet: func() bool {
293352
return a.Cfg.Format != core.FormatUnknown
294353
},
@@ -329,7 +388,16 @@ func (a *App) CLI() *CLI {
329388
Args: "VERSION",
330389
Description: "Highest allowed HTTP version",
331390
Default: "",
332-
Values: []string{"1", "2"},
391+
Values: []core.KeyVal{
392+
{
393+
Key: "1",
394+
Val: "HTTP/1.1",
395+
},
396+
{
397+
Key: "2",
398+
Val: "HTTP/2.0",
399+
},
400+
},
333401
IsSet: func() bool {
334402
return a.Cfg.HTTP != core.HTTPDefault
335403
},
@@ -358,6 +426,21 @@ func (a *App) CLI() *CLI {
358426
Args: "OPTION",
359427
Description: "Enable/disable image rendering",
360428
Default: "",
429+
Values: []core.KeyVal{
430+
{
431+
Key: "auto",
432+
Val: "Automatically decide image display",
433+
},
434+
{
435+
Key: "native",
436+
Val: "Only use builtin decoders",
437+
},
438+
{
439+
Key: "off",
440+
Val: "Disable image display",
441+
},
442+
},
443+
HideValues: true,
361444
IsSet: func() bool {
362445
return a.Cfg.Image != core.ImageUnknown
363446
},
@@ -425,15 +508,28 @@ func (a *App) CLI() *CLI {
425508
Fn: func(value string) error {
426509
key, val, _ := cut(value, "=")
427510
if strings.HasPrefix(val, "@") {
428-
stats, err := os.Stat(val[1:])
511+
path := val[1:]
512+
513+
// Expand '~' to the home directory.
514+
if len(path) >= 2 && path[0] == '~' && path[1] == os.PathSeparator {
515+
home, err := os.UserHomeDir()
516+
if err != nil {
517+
return err
518+
}
519+
path = home + path[1:]
520+
val = "@" + path
521+
}
522+
523+
// Ensure the file exists.
524+
stats, err := os.Stat(path)
429525
if err != nil {
430526
if os.IsNotExist(err) {
431-
return fmt.Errorf("file does not exist: '%s'", val[1:])
527+
return fmt.Errorf("file does not exist: '%s'", path)
432528
}
433529
return err
434530
}
435531
if stats.IsDir() {
436-
return fmt.Errorf("file is a directory: '%s'", val[1:])
532+
return fmt.Errorf("file is a directory: '%s'", path)
437533
}
438534
}
439535
a.Multipart = append(a.Multipart, core.KeyVal{Key: key, Val: val})
@@ -588,7 +684,24 @@ func (a *App) CLI() *CLI {
588684
Args: "VERSION",
589685
Description: "Minimum TLS version",
590686
Default: "",
591-
Values: []string{"1.0", "1.1", "1.2", "1.3"},
687+
Values: []core.KeyVal{
688+
{
689+
Key: "1.0",
690+
Val: "TLS v1.0",
691+
},
692+
{
693+
Key: "1.1",
694+
Val: "TLS v1.1",
695+
},
696+
{
697+
Key: "1.2",
698+
Val: "TLS v1.2",
699+
},
700+
{
701+
Key: "1.3",
702+
Val: "TLS v1.3",
703+
},
704+
},
592705
IsSet: func() bool {
593706
return a.Cfg.TLS != nil
594707
},
@@ -671,7 +784,16 @@ func requestBody(value string) (io.Reader, error) {
671784
case value == "@-":
672785
return os.Stdin, nil
673786
default:
674-
f, err := os.Open(value[1:])
787+
path := value[1:]
788+
// Expand '~' to the home directory.
789+
if len(path) >= 2 && path[0] == '~' && path[1] == os.PathSeparator {
790+
home, err := os.UserHomeDir()
791+
if err != nil {
792+
return nil, err
793+
}
794+
path = home + path[1:]
795+
}
796+
f, err := os.Open(path)
675797
if err != nil {
676798
if os.IsNotExist(err) {
677799
return nil, fileNotExistsError(value[1:])

internal/cli/cli.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ type Flag struct {
2626
Args string
2727
Description string
2828
Default string
29-
Values []string
29+
Values []core.KeyVal
30+
HideValues bool
3031
IsHidden bool
3132
IsSet func() bool
3233
Fn func(value string) error
@@ -301,9 +302,14 @@ func printHelp(cli *CLI, p *core.Printer) {
301302

302303
p.WriteString(flag.Description)
303304

304-
if len(flag.Values) > 0 {
305+
if !flag.HideValues && len(flag.Values) > 0 {
305306
p.WriteString(" [")
306-
p.WriteString(strings.Join(flag.Values, ", "))
307+
for i, kv := range flag.Values {
308+
if i > 0 {
309+
p.WriteString(", ")
310+
}
311+
p.WriteString(kv.Key)
312+
}
307313
p.WriteString("]")
308314
}
309315

0 commit comments

Comments
 (0)