mod.rs
1 use std::fs::{File, OpenOptions}; 2 use std::io::Write; 3 4 mod asciidoc; 5 mod csv; 6 mod json; 7 mod markdown; 8 mod markup; 9 mod orgmode; 10 #[cfg(test)] 11 mod tests; 12 13 use self::asciidoc::AsciidocExporter; 14 use self::csv::CsvExporter; 15 use self::json::JsonExporter; 16 use self::markdown::MarkdownExporter; 17 use self::orgmode::OrgmodeExporter; 18 19 use crate::benchmark::benchmark_result::BenchmarkResult; 20 use crate::options::SortOrder; 21 use crate::util::units::Unit; 22 23 use anyhow::{Context, Result}; 24 use clap::ArgMatches; 25 26 /// The desired form of exporter to use for a given file. 27 #[derive(Clone)] 28 pub enum ExportType { 29 /// Asciidoc Table 30 Asciidoc, 31 32 /// CSV (comma separated values) format 33 Csv, 34 35 /// JSON format 36 Json, 37 38 /// Markdown table 39 Markdown, 40 41 /// Emacs org-mode tables 42 Orgmode, 43 } 44 45 /// Interface for different exporters. 46 trait Exporter { 47 /// Export the given entries in the serialized form. 48 fn serialize( 49 &self, 50 results: &[BenchmarkResult], 51 unit: Option<Unit>, 52 sort_order: SortOrder, 53 ) -> Result<Vec<u8>>; 54 } 55 56 pub enum ExportTarget { 57 File(String), 58 Stdout, 59 } 60 61 struct ExporterWithTarget { 62 exporter: Box<dyn Exporter>, 63 target: ExportTarget, 64 } 65 66 /// Handles the management of multiple file exporters. 67 pub struct ExportManager { 68 exporters: Vec<ExporterWithTarget>, 69 time_unit: Option<Unit>, 70 sort_order: SortOrder, 71 } 72 73 impl ExportManager { 74 /// Build the ExportManager that will export the results specified 75 /// in the given ArgMatches 76 pub fn from_cli_arguments( 77 matches: &ArgMatches, 78 time_unit: Option<Unit>, 79 sort_order: SortOrder, 80 ) -> Result<Self> { 81 let mut export_manager = Self { 82 exporters: vec![], 83 time_unit, 84 sort_order, 85 }; 86 { 87 let mut add_exporter = |flag, exporttype| -> Result<()> { 88 if let Some(filename) = matches.get_one::<String>(flag) { 89 export_manager.add_exporter(exporttype, filename)?; 90 } 91 Ok(()) 92 }; 93 add_exporter("export-asciidoc", ExportType::Asciidoc)?; 94 add_exporter("export-json", ExportType::Json)?; 95 add_exporter("export-csv", ExportType::Csv)?; 96 add_exporter("export-markdown", ExportType::Markdown)?; 97 add_exporter("export-orgmode", ExportType::Orgmode)?; 98 } 99 Ok(export_manager) 100 } 101 102 /// Add an additional exporter to the ExportManager 103 pub fn add_exporter(&mut self, export_type: ExportType, filename: &str) -> Result<()> { 104 let exporter: Box<dyn Exporter> = match export_type { 105 ExportType::Asciidoc => Box::<AsciidocExporter>::default(), 106 ExportType::Csv => Box::<CsvExporter>::default(), 107 ExportType::Json => Box::<JsonExporter>::default(), 108 ExportType::Markdown => Box::<MarkdownExporter>::default(), 109 ExportType::Orgmode => Box::<OrgmodeExporter>::default(), 110 }; 111 112 self.exporters.push(ExporterWithTarget { 113 exporter, 114 target: if filename == "-" { 115 ExportTarget::Stdout 116 } else { 117 let _ = File::create(filename) 118 .with_context(|| format!("Could not create export file '{filename}'"))?; 119 ExportTarget::File(filename.to_string()) 120 }, 121 }); 122 123 Ok(()) 124 } 125 126 /// Write the given results to all Exporters. The 'intermediate' flag specifies 127 /// whether this is being called while still performing benchmarks, or if this 128 /// is the final call after all benchmarks have been finished. In the former case, 129 /// results are written to all file targets (to always have them up to date, even 130 /// if a benchmark fails). In the latter case, we only print to stdout targets (in 131 /// order not to clutter the output of hyperfine with intermediate results). 132 pub fn write_results(&self, results: &[BenchmarkResult], intermediate: bool) -> Result<()> { 133 for e in &self.exporters { 134 let content = || { 135 e.exporter 136 .serialize(results, self.time_unit, self.sort_order) 137 }; 138 139 match e.target { 140 ExportTarget::File(ref filename) => { 141 if intermediate { 142 write_to_file(filename, &content()?)? 143 } 144 } 145 ExportTarget::Stdout => { 146 if !intermediate { 147 println!(); 148 println!("{}", String::from_utf8(content()?).unwrap()); 149 } 150 } 151 } 152 } 153 Ok(()) 154 } 155 } 156 157 /// Write the given content to a file with the specified name 158 fn write_to_file(filename: &str, content: &[u8]) -> Result<()> { 159 let mut file = OpenOptions::new().write(true).open(filename)?; 160 file.write_all(content) 161 .with_context(|| format!("Failed to export results to '{filename}'")) 162 }