Repository: rcoh/angle-grinder
Pull request #184 (2023-04-09 16:48:31)

JSON fixups


Patch:
diff --git a/src/data.rs b/src/data.rs
index dc830f4..b806496 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -21,7 +21,7 @@ pub enum Row {
 
 /// Helper to allow for a custom serde Serializer for the rows in an Aggregate.
 /// Serde serializers are not allowed to emit nested serializations directly, so
-/// it's necesssary to create an intermediary type for each row, and to provide
+/// it's necessary to create an intermediary type for each row, and to provide
 /// it with the columns to enable ordering.
 pub(crate) struct WrappedAggregateRow<'a> {
     pub columns: &'a Vec<String>,
@@ -79,7 +79,7 @@ impl serde::Serialize for Record {
         S: Serializer,
     {
         let mut map = serializer.serialize_map(Some(self.data.len()))?;
-        for (k, v) in &self.data {
+        for (k, v) in self.data.iter().sorted() {
             map.serialize_entry(k, v)?;
         }
         map.end()
diff --git a/src/printer.rs b/src/printer.rs
index 56049e4..c1a1ad5 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -32,20 +32,21 @@ pub trait RecordPrinter {
     }
 }
 
-impl<T: RowPrinter> RecordPrinter for T {
+struct RecordFromRow<T>(T);
+
+impl<T: RowPrinter> RecordPrinter for RecordFromRow<T> {
     fn print(
         &mut self,
         out: &mut dyn Write,
         row: &Record,
         display_config: &DisplayConfig,
     ) -> io::Result<()> {
-        self.print_row(&display_config, out, Some(&row.raw), &mut row.data.iter())
+        self.0
+            .print_row(&display_config, out, Some(&row.raw), &mut row.data.iter())
     }
 }
 
-struct PrintAggregateAsRows<T> {
-    inner: T,
-}
+struct PrintAggregateAsRows<T>(T);
 
 pub(crate) trait RowPrinter {
     fn print_row(
@@ -54,7 +55,7 @@ pub(crate) trait RowPrinter {
         out: &mut dyn Write,
         raw: Option<&str>,
         cols: &mut dyn Iterator<Item = (&String, &Value)>,
-    ) -> std::io::Result<()>;
+    ) -> io::Result<()>;
 }
 
 impl<T: RowPrinter> AggregatePrinter for PrintAggregateAsRows<T> {
@@ -69,7 +70,7 @@ impl<T: RowPrinter> AggregatePrinter for PrintAggregateAsRows<T> {
             let mut kv_pairs = columns
                 .iter()
                 .map(|c| (c, row.get(c).unwrap_or(&Value::None)));
-            self.inner
+            self.0
                 .print_row(display_config, &mut out, None, &mut kv_pairs)
                 .expect("writing to string");
             writeln!(&mut out).unwrap();
@@ -78,7 +79,8 @@ impl<T: RowPrinter> AggregatePrinter for PrintAggregateAsRows<T> {
     }
 }
 
-struct LogFmtPrinter {}
+struct LogFmtPrinter;
+
 impl RowPrinter for LogFmtPrinter {
     fn print_row(
         &mut self,
@@ -110,7 +112,7 @@ pub fn raw_printer(
     let _x = terminal_config.color_enabled;
     let _y = terminal_config.is_tty;
     match mode {
-        OutputMode::Logfmt => Ok(Box::new(LogFmtPrinter {})),
+        OutputMode::Logfmt => Ok(Box::new(RecordFromRow(LogFmtPrinter))),
         OutputMode::Legacy => Ok(Box::new(LegacyPrinter::new(render_config, terminal_config))),
         OutputMode::Json => Ok(Box::new(JsonPrinter {})),
         OutputMode::Format(format_str) => Ok(Box::new(FormatPrinter::new(format_str.to_owned())?)),
@@ -123,12 +125,12 @@ pub fn agg_printer(
     terminal_config: TerminalConfig,
 ) -> Result<Box<dyn AggregatePrinter + Send>, Error> {
     match mode {
-        OutputMode::Logfmt => Ok(Box::new(PrintAggregateAsRows {
-            inner: LogFmtPrinter {},
-        })),
-        //OutputMode::Json => Ok(Box::new(JsonPrinter {})),
-        _ => Ok(Box::new(LegacyPrinter::new(render_config, terminal_config))),
-        //OutputMode::Format(format_str) => Ok(Box::new(FormatPrinter::new(format_str.to_owned())?)),
+        OutputMode::Logfmt => Ok(Box::new(PrintAggregateAsRows(LogFmtPrinter))),
+        OutputMode::Format(format_str) => Ok(Box::new(PrintAggregateAsRows(FormatPrinter::new(
+            format_str.to_owned(),
+        )?))),
+        OutputMode::Json => Ok(Box::new(JsonPrinter {})),
+        OutputMode::Legacy => Ok(Box::new(LegacyPrinter::new(render_config, terminal_config))),
     }
 }
 
@@ -173,18 +175,45 @@ impl FormatPrinter {
     }
 }
 
-pub fn strformat_record(fmtstr: &str, vars: &VMap) -> Result<String, FmtError> {
+pub fn strformat_record<'a>(
+    fmtstr: &str,
+    vars: impl Fn(&str) -> &'a Value,
+    display_config: &DisplayConfig,
+) -> Result<String, FmtError> {
     let formatter = |mut fmt: Formatter| {
-        let v = match vars.get(fmt.key) {
-            Some(v) => v,
-            None => &data::Value::None,
-        };
-        fmt.str(v.to_string().as_str())
+        let v = vars(fmt.key);
+        fmt.str(&format!("{}", ValueDisplay::new(v, display_config)))
     };
 
     strfmt_map(fmtstr, &formatter)
 }
 
+impl RowPrinter for FormatPrinter {
+    fn print_row(
+        &mut self,
+        display_config: &DisplayConfig,
+        out: &mut dyn Write,
+        _raw: Option<&str>,
+        cols: &mut dyn Iterator<Item = (&String, &Value)>,
+    ) -> io::Result<()> {
+        let cols: Vec<_> = cols.collect();
+        write!(
+            out,
+            "{}",
+            strformat_record(
+                &self.format_str,
+                |query| cols
+                    .iter()
+                    .find(|(k, _)| query == *k)
+                    .map(|(_k, v)| v)
+                    .unwrap_or(&&Value::None),
+                display_config,
+            )
+            .unwrap_or_else(|e| format!("{}", e))
+        )
+    }
+}
+
 impl RecordPrinter for FormatPrinter {
     fn print(
         &mut self,
@@ -195,7 +224,11 @@ impl RecordPrinter for FormatPrinter {
         write!(
             out,
             "{}",
-            match strformat_record(&self.format_str, &row.data) {
+            match strformat_record(
+                &self.format_str,
+                |k| row.data.get(k).unwrap_or(&Value::None),
+                _display_config
+            ) {
                 Ok(s) => s,
                 Err(e) => format!("{}", e),
             }
@@ -217,6 +250,13 @@ impl RecordPrinter for JsonPrinter {
     }
 }
 
+impl AggregatePrinter for JsonPrinter {
+    fn print(&mut self, row: &Aggregate, _display_config: &DisplayConfig) -> String {
+        let out = serde_json::to_string(row).expect("failed to format");
+        out + "\n"
+    }
+}
+
 struct PrettyPrinter {
     render_config: RenderConfig,
     column_widths: HashMap<String, usize>,


Test patch:
diff --git a/tests/integration.rs b/tests/integration.rs
index f706aaf..61e4d90 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -28,7 +28,7 @@ mod integration {
     use std::fs;
     use std::io::stdout;
 
-    const _TOUCH: usize = 0;
+    const _TOUCH: usize = 3;
 
     pub struct EmptyErrorReporter;
 
diff --git a/tests/structured_tests/json_output.toml b/tests/structured_tests/json_output.toml
new file mode 100644
index 0000000..d82931c
--- /dev/null
+++ b/tests/structured_tests/json_output.toml
@@ -0,0 +1,12 @@
+query = """* | parse "thing_a:* thing_b:*" as thing_a, thing_b"""
+input = """
+thing_a:5 thing_b:red
+thing_a:6 thing_b:yellow
+thing_a:7 thing_b:blue
+"""
+flags = ["--output", "json"]
+output = """
+{"thing_a":5,"thing_b":"red"}
+{"thing_a":6,"thing_b":"yellow"}
+{"thing_a":7,"thing_b":"blue"}
+"""
diff --git a/tests/structured_tests/json_output_sorted.toml b/tests/structured_tests/json_output_sorted.toml
new file mode 100644
index 0000000..6661bfb
--- /dev/null
+++ b/tests/structured_tests/json_output_sorted.toml
@@ -0,0 +1,10 @@
+query = """* | parse "thing_a:* thing_b:*" as thing_a, thing_b | sort by thing_b"""
+input = """
+thing_a:5 thing_b:red
+thing_a:6 thing_b:yellow
+thing_a:7 thing_b:blue
+"""
+flags = ["--output", "json"]
+output = """
+[{"thing_a":7,"thing_b":"blue"},{"thing_a":5,"thing_b":"red"},{"thing_a":6,"thing_b":"yellow"}]
+"""