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 }