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) } }