diff --git a/Cargo.lock b/Cargo.lock index 72ddd85..f565829 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -905,6 +905,8 @@ version = "0.0.1" dependencies = [ "clap", "regex", + "serde", + "serde_json", "uucore", ] diff --git a/src/uu/dmesg/Cargo.toml b/src/uu/dmesg/Cargo.toml index df3ad04..96df1cc 100644 --- a/src/uu/dmesg/Cargo.toml +++ b/src/uu/dmesg/Cargo.toml @@ -14,3 +14,5 @@ path = "src/main.rs" clap = { workspace = true } uucore = { workspace = true } regex = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index d4ae899..673b8f4 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -3,6 +3,8 @@ use regex::Regex; use std::fs; use uucore::error::UResult; +mod json; + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut dmesg = Dmesg::new(); @@ -42,7 +44,7 @@ mod options { struct Dmesg<'a> { kmsg_file: &'a str, output_format: OutputFormat, - _records: Option<Vec<Record>>, + records: Option<Vec<Record>>, } impl Dmesg<'_> { @@ -50,7 +52,7 @@ impl Dmesg<'_> { Dmesg { kmsg_file: "/dev/kmsg", output_format: OutputFormat::Normal, - _records: None, + records: None, } } @@ -63,7 +65,7 @@ impl Dmesg<'_> { records.push(Record::from_str_fields(pri_fac, seq, time, msg.to_string())); } } - self._records = Some(records); + self.records = Some(records); Ok(self) } @@ -91,7 +93,18 @@ impl Dmesg<'_> { Ok(lines) } - fn print(&self) {} + fn print(&self) { + match self.output_format { + OutputFormat::Json => self.print_json(), + OutputFormat::Normal => unimplemented!(), + } + } + + fn print_json(&self) { + if let Some(records) = &self.records { + println!("{}", json::serialize_records(records)); + } + } } enum OutputFormat { diff --git a/src/uu/dmesg/src/json.rs b/src/uu/dmesg/src/json.rs new file mode 100644 index 0000000..b397e91 --- /dev/null +++ b/src/uu/dmesg/src/json.rs @@ -0,0 +1,137 @@ +use serde::Serialize; +use std::io; + +pub fn serialize_records(records: &Vec<crate::Record>) -> String { + let json = Dmesg::from(records); + let formatter = DmesgFormatter::new(); + let mut buf = vec![]; + let mut serializer = serde_json::Serializer::with_formatter(&mut buf, formatter); + json.serialize(&mut serializer).unwrap(); + String::from_utf8_lossy(&buf).to_string() +} + +#[derive(serde::Serialize)] +struct Dmesg<'a> { + dmesg: Vec<Record<'a>>, +} + +#[derive(serde::Serialize)] +struct Record<'a> { + pri: u32, + time: u64, + msg: &'a str, +} + +impl<'a> From<&'a Vec<crate::Record>> for Dmesg<'a> { + fn from(value: &'a Vec<crate::Record>) -> Self { + let mut dmesg_json = Dmesg { dmesg: vec![] }; + for record in value { + let record_json = Record { + pri: record._priority_facility, + time: record._timestamp_us, + msg: &record._message, + }; + dmesg_json.dmesg.push(record_json); + } + dmesg_json + } +} + +struct DmesgFormatter { + nesting_depth: i32, +} + +impl DmesgFormatter { + const SINGLE_INDENTATION: &[u8] = b" "; + + fn new() -> Self { + DmesgFormatter { nesting_depth: 0 } + } + + fn write_indentation<W>(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + for _ in 0..self.nesting_depth { + writer.write_all(Self::SINGLE_INDENTATION)?; + } + Ok(()) + } +} + +impl serde_json::ser::Formatter for DmesgFormatter { + fn begin_object<W>(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.nesting_depth += 1; + writer.write_all(b"{\n") + } + + fn end_object<W>(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + writer.write_all(b"\n")?; + self.nesting_depth -= 1; + self.write_indentation(writer)?; + writer.write_all(b"}") + } + + fn begin_array<W>(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + self.nesting_depth += 1; + writer.write_all(b"[\n") + } + + fn end_array<W>(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + writer.write_all(b"\n")?; + self.nesting_depth -= 1; + self.write_indentation(writer)?; + writer.write_all(b"]") + } + + fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()> + where + W: ?Sized + io::Write, + { + if !first { + writer.write_all(b",\n")?; + } + self.write_indentation(writer) + } + + fn begin_object_value<W>(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + writer.write_all(b": ") + } + + fn begin_array_value<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()> + where + W: ?Sized + io::Write, + { + if first { + self.write_indentation(writer) + } else { + writer.write_all(b",") + } + } + + fn write_u64<W>(&mut self, writer: &mut W, value: u64) -> io::Result<()> + where + W: ?Sized + io::Write, + { + // The only u64 field in Dmesg is time, which requires a specific format + let seconds = value / 1000000; + let sub_seconds = value % 1000000; + let repr = format!("{:>5}.{:0>6}", seconds, sub_seconds); + writer.write_all(repr.as_bytes()) + } +}