github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/label/create.go (about)

     1  package label
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"math/rand"
     9  	"net/http"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/MakeNowJust/heredoc"
    14  	"github.com/ungtb10d/cli/v2/api"
    15  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
    16  	"github.com/ungtb10d/cli/v2/pkg/cmdutil"
    17  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    18  	"github.com/spf13/cobra"
    19  )
    20  
    21  var randomColors = []string{
    22  	"B60205",
    23  	"D93F0B",
    24  	"FBCA04",
    25  	"0E8A16",
    26  	"006B75",
    27  	"1D76DB",
    28  	"0052CC",
    29  	"5319E7",
    30  	"E99695",
    31  	"F9D0C4",
    32  	"FEF2C0",
    33  	"C2E0C6",
    34  	"BFDADC",
    35  	"C5DEF5",
    36  	"BFD4F2",
    37  	"D4C5F9",
    38  }
    39  
    40  type createOptions struct {
    41  	BaseRepo   func() (ghrepo.Interface, error)
    42  	HttpClient func() (*http.Client, error)
    43  	IO         *iostreams.IOStreams
    44  
    45  	Color       string
    46  	Description string
    47  	Name        string
    48  	Force       bool
    49  }
    50  
    51  func newCmdCreate(f *cmdutil.Factory, runF func(*createOptions) error) *cobra.Command {
    52  	opts := createOptions{
    53  		HttpClient: f.HttpClient,
    54  		IO:         f.IOStreams,
    55  	}
    56  
    57  	cmd := &cobra.Command{
    58  		Use:   "create <name>",
    59  		Short: "Create a new label",
    60  		Long: heredoc.Doc(`
    61  			Create a new label on GitHub, or updates an existing one with --force.
    62  
    63  			Must specify name for the label. The description and color are optional.
    64  			If a color isn't provided, a random one will be chosen.
    65  
    66  			The label color needs to be 6 character hex value.
    67  		`),
    68  		Example: heredoc.Doc(`
    69  			# create new bug label
    70  			$ gh label create bug --description "Something isn't working" --color E99695
    71  		`),
    72  		Args: cmdutil.ExactArgs(1, "cannot create label: name argument required"),
    73  		RunE: func(c *cobra.Command, args []string) error {
    74  			opts.BaseRepo = f.BaseRepo
    75  			opts.Name = args[0]
    76  			opts.Color = strings.TrimPrefix(opts.Color, "#")
    77  			if runF != nil {
    78  				return runF(&opts)
    79  			}
    80  			return createRun(&opts)
    81  		},
    82  	}
    83  
    84  	cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Description of the label")
    85  	cmd.Flags().StringVarP(&opts.Color, "color", "c", "", "Color of the label")
    86  	cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Update the label color and description if label already exists")
    87  
    88  	return cmd
    89  }
    90  
    91  var errLabelAlreadyExists = errors.New("label already exists")
    92  
    93  func createRun(opts *createOptions) error {
    94  	httpClient, err := opts.HttpClient()
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	baseRepo, err := opts.BaseRepo()
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	if opts.Color == "" {
   105  		rand.Seed(time.Now().UnixNano())
   106  		opts.Color = randomColors[rand.Intn(len(randomColors)-1)]
   107  	}
   108  
   109  	opts.IO.StartProgressIndicator()
   110  	err = createLabel(httpClient, baseRepo, opts)
   111  	opts.IO.StopProgressIndicator()
   112  	if err != nil {
   113  		if errors.Is(err, errLabelAlreadyExists) {
   114  			return fmt.Errorf("label with name %q already exists; use `--force` to update its color and description", opts.Name)
   115  		}
   116  		return err
   117  	}
   118  
   119  	if opts.IO.IsStdoutTTY() {
   120  		cs := opts.IO.ColorScheme()
   121  		successMsg := fmt.Sprintf("%s Label %q created in %s\n", cs.SuccessIcon(), opts.Name, ghrepo.FullName(baseRepo))
   122  		fmt.Fprint(opts.IO.Out, successMsg)
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func createLabel(client *http.Client, repo ghrepo.Interface, opts *createOptions) error {
   129  	apiClient := api.NewClientFromHTTP(client)
   130  	path := fmt.Sprintf("repos/%s/%s/labels", repo.RepoOwner(), repo.RepoName())
   131  	requestByte, err := json.Marshal(map[string]string{
   132  		"name":        opts.Name,
   133  		"description": opts.Description,
   134  		"color":       opts.Color,
   135  	})
   136  	if err != nil {
   137  		return err
   138  	}
   139  	requestBody := bytes.NewReader(requestByte)
   140  	result := label{}
   141  	err = apiClient.REST(repo.RepoHost(), "POST", path, requestBody, &result)
   142  
   143  	if httpError, ok := err.(api.HTTPError); ok && isLabelAlreadyExistsError(httpError) {
   144  		err = errLabelAlreadyExists
   145  	}
   146  
   147  	if opts.Force && errors.Is(err, errLabelAlreadyExists) {
   148  		editOpts := editOptions{
   149  			Description: opts.Description,
   150  			Color:       opts.Color,
   151  			Name:        opts.Name,
   152  		}
   153  		return updateLabel(apiClient, repo, &editOpts)
   154  	}
   155  
   156  	return err
   157  }
   158  
   159  func updateLabel(apiClient *api.Client, repo ghrepo.Interface, opts *editOptions) error {
   160  	path := fmt.Sprintf("repos/%s/%s/labels/%s", repo.RepoOwner(), repo.RepoName(), opts.Name)
   161  	properties := map[string]string{}
   162  	if opts.Description != "" {
   163  		properties["description"] = opts.Description
   164  	}
   165  	if opts.Color != "" {
   166  		properties["color"] = opts.Color
   167  	}
   168  	if opts.NewName != "" {
   169  		properties["new_name"] = opts.NewName
   170  	}
   171  	requestByte, err := json.Marshal(properties)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	requestBody := bytes.NewReader(requestByte)
   176  	result := label{}
   177  	err = apiClient.REST(repo.RepoHost(), "PATCH", path, requestBody, &result)
   178  
   179  	if httpError, ok := err.(api.HTTPError); ok && isLabelAlreadyExistsError(httpError) {
   180  		err = errLabelAlreadyExists
   181  	}
   182  
   183  	return err
   184  }
   185  
   186  func isLabelAlreadyExistsError(err api.HTTPError) bool {
   187  	return err.StatusCode == 422 && len(err.Errors) == 1 && err.Errors[0].Field == "name" && err.Errors[0].Code == "already_exists"
   188  }