Skip to content

Commit 0d05bfb

Browse files
authored
Create magician cmd flow to to handle arbitrary commands (#15669)
1 parent 6fd16e2 commit 0d05bfb

File tree

4 files changed

+566
-4
lines changed

4 files changed

+566
-4
lines changed

.ci/magician/cmd/parse_comment.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"regexp"
7+
"strings"
8+
9+
"magician/github"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
// This regex captures the entire line starting with @modular-magician
15+
// Example: "@modular-magician reassign-reviewer user1" or "@modular-magician assign review @user2"
16+
var magicianInvocationRegex = regexp.MustCompile(`@modular-magician\s+([^\n\r]+)`)
17+
18+
// Command patterns for reassign-reviewer with flexible syntax
19+
// Supports: assign-reviewer, reassign-reviewer, assign reviewer, reassign review, etc.
20+
// Captures only valid GitHub usernames: [a-zA-Z0-9-_]
21+
var reassignReviewerRegex = regexp.MustCompile(`^(?:re)?assign[- ]?review(?:er)?\s*@?([a-zA-Z0-9-_]*)`)
22+
23+
var parseCommentCmd = &cobra.Command{
24+
Use: "parse-comment PR_NUMBER COMMENT_AUTHOR",
25+
Short: "Parses a comment from the COMMENT_BODY env var to execute magician commands",
26+
Long: `This command parses GitHub PR comments for @modular-magician invocations.
27+
28+
It supports flexible command syntax including:
29+
- Commands with hyphens: reassign-reviewer
30+
- Commands with spaces: reassign reviewer
31+
- Optional prefixes and suffixes: assign-review, reassign-reviewer
32+
- Optional @ prefix for usernames
33+
34+
The command expects the comment body to be provided in the COMMENT_BODY environment variable and also requires:
35+
1. PR_NUMBER - The pull request number
36+
2. COMMENT_AUTHOR - The GitHub username who made the comment`,
37+
Args: cobra.ExactArgs(2),
38+
RunE: func(cmd *cobra.Command, args []string) error {
39+
prNumber := args[0]
40+
author := args[1]
41+
42+
githubToken, ok := os.LookupEnv("GITHUB_TOKEN")
43+
if !ok {
44+
return fmt.Errorf("did not provide GITHUB_TOKEN environment variable")
45+
}
46+
gh := github.NewClient(githubToken)
47+
48+
if gh.GetUserType(author) != github.CoreContributorUserType {
49+
return fmt.Errorf("comment author %s is not a core contributor", author)
50+
}
51+
52+
comment, ok := os.LookupEnv("COMMENT_BODY")
53+
if !ok {
54+
return fmt.Errorf("did not provide COMMENT_BODY environment variable")
55+
}
56+
if comment == "" {
57+
fmt.Println("COMMENT_BODY is empty. Ignoring.")
58+
return nil
59+
}
60+
61+
return execParseComment(prNumber, comment, gh)
62+
},
63+
}
64+
65+
// execParseComment is the main router that finds and executes the first command
66+
func execParseComment(prNumber, comment string, gh GithubClient) error {
67+
// Find the first @modular-magician invocation in the comment
68+
match := magicianInvocationRegex.FindStringSubmatch(comment)
69+
70+
if match == nil {
71+
fmt.Println("No @modular-magician invocation found. Ignoring comment.")
72+
return nil
73+
}
74+
75+
if len(match) < 2 {
76+
fmt.Printf("Invalid match structure. Ignoring.\n")
77+
return nil
78+
}
79+
80+
commandLine := strings.TrimSpace(match[1])
81+
if commandLine == "" {
82+
fmt.Printf("Empty command after @modular-magician. Ignoring.\n")
83+
return nil
84+
}
85+
86+
fmt.Printf("Processing command: %q\n", commandLine)
87+
88+
// Route to appropriate handler based on command pattern
89+
return routeCommand(prNumber, commandLine, gh)
90+
}
91+
92+
// routeCommand determines which command handler to call based on the command pattern
93+
func routeCommand(prNumber, commandLine string, gh GithubClient) error {
94+
// Check for reassign-reviewer command variants
95+
if matches := reassignReviewerRegex.FindStringSubmatch(commandLine); matches != nil {
96+
reviewer := strings.TrimSpace(matches[1])
97+
return handleReassignReviewer(prNumber, reviewer, gh)
98+
}
99+
100+
// Add more command patterns here as needed
101+
// Example for future commands:
102+
// if matches := cherryPickRegex.FindStringSubmatch(commandLine); matches != nil {
103+
// return handleCherryPick(prNumber, matches[1:], gh)
104+
// }
105+
106+
fmt.Printf("Unknown command format: %q\n", commandLine)
107+
return nil
108+
}
109+
110+
// handleReassignReviewer processes the reassign-reviewer command
111+
func handleReassignReviewer(prNumber, reviewer string, _ GithubClient) error {
112+
// The regex already extracted just the username without @
113+
// and only allows valid GitHub username characters [a-zA-Z0-9-_]
114+
115+
fmt.Printf("Reassigning reviewer for PR #%s", prNumber)
116+
if reviewer != "" {
117+
fmt.Printf(" to @%s", reviewer)
118+
} else {
119+
fmt.Printf(" (selecting random reviewer)")
120+
}
121+
fmt.Println()
122+
123+
fmt.Printf("[DEBUG] - placeholder call - prNumber: %s, reviewer %s\n", prNumber, reviewer)
124+
return nil
125+
126+
// Call the existing reassign reviewer logic
127+
// return execReassignReviewer(prNumber, reviewer, gh)
128+
}
129+
130+
func init() {
131+
rootCmd.AddCommand(parseCommentCmd)
132+
}

0 commit comments

Comments
 (0)