refactor(cli): drop repo create --private and share output and paging helpers
- commit
- 3a3918d7434b25cde72b4e199b11d3f91fdf8a67
- author
- brice · · 2026-10-10 10:21 UTC
- committer
- brice · · 2026-10-10 11:17 UTC
- parent
- 66b7417
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +80 -103
M cmd/ermite/cli.go +46 -11
| @@ -104,18 +104,53 @@ func writeJSON(w io.Writer, v any) error { | |||
| 104 | 104 | return enc.Encode(v) | |
| 105 | 105 | } | |
| 106 | 106 | ||
| 107 | - | // clean makes a server-provided string safe for a terminal: control | |
| 108 | - | // characters (escape sequences, line breaks) are replaced. | |
| 109 | - | func clean(s string) string { | |
| 110 | - | return strings.Map(func(r rune) rune { | |
| 111 | - | switch { | |
| 112 | - | case r == '\t' || r == '\n' || r == '\r': | |
| 113 | - | return ' ' | |
| 114 | - | case r < 0x20 || r == 0x7f || (r >= 0x80 && r < 0xa0): | |
| 115 | - | return '?' | |
| 107 | + | // clean makes a server-provided string safe for a terminal. | |
| 108 | + | func clean(s string) string { return client.Clean(s) } | |
| 109 | + | ||
| 110 | + | // collect reads every page: fetch gets the page token and returns the items | |
| 111 | + | // and the next page token ("" at the end). | |
| 112 | + | func collect[T any](ctx context.Context, fetch func(token string) ([]T, string, error)) ([]T, error) { | |
| 113 | + | var all []T | |
| 114 | + | token := "" | |
| 115 | + | for { | |
| 116 | + | if err := ctx.Err(); err != nil { | |
| 117 | + | return nil, err | |
| 116 | 118 | } | |
| 117 | - | return r | |
| 118 | - | }, s) | |
| 119 | + | items, next, err := fetch(token) | |
| 120 | + | if err != nil { | |
| 121 | + | return nil, err | |
| 122 | + | } | |
| 123 | + | all = append(all, items...) | |
| 124 | + | if next == "" { | |
| 125 | + | return all, nil | |
| 126 | + | } | |
| 127 | + | token = next | |
| 128 | + | } | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // complete builds a shell completion: it opens a client (silently giving up | |
| 132 | + | // on any failure), lists with a 3 s timeout and keeps the entries whose | |
| 133 | + | // first element starts with the prefix. | |
| 134 | + | func complete[T any](env *cliEnv, cmd *cobra.Command, prefix string, | |
| 135 | + | list func(context.Context, *client.Client) ([]T, error), entry func(T) (key, text string), | |
| 136 | + | ) []string { | |
| 137 | + | c, err := env.client(cmd, false) | |
| 138 | + | if err != nil { | |
| 139 | + | return nil | |
| 140 | + | } | |
| 141 | + | ctx, cancel := context.WithTimeout(env.ctx(cmd), 3*time.Second) | |
| 142 | + | defer cancel() | |
| 143 | + | items, err := list(ctx, c) | |
| 144 | + | if err != nil { | |
| 145 | + | return nil | |
| 146 | + | } | |
| 147 | + | var out []string | |
| 148 | + | for _, it := range items { | |
| 149 | + | if k, text := entry(it); strings.HasPrefix(k, prefix) { | |
| 150 | + | out = append(out, k+"\t"+text) | |
| 151 | + | } | |
| 152 | + | } | |
| 153 | + | return out | |
| 119 | 154 | } | |
| 120 | 155 | ||
| 121 | 156 | // table prints aligned columns under upper-case headers. | |
| @@ -104,18 +104,53 @@ func writeJSON(w io.Writer, v any) error { | |||||
| 104 | return enc.Encode(v) | 104 | return enc.Encode(v) | ||
| 105 | } | 105 | } | ||
| 106 | 106 | ||||
| 107 | - | // clean makes a server-provided string safe for a terminal: control | 107 | + | // clean makes a server-provided string safe for a terminal. |
| 108 | - | // characters (escape sequences, line breaks) are replaced. | 108 | + | func clean(s string) string { return client.Clean(s) } |
| 109 | - | func clean(s string) string { | 109 | + | |
| 110 | - | return strings.Map(func(r rune) rune { | 110 | + | // collect reads every page: fetch gets the page token and returns the items |
| 111 | - | switch { | 111 | + | // and the next page token ("" at the end). |
| 112 | - | case r == '\t' || r == '\n' || r == '\r': | 112 | + | func collect[T any](ctx context.Context, fetch func(token string) ([]T, string, error)) ([]T, error) { |
| 113 | - | return ' ' | 113 | + | var all []T |
| 114 | - | case r < 0x20 || r == 0x7f || (r >= 0x80 && r < 0xa0): | 114 | + | token := "" |
| 115 | - | return '?' | 115 | + | for { |
| 116 | + | if err := ctx.Err(); err != nil { | |||
| 117 | + | return nil, err | |||
| 116 | } | 118 | } | ||
| 117 | - | return r | 119 | + | items, next, err := fetch(token) |
| 118 | - | }, s) | 120 | + | if err != nil { |
| 121 | + | return nil, err | |||
| 122 | + | } | |||
| 123 | + | all = append(all, items...) | |||
| 124 | + | if next == "" { | |||
| 125 | + | return all, nil | |||
| 126 | + | } | |||
| 127 | + | token = next | |||
| 128 | + | } | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // complete builds a shell completion: it opens a client (silently giving up | |||
| 132 | + | // on any failure), lists with a 3 s timeout and keeps the entries whose | |||
| 133 | + | // first element starts with the prefix. | |||
| 134 | + | func complete[T any](env *cliEnv, cmd *cobra.Command, prefix string, | |||
| 135 | + | list func(context.Context, *client.Client) ([]T, error), entry func(T) (key, text string), | |||
| 136 | + | ) []string { | |||
| 137 | + | c, err := env.client(cmd, false) | |||
| 138 | + | if err != nil { | |||
| 139 | + | return nil | |||
| 140 | + | } | |||
| 141 | + | ctx, cancel := context.WithTimeout(env.ctx(cmd), 3*time.Second) | |||
| 142 | + | defer cancel() | |||
| 143 | + | items, err := list(ctx, c) | |||
| 144 | + | if err != nil { | |||
| 145 | + | return nil | |||
| 146 | + | } | |||
| 147 | + | var out []string | |||
| 148 | + | for _, it := range items { | |||
| 149 | + | if k, text := entry(it); strings.HasPrefix(k, prefix) { | |||
| 150 | + | out = append(out, k+"\t"+text) | |||
| 151 | + | } | |||
| 152 | + | } | |||
| 153 | + | return out | |||
| 119 | } | 154 | } | ||
| 120 | 155 | ||||
| 121 | // table prints aligned columns under upper-case headers. | 156 | // table prints aligned columns under upper-case headers. | ||
M cmd/ermite/cli_repo.go +26 -58
| @@ -9,7 +9,6 @@ import ( | |||
| 9 | 9 | "fmt" | |
| 10 | 10 | "io" | |
| 11 | 11 | "strings" | |
| 12 | - | "time" | |
| 13 | 12 | ||
| 14 | 13 | "connectrpc.com/connect" | |
| 15 | 14 | "github.com/spf13/cobra" | |
| @@ -87,19 +86,13 @@ func newRepoCmd(env *cliEnv) *cobra.Command { | |||
| 87 | 86 | ||
| 88 | 87 | // listRepos reads every page of repositories. | |
| 89 | 88 | func listRepos(ctx context.Context, c *client.Client) ([]*ermite.Repo, error) { | |
| 90 | - | var repos []*ermite.Repo | |
| 91 | - | token := "" | |
| 92 | - | for { | |
| 89 | + | return collect(ctx, func(token string) ([]*ermite.Repo, string, error) { | |
| 93 | 90 | res, err := c.Repos().ListRepos(ctx, connect.NewRequest(&ermite.ListReposRequest{PageToken: token})) | |
| 94 | 91 | if err != nil { | |
| 95 | - | return nil, c.Describe(err) | |
| 92 | + | return nil, "", c.Describe(err) | |
| 96 | 93 | } | |
| 97 | - | repos = append(repos, res.Msg.GetRepos()...) | |
| 98 | - | token = res.Msg.GetNextPageToken() | |
| 99 | - | if token == "" { | |
| 100 | - | return repos, nil | |
| 101 | - | } | |
| 102 | - | } | |
| 94 | + | return res.Msg.GetRepos(), res.Msg.GetNextPageToken(), nil | |
| 95 | + | }) | |
| 103 | 96 | } | |
| 104 | 97 | ||
| 105 | 98 | // completeRepoNames completes the first argument with repository names. Any | |
| @@ -109,36 +102,28 @@ func completeRepoNames(env *cliEnv) func(*cobra.Command, []string, string) ([]st | |||
| 109 | 102 | if len(args) > 0 { | |
| 110 | 103 | return nil, cobra.ShellCompDirectiveNoFileComp | |
| 111 | 104 | } | |
| 112 | - | names, err := repoNames(env.ctx(cmd), env, cmd, toComplete) | |
| 113 | - | if err != nil { | |
| 114 | - | return nil, cobra.ShellCompDirectiveNoFileComp | |
| 115 | - | } | |
| 116 | - | return names, cobra.ShellCompDirectiveNoFileComp | |
| 105 | + | return repoNames(env, cmd, toComplete), cobra.ShellCompDirectiveNoFileComp | |
| 117 | 106 | } | |
| 118 | 107 | } | |
| 119 | 108 | ||
| 120 | - | func repoNames(ctx context.Context, env *cliEnv, cmd *cobra.Command, prefix string) ([]string, error) { | |
| 121 | - | c, err := env.client(cmd, false) | |
| 122 | - | if err != nil { | |
| 123 | - | return nil, err | |
| 124 | - | } | |
| 125 | - | ctx, cancel := context.WithTimeout(ctx, 3*time.Second) | |
| 126 | - | defer cancel() | |
| 127 | - | repos, err := listRepos(ctx, c) | |
| 128 | - | if err != nil { | |
| 129 | - | return nil, err | |
| 130 | - | } | |
| 131 | - | var names []string | |
| 132 | - | for _, r := range repos { | |
| 133 | - | if strings.HasPrefix(r.GetName(), prefix) { | |
| 134 | - | desc := visibilityName(r.GetVisibility()) | |
| 135 | - | if d := clean(r.GetDescription()); d != "" { | |
| 136 | - | desc += ", " + d | |
| 137 | - | } | |
| 138 | - | names = append(names, r.GetName()+"\t"+desc) | |
| 109 | + | // repoNames lists the repository names starting with prefix, with their | |
| 110 | + | // visibility and description as completion hints. | |
| 111 | + | func repoNames(env *cliEnv, cmd *cobra.Command, prefix string) []string { | |
| 112 | + | return complete(env, cmd, prefix, listRepos, func(r *ermite.Repo) (string, string) { | |
| 113 | + | desc := visibilityName(r.GetVisibility()) | |
| 114 | + | if d := clean(r.GetDescription()); d != "" { | |
| 115 | + | desc += ", " + d | |
| 139 | 116 | } | |
| 117 | + | return r.GetName(), desc | |
| 118 | + | }) | |
| 119 | + | } | |
| 120 | + | ||
| 121 | + | // outputRepo prints a repository as JSON or as fields. | |
| 122 | + | func outputRepo(cmd *cobra.Command, r *ermite.Repo, asJSON bool) error { | |
| 123 | + | if asJSON { | |
| 124 | + | return writeJSON(cmd.OutOrStdout(), toJSONRepo(r)) | |
| 140 | 125 | } | |
| 141 | - | return names, nil | |
| 126 | + | return printRepo(cmd.OutOrStdout(), r) | |
| 142 | 127 | } | |
| 143 | 128 | ||
| 144 | 129 | func newRepoListCmd(env *cliEnv) *cobra.Command { | |
| @@ -214,10 +199,7 @@ func newRepoShowCmd(env *cliEnv) *cobra.Command { | |||
| 214 | 199 | if err != nil { | |
| 215 | 200 | return c.Describe(err) | |
| 216 | 201 | } | |
| 217 | - | if *asJSON { | |
| 218 | - | return writeJSON(cmd.OutOrStdout(), toJSONRepo(res.Msg.GetRepo())) | |
| 219 | - | } | |
| 220 | - | return printRepo(cmd.OutOrStdout(), res.Msg.GetRepo()) | |
| 202 | + | return outputRepo(cmd, res.Msg.GetRepo(), *asJSON) | |
| 221 | 203 | } | |
| 222 | 204 | return cmd | |
| 223 | 205 | } | |
| @@ -226,7 +208,6 @@ func newRepoCreateCmd(env *cliEnv) *cobra.Command { | |||
| 226 | 208 | var ( | |
| 227 | 209 | description string | |
| 228 | 210 | visibility string | |
| 229 | - | private bool | |
| 230 | 211 | ) | |
| 231 | 212 | cmd := &cobra.Command{ | |
| 232 | 213 | Use: "create <name>", | |
| @@ -237,11 +218,10 @@ func newRepoCreateCmd(env *cliEnv) *cobra.Command { | |||
| 237 | 218 | } | |
| 238 | 219 | cmd.Flags().StringVar(&description, "description", "", "one-line description") | |
| 239 | 220 | cmd.Flags().StringVar(&visibility, "visibility", visibilityPrivate, "visibility: public or private") | |
| 240 | - | cmd.Flags().BoolVar(&private, "private", false, "create a private repository (the default; same as --visibility private)") | |
| 241 | 221 | _ = cmd.RegisterFlagCompletionFunc("visibility", visibilityCompletion) | |
| 242 | 222 | asJSON := addJSONFlag(cmd) | |
| 243 | 223 | cmd.RunE = func(cmd *cobra.Command, args []string) error { | |
| 244 | - | req, err := buildCreateRepo(cmd, args[0], description, visibility, private) | |
| 224 | + | req, err := buildCreateRepo(args[0], description, visibility) | |
| 245 | 225 | if err != nil { | |
| 246 | 226 | return err | |
| 247 | 227 | } | |
| @@ -253,10 +233,7 @@ func newRepoCreateCmd(env *cliEnv) *cobra.Command { | |||
| 253 | 233 | if err != nil { | |
| 254 | 234 | return c.Describe(err) | |
| 255 | 235 | } | |
| 256 | - | if *asJSON { | |
| 257 | - | return writeJSON(cmd.OutOrStdout(), toJSONRepo(res.Msg.GetRepo())) | |
| 258 | - | } | |
| 259 | - | return printRepo(cmd.OutOrStdout(), res.Msg.GetRepo()) | |
| 236 | + | return outputRepo(cmd, res.Msg.GetRepo(), *asJSON) | |
| 260 | 237 | } | |
| 261 | 238 | return cmd | |
| 262 | 239 | } | |
| @@ -266,17 +243,11 @@ func visibilityCompletion(*cobra.Command, []string, string) ([]string, cobra.She | |||
| 266 | 243 | } | |
| 267 | 244 | ||
| 268 | 245 | // buildCreateRepo maps the flags of `repo create` to the request. | |
| 269 | - | func buildCreateRepo(cmd *cobra.Command, name, description, visibility string, private bool) (*ermite.CreateRepoRequest, error) { | |
| 246 | + | func buildCreateRepo(name, description, visibility string) (*ermite.CreateRepoRequest, error) { | |
| 270 | 247 | vis, err := parseVisibility(visibility) | |
| 271 | 248 | if err != nil { | |
| 272 | 249 | return nil, err | |
| 273 | 250 | } | |
| 274 | - | if private { | |
| 275 | - | if cmd.Flags().Changed("visibility") && vis != ermite.Visibility_VISIBILITY_PRIVATE { | |
| 276 | - | return nil, errors.New("--private conflicts with --visibility " + visibilityPublic) | |
| 277 | - | } | |
| 278 | - | vis = ermite.Visibility_VISIBILITY_PRIVATE | |
| 279 | - | } | |
| 280 | 251 | return &ermite.CreateRepoRequest{Name: name, Description: description, Visibility: vis}, nil | |
| 281 | 252 | } | |
| 282 | 253 | ||
| @@ -308,10 +279,7 @@ func newRepoEditCmd(env *cliEnv) *cobra.Command { | |||
| 308 | 279 | if err != nil { | |
| 309 | 280 | return c.Describe(err) | |
| 310 | 281 | } | |
| 311 | - | if *asJSON { | |
| 312 | - | return writeJSON(cmd.OutOrStdout(), toJSONRepo(res.Msg.GetRepo())) | |
| 313 | - | } | |
| 314 | - | return printRepo(cmd.OutOrStdout(), res.Msg.GetRepo()) | |
| 282 | + | return outputRepo(cmd, res.Msg.GetRepo(), *asJSON) | |
| 315 | 283 | } | |
| 316 | 284 | return cmd | |
| 317 | 285 | } | |
| @@ -9,7 +9,6 @@ import ( | |||||
| 9 | "fmt" | 9 | "fmt" | ||
| 10 | "io" | 10 | "io" | ||
| 11 | "strings" | 11 | "strings" | ||
| 12 | - | "time" | |||
| 13 | 12 | ||||
| 14 | "connectrpc.com/connect" | 13 | "connectrpc.com/connect" | ||
| 15 | "github.com/spf13/cobra" | 14 | "github.com/spf13/cobra" | ||
| @@ -87,19 +86,13 @@ func newRepoCmd(env *cliEnv) *cobra.Command { | |||||
| 87 | 86 | ||||
| 88 | // listRepos reads every page of repositories. | 87 | // listRepos reads every page of repositories. | ||
| 89 | func listRepos(ctx context.Context, c *client.Client) ([]*ermite.Repo, error) { | 88 | func listRepos(ctx context.Context, c *client.Client) ([]*ermite.Repo, error) { | ||
| 90 | - | var repos []*ermite.Repo | 89 | + | return collect(ctx, func(token string) ([]*ermite.Repo, string, error) { |
| 91 | - | token := "" | |||
| 92 | - | for { | |||
| 93 | res, err := c.Repos().ListRepos(ctx, connect.NewRequest(&ermite.ListReposRequest{PageToken: token})) | 90 | res, err := c.Repos().ListRepos(ctx, connect.NewRequest(&ermite.ListReposRequest{PageToken: token})) | ||
| 94 | if err != nil { | 91 | if err != nil { | ||
| 95 | - | return nil, c.Describe(err) | 92 | + | return nil, "", c.Describe(err) |
| 96 | } | 93 | } | ||
| 97 | - | repos = append(repos, res.Msg.GetRepos()...) | 94 | + | return res.Msg.GetRepos(), res.Msg.GetNextPageToken(), nil |
| 98 | - | token = res.Msg.GetNextPageToken() | 95 | + | }) |
| 99 | - | if token == "" { | |||
| 100 | - | return repos, nil | |||
| 101 | - | } | |||
| 102 | - | } | |||
| 103 | } | 96 | } | ||
| 104 | 97 | ||||
| 105 | // completeRepoNames completes the first argument with repository names. Any | 98 | // completeRepoNames completes the first argument with repository names. Any | ||
| @@ -109,36 +102,28 @@ func completeRepoNames(env *cliEnv) func(*cobra.Command, []string, string) ([]st | |||||
| 109 | if len(args) > 0 { | 102 | if len(args) > 0 { | ||
| 110 | return nil, cobra.ShellCompDirectiveNoFileComp | 103 | return nil, cobra.ShellCompDirectiveNoFileComp | ||
| 111 | } | 104 | } | ||
| 112 | - | names, err := repoNames(env.ctx(cmd), env, cmd, toComplete) | 105 | + | return repoNames(env, cmd, toComplete), cobra.ShellCompDirectiveNoFileComp |
| 113 | - | if err != nil { | |||
| 114 | - | return nil, cobra.ShellCompDirectiveNoFileComp | |||
| 115 | - | } | |||
| 116 | - | return names, cobra.ShellCompDirectiveNoFileComp | |||
| 117 | } | 106 | } | ||
| 118 | } | 107 | } | ||
| 119 | 108 | ||||
| 120 | - | func repoNames(ctx context.Context, env *cliEnv, cmd *cobra.Command, prefix string) ([]string, error) { | 109 | + | // repoNames lists the repository names starting with prefix, with their |
| 121 | - | c, err := env.client(cmd, false) | 110 | + | // visibility and description as completion hints. |
| 122 | - | if err != nil { | 111 | + | func repoNames(env *cliEnv, cmd *cobra.Command, prefix string) []string { |
| 123 | - | return nil, err | 112 | + | return complete(env, cmd, prefix, listRepos, func(r *ermite.Repo) (string, string) { |
| 124 | - | } | 113 | + | desc := visibilityName(r.GetVisibility()) |
| 125 | - | ctx, cancel := context.WithTimeout(ctx, 3*time.Second) | 114 | + | if d := clean(r.GetDescription()); d != "" { |
| 126 | - | defer cancel() | 115 | + | desc += ", " + d |
| 127 | - | repos, err := listRepos(ctx, c) | |||
| 128 | - | if err != nil { | |||
| 129 | - | return nil, err | |||
| 130 | - | } | |||
| 131 | - | var names []string | |||
| 132 | - | for _, r := range repos { | |||
| 133 | - | if strings.HasPrefix(r.GetName(), prefix) { | |||
| 134 | - | desc := visibilityName(r.GetVisibility()) | |||
| 135 | - | if d := clean(r.GetDescription()); d != "" { | |||
| 136 | - | desc += ", " + d | |||
| 137 | - | } | |||
| 138 | - | names = append(names, r.GetName()+"\t"+desc) | |||
| 139 | } | 116 | } | ||
| 117 | + | return r.GetName(), desc | |||
| 118 | + | }) | |||
| 119 | + | } | |||
| 120 | + | ||||
| 121 | + | // outputRepo prints a repository as JSON or as fields. | |||
| 122 | + | func outputRepo(cmd *cobra.Command, r *ermite.Repo, asJSON bool) error { | |||
| 123 | + | if asJSON { | |||
| 124 | + | return writeJSON(cmd.OutOrStdout(), toJSONRepo(r)) | |||
| 140 | } | 125 | } | ||
| 141 | - | return names, nil | 126 | + | return printRepo(cmd.OutOrStdout(), r) |
| 142 | } | 127 | } | ||
| 143 | 128 | ||||
| 144 | func newRepoListCmd(env *cliEnv) *cobra.Command { | 129 | func newRepoListCmd(env *cliEnv) *cobra.Command { | ||
| @@ -214,10 +199,7 @@ func newRepoShowCmd(env *cliEnv) *cobra.Command { | |||||
| 214 | if err != nil { | 199 | if err != nil { | ||
| 215 | return c.Describe(err) | 200 | return c.Describe(err) | ||
| 216 | } | 201 | } | ||
| 217 | - | if *asJSON { | 202 | + | return outputRepo(cmd, res.Msg.GetRepo(), *asJSON) |
| 218 | - | return writeJSON(cmd.OutOrStdout(), toJSONRepo(res.Msg.GetRepo())) | |||
| 219 | - | } | |||
| 220 | - | return printRepo(cmd.OutOrStdout(), res.Msg.GetRepo()) | |||
| 221 | } | 203 | } | ||
| 222 | return cmd | 204 | return cmd | ||
| 223 | } | 205 | } | ||
| @@ -226,7 +208,6 @@ func newRepoCreateCmd(env *cliEnv) *cobra.Command { | |||||
| 226 | var ( | 208 | var ( | ||
| 227 | description string | 209 | description string | ||
| 228 | visibility string | 210 | visibility string | ||
| 229 | - | private bool | |||
| 230 | ) | 211 | ) | ||
| 231 | cmd := &cobra.Command{ | 212 | cmd := &cobra.Command{ | ||
| 232 | Use: "create <name>", | 213 | Use: "create <name>", | ||
| @@ -237,11 +218,10 @@ func newRepoCreateCmd(env *cliEnv) *cobra.Command { | |||||
| 237 | } | 218 | } | ||
| 238 | cmd.Flags().StringVar(&description, "description", "", "one-line description") | 219 | cmd.Flags().StringVar(&description, "description", "", "one-line description") | ||
| 239 | cmd.Flags().StringVar(&visibility, "visibility", visibilityPrivate, "visibility: public or private") | 220 | cmd.Flags().StringVar(&visibility, "visibility", visibilityPrivate, "visibility: public or private") | ||
| 240 | - | cmd.Flags().BoolVar(&private, "private", false, "create a private repository (the default; same as --visibility private)") | |||
| 241 | _ = cmd.RegisterFlagCompletionFunc("visibility", visibilityCompletion) | 221 | _ = cmd.RegisterFlagCompletionFunc("visibility", visibilityCompletion) | ||
| 242 | asJSON := addJSONFlag(cmd) | 222 | asJSON := addJSONFlag(cmd) | ||
| 243 | cmd.RunE = func(cmd *cobra.Command, args []string) error { | 223 | cmd.RunE = func(cmd *cobra.Command, args []string) error { | ||
| 244 | - | req, err := buildCreateRepo(cmd, args[0], description, visibility, private) | 224 | + | req, err := buildCreateRepo(args[0], description, visibility) |
| 245 | if err != nil { | 225 | if err != nil { | ||
| 246 | return err | 226 | return err | ||
| 247 | } | 227 | } | ||
| @@ -253,10 +233,7 @@ func newRepoCreateCmd(env *cliEnv) *cobra.Command { | |||||
| 253 | if err != nil { | 233 | if err != nil { | ||
| 254 | return c.Describe(err) | 234 | return c.Describe(err) | ||
| 255 | } | 235 | } | ||
| 256 | - | if *asJSON { | 236 | + | return outputRepo(cmd, res.Msg.GetRepo(), *asJSON) |
| 257 | - | return writeJSON(cmd.OutOrStdout(), toJSONRepo(res.Msg.GetRepo())) | |||
| 258 | - | } | |||
| 259 | - | return printRepo(cmd.OutOrStdout(), res.Msg.GetRepo()) | |||
| 260 | } | 237 | } | ||
| 261 | return cmd | 238 | return cmd | ||
| 262 | } | 239 | } | ||
| @@ -266,17 +243,11 @@ func visibilityCompletion(*cobra.Command, []string, string) ([]string, cobra.She | |||||
| 266 | } | 243 | } | ||
| 267 | 244 | ||||
| 268 | // buildCreateRepo maps the flags of `repo create` to the request. | 245 | // buildCreateRepo maps the flags of `repo create` to the request. | ||
| 269 | - | func buildCreateRepo(cmd *cobra.Command, name, description, visibility string, private bool) (*ermite.CreateRepoRequest, error) { | 246 | + | func buildCreateRepo(name, description, visibility string) (*ermite.CreateRepoRequest, error) { |
| 270 | vis, err := parseVisibility(visibility) | 247 | vis, err := parseVisibility(visibility) | ||
| 271 | if err != nil { | 248 | if err != nil { | ||
| 272 | return nil, err | 249 | return nil, err | ||
| 273 | } | 250 | } | ||
| 274 | - | if private { | |||
| 275 | - | if cmd.Flags().Changed("visibility") && vis != ermite.Visibility_VISIBILITY_PRIVATE { | |||
| 276 | - | return nil, errors.New("--private conflicts with --visibility " + visibilityPublic) | |||
| 277 | - | } | |||
| 278 | - | vis = ermite.Visibility_VISIBILITY_PRIVATE | |||
| 279 | - | } | |||
| 280 | return &ermite.CreateRepoRequest{Name: name, Description: description, Visibility: vis}, nil | 251 | return &ermite.CreateRepoRequest{Name: name, Description: description, Visibility: vis}, nil | ||
| 281 | } | 252 | } | ||
| 282 | 253 | ||||
| @@ -308,10 +279,7 @@ func newRepoEditCmd(env *cliEnv) *cobra.Command { | |||||
| 308 | if err != nil { | 279 | if err != nil { | ||
| 309 | return c.Describe(err) | 280 | return c.Describe(err) | ||
| 310 | } | 281 | } | ||
| 311 | - | if *asJSON { | 282 | + | return outputRepo(cmd, res.Msg.GetRepo(), *asJSON) |
| 312 | - | return writeJSON(cmd.OutOrStdout(), toJSONRepo(res.Msg.GetRepo())) | |||
| 313 | - | } | |||
| 314 | - | return printRepo(cmd.OutOrStdout(), res.Msg.GetRepo()) | |||
| 315 | } | 283 | } | ||
| 316 | return cmd | 284 | return cmd | ||
| 317 | } | 285 | } | ||
M cmd/ermite/cli_repo_test.go +0 -3
| @@ -114,9 +114,7 @@ func TestRepoCreateMapsFlags(t *testing.T) { | |||
| 114 | 114 | desc string | |
| 115 | 115 | }{ | |
| 116 | 116 | {"default is private", []string{"x"}, ermite.Visibility_VISIBILITY_PRIVATE, ""}, | |
| 117 | - | {"--private", []string{"x", "--private"}, ermite.Visibility_VISIBILITY_PRIVATE, ""}, | |
| 118 | 117 | {"--visibility public", []string{"x", "--visibility", "public", "--description", "d d"}, ermite.Visibility_VISIBILITY_PUBLIC, "d d"}, | |
| 119 | - | {"--visibility private --private", []string{"x", "--visibility", "private", "--private"}, ermite.Visibility_VISIBILITY_PRIVATE, ""}, | |
| 120 | 118 | {"case insensitive", []string{"x", "--visibility", "Public"}, ermite.Visibility_VISIBILITY_PUBLIC, ""}, | |
| 121 | 119 | } | |
| 122 | 120 | for _, tt := range tests { | |
| @@ -140,7 +138,6 @@ func TestRepoCreateRejectsBadFlagsWithoutCalling(t *testing.T) { | |||
| 140 | 138 | url := f.serve(t) | |
| 141 | 139 | for _, args := range [][]string{ | |
| 142 | 140 | {"repo", "create", "x", "--visibility", "secret"}, | |
| 143 | - | {"repo", "create", "x", "--visibility", "public", "--private"}, | |
| 144 | 141 | } { | |
| 145 | 142 | res := runCLI(t, envFor(url), "", args...) | |
| 146 | 143 | require.Error(t, res.err, "%v", args) | |
| @@ -114,9 +114,7 @@ func TestRepoCreateMapsFlags(t *testing.T) { | |||||
| 114 | desc string | 114 | desc string | ||
| 115 | }{ | 115 | }{ | ||
| 116 | {"default is private", []string{"x"}, ermite.Visibility_VISIBILITY_PRIVATE, ""}, | 116 | {"default is private", []string{"x"}, ermite.Visibility_VISIBILITY_PRIVATE, ""}, | ||
| 117 | - | {"--private", []string{"x", "--private"}, ermite.Visibility_VISIBILITY_PRIVATE, ""}, | |||
| 118 | {"--visibility public", []string{"x", "--visibility", "public", "--description", "d d"}, ermite.Visibility_VISIBILITY_PUBLIC, "d d"}, | 117 | {"--visibility public", []string{"x", "--visibility", "public", "--description", "d d"}, ermite.Visibility_VISIBILITY_PUBLIC, "d d"}, | ||
| 119 | - | {"--visibility private --private", []string{"x", "--visibility", "private", "--private"}, ermite.Visibility_VISIBILITY_PRIVATE, ""}, | |||
| 120 | {"case insensitive", []string{"x", "--visibility", "Public"}, ermite.Visibility_VISIBILITY_PUBLIC, ""}, | 118 | {"case insensitive", []string{"x", "--visibility", "Public"}, ermite.Visibility_VISIBILITY_PUBLIC, ""}, | ||
| 121 | } | 119 | } | ||
| 122 | for _, tt := range tests { | 120 | for _, tt := range tests { | ||
| @@ -140,7 +138,6 @@ func TestRepoCreateRejectsBadFlagsWithoutCalling(t *testing.T) { | |||||
| 140 | url := f.serve(t) | 138 | url := f.serve(t) | ||
| 141 | for _, args := range [][]string{ | 139 | for _, args := range [][]string{ | ||
| 142 | {"repo", "create", "x", "--visibility", "secret"}, | 140 | {"repo", "create", "x", "--visibility", "secret"}, | ||
| 143 | - | {"repo", "create", "x", "--visibility", "public", "--private"}, | |||
| 144 | } { | 141 | } { | ||
| 145 | res := runCLI(t, envFor(url), "", args...) | 142 | res := runCLI(t, envFor(url), "", args...) | ||
| 146 | require.Error(t, res.err, "%v", args) | 143 | require.Error(t, res.err, "%v", args) | ||
M cmd/ermite/cli_token.go +8 -31
| @@ -97,11 +97,7 @@ func newTokenCreateCmd(env *cliEnv) *cobra.Command { | |||
| 97 | 97 | return client.ScopeNames(client.AllScopes()), cobra.ShellCompDirectiveNoFileComp | |
| 98 | 98 | }) | |
| 99 | 99 | _ = cmd.RegisterFlagCompletionFunc("repo", func(c *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { | |
| 100 | - | names, err := repoNames(env.ctx(c), env, c, toComplete) | |
| 101 | - | if err != nil { | |
| 102 | - | return nil, cobra.ShellCompDirectiveNoFileComp | |
| 103 | - | } | |
| 104 | - | return names, cobra.ShellCompDirectiveNoFileComp | |
| 100 | + | return repoNames(env, c, toComplete), cobra.ShellCompDirectiveNoFileComp | |
| 105 | 101 | }) | |
| 106 | 102 | asJSON := addJSONFlag(cmd) | |
| 107 | 103 | cmd.RunE = func(cmd *cobra.Command, args []string) error { | |
| @@ -169,19 +165,13 @@ func printCreatedToken(cmd *cobra.Command, res *ermite.CreateTokenResponse, asJS | |||
| 169 | 165 | } | |
| 170 | 166 | ||
| 171 | 167 | func listTokens(ctx context.Context, c *client.Client) ([]*ermite.Token, error) { | |
| 172 | - | var tokens []*ermite.Token | |
| 173 | - | page := "" | |
| 174 | - | for { | |
| 168 | + | return collect(ctx, func(page string) ([]*ermite.Token, string, error) { | |
| 175 | 169 | res, err := c.Tokens().ListTokens(ctx, connect.NewRequest(&ermite.ListTokensRequest{PageToken: page})) | |
| 176 | 170 | if err != nil { | |
| 177 | - | return nil, c.Describe(err) | |
| 171 | + | return nil, "", c.Describe(err) | |
| 178 | 172 | } | |
| 179 | - | tokens = append(tokens, res.Msg.GetTokens()...) | |
| 180 | - | page = res.Msg.GetNextPageToken() | |
| 181 | - | if page == "" { | |
| 182 | - | return tokens, nil | |
| 183 | - | } | |
| 184 | - | } | |
| 173 | + | return res.Msg.GetTokens(), res.Msg.GetNextPageToken(), nil | |
| 174 | + | }) | |
| 185 | 175 | } | |
| 186 | 176 | ||
| 187 | 177 | func newTokenListCmd(env *cliEnv) *cobra.Command { | |
| @@ -238,22 +228,9 @@ func newTokenRevokeCmd(env *cliEnv) *cobra.Command { | |||
| 238 | 228 | if len(args) > 0 { | |
| 239 | 229 | return nil, cobra.ShellCompDirectiveNoFileComp | |
| 240 | 230 | } | |
| 241 | - | c, err := env.client(cmd, false) | |
| 242 | - | if err != nil { | |
| 243 | - | return nil, cobra.ShellCompDirectiveNoFileComp | |
| 244 | - | } | |
| 245 | - | ctx, cancel := context.WithTimeout(env.ctx(cmd), 3*time.Second) | |
| 246 | - | defer cancel() | |
| 247 | - | tokens, err := listTokens(ctx, c) | |
| 248 | - | if err != nil { | |
| 249 | - | return nil, cobra.ShellCompDirectiveNoFileComp | |
| 250 | - | } | |
| 251 | - | var ids []string | |
| 252 | - | for _, t := range tokens { | |
| 253 | - | if strings.HasPrefix(t.GetId(), toComplete) { | |
| 254 | - | ids = append(ids, t.GetId()+"\t"+clean(t.GetLabel())) | |
| 255 | - | } | |
| 256 | - | } | |
| 231 | + | ids := complete(env, cmd, toComplete, listTokens, func(t *ermite.Token) (string, string) { | |
| 232 | + | return t.GetId(), clean(t.GetLabel()) | |
| 233 | + | }) | |
| 257 | 234 | return ids, cobra.ShellCompDirectiveNoFileComp | |
| 258 | 235 | }, | |
| 259 | 236 | RunE: func(cmd *cobra.Command, args []string) error { | |
| @@ -97,11 +97,7 @@ func newTokenCreateCmd(env *cliEnv) *cobra.Command { | |||||
| 97 | return client.ScopeNames(client.AllScopes()), cobra.ShellCompDirectiveNoFileComp | 97 | return client.ScopeNames(client.AllScopes()), cobra.ShellCompDirectiveNoFileComp | ||
| 98 | }) | 98 | }) | ||
| 99 | _ = cmd.RegisterFlagCompletionFunc("repo", func(c *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { | 99 | _ = cmd.RegisterFlagCompletionFunc("repo", func(c *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||
| 100 | - | names, err := repoNames(env.ctx(c), env, c, toComplete) | 100 | + | return repoNames(env, c, toComplete), cobra.ShellCompDirectiveNoFileComp |
| 101 | - | if err != nil { | |||
| 102 | - | return nil, cobra.ShellCompDirectiveNoFileComp | |||
| 103 | - | } | |||
| 104 | - | return names, cobra.ShellCompDirectiveNoFileComp | |||
| 105 | }) | 101 | }) | ||
| 106 | asJSON := addJSONFlag(cmd) | 102 | asJSON := addJSONFlag(cmd) | ||
| 107 | cmd.RunE = func(cmd *cobra.Command, args []string) error { | 103 | cmd.RunE = func(cmd *cobra.Command, args []string) error { | ||
| @@ -169,19 +165,13 @@ func printCreatedToken(cmd *cobra.Command, res *ermite.CreateTokenResponse, asJS | |||||
| 169 | } | 165 | } | ||
| 170 | 166 | ||||
| 171 | func listTokens(ctx context.Context, c *client.Client) ([]*ermite.Token, error) { | 167 | func listTokens(ctx context.Context, c *client.Client) ([]*ermite.Token, error) { | ||
| 172 | - | var tokens []*ermite.Token | 168 | + | return collect(ctx, func(page string) ([]*ermite.Token, string, error) { |
| 173 | - | page := "" | |||
| 174 | - | for { | |||
| 175 | res, err := c.Tokens().ListTokens(ctx, connect.NewRequest(&ermite.ListTokensRequest{PageToken: page})) | 169 | res, err := c.Tokens().ListTokens(ctx, connect.NewRequest(&ermite.ListTokensRequest{PageToken: page})) | ||
| 176 | if err != nil { | 170 | if err != nil { | ||
| 177 | - | return nil, c.Describe(err) | 171 | + | return nil, "", c.Describe(err) |
| 178 | } | 172 | } | ||
| 179 | - | tokens = append(tokens, res.Msg.GetTokens()...) | 173 | + | return res.Msg.GetTokens(), res.Msg.GetNextPageToken(), nil |
| 180 | - | page = res.Msg.GetNextPageToken() | 174 | + | }) |
| 181 | - | if page == "" { | |||
| 182 | - | return tokens, nil | |||
| 183 | - | } | |||
| 184 | - | } | |||
| 185 | } | 175 | } | ||
| 186 | 176 | ||||
| 187 | func newTokenListCmd(env *cliEnv) *cobra.Command { | 177 | func newTokenListCmd(env *cliEnv) *cobra.Command { | ||
| @@ -238,22 +228,9 @@ func newTokenRevokeCmd(env *cliEnv) *cobra.Command { | |||||
| 238 | if len(args) > 0 { | 228 | if len(args) > 0 { | ||
| 239 | return nil, cobra.ShellCompDirectiveNoFileComp | 229 | return nil, cobra.ShellCompDirectiveNoFileComp | ||
| 240 | } | 230 | } | ||
| 241 | - | c, err := env.client(cmd, false) | 231 | + | ids := complete(env, cmd, toComplete, listTokens, func(t *ermite.Token) (string, string) { |
| 242 | - | if err != nil { | 232 | + | return t.GetId(), clean(t.GetLabel()) |
| 243 | - | return nil, cobra.ShellCompDirectiveNoFileComp | 233 | + | }) |
| 244 | - | } | |||
| 245 | - | ctx, cancel := context.WithTimeout(env.ctx(cmd), 3*time.Second) | |||
| 246 | - | defer cancel() | |||
| 247 | - | tokens, err := listTokens(ctx, c) | |||
| 248 | - | if err != nil { | |||
| 249 | - | return nil, cobra.ShellCompDirectiveNoFileComp | |||
| 250 | - | } | |||
| 251 | - | var ids []string | |||
| 252 | - | for _, t := range tokens { | |||
| 253 | - | if strings.HasPrefix(t.GetId(), toComplete) { | |||
| 254 | - | ids = append(ids, t.GetId()+"\t"+clean(t.GetLabel())) | |||
| 255 | - | } | |||
| 256 | - | } | |||
| 257 | return ids, cobra.ShellCompDirectiveNoFileComp | 234 | return ids, cobra.ShellCompDirectiveNoFileComp | ||
| 258 | }, | 235 | }, | ||
| 259 | RunE: func(cmd *cobra.Command, args []string) error { | 236 | RunE: func(cmd *cobra.Command, args []string) error { | ||