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,
+    };
+}