Skip to content

Commit 14d2b50

Browse files
committed
WIP: move config to separate module, use rustfmt
1 parent fcb3e1b commit 14d2b50

File tree

3 files changed

+455
-218
lines changed

3 files changed

+455
-218
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
unstable_features = true
2+
3+
format_macro_matchers = true
4+
format_code_in_doc_comments=true
5+
wrap_comments = true
6+
max_width=100
7+
comment_width=100
8+
doc_comment_code_block_width=100
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
//! Configuration for the clippy-annotation-reporter
2+
//!
3+
//! This module handles all configuration-related logic including
4+
//! command-line arguments, GitHub context, and environment variables.
5+
6+
use anyhow::{Context as _, Result};
7+
use clap::Parser;
8+
use serde_json::Value;
9+
use std::env;
10+
use std::fs;
11+
12+
/// Command-line arguments for the clippy-annotation-reporter
13+
#[derive(Parser, Clone, Debug)]
14+
#[command(name = "clippy-annotation-reporter")]
15+
#[command(about = "Reports changes in clippy allow annotations")]
16+
pub struct Args {
17+
/// GitHub token for API access
18+
#[arg(long)]
19+
pub token: String,
20+
/// Comma-separated list of clippy rules to track
21+
#[arg(
22+
long,
23+
default_value = "unwrap_used,expect_used,todo,unimplemented,panic,unreachable"
24+
)]
25+
pub rules: String,
26+
/// GitHub repository (owner/repo) - defaults to current repository
27+
#[arg(long)]
28+
pub repo: Option<String>,
29+
/// Pull request number - defaults to PR from event context
30+
#[arg(long)]
31+
pub pr: Option<u64>,
32+
/// Base branch to compare against (defaults to the PR's base branch)
33+
#[arg(long)]
34+
pub base_branch: Option<String>,
35+
}
36+
37+
impl Args {
38+
/// Create a new Args instance from command-line arguments
39+
pub fn from_cli() -> Result<Self> {
40+
let mut args = Self::parse();
41+
42+
if args.token.is_empty() {
43+
args.token = env::var("GITHUB_TOKEN").map_err(|_| {
44+
anyhow::anyhow!("No token provided and GITHUB_TOKEN environment variable not set")
45+
})?;
46+
}
47+
48+
Ok(args)
49+
}
50+
51+
/// Parse the rules list from the comma-separated string
52+
pub fn parse_rules(&self) -> Vec<String> {
53+
self.rules
54+
.split(',')
55+
.map(|s| s.trim().to_string())
56+
.collect()
57+
}
58+
}
59+
60+
/// GitHub event context extracted from environment
61+
#[derive(Debug)]
62+
pub struct GitHubContext {
63+
pub repository: String,
64+
pub pr_number: u64,
65+
pub event_name: String,
66+
pub base_ref: String,
67+
pub head_ref: String,
68+
}
69+
70+
impl GitHubContext {
71+
/// Try to extract GitHub context from environment variables and event file
72+
pub fn from_env() -> Result<Self> {
73+
// Get repository from env
74+
let repository = env::var("GITHUB_REPOSITORY")
75+
.context("GITHUB_REPOSITORY environment variable not set")?;
76+
77+
// Get event name (pull_request, push, etc.)
78+
let event_name = env::var("GITHUB_EVENT_NAME")
79+
.context("GITHUB_EVENT_NAME environment variable not set")?;
80+
81+
// For PR events, get PR number and refs from event payload
82+
let event_path = env::var("GITHUB_EVENT_PATH")
83+
.context("GITHUB_EVENT_PATH environment variable not set")?;
84+
85+
println!("Event name: {}", event_name);
86+
println!("Event path: {}", event_path);
87+
88+
let event_data =
89+
fs::read_to_string(event_path).context("Failed to read GitHub event file")?;
90+
91+
let event_json: Value =
92+
serde_json::from_str(&event_data).context("Failed to parse GitHub event JSON")?;
93+
94+
// Extract values from event JSON
95+
let (pr_number, base_ref, head_ref) = match event_name.as_str() {
96+
"pull_request" | "pull_request_target" => {
97+
let pr_number = event_json["pull_request"]["number"]
98+
.as_u64()
99+
.context("Could not find pull_request.number in event data")?;
100+
101+
// Direct access to base.ref
102+
let base_ref = match event_json["pull_request"]["base"]["ref"].as_str() {
103+
Some(val) => {
104+
println!("Successfully found base.ref in event data: {}", val);
105+
val.to_string()
106+
}
107+
None => {
108+
println!(
109+
"Warning: Could not extract base.ref as string, raw value: {:?}",
110+
event_json["pull_request"]["base"]["ref"]
111+
);
112+
113+
// Fallback to main if we can't extract the value
114+
println!("Falling back to 'main' as base branch");
115+
"main".to_string()
116+
}
117+
};
118+
119+
// Direct access to head.ref
120+
let head_ref = match event_json["pull_request"]["head"]["ref"].as_str() {
121+
Some(val) => {
122+
println!("Successfully found head.ref in event data: {}", val);
123+
val.to_string()
124+
}
125+
None => {
126+
println!("Warning: Could not extract head.ref as string");
127+
// We'll use the current branch as fallback
128+
String::new()
129+
}
130+
};
131+
132+
(pr_number, base_ref, head_ref)
133+
}
134+
_ => {
135+
// For other events, default values (will be overridden by args)
136+
(0, "main".to_string(), "".to_string())
137+
}
138+
};
139+
140+
println!("Extracted PR number: {}", pr_number);
141+
println!("Extracted base branch: {}", base_ref);
142+
println!("Extracted head branch: {}", head_ref);
143+
144+
Ok(GitHubContext {
145+
repository,
146+
pr_number,
147+
event_name,
148+
base_ref,
149+
head_ref,
150+
})
151+
}
152+
}
153+
154+
/// Configuration combining command-line arguments and GitHub context
155+
pub struct Config {
156+
pub repository: String,
157+
pub pr_number: u64,
158+
pub base_branch: String,
159+
pub head_branch: String,
160+
pub rules: Vec<String>,
161+
pub token: String,
162+
pub owner: String,
163+
pub repo: String,
164+
}
165+
166+
impl Config {
167+
/// Create a new configuration from command-line arguments and GitHub context
168+
pub fn new(args: Args, github_ctx: GitHubContext) -> Result<Self> {
169+
// Use provided values from args if available, otherwise use context
170+
// TODO: EK - FIX THIS CLONE
171+
let repository = args.clone().repo.unwrap_or(github_ctx.repository);
172+
173+
let pr_number = match args.pr {
174+
Some(pr) => pr,
175+
None => {
176+
if github_ctx.pr_number == 0 {
177+
return Err(anyhow::anyhow!(
178+
"No PR number found in event context. Please provide --pr argument."
179+
));
180+
}
181+
github_ctx.pr_number
182+
}
183+
};
184+
185+
// Set base branch (default to the PR's base branch or 'main')
186+
// TODO: EK - FIX THIS CLONE
187+
// TODO: EK - unclear if we even need command line args for this
188+
let base_branch_arg = args.clone().base_branch.unwrap_or_default();
189+
190+
let base_branch = if !base_branch_arg.is_empty() {
191+
format!("origin/{}", base_branch_arg)
192+
} else {
193+
if !github_ctx.base_ref.is_empty() {
194+
format!("origin/{}", github_ctx.base_ref)
195+
} else {
196+
"origin/main".to_string()
197+
}
198+
};
199+
200+
// Set head branch (PR's head branch)
201+
let head_branch = if !github_ctx.head_ref.is_empty() {
202+
format!("origin/{}", github_ctx.head_ref)
203+
} else {
204+
env::var("GITHUB_HEAD_REF")
205+
.map(|ref_name| format!("origin/{}", ref_name))
206+
.unwrap_or_else(|_| "HEAD".to_string())
207+
};
208+
209+
// Parse repository into owner and repo
210+
let parts: Vec<&str> = repository.split('/').collect();
211+
if parts.len() != 2 {
212+
return Err(anyhow::anyhow!(
213+
"Invalid repository format. Expected 'owner/repo', got '{}'",
214+
repository
215+
));
216+
}
217+
218+
let owner = parts[0].to_string();
219+
let repo = parts[1].to_string();
220+
221+
// Parse rules list
222+
let rules = args.parse_rules();
223+
224+
Ok(Config {
225+
repository,
226+
pr_number,
227+
base_branch,
228+
head_branch,
229+
rules,
230+
owner,
231+
repo,
232+
token: args.token,
233+
})
234+
}
235+
}
236+
237+
/// Builder for creating Config instances
238+
pub struct ConfigBuilder {
239+
args: Option<Args>,
240+
github_ctx: Option<GitHubContext>,
241+
}
242+
243+
impl ConfigBuilder {
244+
/// Create a new empty builder
245+
pub fn new() -> Self {
246+
Self {
247+
args: None,
248+
github_ctx: None,
249+
}
250+
}
251+
252+
/// Set the command-line arguments
253+
pub fn with_args(mut self, args: Args) -> Self {
254+
self.args = Some(args);
255+
self
256+
}
257+
258+
/// Set the GitHub context
259+
pub fn with_github_context(mut self, github_ctx: GitHubContext) -> Self {
260+
self.github_ctx = Some(github_ctx);
261+
self
262+
}
263+
264+
/// Build the Config instance, using defaults for any unset values
265+
pub fn build(self) -> Result<Config> {
266+
// Get command line arguments if not provided
267+
let args = match self.args {
268+
Some(args) => args,
269+
None => Args::from_cli()?,
270+
};
271+
272+
// Get GitHub context if not provided
273+
let github_ctx = match self.github_ctx {
274+
Some(ctx) => ctx,
275+
None => GitHubContext::from_env()?,
276+
};
277+
278+
Config::new(args, github_ctx)
279+
}
280+
}

0 commit comments

Comments
 (0)