github.com/zaquestion/lab@v0.25.1/cmd/issue_create.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strconv"
     9  	"strings"
    10  	"text/template"
    11  
    12  	"github.com/MakeNowJust/heredoc/v2"
    13  	"github.com/rsteube/carapace"
    14  	"github.com/spf13/cobra"
    15  	gitlab "github.com/xanzy/go-gitlab"
    16  	"github.com/zaquestion/lab/internal/action"
    17  	"github.com/zaquestion/lab/internal/git"
    18  	lab "github.com/zaquestion/lab/internal/gitlab"
    19  )
    20  
    21  var issueCreateCmd = &cobra.Command{
    22  	Use:     "create [remote]",
    23  	Aliases: []string{"new"},
    24  	Short:   "Open an issue on GitLab",
    25  	Args:    cobra.MaximumNArgs(1),
    26  	Example: heredoc.Doc(`
    27  		lab issue create
    28  		lab issue create origin -a johndoe -a janedoe
    29  		lab issue create origin -l bug
    30  		lab issue create upstream -m "new issue related to the --help arg"
    31  		lab issue create upstream --milestone "July"
    32  		lab issue create upstream --template "API-BUG"`),
    33  	PersistentPreRun: labPersistentPreRun,
    34  	Run: func(cmd *cobra.Command, args []string) {
    35  		msgs, err := cmd.Flags().GetStringArray("message")
    36  		if err != nil {
    37  			log.Fatal(err)
    38  		}
    39  		assignees, err := cmd.Flags().GetStringSlice("assignees")
    40  		if err != nil {
    41  			log.Fatal(err)
    42  		}
    43  		labelTerms, err := cmd.Flags().GetStringSlice("label")
    44  		if err != nil {
    45  			log.Fatal(err)
    46  		}
    47  		milestoneName, err := cmd.Flags().GetString("milestone")
    48  		if err != nil {
    49  			log.Fatal(err)
    50  		}
    51  		templateName, err := cmd.Flags().GetString("template")
    52  		if err != nil {
    53  			log.Fatal(err)
    54  		}
    55  		remote := defaultRemote
    56  		if len(args) > 0 {
    57  			ok, err := git.IsRemote(args[0])
    58  			if err != nil {
    59  				log.Fatal(err)
    60  			}
    61  			if ok {
    62  				remote = args[0]
    63  			}
    64  		}
    65  		rn, err := git.PathWithNamespace(remote)
    66  		if err != nil {
    67  			log.Fatal(err)
    68  		}
    69  
    70  		labels, err := mapLabels(rn, labelTerms)
    71  		if err != nil {
    72  			log.Fatal(err)
    73  		}
    74  
    75  		var milestoneID *int
    76  		if milestoneName != "" {
    77  			milestone, err := lab.MilestoneGet(rn, milestoneName)
    78  			if err != nil {
    79  				log.Fatal(err)
    80  			}
    81  			milestoneID = &milestone.ID
    82  		}
    83  
    84  		title, body, err := issueMsg(templateName, msgs)
    85  		if err != nil {
    86  			_, f, l, _ := runtime.Caller(0)
    87  			log.Fatal(f+":"+strconv.Itoa(l)+" ", err)
    88  		}
    89  		if title == "" {
    90  			log.Fatal("aborting issue due to empty issue msg")
    91  		}
    92  
    93  		linebreak, _ := cmd.Flags().GetBool("force-linebreak")
    94  		if linebreak {
    95  			body = textToMarkdown(body)
    96  		}
    97  
    98  		assigneeIDs := make([]int, len(assignees))
    99  		for i, a := range assignees {
   100  			assigneeIDs[i] = *getUserID(a)
   101  		}
   102  
   103  		issueURL, err := lab.IssueCreate(rn, &gitlab.CreateIssueOptions{
   104  			Title:       &title,
   105  			Description: &body,
   106  			Labels:      labels,
   107  			AssigneeIDs: assigneeIDs,
   108  			MilestoneID: milestoneID,
   109  		})
   110  		if err != nil {
   111  			log.Fatal(err)
   112  		}
   113  		fmt.Println(issueURL)
   114  	},
   115  }
   116  
   117  func issueMsg(templateName string, msgs []string) (string, string, error) {
   118  	if len(msgs) > 0 {
   119  		return msgs[0], strings.Join(msgs[1:], "\n\n"), nil
   120  	}
   121  
   122  	text, err := issueText(templateName)
   123  	if err != nil {
   124  		return "", "", err
   125  	}
   126  	return git.Edit("ISSUE", text)
   127  }
   128  
   129  func issueText(templateName string) (string, error) {
   130  	tmpl := heredoc.Doc(`
   131  		{{.InitMsg}}
   132  		{{.CommentChar}} Write a message for this issue. The first block
   133  		{{.CommentChar}} of text is the title and the rest is the description.`)
   134  
   135  	templateFile := filepath.Join("issue_templates", templateName)
   136  	templateFile += ".md"
   137  	issueTmpl := lab.LoadGitLabTmpl(templateFile)
   138  
   139  	initMsg := "\n"
   140  	if issueTmpl != "" {
   141  		initMsg = "\n\n" + issueTmpl
   142  	}
   143  
   144  	commentChar := git.CommentChar()
   145  
   146  	t, err := template.New("tmpl").Parse(tmpl)
   147  	if err != nil {
   148  		return "", err
   149  	}
   150  
   151  	msg := &struct {
   152  		InitMsg     string
   153  		CommentChar string
   154  	}{
   155  		InitMsg:     initMsg,
   156  		CommentChar: commentChar,
   157  	}
   158  
   159  	var b bytes.Buffer
   160  	err = t.Execute(&b, msg)
   161  	if err != nil {
   162  		return "", err
   163  	}
   164  
   165  	return b.String(), nil
   166  }
   167  
   168  func init() {
   169  	issueCreateCmd.Flags().StringArrayP("message", "m", []string{}, "use the given <msg>; multiple -m are concatenated as separate paragraphs")
   170  	issueCreateCmd.Flags().StringSliceP("label", "l", []string{}, "set the given label(s) on the created issue")
   171  	issueCreateCmd.Flags().StringSliceP("assignees", "a", []string{}, "set assignees by username")
   172  	issueCreateCmd.Flags().String("milestone", "", "set milestone by title")
   173  	issueCreateCmd.Flags().StringP("template", "t", "default", "use the given issue template")
   174  	issueCreateCmd.Flags().Bool("force-linebreak", false, "append 2 spaces to the end of each line to force markdown linebreaks")
   175  
   176  	issueCmd.AddCommand(issueCreateCmd)
   177  
   178  	carapace.Gen(issueCreateCmd).FlagCompletion(carapace.ActionMap{
   179  		"label": carapace.ActionMultiParts(",", func(c carapace.Context) carapace.Action {
   180  			project, _, err := parseArgsRemoteAndProject(c.Args)
   181  			if err != nil {
   182  				return carapace.ActionMessage(err.Error())
   183  			}
   184  			return action.Labels(project).Invoke(c).Filter(c.Parts).ToA()
   185  		}),
   186  		"milestone": carapace.ActionCallback(func(c carapace.Context) carapace.Action {
   187  			project, _, err := parseArgsRemoteAndProject(c.Args)
   188  			if err != nil {
   189  				return carapace.ActionMessage(err.Error())
   190  			}
   191  			return action.Milestones(project, action.MilestoneOpts{Active: true})
   192  		}),
   193  	})
   194  
   195  	carapace.Gen(issueCreateCmd).PositionalCompletion(
   196  		action.Remotes(),
   197  	)
   198  }