From 9fea47a2759676d7c2191b565e825e30588e10d8 Mon Sep 17 00:00:00 2001 From: askiiart <dev@askiiart.net> Date: Mon, 30 Dec 2024 14:51:22 -0600 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 7 ++++ Cargo.toml | 9 +++++ src/lib.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fc9817f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "better-commands" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2ff9ef6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "better-commands" +description = "Better commands - get stdout and stderr together, with timestamps" +version = "0.1.0" +edition = "2021" +license = "GPL-3.0-only" +keywords = ["command", "cmd"] + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6541d02 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,108 @@ +use std::cmp::Ordering; +use std::io::{BufRead, BufReader}; +use std::process::{Command, Stdio}; +use std::thread; +use std::time::Instant; + +/// Holds the output for a command +#[derive(Debug, Clone)] +pub struct CmdOutput { + lines: Vec<Line>, + status: Option<i32>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Ord)] +pub struct Line { + pub stdout: bool, + pub time: Instant, + pub content: String, +} + +impl PartialOrd for Line { + fn ge(&self, other: &Line) -> bool { + if self.time >= other.time { + return true; + } + return false; + } + + fn gt(&self, other: &Self) -> bool { + if self.time > other.time { + return true; + } + return false; + } + + fn le(&self, other: &Self) -> bool { + if self.time <= other.time { + return true; + } + return false; + } + + fn lt(&self, other: &Self) -> bool { + if self.time < other.time { + return true; + } + return false; + } + + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + if self < other { + return Some(Ordering::Less); + } + if self > other { + return Some(Ordering::Greater); + } + return Some(Ordering::Equal); + } +} + +pub fn run(command: &mut Command) -> CmdOutput { + // https://stackoverflow.com/a/72831067/16432246 + let mut child = command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + let child_stdout = child.stdout.take().unwrap(); + let child_stderr = child.stderr.take().unwrap(); + + let (stdout_tx, stdout_rx) = std::sync::mpsc::channel(); + let (stderr_tx, stderr_rx) = std::sync::mpsc::channel(); + + let stdout_lines = BufReader::new(child_stdout).lines(); + thread::spawn(move || { + for line in stdout_lines { + stdout_tx.send(Line { + content: line.unwrap(), + stdout: true, + time: Instant::now(), + }).unwrap(); + } + }); + + let stderr_lines = BufReader::new(child_stderr).lines(); + thread::spawn(move || { + for line in stderr_lines { + let time = Instant::now(); + stderr_tx.send(Line { + content: line.unwrap(), + stdout: true, + time: time, + }).unwrap(); + } + }); + + let status = child.wait().unwrap().code(); + + let mut lines = stdout_rx.into_iter().collect::<Vec<Line>>(); + lines.append(&mut stderr_rx.into_iter().collect::<Vec<Line>>()); + lines.sort(); + + return CmdOutput { + lines: lines, + status: status, + }; +}