package codespace import ( "context" "errors" "fmt" "strings" "github.com/cli/cli/v2/internal/codespaces/api" "github.com/spf13/cobra" ) type CodespaceSelector struct { api apiClient repoName string codespaceName string repoOwner string } var errNoFilteredCodespaces = errors.New("you have no codespaces meeting the filter criteria") // AddCodespaceSelector adds persistent flags for selecting a codespace to the given command and returns a CodespaceSelector which applies them func AddCodespaceSelector(cmd *cobra.Command, api apiClient) *CodespaceSelector { cs := &CodespaceSelector{api: api} cmd.PersistentFlags().StringVarP(&cs.codespaceName, "codespace", "c", "", "Name of the codespace") cmd.PersistentFlags().StringVarP(&cs.repoName, "repo", "R", "", "Filter codespace selection by repository name (user/repo)") cmd.PersistentFlags().StringVar(&cs.repoOwner, "repo-owner", "", "Filter codespace selection by repository owner (username or org)") cmd.MarkFlagsMutuallyExclusive("codespace", "repo") cmd.MarkFlagsMutuallyExclusive("codespace", "repo-owner") return cs } func (cs *CodespaceSelector) Select(ctx context.Context) (codespace *api.Codespace, err error) { if cs.codespaceName != "" { codespace, err = cs.api.GetCodespace(ctx, cs.codespaceName, true) if err != nil { return nil, fmt.Errorf("getting full codespace details: %w", err) } } else { codespaces, err := cs.fetchCodespaces(ctx) if err != nil { return nil, err } codespace, err = cs.chooseCodespace(ctx, codespaces) if err != nil { return nil, err } } if codespace.PendingOperation { return nil, fmt.Errorf( "codespace is disabled while it has a pending operation: %s", codespace.PendingOperationDisabledReason, ) } return codespace, nil } func (cs *CodespaceSelector) SelectName(ctx context.Context) (string, error) { if cs.codespaceName != "" { return cs.codespaceName, nil } codespaces, err := cs.fetchCodespaces(ctx) if err != nil { return "", err } codespace, err := cs.chooseCodespace(ctx, codespaces) if err != nil { return "", err } return codespace.Name, nil } func (cs *CodespaceSelector) fetchCodespaces(ctx context.Context) (codespaces []*api.Codespace, err error) { codespaces, err = cs.api.ListCodespaces(ctx, api.ListCodespacesOptions{}) if err != nil { return nil, fmt.Errorf("error getting codespaces: %w", err) } if len(codespaces) == 0 { return nil, errNoCodespaces } // Note that repo filtering done here can also be done in api.ListCodespaces. // We do it here instead so that we can differentiate no codespaces in general vs. none after filtering. if cs.repoName != "" { var filteredCodespaces []*api.Codespace for _, c := range codespaces { if !strings.EqualFold(c.Repository.FullName, cs.repoName) { continue } filteredCodespaces = append(filteredCodespaces, c) } codespaces = filteredCodespaces } if cs.repoOwner != "" { codespaces = filterCodespacesByRepoOwner(codespaces, cs.repoOwner) } if len(codespaces) == 0 { return nil, errNoFilteredCodespaces } return codespaces, err } func (cs *CodespaceSelector) chooseCodespace(ctx context.Context, codespaces []*api.Codespace) (codespace *api.Codespace, err error) { skipPromptForSingleOption := cs.repoName != "" codespace, err = chooseCodespaceFromList(ctx, codespaces, false, skipPromptForSingleOption) if err != nil { if err == errNoCodespaces { return nil, err } return nil, fmt.Errorf("choosing codespace: %w", err) } return codespace, nil }