Skip to content

Commit 730f863

Browse files
committed
FIXME: partially functional improvements
- need to fix CLI formatting
1 parent 23d2f19 commit 730f863

File tree

3 files changed

+224
-3
lines changed

3 files changed

+224
-3
lines changed

src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ fn cli() -> Result<(), Error> {
139139
}
140140
}
141141
Command::ForTeam { name } => {
142-
println!("Team: {name}")
142+
let team_ownership = ownership.for_team(&name);
143+
match team_ownership {
144+
Ok(output) => println!("{:?}", output),
145+
Err(err) => eprintln!("{}", err),
146+
}
143147
}
144148
}
145149

src/ownership.rs

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ pub struct FileOwner {
3232
pub sources: Vec<Source>,
3333
}
3434

35+
#[derive(Debug, Default, Clone, PartialEq)]
36+
pub struct TeamOwnership {
37+
pub heading: String,
38+
pub globs: Vec<String>,
39+
}
40+
3541
impl Display for FileOwner {
3642
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3743
let sources = self
@@ -121,6 +127,14 @@ impl Ownership {
121127
.collect())
122128
}
123129

130+
#[instrument(level = "debug", skip_all)]
131+
pub fn for_team(&self, team_name: &str) -> Result<Vec<TeamOwnership>, Box<dyn std::error::Error>> {
132+
info!("getting team ownership for {}", team_name);
133+
let team = self.project.get_team(team_name).ok_or("Team not found")?;
134+
135+
parse_for_team(team.github_team, &self.project.codeowners_file)
136+
}
137+
124138
#[instrument(level = "debug", skip_all)]
125139
pub fn generate_file(&self) -> String {
126140
info!("generating codeowners file");
@@ -141,9 +155,42 @@ impl Ownership {
141155
}
142156
}
143157

158+
fn parse_for_team(team_name: String, codeowners_file: &str) -> Result<Vec<TeamOwnership>, Box<dyn std::error::Error>> {
159+
let mut output = vec![];
160+
let mut current_section = TeamOwnership::default();
161+
let input: String = codeowners_file.replace(&FileGenerator::disclaimer().join("\n"), "");
162+
163+
for line in input.trim_start().lines() {
164+
match line {
165+
comment if comment.starts_with("#") => {
166+
current_section.heading = comment.to_string();
167+
}
168+
"" => {
169+
if current_section != TeamOwnership::default() {
170+
output.push(current_section.clone());
171+
current_section = TeamOwnership::default();
172+
}
173+
}
174+
team_line if team_line.ends_with(&team_name) => {
175+
let v: Vec<&str> = team_line.split(' ').collect();
176+
let s = v.first().ok_or("malformed team line")?;
177+
178+
current_section.globs.push(s.to_string());
179+
}
180+
_ => {}
181+
}
182+
}
183+
if current_section != TeamOwnership::default() {
184+
output.push(current_section.clone());
185+
}
186+
187+
Ok(output)
188+
}
189+
144190
#[cfg(test)]
145191
mod tests {
146-
use crate::common_test::tests::build_ownership_with_all_mappers;
192+
use super::*;
193+
use crate::common_test::tests::{build_ownership_with_all_mappers, vecs_match};
147194

148195
#[test]
149196
fn test_for_file_owner() -> Result<(), Box<dyn std::error::Error>> {
@@ -162,4 +209,133 @@ mod tests {
162209
assert_eq!(file_owners.len(), 0);
163210
Ok(())
164211
}
212+
213+
#[test]
214+
fn test_for_team_not_found() -> Result<(), Box<dyn std::error::Error>> {
215+
let ownership = build_ownership_with_all_mappers()?;
216+
let team_ownership = ownership.for_team("Nope");
217+
assert!(team_ownership.is_err(), "Team not found");
218+
Ok(())
219+
}
220+
221+
#[test]
222+
fn test_for_team() -> Result<(), Box<dyn std::error::Error>> {
223+
let ownership = build_ownership_with_all_mappers()?;
224+
let team_ownership = ownership.for_team("Bar");
225+
assert!(team_ownership.is_ok());
226+
Ok(())
227+
}
228+
229+
#[test]
230+
fn test_parse_for_team_trims_header() -> Result<(), Box<dyn std::error::Error>> {
231+
let codeownership_file = r#"
232+
# STOP! - DO NOT EDIT THIS FILE MANUALLY
233+
# This file was automatically generated by "bin/codeownership validate".
234+
#
235+
# CODEOWNERS is used for GitHub to suggest code/file owners to various GitHub
236+
# teams. This is useful when developers create Pull Requests since the
237+
# code/file owner is notified. Reference GitHub docs for more details:
238+
# https://help.github.com/en/articles/about-code-owners
239+
240+
241+
"#;
242+
243+
let team_ownership = parse_for_team("@Bar".to_string(), &codeownership_file)?;
244+
assert!(team_ownership.is_empty());
245+
Ok(())
246+
}
247+
248+
#[test]
249+
fn test_parse_for_team_includes_owned_globs() -> Result<(), Box<dyn std::error::Error>> {
250+
let codeownership_file = r#"
251+
# First Section
252+
/path/to/owned/**/* @Foo
253+
/path/to/not/owned/**/* @Bar
254+
255+
# Last Section
256+
/another/owned/path @Foo
257+
"#;
258+
259+
let team_ownership = parse_for_team("@Foo".to_string(), &codeownership_file)?;
260+
vecs_match(
261+
&team_ownership,
262+
&vec![
263+
TeamOwnership {
264+
heading: "# First Section".to_string(),
265+
globs: vec!["/path/to/owned/**/*".to_string()],
266+
},
267+
TeamOwnership {
268+
heading: "# Last Section".to_string(),
269+
globs: vec!["/another/owned/path".to_string()],
270+
},
271+
],
272+
);
273+
Ok(())
274+
}
275+
276+
#[test]
277+
fn test_parse_for_team_with_partial_team_match() -> Result<(), Box<dyn std::error::Error>> {
278+
let codeownership_file = r#"
279+
# First Section
280+
/path/to/owned @Foo
281+
/path/to/not/owned @FooBar
282+
"#;
283+
284+
let team_ownership = parse_for_team("@Foo".to_string(), &codeownership_file)?;
285+
vecs_match(
286+
&team_ownership,
287+
&vec![TeamOwnership {
288+
heading: "# First Section".to_string(),
289+
globs: vec!["/path/to/owned".to_string()],
290+
}],
291+
);
292+
Ok(())
293+
}
294+
295+
#[test]
296+
fn test_parse_for_team_with_trailing_newlines() -> Result<(), Box<dyn std::error::Error>> {
297+
let codeownership_file = r#"
298+
# First Section
299+
/path/to/owned @Foo
300+
301+
# Last Section
302+
/another/owned/path @Foo
303+
304+
305+
306+
"#;
307+
308+
let team_ownership = parse_for_team("@Foo".to_string(), &codeownership_file)?;
309+
vecs_match(
310+
&team_ownership,
311+
&vec![
312+
TeamOwnership {
313+
heading: "# First Section".to_string(),
314+
globs: vec!["/path/to/owned".to_string()],
315+
},
316+
TeamOwnership {
317+
heading: "# Last Section".to_string(),
318+
globs: vec!["/another/owned/path".to_string()],
319+
},
320+
],
321+
);
322+
Ok(())
323+
}
324+
325+
#[test]
326+
fn test_parse_for_team_without_trailing_newline() -> Result<(), Box<dyn std::error::Error>> {
327+
let codeownership_file = r#"
328+
# First Section
329+
/path/to/owned @Foo"#;
330+
331+
let team_ownership = parse_for_team("@Foo".to_string(), &codeownership_file)?;
332+
vecs_match(
333+
&team_ownership,
334+
&vec![TeamOwnership {
335+
heading: "# First Section".to_string(),
336+
globs: vec!["/path/to/owned".to_string()],
337+
}],
338+
);
339+
Ok(())
340+
}
165341
}

tests/valid_project_test.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,55 @@ fn test_for_file() -> Result<(), Box<dyn Error>> {
5050

5151
#[test]
5252
fn test_for_team() -> Result<(), Box<dyn Error>> {
53+
let expected_stdout = r#"
54+
# Code Ownership Report for `Payroll` Team
55+
56+
## Annotations at the top of file
57+
/javascript/packages/PayrollFlow/index.tsx
58+
/ruby/app/models/payroll.rb
59+
60+
## Team-specific owned globs
61+
62+
## Owner in .codeowner
63+
/ruby/app/payroll/**/**
64+
65+
## Owner metadata key in package.yml
66+
/ruby/packages/payroll_flow/**/**
67+
68+
## Owner metadata key in package.json
69+
/javascript/packages/PayrollFlow/**/**
70+
71+
## Team YML ownership
72+
/config/teams/payroll.yml
73+
74+
## Team owned gems
75+
/gems/payroll_calculator/**/**
76+
77+
"#
78+
.trim_start();
79+
5380
Command::cargo_bin("codeowners")?
5481
.arg("--project-root")
5582
.arg("tests/fixtures/valid_project")
5683
.arg("for-team")
5784
.arg("Payroll")
5885
.assert()
5986
.success()
60-
.stdout(predicate::str::contains("Team: Payroll"));
87+
.stdout(predicate::eq(expected_stdout));
88+
89+
Ok(())
90+
}
91+
92+
#[test]
93+
fn test_for_missing_team() -> Result<(), Box<dyn Error>> {
94+
Command::cargo_bin("codeowners")?
95+
.arg("--project-root")
96+
.arg("tests/fixtures/valid_project")
97+
.arg("for-team")
98+
.arg("Nope")
99+
.assert()
100+
.success()
101+
.stderr(predicate::str::contains("Team not found"));
61102

62103
Ok(())
63104
}

0 commit comments

Comments
 (0)