From 2443383fc8e62da7c4fec840764426fb4b33feb4 Mon Sep 17 00:00:00 2001
From: Philipp Paulweber
Date: Mon, 21 Mar 2022 21:45:24 +0100
Subject: [PATCH] Export of Emacs org-mode table support
* added new command-line option '--export-orgmode ' to create
Emacs org-mode table output by reusing markup result formatting
* based on the new MarkdownFormatter trait, see PR #490
---
src/cli.rs | 7 ++
src/export/mod.rs | 7 ++
src/export/orgmode.rs | 182 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 196 insertions(+)
create mode 100644 src/export/orgmode.rs
diff --git a/src/cli.rs b/src/cli.rs
index 14ff884..d5af45e 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -241,6 +241,13 @@ fn build_command() -> Command<'static> {
.value_name("FILE")
.help("Export the timing summary statistics as a Markdown table to the given FILE."),
)
+ .arg(
+ Arg::new("export-orgmode")
+ .long("export-orgmode")
+ .takes_value(true)
+ .value_name("FILE")
+ .help("Export the timing summary statistics as a Emacs org-mode table to the given FILE."),
+ )
.arg(
Arg::new("show-output")
.long("show-output")
diff --git a/src/export/mod.rs b/src/export/mod.rs
index bdd5d3c..62947d2 100644
--- a/src/export/mod.rs
+++ b/src/export/mod.rs
@@ -6,11 +6,13 @@ mod csv;
mod json;
mod markdown;
mod markup;
+mod orgmode;
use self::asciidoc::AsciidocExporter;
use self::csv::CsvExporter;
use self::json::JsonExporter;
use self::markdown::MarkdownExporter;
+use self::orgmode::OrgmodeExporter;
use crate::benchmark::benchmark_result::BenchmarkResult;
use crate::util::units::Unit;
@@ -32,6 +34,9 @@ pub enum ExportType {
/// Markdown table
Markdown,
+
+ /// Emacs org-mode tables
+ Orgmode,
}
/// Interface for different exporters.
@@ -67,6 +72,7 @@ impl ExportManager {
add_exporter("export-json", ExportType::Json)?;
add_exporter("export-csv", ExportType::Csv)?;
add_exporter("export-markdown", ExportType::Markdown)?;
+ add_exporter("export-orgmode", ExportType::Orgmode)?;
}
Ok(export_manager)
}
@@ -81,6 +87,7 @@ impl ExportManager {
ExportType::Csv => Box::new(CsvExporter::default()),
ExportType::Json => Box::new(JsonExporter::default()),
ExportType::Markdown => Box::new(MarkdownExporter::default()),
+ ExportType::Orgmode => Box::new(OrgmodeExporter::default()),
};
self.exporters.push(ExporterWithFilename {
exporter,
diff --git a/src/export/orgmode.rs b/src/export/orgmode.rs
new file mode 100644
index 0000000..ed37b7d
--- /dev/null
+++ b/src/export/orgmode.rs
@@ -0,0 +1,182 @@
+use super::Exporter;
+use crate::benchmark::benchmark_result::BenchmarkResult;
+use crate::benchmark::relative_speed;
+use crate::export::markup::MarkupFormatter;
+use crate::util::units::Unit;
+
+use anyhow::{anyhow, Result};
+
+#[derive(Default)]
+pub struct OrgmodeFormatter;
+
+impl MarkupFormatter for OrgmodeFormatter {
+ fn table_data(&self, data: &[&str]) -> String {
+ format!(
+ "| {} | {} |\n",
+ data.first().unwrap(),
+ &data[1..].join(" | ")
+ )
+ }
+
+ fn table_line(&self, size: usize) -> String {
+ format!("|{}--|\n", "--+".repeat(size - 1))
+ }
+
+ fn command(&self, cmd: &str) -> String {
+ format!("={}=", cmd)
+ }
+}
+
+#[derive(Default)]
+pub struct OrgmodeExporter {}
+
+impl Exporter for OrgmodeExporter {
+ fn serialize(&self, results: &[BenchmarkResult], unit: Option) -> Result> {
+ let unit = self.unit(results, unit);
+ let entries = relative_speed::compute(results);
+ if entries.is_none() {
+ return Err(anyhow!(
+ "Relative speed comparison is not available for Emacs org-mode export."
+ ));
+ }
+
+ let formatter = OrgmodeFormatter::default();
+ let table = formatter.table_results(&entries.unwrap(), unit);
+ Ok(table.as_bytes().to_vec())
+ }
+}
+
+/// Check Emacs org-mode data row formatting
+#[test]
+fn test_orgmode_formatter_table_data() {
+ let formatter = OrgmodeFormatter::default();
+ let data = vec!["a", "b", "c"];
+
+ let actual = formatter.table_data(&data);
+ let expect = "| a | b | c |\n";
+
+ assert_eq!(expect, actual);
+}
+
+/// Check Emacs org-mode horizontal line formatting
+#[test]
+fn test_orgmode_formatter_table_line() {
+ let formatter = OrgmodeFormatter::default();
+ let size = 5;
+
+ let actual = formatter.table_line(size);
+ let expect = "|--+--+--+--+--|\n";
+
+ assert_eq!(expect, actual);
+}
+
+/// Test helper function to create unit-based header and horizontal line
+/// independently from the markup functionality for Emacs org-mode.
+#[cfg(test)]
+fn cfg_test_table_header(unit_short_name: String) -> String {
+ format!(
+ "| Command | Mean [{unit}] | Min [{unit}] | Max [{unit}] | Relative |\n|--+--+--+--+--|\n",
+ unit = unit_short_name
+ )
+}
+
+/// Ensure the Emacs org-mode output includes the table header and the multiple
+/// benchmark results as a table. The list of actual times is not included
+/// in the output.
+///
+/// This also demonstrates that the first entry's units (ms) are used to set
+/// the units for all entries when the time unit is not given.
+#[test]
+fn test_orgmode_format_ms() {
+ use std::collections::BTreeMap;
+ let exporter = OrgmodeExporter::default();
+
+ let results = vec![
+ BenchmarkResult {
+ command: String::from("sleep 0.1"),
+ mean: 0.1057,
+ stddev: Some(0.0016),
+ median: 0.1057,
+ user: 0.0009,
+ system: 0.0011,
+ min: 0.1023,
+ max: 0.1080,
+ times: Some(vec![0.1, 0.1, 0.1]),
+ exit_codes: vec![Some(0), Some(0), Some(0)],
+ parameters: BTreeMap::new(),
+ },
+ BenchmarkResult {
+ command: String::from("sleep 2"),
+ mean: 2.0050,
+ stddev: Some(0.0020),
+ median: 2.0050,
+ user: 0.0009,
+ system: 0.0012,
+ min: 2.0020,
+ max: 2.0080,
+ times: Some(vec![2.0, 2.0, 2.0]),
+ exit_codes: vec![Some(0), Some(0), Some(0)],
+ parameters: BTreeMap::new(),
+ },
+ ];
+
+ let actual = String::from_utf8(exporter.serialize(&results, None).unwrap()).unwrap();
+ let expect = format!(
+ "{}\
+| =sleep 0.1= | 105.7 ± 1.6 | 102.3 | 108.0 | 1.00 |
+| =sleep 2= | 2005.0 ± 2.0 | 2002.0 | 2008.0 | 18.97 ± 0.29 |
+",
+ cfg_test_table_header("ms".to_string())
+ );
+
+ assert_eq!(expect, actual);
+}
+
+/// This test demonstrates that the given unit (s) is used to set
+/// the units for all entries.
+#[test]
+fn test_orgmode_format_s() {
+ use std::collections::BTreeMap;
+ let exporter = OrgmodeExporter::default();
+
+ let results = vec![
+ BenchmarkResult {
+ command: String::from("sleep 2"),
+ mean: 2.0050,
+ stddev: Some(0.0020),
+ median: 2.0050,
+ user: 0.0009,
+ system: 0.0012,
+ min: 2.0020,
+ max: 2.0080,
+ times: Some(vec![2.0, 2.0, 2.0]),
+ exit_codes: vec![Some(0), Some(0), Some(0)],
+ parameters: BTreeMap::new(),
+ },
+ BenchmarkResult {
+ command: String::from("sleep 0.1"),
+ mean: 0.1057,
+ stddev: Some(0.0016),
+ median: 0.1057,
+ user: 0.0009,
+ system: 0.0011,
+ min: 0.1023,
+ max: 0.1080,
+ times: Some(vec![0.1, 0.1, 0.1]),
+ exit_codes: vec![Some(0), Some(0), Some(0)],
+ parameters: BTreeMap::new(),
+ },
+ ];
+
+ let actual =
+ String::from_utf8(exporter.serialize(&results, Some(Unit::Second)).unwrap()).unwrap();
+ let expect = format!(
+ "{}\
+| =sleep 2= | 2.005 ± 0.002 | 2.002 | 2.008 | 18.97 ± 0.29 |
+| =sleep 0.1= | 0.106 ± 0.002 | 0.102 | 0.108 | 1.00 |
+",
+ cfg_test_table_header("s".to_string())
+ );
+
+ assert_eq!(expect, actual);
+}