1+ use clap:: { CommandFactory , Parser , Subcommand } ;
2+ use clap_complete:: {
3+ generate,
4+ shells:: { Bash , Fish , Zsh } ,
5+ } ;
16use std:: path:: PathBuf ;
2- use clap:: { Parser , Subcommand , CommandFactory } ;
3- use clap_complete:: { generate, shells:: { Bash , Fish , Zsh } } ;
47
5- use core:: commands;
8+ use core:: { commands, types :: OutputFormat } ;
69use utils:: app_config:: AppConfig ;
710use utils:: error:: Result ;
811use utils:: types:: LogLevel ;
912
10-
1113#[ derive( Parser , Debug ) ]
1214#[ command(
1315 name = "codeinput" ,
@@ -25,11 +27,16 @@ pub struct Cli {
2527 pub config : Option < PathBuf > ,
2628
2729 /// Set a custom config file
28- #[ arg( name= "debug" , short, long= "debug" , value_name = "DEBUG" ) ]
30+ #[ arg( name = "debug" , short, long = "debug" , value_name = "DEBUG" ) ]
2931 pub debug : Option < bool > ,
3032
31- /// Set Log Level
32- #[ arg( name="log_level" , short, long="log-level" , value_name = "LOG_LEVEL" ) ]
33+ /// Set Log Level
34+ #[ arg(
35+ name = "log_level" ,
36+ short,
37+ long = "log-level" ,
38+ value_name = "LOG_LEVEL"
39+ ) ]
3340 pub log_level : Option < LogLevel > ,
3441
3542 /// Subcommands
@@ -40,26 +47,23 @@ pub struct Cli {
4047#[ derive( Subcommand , Debug ) ]
4148enum Commands {
4249 #[ clap(
43- name = "hazard " ,
44- about = "Generate a hazardous occurance " ,
45- long_about = None ,
50+ name = "codeowners " ,
51+ about = "Manage and analyze CODEOWNERS files " ,
52+ long_about = "Tools for parsing, validating and querying CODEOWNERS files"
4653 ) ]
47- Hazard ,
48- #[ clap(
49- name = "error" ,
50- about = "Simulate an error" ,
51- long_about = None ,
52- ) ]
53- Error ,
54+ Codeowners {
55+ #[ clap( subcommand) ]
56+ subcommand : CodeownersSubcommand ,
57+ } ,
5458 #[ clap(
5559 name = "completion" ,
5660 about = "Generate completion scripts" ,
5761 long_about = None ,
5862 ) ]
59- Completion {
60- #[ clap( subcommand) ]
61- subcommand : CompletionSubcommand ,
62- } ,
63+ Completion {
64+ #[ clap( subcommand) ]
65+ subcommand : CompletionSubcommand ,
66+ } ,
6367 #[ clap(
6468 name = "config" ,
6569 about = "Show Configuration" ,
@@ -78,6 +82,102 @@ enum CompletionSubcommand {
7882 Fish ,
7983}
8084
85+ #[ derive( Subcommand , PartialEq , Debug ) ]
86+ enum CodeownersSubcommand {
87+ #[ clap(
88+ name = "parse" ,
89+ about = "Preprocess CODEOWNERS files and build ownership map"
90+ ) ]
91+ Parse {
92+ /// Directory path to analyze (default: current directory)
93+ #[ arg( default_value = "." ) ]
94+ path : PathBuf ,
95+
96+ /// Custom cache file location
97+ #[ arg( long, value_name = "FILE" ) ]
98+ cache_file : Option < PathBuf > ,
99+ } ,
100+
101+ #[ clap(
102+ name = "list-files" ,
103+ about = "Find and list files with their owners based on filter criteria"
104+ ) ]
105+ ListFiles {
106+ /// Directory path to analyze (default: current directory)
107+ #[ arg( default_value = "." ) ]
108+ path : Option < PathBuf > ,
109+
110+ /// Only show files with specified tags
111+ #[ arg( long, value_name = "LIST" ) ]
112+ tags : Option < String > ,
113+
114+ /// Only show files owned by these owners
115+ #[ arg( long, value_name = "LIST" ) ]
116+ owners : Option < String > ,
117+
118+ /// Show only unowned files
119+ #[ arg( long) ]
120+ unowned : bool ,
121+
122+ /// Output format: text|json|bincode
123+ #[ arg( long, value_name = "FORMAT" , default_value = "text" , value_parser = parse_output_format) ]
124+ format : OutputFormat ,
125+ } ,
126+
127+ #[ clap(
128+ name = "list-owners" ,
129+ about = "Display aggregated owner statistics and associations"
130+ ) ]
131+ ListOwners {
132+ /// Comma-separated tags filter
133+ #[ arg( long, value_name = "LIST" ) ]
134+ filter_tags : Option < String > ,
135+
136+ /// Display tags in output
137+ #[ arg( long) ]
138+ show_tags : bool ,
139+
140+ /// Only show owners with >= NUM files
141+ #[ arg( long, value_name = "NUM" ) ]
142+ min_files : Option < u32 > ,
143+
144+ /// Output format: text|json|bincode
145+ #[ arg( long, value_name = "FORMAT" , default_value = "text" , value_parser = parse_output_format) ]
146+ format : OutputFormat ,
147+ } ,
148+ #[ clap(
149+ name = "list-tags" ,
150+ about = "Audit and analyze tag usage across CODEOWNERS files"
151+ ) ]
152+ ListTags {
153+ /// Warn if tags have fewer than NUM owners
154+ #[ arg( long, value_name = "NUM" ) ]
155+ verify_owners : Option < u32 > ,
156+
157+ /// Sort by: tag|count|owners
158+ #[ arg( long, value_name = "FIELD" , default_value = "tag" ) ]
159+ sort : String ,
160+
161+ /// Output format: text|json|bincode
162+ #[ arg( long, value_name = "FORMAT" , default_value = "text" , value_parser = parse_output_format) ]
163+ format : OutputFormat ,
164+ } ,
165+
166+ #[ clap(
167+ name = "validate" ,
168+ about = "Validate CODEOWNERS files for errors and potential issues"
169+ ) ]
170+ Validate {
171+ /// Treat warnings as errors
172+ #[ arg( long) ]
173+ strict : bool ,
174+
175+ /// Output format: text|json|bincode
176+ #[ arg( long, value_name = "FORMAT" , default_value = "text" , value_parser = parse_output_format) ]
177+ output : OutputFormat ,
178+ } ,
179+ }
180+
81181pub fn cli_match ( ) -> Result < ( ) > {
82182 // Parse the command line arguments
83183 let cli = Cli :: parse ( ) ;
@@ -87,14 +187,13 @@ pub fn cli_match() -> Result<()> {
87187
88188 let app = Cli :: command ( ) ;
89189 let matches = app. get_matches ( ) ;
90-
190+
91191 AppConfig :: merge_args ( matches) ?;
92192
93193 // Execute the subcommand
94194 match & cli. command {
95- Commands :: Hazard => commands:: hazard ( ) ?,
96- Commands :: Error => commands:: simulate_error ( ) ?,
97- Commands :: Completion { subcommand} => {
195+ Commands :: Codeowners { subcommand } => codeowners ( subcommand) ?,
196+ Commands :: Completion { subcommand } => {
98197 let mut app = Cli :: command ( ) ;
99198 match subcommand {
100199 CompletionSubcommand :: Bash => {
@@ -113,3 +212,53 @@ pub fn cli_match() -> Result<()> {
113212
114213 Ok ( ( ) )
115214}
215+
216+ /// Handle codeowners subcommands
217+ pub ( crate ) fn codeowners ( subcommand : & CodeownersSubcommand ) -> Result < ( ) > {
218+ match subcommand {
219+ CodeownersSubcommand :: Parse { path, cache_file } => {
220+ commands:: codeowners_parse ( path, cache_file. as_deref ( ) )
221+ }
222+ CodeownersSubcommand :: ListFiles {
223+ path,
224+ tags,
225+ owners,
226+ unowned,
227+ format,
228+ } => commands:: codeowners_list_files (
229+ path. as_deref ( ) ,
230+ tags. as_deref ( ) ,
231+ owners. as_deref ( ) ,
232+ * unowned,
233+ format,
234+ ) ,
235+ CodeownersSubcommand :: ListOwners {
236+ filter_tags,
237+ show_tags,
238+ min_files,
239+ format,
240+ } => commands:: codeowners_list_owners (
241+ filter_tags. as_deref ( ) ,
242+ * show_tags,
243+ min_files. as_ref ( ) ,
244+ format,
245+ ) ,
246+ CodeownersSubcommand :: ListTags {
247+ verify_owners,
248+ sort,
249+ format,
250+ } => commands:: codeowners_list_tags ( verify_owners. as_ref ( ) , sort, format) ,
251+ CodeownersSubcommand :: Validate { strict, output } => {
252+ commands:: codeowners_validate ( * strict, output)
253+ }
254+ }
255+ }
256+
257+ fn parse_output_format ( s : & str ) -> std:: result:: Result < OutputFormat , String > {
258+ match s. to_lowercase ( ) . as_str ( ) {
259+ "text" => Ok ( OutputFormat :: Text ) ,
260+ "json" => Ok ( OutputFormat :: Json ) ,
261+ "bincode" => Ok ( OutputFormat :: Bincode ) ,
262+ _ => Err ( format ! ( "Invalid output format: {}" , s) ) ,
263+ }
264+ }
0 commit comments