diff --git a/src/hyperfine/export/csv.rs b/src/hyperfine/export/csv.rs index 9653b91..f9c83db 100644 --- a/src/hyperfine/export/csv.rs +++ b/src/hyperfine/export/csv.rs @@ -3,10 +3,11 @@ use super::{ExportEntry, Exporter}; use csv::WriterBuilder; use std::io::{Error, ErrorKind, Result}; +#[derive(Default)] pub struct CsvExporter {} impl Exporter for CsvExporter { - fn serialize(&self, results: &Vec) -> Result { + fn serialize(&self, results: &Vec) -> Result> { let mut writer = WriterBuilder::new().from_writer(vec![]); for res in results { writer.serialize(res)?; @@ -14,14 +15,6 @@ impl Exporter for CsvExporter { writer .into_inner() - .ok() - .and_then(|bytes| String::from_utf8(bytes).ok()) - .ok_or(Error::new(ErrorKind::Other, "Error serializing to CSV")) - } -} - -impl CsvExporter { - pub fn new() -> Self { - CsvExporter {} + .map_err(|e| Error::new(ErrorKind::Other, e)) } } diff --git a/src/hyperfine/export/json.rs b/src/hyperfine/export/json.rs index 39ff4fb..97034e1 100644 --- a/src/hyperfine/export/json.rs +++ b/src/hyperfine/export/json.rs @@ -2,23 +2,18 @@ use super::{ExportEntry, Exporter}; use std::io::{Error, ErrorKind, Result}; -use serde_json::to_string_pretty; +use serde_json::to_vec_pretty; #[derive(Serialize, Debug)] struct HyperfineSummary<'a> { results: &'a Vec, } +#[derive(Default)] pub struct JsonExporter {} impl Exporter for JsonExporter { - fn serialize(&self, results: &Vec) -> Result { - to_string_pretty(&HyperfineSummary { results }).map_err(|e| Error::new(ErrorKind::Other, e)) - } -} - -impl JsonExporter { - pub fn new() -> Self { - JsonExporter {} + fn serialize(&self, results: &Vec) -> Result> { + to_vec_pretty(&HyperfineSummary { results }).map_err(|e| Error::new(ErrorKind::Other, e)) } } diff --git a/src/hyperfine/export/markdown.rs b/src/hyperfine/export/markdown.rs new file mode 100644 index 0000000..509a236 --- /dev/null +++ b/src/hyperfine/export/markdown.rs @@ -0,0 +1,38 @@ +use super::{ExportEntry, Exporter}; + +use std::io::Result; + +const MULTIPLIER: f64 = 1e3; + +#[derive(Default)] +pub struct MarkdownExporter {} + +impl Exporter for MarkdownExporter { + fn serialize(&self, results: &Vec) -> Result> { + let mut destination = start_table(); + + for result in results { + add_table_row(&mut destination, result); + } + + Ok(destination) + } +} + +fn start_table() -> Vec { + "| Benchmark | Mean [ms] | Min. [ms] | Max. [ms] |\n|----|----|----|----|\n" + .bytes() + .collect() +} +fn add_table_row(dest: &mut Vec, entry: &ExportEntry) { + dest.extend( + format!( + "| {} | {:.1} ± {:.1} | {:.1} | {:.1} |\n", + entry.command.replace("|", "\\|"), + entry.mean * MULTIPLIER, + entry.stddev * MULTIPLIER, + entry.min * MULTIPLIER, + entry.max * MULTIPLIER + ).as_bytes(), + ); +} diff --git a/src/hyperfine/export/mod.rs b/src/hyperfine/export/mod.rs index f886519..183ac2f 100644 --- a/src/hyperfine/export/mod.rs +++ b/src/hyperfine/export/mod.rs @@ -1,8 +1,10 @@ mod csv; mod json; +mod markdown; use self::csv::CsvExporter; use self::json::JsonExporter; +use self::markdown::MarkdownExporter; use std::io::{Result, Write}; use std::fs::File; @@ -65,12 +67,15 @@ pub enum ExportType { /// JSON format Json, + + /// Markdown table + Markdown, } /// Interface for different exporters. trait Exporter { /// Export the given entries in the serialized form. - fn serialize(&self, results: &Vec) -> Result; + fn serialize(&self, results: &Vec) -> Result>; } struct ExporterWithFilename { @@ -94,8 +99,9 @@ impl ExportManager { /// Add an additional exporter to the ExportManager pub fn add_exporter(&mut self, export_type: ExportType, filename: &str) { let exporter: Box = match export_type { - ExportType::Csv => Box::new(CsvExporter::new()), - ExportType::Json => Box::new(JsonExporter::new()), + ExportType::Csv => Box::new(CsvExporter::default()), + ExportType::Json => Box::new(JsonExporter::default()), + ExportType::Markdown => Box::new(MarkdownExporter::default()), }; self.exporters.push(ExporterWithFilename { exporter, @@ -107,14 +113,14 @@ impl ExportManager { pub fn write_results(&self, results: Vec) -> Result<()> { for e in &self.exporters { let file_content = e.exporter.serialize(&results)?; - write_to_file(&e.filename, file_content)?; + write_to_file(&e.filename, &file_content)?; } Ok(()) } } /// Write the given content to a file with the specified name -fn write_to_file(filename: &String, content: String) -> Result<()> { +fn write_to_file(filename: &String, content: &Vec) -> Result<()> { let mut file = File::create(filename)?; - file.write_all(content.as_bytes()) + file.write_all(content) } diff --git a/src/main.rs b/src/main.rs index 9527830..84a0385 100644 --- a/src/main.rs +++ b/src/main.rs @@ -152,6 +152,13 @@ fn main() { .value_name("FILE") .help("Export the timing results as JSON to the given FILE."), ) + .arg( + Arg::with_name("export-md") + .long("export-md") + .takes_value(true) + .value_name("FILE") + .help("Export the timing results to a markdown table in the given file"), + ) .help_message("Print this help message.") .version_message("Show version information.") .get_matches(); @@ -188,6 +195,9 @@ fn main() { if let Some(filename) = matches.value_of("export-csv") { export_manager.add_exporter(ExportType::Csv, filename); } + if let Some(filename) = matches.value_of("export-md") { + export_manager.add_exporter(ExportType::Markdown, filename); + } // We default Windows to NoColor if full had been specified. if cfg!(windows) && options.output_style == OutputStyleOption::Full {