diff options
-rw-r--r-- | src/crates/cli/src/candidates.rs | 4 | ||||
-rw-r--r-- | src/crates/cli/src/display.rs | 146 | ||||
-rw-r--r-- | src/crates/cli/src/estimate.rs | 8 | ||||
-rw-r--r-- | src/crates/cli/src/main.rs | 6 | ||||
-rw-r--r-- | src/crates/cli/src/profile.rs | 4 |
5 files changed, 111 insertions, 57 deletions
diff --git a/src/crates/cli/src/candidates.rs b/src/crates/cli/src/candidates.rs index 6d76da3..e8171e3 100644 --- a/src/crates/cli/src/candidates.rs +++ b/src/crates/cli/src/candidates.rs @@ -2,7 +2,7 @@ use anyhow::Result; use argh::FromArgs; use log::info; -use crate::{display::print_table, State}; +use crate::State; /// List selection sites and their candidates #[derive(FromArgs)] @@ -16,7 +16,7 @@ impl State { let candidates = self.inner.project_candidate_list(proj)?; for (file, con_types) in candidates.iter_2d() { - print_table(con_types.flat_map(|(ctn, candidates)| { + self.print_table(con_types.flat_map(|(ctn, candidates)| { candidates.iter().map(move |v| { ( ctn, diff --git a/src/crates/cli/src/display.rs b/src/crates/cli/src/display.rs index b48e370..38e5c44 100644 --- a/src/crates/cli/src/display.rs +++ b/src/crates/cli/src/display.rs @@ -1,57 +1,105 @@ use std::{collections::HashSet, fmt::Display, hash::Hash, iter::once}; use candelabra::profiler::UsageProfile; -use tabled::{builder::Builder, settings::Style}; - -// Print the given 2D map as a table, where the first key is the left-most column, and the second key the column index -pub fn print_table<I1, I2, A, B, C>(map: I1) -where - I1: IntoIterator<Item = (A, I2)>, - I2: IntoIterator<Item = (B, C)>, - A: Display, - B: Display + Eq + Hash, - C: Display, -{ - // Collect everything up - let map = map - .into_iter() - .map(|(k, v)| (k, v.into_iter().collect::<Vec<(_, _)>>())) - .collect::<Vec<(_, _)>>(); - - // Get keys - let keys = map - .iter() - .flat_map(|(_, vs)| vs.iter().map(|(k, _)| k)) - .collect::<HashSet<_>>() // dedup - .into_iter() - .collect::<Vec<_>>(); // consistent order - - let mut builder = Builder::new(); - builder.set_header(once("".to_string()).chain(keys.iter().map(|s| s.to_string()))); - - // Add rows - for (key, vals) in map.iter() { - builder.push_record(once(key.to_string()).chain(keys.iter().map(|k| { - vals.iter() - .find(|(search, _)| search == *k) - .map(|(_, v)| v.to_string()) - .unwrap_or("".to_string()) - }))); +use tabled::{builder::Builder, grid::records::vec_records::Cell, settings::Style, Table}; + +use crate::State; + +impl State { + // Print the given 2D map as a table, where the first key is the left-most column, and the second key the column index + pub fn print_table<I1, I2, A, B, C>(&self, map: I1) + where + I1: IntoIterator<Item = (A, I2)>, + I2: IntoIterator<Item = (B, C)>, + A: Display, + B: Display + Eq + Hash, + C: Display, + { + // Collect everything up + let map = map + .into_iter() + .map(|(k, v)| (k, v.into_iter().collect::<Vec<(_, _)>>())) + .collect::<Vec<(_, _)>>(); + + // Get keys + let keys = map + .iter() + .flat_map(|(_, vs)| vs.iter().map(|(k, _)| k)) + .collect::<HashSet<_>>() // dedup + .into_iter() + .collect::<Vec<_>>(); // consistent order + + let mut builder = Builder::new(); + builder.set_header(once("".to_string()).chain(keys.iter().map(|s| s.to_string()))); + + // Add rows + for (key, vals) in map.iter() { + builder.push_record(once(key.to_string()).chain(keys.iter().map(|k| { + vals.iter() + .find(|(search, _)| search == *k) + .map(|(_, v)| v.to_string()) + .unwrap_or("".to_string()) + }))); + } + + let mut table = builder.build(); + println!("{}", table.with(Style::sharp())); + + if self.with_latex { + print_latex(table); + } } - println!("{}", builder.build().with(Style::sharp())); + pub fn display_usage_info(&self, profile_info: &UsageProfile) { + self.print_table(profile_info.0.iter().enumerate().map(|(i, p)| { + ( + i, + [ + ("n".to_string(), p.avg_n), + ("occurences".to_string(), p.occurences), + ] + .into_iter() + .chain(p.avg_op_counts.clone()), + ) + })) + } } -pub fn display_usage_info(profile_info: &UsageProfile) { - print_table(profile_info.0.iter().enumerate().map(|(i, p)| { - ( - i, - [ - ("n".to_string(), p.avg_n), - ("occurences".to_string(), p.occurences), - ] - .into_iter() - .chain(p.avg_op_counts.clone()), - ) - })) +fn print_latex(t: Table) { + println!( + "\\begin{{center}}\n\\begin{{tabular}}{{{}|}}", + "|c".repeat(t.shape().1) + ); + for (i, row) in t.get_records().iter().enumerate() { + if row.is_empty() { + continue; + } + + print!("{} ", escape_latex(row[0].text())); + for col in &row[1..] { + print!("& {} ", escape_latex(col.text())); + } + println!("\\\\"); + + // header line + if i == 0 { + println!("\\hline"); + } + } + println!("\\end{{tabular}}\n\\end{{center}}"); +} + +fn escape_latex(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for c in s.chars() { + match c { + '\\' | '{' | '}' | '_' | '^' | '#' | '&' | '$' | '%' | '~' => { + out.push('\\'); + out.push(c); + } + c => out.push(c), + } + } + + out } diff --git a/src/crates/cli/src/estimate.rs b/src/crates/cli/src/estimate.rs index be5dc5c..0ba0615 100644 --- a/src/crates/cli/src/estimate.rs +++ b/src/crates/cli/src/estimate.rs @@ -6,7 +6,7 @@ use candelabra::{select::Selection, types::Mapping2D}; use cargo_metadata::camino::Utf8PathBuf; use log::info; -use crate::{display::print_table, State}; +use crate::State; /// Estimate the cost of a given set of assignments for a specific project, and detail how that was reached. #[derive(FromArgs)] @@ -76,7 +76,7 @@ impl State { } } - print_table(acc.into_iter_2d()); + self.print_table(acc.into_iter_2d()); info!("Breakdown by split"); profile_info @@ -93,7 +93,7 @@ impl State { }) .for_each(|(f, ctn, table)| { info!("{} / {}:", f, ctn); - print_table(table); + self.print_table(table); }); if !args.run { @@ -102,7 +102,7 @@ impl State { info!("Running benchmarks for comparison"); - print_table( + self.print_table( self.inner .run_benchmarks_with( proj, diff --git a/src/crates/cli/src/main.rs b/src/crates/cli/src/main.rs index 04bcc96..552e5a9 100644 --- a/src/crates/cli/src/main.rs +++ b/src/crates/cli/src/main.rs @@ -22,6 +22,10 @@ pub struct Args { #[argh(option, short = 'p')] pub project: Option<String>, + /// also output LaTeX tables + #[argh(switch, short = 'l')] + pub with_latex: bool, + #[argh(subcommand)] pub cmd: Subcommand, } @@ -47,6 +51,7 @@ fn main() -> Result<()> { let state = State { inner: candelabra::State::new(paths)?, projects: get_projects(&args)?, + with_latex: args.with_latex, }; match args.cmd { @@ -62,6 +67,7 @@ fn main() -> Result<()> { struct State { inner: candelabra::State, projects: Vec<Project>, + with_latex: bool, } fn get_projects(args: &Args) -> Result<Vec<Project>> { diff --git a/src/crates/cli/src/profile.rs b/src/crates/cli/src/profile.rs index af437ba..27a16a1 100644 --- a/src/crates/cli/src/profile.rs +++ b/src/crates/cli/src/profile.rs @@ -2,7 +2,7 @@ use anyhow::Result; use argh::FromArgs; use log::info; -use crate::{display::display_usage_info, State}; +use crate::State; /// Profile the selected projects and print the results #[derive(FromArgs)] @@ -18,7 +18,7 @@ impl State { for (f, ctn, usage_profile) in all_info.iter() { info!("{} / {}:", f, ctn); - display_usage_info(usage_profile); + self.display_usage_info(usage_profile); } } Ok(()) |