diff --git a/cargo-geiger/src/args.rs b/cargo-geiger/src/args.rs index 00b31eaf..44131faa 100644 --- a/cargo-geiger/src/args.rs +++ b/cargo-geiger/src/args.rs @@ -31,7 +31,7 @@ OPTIONS: --format Format string used for printing dependencies [default: {p}]. --output-format Output format for the report: Ascii, GitHubMarkdown, - Json, Utf8 [default: Utf8] + Json, Utf8, Ratio [default: Utf8] --update-readme Writes output to ./README.md. Looks for a Safety Report section, replaces if found, adds if not. Throws an error if no README.md exists. diff --git a/cargo-geiger/src/format/print_config.rs b/cargo-geiger/src/format/print_config.rs index aa56637e..8f9f8158 100644 --- a/cargo-geiger/src/format/print_config.rs +++ b/cargo-geiger/src/format/print_config.rs @@ -21,6 +21,7 @@ pub enum OutputFormat { Ascii, Json, GitHubMarkdown, + Ratio, Utf8, } diff --git a/cargo-geiger/src/format/table.rs b/cargo-geiger/src/format/table.rs index 330d0967..e884370c 100644 --- a/cargo-geiger/src/format/table.rs +++ b/cargo-geiger/src/format/table.rs @@ -101,7 +101,7 @@ pub struct TableParameters<'a> { pub rs_files_used: &'a HashSet, } -fn table_footer( +fn table_footer_unsafe_counts( used: CounterBlock, not_used: CounterBlock, output_format: OutputFormat, @@ -121,18 +121,110 @@ fn table_footer( colorize(&status, output_format, output) } -fn table_row(used: &CounterBlock, not_used: &CounterBlock) -> String { +fn table_footer_safe_ratio( + used: CounterBlock, + not_used: CounterBlock, + output_format: OutputFormat, + status: CrateDetectionStatus, +) -> ColoredString { let fmt = |used: &Count, not_used: &Count| { - format!("{}/{}", used.unsafe_, used.unsafe_ + not_used.unsafe_) + format!( + "{:>5}/{:<}={:.2}%", + (used.safe + not_used.safe), + (used.safe + used.unsafe_ + not_used.unsafe_ + not_used.safe), + if used.safe + used.unsafe_ + not_used.unsafe_ + not_used.safe == 0 + { + 100.0 + } else { + (100.00 * (used.safe + not_used.safe) as f32) + / ((used.safe + + used.unsafe_ + + not_used.unsafe_ + + not_used.safe) as f32) + } + ) }; - format!( - "{: <10} {: <12} {: <6} {: <7} {: <7}", + let output = format!( + "{: <12} {: <18} {: <18} {: <12} {: <12}", fmt(&used.functions, ¬_used.functions), fmt(&used.exprs, ¬_used.exprs), fmt(&used.item_impls, ¬_used.item_impls), fmt(&used.item_traits, ¬_used.item_traits), fmt(&used.methods, ¬_used.methods), - ) + ); + colorize(&status, output_format, output) +} + +fn table_footer( + used: CounterBlock, + not_used: CounterBlock, + output_format: OutputFormat, + status: CrateDetectionStatus, +) -> ColoredString { + match output_format { + OutputFormat::Ratio => { + table_footer_safe_ratio(used, not_used, output_format, status) + } + _ => table_footer_unsafe_counts(used, not_used, output_format, status), + } +} + +fn table_row( + used: &CounterBlock, + not_used: &CounterBlock, + output_format: OutputFormat, +) -> String { + match output_format { + OutputFormat::Ratio => { + // print safe ratio + let fmt = |used: &Count, not_used: &Count| { + format!( + "{:>5}/{:<}={:.2}%", + (used.safe + not_used.safe), + (used.safe + + used.unsafe_ + + not_used.unsafe_ + + not_used.safe), + if used.safe + + used.unsafe_ + + not_used.unsafe_ + + not_used.safe + == 0 + { + 100.0 + } else { + (100.00 * (used.safe + not_used.safe) as f32) + / ((used.safe + + used.unsafe_ + + not_used.unsafe_ + + not_used.safe) + as f32) + } + ) + }; + format!( + "{: <12} {: <18} {: <18} {: <12} {: <12}", + fmt(&used.functions, ¬_used.functions), + fmt(&used.exprs, ¬_used.exprs), + fmt(&used.item_impls, ¬_used.item_impls), + fmt(&used.item_traits, ¬_used.item_traits), + fmt(&used.methods, ¬_used.methods) + ) + } + _ => { + let fmt = |used: &Count, not_used: &Count| { + format!("{}/{}", used.unsafe_, used.unsafe_ + not_used.unsafe_) + }; + format!( + "{: <10} {: <12} {: <6} {: <7} {: <7}", + fmt(&used.functions, ¬_used.functions), + fmt(&used.exprs, ¬_used.exprs), + fmt(&used.item_impls, ¬_used.item_impls), + fmt(&used.item_traits, ¬_used.item_traits), + fmt(&used.methods, ¬_used.methods) + ) + } + } } fn table_row_empty() -> String { @@ -142,7 +234,7 @@ fn table_row_empty() -> String { .iter() .map(|s| s.len()) .sum::() - + headers_but_last.len() // Space after each column + + headers_but_last.len() + 4// Space after each column + 2 // Unsafety symbol width + 1; // Space after symbol " ".repeat(n) @@ -171,6 +263,10 @@ mod table_tests { OutputFormat::GitHubMarkdown, String::from("2/4 4/8 6/12 8/16 10/20 ") ), + case( + OutputFormat::Ratio, + String::from(" 2/6=33.33% 6/14=42.86% 10/22=45.45% 14/30=46.67% 18/38=47.37%") + ), case( OutputFormat::Utf8, String::from("2/4 4/8 6/12 8/16 10/20 ") @@ -232,14 +328,15 @@ mod table_tests { .collect(); let unsafety = unsafe_stats(&package_metrics, &rs_files_used); - let table_row = table_row(&unsafety.used, &unsafety.unused); + let table_row = + table_row(&unsafety.used, &unsafety.unused, OutputFormat::Ascii); assert_eq!(table_row, "4/6 8/12 12/18 16/24 20/30 "); } #[rstest] fn table_row_empty_test() { let empty_table_row = table_row_empty(); - assert_eq!(empty_table_row.len(), 51); + assert_eq!(empty_table_row.len(), 55); } #[rstest( diff --git a/cargo-geiger/src/format/table/handle_text_tree_line.rs b/cargo-geiger/src/format/table/handle_text_tree_line.rs index 6ea665d3..429dfffa 100644 --- a/cargo-geiger/src/format/table/handle_text_tree_line.rs +++ b/cargo-geiger/src/format/table/handle_text_tree_line.rs @@ -104,7 +104,11 @@ pub fn handle_text_tree_line_package( let unsafe_info = colorize( &crate_detection_status, table_parameters.print_config.output_format, - table_row(&unsafe_info.used, &unsafe_info.unused), + table_row( + &unsafe_info.used, + &unsafe_info.unused, + table_parameters.print_config.output_format, + ), ); let shift_chars = unsafe_info.chars().count() + 4; diff --git a/cargo-geiger/src/scan/default/table.rs b/cargo-geiger/src/scan/default/table.rs index 301e3c76..98249ccd 100644 --- a/cargo-geiger/src/scan/default/table.rs +++ b/cargo-geiger/src/scan/default/table.rs @@ -93,11 +93,27 @@ fn construct_key_lines( let mut output_key_lines = Vec::::new(); output_key_lines.push(String::new()); - output_key_lines.push(String::from("Metric output format: x/y")); - output_key_lines - .push(String::from(" x = unsafe code used by the build")); - output_key_lines - .push(String::from(" y = total unsafe code found in the crate")); + match output_format { + OutputFormat::Ratio => { + // Change the prompt for Safe Ratio report: + output_key_lines.push(String::from("Metric output format: x/y=z%")); + output_key_lines + .push(String::from(" x = safe code found in the crate")); + output_key_lines + .push(String::from(" y = total code found in the crate")); + output_key_lines.push(String::from( + " z = percentage of safe ratio as defined by x/y", + )); + } + _ => { + output_key_lines.push(String::from("Metric output format: x/y")); + output_key_lines + .push(String::from(" x = unsafe code used by the build")); + output_key_lines.push(String::from( + " y = total unsafe code found in the crate", + )); + } + } output_key_lines.push(String::new()); output_key_lines.push(String::from("Symbols: "));