package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"strings"
"time"
"go.fcuny.net/x/internal/github"
)
type Config struct {
Token string
Repo string
StartDate time.Time
EndDate time.Time
APIURL string
}
func parseFlags() (*Config, error) {
config := &Config{}
flag.StringVar(&config.Token, "token", "", "GitHub personal access token (required)")
flag.StringVar(&config.Repo, "repo", "", "Repository in owner/repo format (required)")
startDateStr := flag.String(
"start",
time.Now().AddDate(0, -1, 0).Format("2006-01-02"),
"Start date (YYYY-MM-DD)",
)
endDateStr := flag.String("end", time.Now().Format("2006-01-02"), "End date (YYYY-MM-DD)")
flag.StringVar(
&config.APIURL,
"api-url",
"https://api.github.com",
"GitHub API URL (for GitHub Enterprise)",
)
flag.Parse()
if config.Token == "" {
return nil, fmt.Errorf("--token is required")
}
if config.Repo == "" {
return nil, fmt.Errorf("--repo is required")
}
startDate, err := time.Parse("2006-01-02", *startDateStr)
if err != nil {
return nil, fmt.Errorf("invalid start date format: %v", err)
}
config.StartDate = startDate
endDate, err := time.Parse("2006-01-02", *endDateStr)
if err != nil {
return nil, fmt.Errorf("invalid end date format: %v", err)
}
config.EndDate = endDate
return config, nil
}
func convertToAnalyzerTypes(
prs []github.PullRequest,
reviews map[int][]github.Review,
comments map[int][]github.ReviewComment,
) []PRData {
var prData []PRData
for _, pr := range prs {
prReviews := reviews[pr.Number]
if prReviews == nil {
prReviews = []github.Review{}
}
prComments := comments[pr.Number]
if prComments == nil {
prComments = []github.ReviewComment{}
}
commentsByReview := make(map[int64][]CommentData)
for _, comment := range prComments {
commentsByReview[comment.ReviewID] = append(
commentsByReview[comment.ReviewID],
CommentData{
Reviewer: comment.User.Login,
CreatedAt: comment.CreatedAt,
Path: comment.Path,
Line: comment.Line,
},
)
}
var reviewData []ReviewData
for _, review := range prReviews {
reviewData = append(reviewData, ReviewData{
Reviewer: review.User.Login,
State: review.State,
SubmittedAt: review.SubmittedAt,
Comments: commentsByReview[review.ID],
})
}
prData = append(prData, PRData{
Number: pr.Number,
CreatedAt: pr.CreatedAt,
Reviews: reviewData,
})
}
return prData
}
func runAnalysis(ctx context.Context, config *Config) error {
client := github.NewClient(config.Token, config.APIURL)
analyzer := NewAnalyzer(config.StartDate, config.EndDate)
formatter := NewFormatter(config.Repo, config.StartDate, config.EndDate)
parts := strings.Split(config.Repo, "/")
if len(parts) != 2 {
return fmt.Errorf("invalid repo format: %s (expected owner/repo)", config.Repo)
}
owner, repo := parts[0], parts[1]
fmt.Print(formatter.FormatProgress("Fetching pull requests..."))
prs, err := client.GetPullRequests(ctx, owner, repo, config.StartDate, config.EndDate)
if err != nil {
return fmt.Errorf("fetching pull requests: %w", err)
}
reviews := make(map[int][]github.Review)
comments := make(map[int][]github.ReviewComment)
for i, pr := range prs {
fmt.Print(
formatter.FormatProgress(
fmt.Sprintf("Processing PR %d/%d: #%d", i+1, len(prs), pr.Number),
),
)
prReviews, err := client.GetPullRequestReviews(ctx, owner, repo, pr.Number)
if err != nil {
return fmt.Errorf("fetching reviews for PR #%d: %w", pr.Number, err)
}
reviews[pr.Number] = prReviews
prComments, err := client.GetPullRequestReviewComments(ctx, owner, repo, pr.Number)
if err != nil {
return fmt.Errorf("fetching comments for PR #%d: %w", pr.Number, err)
}
comments[pr.Number] = prComments
}
prData := convertToAnalyzerTypes(prs, reviews, comments)
fmt.Print(formatter.FormatProgress("Analyzing PR data..."))
metrics, err := analyzer.AnalyzePRs(prData)
if err != nil {
return fmt.Errorf("analyzing PRs: %w", err)
}
fmt.Print(formatter.FormatTable(metrics))
return nil
}
func main() {
config, err := parseFlags()
if err != nil {
log.Printf("Error: %v\n", err)
flag.Usage()
os.Exit(1)
}
ctx := context.Background()
if err := runAnalysis(ctx, config); err != nil {
log.Printf("Error: %v\n", err)
os.Exit(1)
}
}