@@ -9,11 +9,25 @@ pub struct Flags {
99 pub fix_ids : Vec < String > ,
1010 pub fix_prefixes : Vec < String > ,
1111 pub help : bool ,
12- pub json : bool ,
12+ pub output_format : OutputFormat ,
1313 pub only_checks : Option < Vec < String > > ,
1414 pub skip_checks : Option < Vec < String > > ,
1515}
1616
17+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
18+ pub enum OutputFormat {
19+ Text ,
20+ Json ,
21+ Markdown ,
22+ Sarif ,
23+ }
24+
25+ impl Default for OutputFormat {
26+ fn default ( ) -> Self {
27+ Self :: Text
28+ }
29+ }
30+
1731#[ derive( Debug , Clone , PartialEq , Eq , Default ) ]
1832pub struct ParsedArgs {
1933 pub command : Option < String > ,
@@ -24,13 +38,23 @@ pub struct ParsedArgs {
2438#[ derive( Debug , Clone , PartialEq , Eq ) ]
2539pub enum ArgsError {
2640 EmptyValue ( & ' static str ) ,
41+ ConflictingValue ( & ' static str , & ' static str ) ,
42+ InvalidValue ( & ' static str , String , & ' static str ) ,
2743 MissingValue ( & ' static str ) ,
2844}
2945
3046impl Display for ArgsError {
3147 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
3248 match self {
3349 Self :: EmptyValue ( flag) => write ! ( f, "Option \" {flag}\" requires a non-empty value." ) ,
50+ Self :: ConflictingValue ( left, right) => write ! (
51+ f,
52+ "Option \" {left}\" cannot be combined with option \" {right}\" ."
53+ ) ,
54+ Self :: InvalidValue ( flag, value, expected) => write ! (
55+ f,
56+ "Option \" {flag}\" received unsupported value \" {value}\" . Use one of: {expected}."
57+ ) ,
3458 Self :: MissingValue ( flag) => write ! ( f, "Option \" {flag}\" requires a value." ) ,
3559 }
3660 }
4367{
4468 let mut args = Vec :: new ( ) ;
4569 let mut flags = Flags :: default ( ) ;
70+ let mut output_format_source = None ;
4671 let mut tokens = argv. into_iter ( ) . map ( Into :: into) ;
4772
4873 while let Some ( token) = tokens. next ( ) {
7196 . get_or_insert_with ( Vec :: new)
7297 . extend ( values) ;
7398 }
99+ Some ( "--format" ) => {
100+ let value = next_option_value ( tokens. next ( ) , "--format" ) ?;
101+ let output_format = parse_output_format ( & value) ?;
102+ set_output_format (
103+ & mut flags,
104+ & mut output_format_source,
105+ output_format,
106+ "--format" ,
107+ ) ?;
108+ }
74109 Some ( "--skip" ) => {
75110 let value = next_option_value ( tokens. next ( ) , "--skip" ) ?;
76111 let values = split_csv_values ( & value, "--skip" ) ?;
@@ -79,7 +114,14 @@ where
79114 . get_or_insert_with ( Vec :: new)
80115 . extend ( values) ;
81116 }
82- Some ( "--json" ) => flags. json = true ,
117+ Some ( "--json" ) => {
118+ set_output_format (
119+ & mut flags,
120+ & mut output_format_source,
121+ OutputFormat :: Json ,
122+ "--json" ,
123+ ) ?;
124+ }
83125 Some ( "--help" ) | Some ( "-h" ) => flags. help = true ,
84126 _ => args. push ( token) ,
85127 }
@@ -125,11 +167,43 @@ fn split_csv_values(value: &OsString, flag: &'static str) -> Result<Vec<String>,
125167 Ok ( values)
126168}
127169
170+ fn parse_output_format ( value : & OsString ) -> Result < OutputFormat , ArgsError > {
171+ match value. to_string_lossy ( ) . as_ref ( ) {
172+ "text" => Ok ( OutputFormat :: Text ) ,
173+ "json" => Ok ( OutputFormat :: Json ) ,
174+ "markdown" => Ok ( OutputFormat :: Markdown ) ,
175+ "sarif" => Ok ( OutputFormat :: Sarif ) ,
176+ value => Err ( ArgsError :: InvalidValue (
177+ "--format" ,
178+ value. to_string ( ) ,
179+ "text, json, markdown, sarif" ,
180+ ) ) ,
181+ }
182+ }
183+
184+ fn set_output_format (
185+ flags : & mut Flags ,
186+ source : & mut Option < & ' static str > ,
187+ output_format : OutputFormat ,
188+ flag : & ' static str ,
189+ ) -> Result < ( ) , ArgsError > {
190+ if source. is_some ( ) && flags. output_format != output_format {
191+ return Err ( ArgsError :: ConflictingValue (
192+ source. unwrap_or ( "--format" ) ,
193+ flag,
194+ ) ) ;
195+ }
196+
197+ flags. output_format = output_format;
198+ * source = Some ( flag) ;
199+ Ok ( ( ) )
200+ }
201+
128202#[ cfg( test) ]
129203mod tests {
130204 use std:: ffi:: OsString ;
131205
132- use super :: { parse_args, ArgsError , Flags , ParsedArgs } ;
206+ use super :: { parse_args, ArgsError , Flags , OutputFormat , ParsedArgs } ;
133207
134208 #[ test]
135209 fn parse_args_collects_known_flags_and_positionals ( ) {
@@ -148,7 +222,7 @@ mod tests {
148222 fix_ids: Vec :: new( ) ,
149223 fix_prefixes: Vec :: new( ) ,
150224 help: false ,
151- json : true ,
225+ output_format : OutputFormat :: Json ,
152226 only_checks: None ,
153227 skip_checks: None ,
154228 } ,
@@ -205,6 +279,40 @@ mod tests {
205279 assert ! ( parsed. flags. diff) ;
206280 }
207281
282+ #[ test]
283+ fn parse_args_collects_format_output_values ( ) {
284+ let parsed = parse_args ( [ "audit" , "--format" , "markdown" ] ) . expect ( "args should parse" ) ;
285+ assert_eq ! ( parsed. flags. output_format, OutputFormat :: Markdown ) ;
286+
287+ let parsed = parse_args ( [ "audit" , "--format" , "sarif" ] ) . expect ( "args should parse" ) ;
288+ assert_eq ! ( parsed. flags. output_format, OutputFormat :: Sarif ) ;
289+
290+ let parsed = parse_args ( [ "audit" , "--format" , "json" ] ) . expect ( "args should parse" ) ;
291+ assert_eq ! ( parsed. flags. output_format, OutputFormat :: Json ) ;
292+
293+ let parsed = parse_args ( [ "audit" , "--format" , "text" ] ) . expect ( "args should parse" ) ;
294+ assert_eq ! ( parsed. flags. output_format, OutputFormat :: Text ) ;
295+ }
296+
297+ #[ test]
298+ fn parse_args_errors_when_output_format_flags_conflict ( ) {
299+ let error = parse_args ( [ "audit" , "--json" , "--format" , "markdown" ] )
300+ . expect_err ( "conflicting output formats should fail" ) ;
301+
302+ assert_eq ! ( error, ArgsError :: ConflictingValue ( "--json" , "--format" ) ) ;
303+ }
304+
305+ #[ test]
306+ fn parse_args_errors_when_output_format_value_is_invalid ( ) {
307+ let error = parse_args ( [ "audit" , "--format" , "xml" ] )
308+ . expect_err ( "unknown output format should fail" ) ;
309+
310+ assert_eq ! (
311+ error,
312+ ArgsError :: InvalidValue ( "--format" , "xml" . to_string( ) , "text, json, markdown, sarif" )
313+ ) ;
314+ }
315+
208316 #[ test]
209317 fn parse_args_errors_when_fix_selector_value_is_missing ( ) {
210318 let error = parse_args ( [ "fix" , "--fix-id" ] ) . expect_err ( "missing selector should fail" ) ;
0 commit comments