Skip to content

Commit fd7f0eb

Browse files
committed
Spike on new options resolver pattern
1 parent ef52376 commit fd7f0eb

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed

pkg/cmd/root/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
repoCmd "github.com/cli/cli/pkg/cmd/repo"
2121
creditsCmd "github.com/cli/cli/pkg/cmd/repo/credits"
2222
versionCmd "github.com/cli/cli/pkg/cmd/version"
23+
whatCmd "github.com/cli/cli/pkg/cmd/what"
2324
"github.com/cli/cli/pkg/cmdutil"
2425
"github.com/spf13/cobra"
2526
)
@@ -93,6 +94,8 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
9394
// Help topics
9495
cmd.AddCommand(NewHelpTopic("environment"))
9596

97+
cmd.AddCommand(whatCmd.NewCmdWhat(f, nil))
98+
9699
cmdutil.DisableAuthCheck(cmd)
97100

98101
return cmd

pkg/cmd/what/what.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package what
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
9+
"github.com/AlecAivazis/survey/v2"
10+
"github.com/cli/cli/internal/config"
11+
"github.com/cli/cli/pkg/cmdutil"
12+
"github.com/cli/cli/pkg/iostreams"
13+
"github.com/cli/cli/pkg/prompt"
14+
"github.com/cli/cli/pkg/resolver"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
type CreateOptions struct {
19+
HttpClient func() (*http.Client, error)
20+
Config func() (config.Config, error)
21+
IO *iostreams.IOStreams
22+
NameResolver resolver.ResolverFunction
23+
VisibilityResolver resolver.ResolverFunction
24+
Resolver *resolver.Resolver
25+
Description string
26+
}
27+
28+
func NewCmdWhat(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
29+
opts := &CreateOptions{
30+
IO: f.IOStreams,
31+
HttpClient: f.HttpClient,
32+
Config: f.Config,
33+
}
34+
35+
cmd := &cobra.Command{
36+
Use: "what [<name>]",
37+
Args: cobra.MaximumNArgs(1),
38+
PreRunE: func(cmd *cobra.Command, args []string) error {
39+
opts.NameResolver = resolver.ResolverFunc(resolver.EnvStrat("REPO_NAME"), resolver.ArgsStrat(args, 0), promptNameStrat(opts.IO.CanPrompt()))
40+
41+
r := resolver.New()
42+
43+
r.AddStrat("visibility", resolver.MutuallyExclusiveBoolFlagsStrat(cmd, "public", "private", "internal"))
44+
r.AddStrat("visibility", promptVisibilityStrat(opts.IO.CanPrompt()))
45+
46+
r.AddStrat("editor", resolver.StringFlagStrat(cmd, "editor"))
47+
r.AddStrat("editor", resolver.ConfigStrat(opts.Config, "", "editor"))
48+
r.AddStrat("editor", resolver.ValueStrat("cat"))
49+
50+
opts.VisibilityResolver = r.ResolverFunc("visibility")
51+
opts.Resolver = r
52+
53+
return nil
54+
},
55+
RunE: func(cmd *cobra.Command, args []string) error {
56+
if runF != nil {
57+
return runF(opts)
58+
}
59+
60+
return createRun(opts)
61+
},
62+
}
63+
64+
cmd.Flags().StringP("editor", "e", "", "some editor")
65+
cmd.Flags().Bool("public", false, "some visibility")
66+
cmd.Flags().Bool("private", false, "less visibility")
67+
cmd.Flags().Bool("internal", false, "no visibility")
68+
cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Description of repository")
69+
70+
return cmd
71+
}
72+
73+
func createRun(opts *CreateOptions) error {
74+
name, err := opts.NameResolver()
75+
if err != nil {
76+
return err
77+
}
78+
79+
visibility, err := opts.VisibilityResolver()
80+
if err != nil {
81+
return err
82+
}
83+
84+
editor, err := opts.Resolver.Resolve("editor")
85+
if err != nil {
86+
return err
87+
}
88+
89+
fmt.Fprintf(opts.IO.Out, "%s - %s - %s", name, visibility, editor)
90+
91+
return nil
92+
}
93+
94+
func promptNameStrat(canPrompt bool) resolver.Strat {
95+
return func() (interface{}, error) {
96+
if !canPrompt {
97+
return nil, errors.New("name argument required when not running interactively")
98+
}
99+
100+
qs := []*survey.Question{}
101+
getNameQuestion := &survey.Question{
102+
Name: "repoName",
103+
Prompt: &survey.Input{
104+
Message: "Repo name",
105+
},
106+
}
107+
qs = append(qs, getNameQuestion)
108+
109+
answer := struct {
110+
RepoName string
111+
}{}
112+
113+
err := prompt.SurveyAsk(qs, &answer)
114+
if err != nil {
115+
return nil, err
116+
}
117+
118+
return answer.RepoName, nil
119+
}
120+
}
121+
122+
func promptVisibilityStrat(canPrompt bool) resolver.Strat {
123+
return func() (interface{}, error) {
124+
if !canPrompt {
125+
return nil, errors.New("visibility argument required when not running interactively")
126+
}
127+
128+
qs := []*survey.Question{}
129+
130+
getVisibilityQuestion := &survey.Question{
131+
Name: "repoVisibility",
132+
Prompt: &survey.Select{
133+
Message: "Visibility",
134+
Options: []string{"Public", "Private", "Internal"},
135+
},
136+
}
137+
qs = append(qs, getVisibilityQuestion)
138+
139+
answer := struct {
140+
RepoVisibility string
141+
}{}
142+
143+
err := prompt.SurveyAsk(qs, &answer)
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
return strings.ToUpper(answer.RepoVisibility), nil
149+
}
150+
}

pkg/resolver/resolver.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package resolver
2+
3+
import (
4+
"errors"
5+
"os"
6+
7+
"github.com/cli/cli/internal/config"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
type Strat func() (interface{}, error)
12+
13+
type ResolverFunction func() (interface{}, error)
14+
15+
type Resolver struct {
16+
strats map[string][]Strat
17+
}
18+
19+
func New() *Resolver {
20+
return &Resolver{
21+
strats: make(map[string][]Strat),
22+
}
23+
}
24+
25+
func (r *Resolver) AddStrat(name string, strat Strat) {
26+
r.strats[name] = append(r.strats[name], strat)
27+
}
28+
29+
func (r *Resolver) GetStrats(name string) []Strat {
30+
return r.strats[name]
31+
}
32+
33+
func (r *Resolver) ResolverFunc(name string) ResolverFunction {
34+
return ResolverFunc(r.GetStrats(name)...)
35+
}
36+
37+
func (r *Resolver) Resolve(name string) (interface{}, error) {
38+
return ResolverFunc(r.GetStrats(name)...)()
39+
}
40+
41+
func ResolverFunc(strategies ...Strat) ResolverFunction {
42+
return func() (interface{}, error) {
43+
for _, strat := range strategies {
44+
out, err := strat()
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
if out != nil {
50+
return out, nil
51+
}
52+
}
53+
54+
return "", errors.New("No strategies resulted in output")
55+
}
56+
}
57+
58+
func EnvStrat(name string) Strat {
59+
return func() (interface{}, error) {
60+
val, ok := os.LookupEnv(name)
61+
if !ok {
62+
return nil, nil
63+
} else {
64+
return val, nil
65+
}
66+
}
67+
}
68+
69+
func ConfigStrat(config func() (config.Config, error), hostname, name string) Strat {
70+
return func() (interface{}, error) {
71+
cfg, err := config()
72+
if err != nil {
73+
return nil, err
74+
}
75+
76+
return cfg.Get(hostname, name)
77+
}
78+
}
79+
80+
func ArgsStrat(args []string, index int) Strat {
81+
return func() (interface{}, error) {
82+
if len(args) > index {
83+
return args[index], nil
84+
}
85+
return nil, nil
86+
}
87+
}
88+
89+
func StringFlagStrat(cmd *cobra.Command, name string) Strat {
90+
return func() (interface{}, error) {
91+
if !cmd.Flags().Changed(name) {
92+
return nil, nil
93+
}
94+
return cmd.Flags().GetString(name)
95+
}
96+
}
97+
98+
func BoolFlagStrat(cmd *cobra.Command, name string) Strat {
99+
return func() (interface{}, error) {
100+
return cmd.Flags().GetBool(name)
101+
}
102+
}
103+
104+
func StringSliceFlagsStrat(cmd *cobra.Command, name string) Strat {
105+
return func() (interface{}, error) {
106+
if !cmd.Flags().Changed(name) {
107+
return nil, nil
108+
}
109+
return cmd.Flags().GetStringSlice(name)
110+
}
111+
}
112+
113+
func MutuallyExclusiveBoolFlagsStrat(cmd *cobra.Command, names ...string) Strat {
114+
return func() (interface{}, error) {
115+
flags := cmd.Flags()
116+
enabledFlagCount := 0
117+
enabledFlag := ""
118+
for _, name := range names {
119+
val, err := flags.GetBool(name)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
if val {
125+
enabledFlagCount = enabledFlagCount + 1
126+
enabledFlag = name
127+
}
128+
129+
if enabledFlagCount > 1 {
130+
break
131+
}
132+
}
133+
134+
if enabledFlagCount == 0 {
135+
return nil, nil
136+
} else if enabledFlagCount == 1 {
137+
return enabledFlag, nil
138+
}
139+
140+
return nil, errors.New("expected exactly one of boolean flags to be true")
141+
}
142+
}
143+
144+
func ValueStrat(name string) Strat {
145+
return func() (interface{}, error) {
146+
return name, nil
147+
}
148+
}
149+
150+
func ErrorStrat(err error) Strat {
151+
return func() (interface{}, error) {
152+
return nil, err
153+
}
154+
}

0 commit comments

Comments
 (0)