@@ -12,6 +12,7 @@ use tracing::{info, instrument};
1212mod file_generator;
1313mod file_owner_finder;
1414pub ( crate ) mod mapper;
15+ mod parser;
1516mod validator;
1617
1718use crate :: {
@@ -24,6 +25,7 @@ pub use validator::Errors as ValidatorErrors;
2425use self :: {
2526 file_generator:: FileGenerator ,
2627 mapper:: { JavascriptPackageMapper , Mapper , RubyPackageMapper , TeamFileMapper , TeamGemMapper , TeamGlobMapper , TeamYmlMapper } ,
28+ parser:: parse_for_team,
2729 validator:: Validator ,
2830} ;
2931
@@ -122,6 +124,12 @@ impl Ownership {
122124 validator. validate ( )
123125 }
124126
127+ #[ instrument( level = "debug" , skip_all) ]
128+ pub fn fast_for_file ( & self , file_path : & str ) -> Result < ( String , String ) , ValidatorErrors > {
129+ let file_owner = self . for_file ( file_path) ?;
130+ Ok ( ( file_owner[ 0 ] . team . name . clone ( ) , file_owner[ 0 ] . team . github_team . clone ( ) ) )
131+ }
132+
125133 #[ instrument( level = "debug" , skip_all) ]
126134 pub fn for_file ( & self , file_path : & str ) -> Result < Vec < FileOwner > , ValidatorErrors > {
127135 info ! ( "getting file ownership for {}" , file_path) ;
@@ -176,47 +184,10 @@ impl Ownership {
176184 }
177185}
178186
179- fn parse_for_team ( team_name : String , codeowners_file : & str ) -> Result < Vec < TeamOwnership > , Box < dyn Error > > {
180- let mut output = vec ! [ ] ;
181- let mut current_section: Option < TeamOwnership > = None ;
182- let input: String = codeowners_file. replace ( & FileGenerator :: disclaimer ( ) . join ( "\n " ) , "" ) ;
183- let error_message = "CODEOWNERS out of date. Run `codeowners generate` to update the CODEOWNERS file" ;
184-
185- for line in input. trim_start ( ) . lines ( ) {
186- match line {
187- comment if comment. starts_with ( "#" ) => {
188- if let Some ( section) = current_section. take ( ) {
189- output. push ( section) ;
190- }
191- current_section = Some ( TeamOwnership :: new ( comment. to_string ( ) ) ) ;
192- }
193- "" => {
194- if let Some ( section) = current_section. take ( ) {
195- output. push ( section) ;
196- }
197- }
198- team_line if team_line. ends_with ( & team_name) => {
199- let section = current_section. as_mut ( ) . ok_or ( error_message) ?;
200-
201- let glob = line. split_once ( ' ' ) . ok_or ( error_message) ?. 0 . to_string ( ) ;
202- section. globs . push ( glob) ;
203- }
204- _ => { }
205- }
206- }
207-
208- if let Some ( cs) = current_section {
209- output. push ( cs. clone ( ) ) ;
210- }
211-
212- Ok ( output)
213- }
214-
215187#[ cfg( test) ]
216188mod tests {
217189 use super :: * ;
218- use crate :: common_test:: tests:: { build_ownership_with_all_mappers, vecs_match} ;
219- use indoc:: indoc;
190+ use crate :: common_test:: tests:: build_ownership_with_all_mappers;
220191
221192 #[ test]
222193 fn test_for_file_owner ( ) -> Result < ( ) , Box < dyn Error > > {
@@ -251,173 +222,4 @@ mod tests {
251222 assert ! ( team_ownership. is_err( ) , "Team not found" ) ;
252223 Ok ( ( ) )
253224 }
254-
255- #[ test]
256- fn test_parse_for_team_trims_header ( ) -> Result < ( ) , Box < dyn Error > > {
257- let codeownership_file = indoc ! { "
258- # STOP! - DO NOT EDIT THIS FILE MANUALLY
259- # This file was automatically generated by \" bin/codeownership validate\" .
260- #
261- # CODEOWNERS is used for GitHub to suggest code/file owners to various GitHub
262- # teams. This is useful when developers create Pull Requests since the
263- # code/file owner is notified. Reference GitHub docs for more details:
264- # https://help.github.com/en/articles/about-code-owners
265-
266-
267- " } ;
268-
269- let team_ownership = parse_for_team ( "@Bar" . to_string ( ) , codeownership_file) ?;
270- assert ! ( team_ownership. is_empty( ) ) ;
271- Ok ( ( ) )
272- }
273-
274- #[ test]
275- fn test_parse_for_team_includes_owned_globs ( ) -> Result < ( ) , Box < dyn Error > > {
276- let codeownership_file = indoc ! { "
277- # First Section
278- /path/to/owned @Foo
279- /path/to/not/owned @Bar
280-
281- # Last Section
282- /another/owned/path @Foo
283- " } ;
284-
285- let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , codeownership_file) ?;
286- vecs_match (
287- & team_ownership,
288- & vec ! [
289- TeamOwnership {
290- heading: "# First Section" . to_string( ) ,
291- globs: vec![ "/path/to/owned" . to_string( ) ] ,
292- } ,
293- TeamOwnership {
294- heading: "# Last Section" . to_string( ) ,
295- globs: vec![ "/another/owned/path" . to_string( ) ] ,
296- } ,
297- ] ,
298- ) ;
299- Ok ( ( ) )
300- }
301-
302- #[ test]
303- fn test_parse_for_team_with_partial_team_match ( ) -> Result < ( ) , Box < dyn Error > > {
304- let codeownership_file = indoc ! { "
305- # First Section
306- /path/to/owned @Foo
307- /path/to/not/owned @FooBar
308- " } ;
309-
310- let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , codeownership_file) ?;
311- vecs_match (
312- & team_ownership,
313- & vec ! [ TeamOwnership {
314- heading: "# First Section" . to_string( ) ,
315- globs: vec![ "/path/to/owned" . to_string( ) ] ,
316- } ] ,
317- ) ;
318- Ok ( ( ) )
319- }
320-
321- #[ test]
322- fn test_parse_for_team_with_trailing_newlines ( ) -> Result < ( ) , Box < dyn Error > > {
323- let codeownership_file = indoc ! { "
324- # First Section
325- /path/to/owned @Foo
326-
327- # Last Section
328- /another/owned/path @Foo
329-
330-
331-
332- " } ;
333-
334- let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , codeownership_file) ?;
335- vecs_match (
336- & team_ownership,
337- & vec ! [
338- TeamOwnership {
339- heading: "# First Section" . to_string( ) ,
340- globs: vec![ "/path/to/owned" . to_string( ) ] ,
341- } ,
342- TeamOwnership {
343- heading: "# Last Section" . to_string( ) ,
344- globs: vec![ "/another/owned/path" . to_string( ) ] ,
345- } ,
346- ] ,
347- ) ;
348- Ok ( ( ) )
349- }
350-
351- #[ test]
352- fn test_parse_for_team_without_trailing_newline ( ) -> Result < ( ) , Box < dyn Error > > {
353- let codeownership_file = indoc ! { "
354- # First Section
355- /path/to/owned @Foo" } ;
356-
357- let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , codeownership_file) ?;
358- vecs_match (
359- & team_ownership,
360- & vec ! [ TeamOwnership {
361- heading: "# First Section" . to_string( ) ,
362- globs: vec![ "/path/to/owned" . to_string( ) ] ,
363- } ] ,
364- ) ;
365- Ok ( ( ) )
366- }
367-
368- #[ test]
369- fn test_parse_for_team_with_missing_section_header ( ) -> Result < ( ) , Box < dyn Error > > {
370- let codeownership_file = indoc ! { "
371- # First Section
372- /path/to/owned @Foo
373-
374- /another/owned/path @Foo
375- " } ;
376-
377- let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , codeownership_file) ;
378- assert ! (
379- team_ownership
380- . is_err_and( |e| e. to_string( ) == "CODEOWNERS out of date. Run `codeowners generate` to update the CODEOWNERS file" )
381- ) ;
382- Ok ( ( ) )
383- }
384-
385- #[ test]
386- fn test_parse_for_team_with_malformed_team_line ( ) -> Result < ( ) , Box < dyn Error > > {
387- let codeownership_file = indoc ! { "
388- # First Section
389- @Foo
390- " } ;
391-
392- let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , codeownership_file) ;
393- assert ! (
394- team_ownership
395- . is_err_and( |e| e. to_string( ) == "CODEOWNERS out of date. Run `codeowners generate` to update the CODEOWNERS file" )
396- ) ;
397- Ok ( ( ) )
398- }
399-
400- #[ test]
401- fn test_parse_for_team_with_invalid_file ( ) -> Result < ( ) , Box < dyn Error > > {
402- let codeownership_file = indoc ! { "
403- # First Section
404- # Second Section
405- path/to/owned @Foo
406- " } ;
407- let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , codeownership_file) ?;
408- vecs_match (
409- & team_ownership,
410- & vec ! [
411- TeamOwnership {
412- heading: "# First Section" . to_string( ) ,
413- globs: vec![ ] ,
414- } ,
415- TeamOwnership {
416- heading: "# Second Section" . to_string( ) ,
417- globs: vec![ "path/to/owned" . to_string( ) ] ,
418- } ,
419- ] ,
420- ) ;
421- Ok ( ( ) )
422- }
423225}
0 commit comments