operator based command spawning and direction in rust (wip)
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml9
-rw-r--r--src/lib.rs167
-rw-r--r--src/output.rs42
-rw-r--r--src/processor.rs112
-rw-r--r--src/util.rs36
7 files changed, 383 insertions, 0 deletions
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..e027715
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,16 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "shal"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9fab9d4
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "shal"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.75"
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..1a5055f
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,167 @@
+#![allow(non_camel_case_types)]
+use std::{
+ ffi::{OsStr, OsString},
+ io::{self, Read, Write},
+ ops::{BitOr, Shr},
+ process::{self, Child, ChildStderr, ChildStdin, ChildStdout, Stdio},
+};
+mod output;
+mod processor;
+mod util;
+pub use output::null;
+use processor::Io;
+pub use processor::{Process, Processor};
+pub use util::echo;
+
+/// generates a impl of $a | $b => [`Processor`].
+macro_rules! imp {
+ (Processor,$b:ty) => {
+ impl BitOr<$b> for Processor {
+ type Output = Processor;
+ fn bitor(mut self, rhs: $b) -> Self::Output {
+ self.add(rhs);
+ self
+ }
+ }
+ };
+ ($a:ty,$b:ty) => {
+ impl BitOr<$b> for $a {
+ type Output = Processor;
+ fn bitor(self, rhs: $b) -> Self::Output {
+ let mut p = Processor::new(512);
+ p.add(self);
+ p.add(rhs);
+ p
+ }
+ }
+ };
+ (output $from:ty) => {
+ impl Shr<&mut String> for $from {
+ type Output = anyhow::Result<()>;
+ fn shr(self, rhs: &mut String) -> Self::Output {
+ let mut p = Processor::new(512);
+ p.add(self);
+ p >> rhs
+ }
+ }
+
+ impl Shr<()> for $from {
+ type Output = anyhow::Result<String>;
+ fn shr(self, _: ()) -> Self::Output {
+ let mut s = String::new();
+ (self >> &mut s)?;
+ Ok(s)
+ }
+ }
+ };
+}
+
+// imp!(Command, grep); // cargo | grep
+// imp!(echo, grep); // echo | grep
+// imp!(Processor, grep); // a | b | grep
+imp!(Command, Command); // command | command
+imp!(echo, Command); // echo | command
+imp!(Processor, Command); // a | b | command
+imp!(output Command);
+imp!(output echo);
+
+impl Shr<&mut String> for Processor {
+ type Output = anyhow::Result<()>;
+
+ fn shr(mut self, rhs: &mut String) -> Self::Output {
+ self.add(output::StringOut(rhs as *mut _));
+ self.complete()?;
+ Ok(())
+ }
+}
+
+impl Shr<()> for Processor {
+ type Output = anyhow::Result<String>;
+
+ fn shr(self, _: ()) -> Self::Output {
+ let mut s = String::new();
+ (self >> &mut s)?;
+ Ok(s)
+ }
+}
+
+#[derive(Debug)]
+pub struct Command {
+ stdout: ChildStdout,
+ stderr: ChildStderr,
+ stdin: ChildStdin,
+ proc: Child,
+}
+
+unsafe impl Process for Command {
+ fn run(&mut self, on: processor::Io) -> anyhow::Result<usize> {
+ match on {
+ Io::First(b) => Ok(self.stdout.read(b)?),
+ Io::Middle(i, o) => {
+ self.stdin.write(i)?;
+ Ok(self.stdout.read(o)?)
+ }
+ Io::Last(i) => {
+ self.stdin.write(i)?;
+ Ok(0)
+ }
+ }
+ }
+
+ fn done(&mut self, _: Option<bool>) -> anyhow::Result<bool> {
+ Ok(self.proc.try_wait()?.is_some())
+ }
+}
+
+#[derive(Debug)]
+pub struct CommandBuilder {
+ command: OsString,
+ args: Vec<OsString>,
+}
+
+impl CommandBuilder {
+ pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut CommandBuilder {
+ self.args.push(arg.as_ref().to_os_string());
+ self
+ }
+
+ pub fn args(&mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>> + ExactSizeIterator) {
+ self.args.reserve(self.args.len() + args.len());
+ for arg in args {
+ self.args.push(arg.as_ref().to_os_string());
+ }
+ }
+
+ pub fn spawn(&mut self) -> io::Result<Command> {
+ let mut proc = process::Command::new(&self.command)
+ .args(&self.args)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .stdin(Stdio::piped())
+ .spawn()?;
+ Ok(Command {
+ stdout: proc.stdout.take().unwrap(),
+ stderr: proc.stderr.take().unwrap(),
+ stdin: proc.stdin.take().unwrap(),
+ proc,
+ })
+ }
+}
+
+impl Command {
+ pub fn new(command: impl AsRef<OsStr>) -> CommandBuilder {
+ CommandBuilder {
+ command: command.as_ref().to_os_string(),
+ args: vec![],
+ }
+ }
+}
+
+#[test]
+fn usage() {
+ let o = ((Command::new("cargo").spawn().unwrap()
+ | Command::new("grep").arg("test").spawn().unwrap())
+ >> ())
+ .unwrap();
+ assert_eq!(o, "");
+}
diff --git a/src/output.rs b/src/output.rs
new file mode 100644
index 0000000..f2a7dc2
--- /dev/null
+++ b/src/output.rs
@@ -0,0 +1,42 @@
+
+
+use crate::{processor::Io, Process};
+
+trait Output: std::fmt::Debug {
+ fn take(&mut self, bytes: &[u8]) -> anyhow::Result<()>;
+}
+
+unsafe impl<T: Output> Process for T {
+ fn run(&mut self, on: Io) -> anyhow::Result<usize> {
+ match on {
+ Io::Last(bytes) => self.take(bytes)?,
+ Io::First(_) | Io::Middle(_, _) => unreachable!("outputs must be at the end"),
+ }
+ Ok(0)
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct StringOut(pub *mut String);
+
+impl Output for StringOut {
+ fn take(&mut self, bytes: &[u8]) -> anyhow::Result<()> {
+ unsafe { &mut *self.0 }.push_str(std::str::from_utf8(bytes)?);
+ Ok(())
+ }
+}
+
+/// `/dev/null`
+pub struct null {}
+
+impl std::fmt::Debug for null {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "/dev/null")
+ }
+}
+
+impl Output for null {
+ fn take(&mut self, _: &[u8]) -> anyhow::Result<()> {
+ Ok(())
+ }
+}
diff --git a/src/processor.rs b/src/processor.rs
new file mode 100644
index 0000000..1d8b129
--- /dev/null
+++ b/src/processor.rs
@@ -0,0 +1,112 @@
+// Process("echo a")[Io::Front] => Process(grep("a"))[Io::Middle] => Process(File)
+/// # Safety
+///
+/// this will return the number of bytes passed to the buffer, and ret < buffer bounds
+pub unsafe trait Process: std::fmt::Debug {
+ fn run(&mut self, on: Io) -> anyhow::Result<usize>;
+ fn done(&mut self, before: Option<bool>) -> anyhow::Result<bool> {
+ Ok(before.unwrap_or(false))
+ }
+}
+
+#[derive(Debug)]
+pub struct Processor {
+ pub(crate) processes: Vec<Box<dyn Process>>,
+ buffer: Vec<u8>,
+ buffer_size: u32,
+}
+
+pub enum Io<'a> {
+ /// echo "a" => next
+ First(&'a mut [u8]),
+ /// => file
+ Last(&'a [u8]),
+ /// => this =>
+ Middle(&'a [u8], &'a mut [u8]),
+}
+
+#[derive(Copy, Clone, Debug)]
+enum Buf {
+ A,
+ B,
+}
+
+impl Processor {
+ pub fn new(buffer: u32) -> Self {
+ Processor {
+ processes: Vec::with_capacity(2),
+ buffer: vec![0; buffer as usize * 2],
+ buffer_size: buffer,
+ }
+ }
+
+ pub(crate) fn add(&mut self, p: impl Process + 'static) {
+ self.processes.push(Box::new(p));
+ }
+
+ pub fn complete(&mut self) -> anyhow::Result<()> {
+ while !self.done()? {
+ self.step()?;
+ }
+ Ok(())
+ }
+
+ pub fn done(&mut self) -> anyhow::Result<bool> {
+ let mut done = true;
+ let mut last = None;
+ for p in &mut self.processes {
+ let result = p.done(last)?;
+ done &= result;
+ last = Some(result);
+ }
+ Ok(done)
+ }
+
+ pub fn step(&mut self) -> anyhow::Result<()> {
+ macro_rules! a {
+ () => {
+ &mut self.buffer[..self.buffer_size as usize]
+ };
+ }
+ macro_rules! b {
+ () => {
+ &mut self.buffer[self.buffer_size as usize..]
+ };
+ }
+ // [a,b,c,d,e]
+ // a (first(a_buf))
+ // b (mid(a_buf, b_buf))
+ // c (mid(b_buf, a_buf))
+ // d (mid(a_buf, b_buf))
+ // e (last(b_buf))
+ let mut last = Buf::A;
+ let mut read = 0;
+ let size = self.processes.len();
+ for (i, p) in self.processes.iter_mut().enumerate() {
+ match i {
+ 0 => read = p.run(Io::First(a!()))?,
+ n if n + 1 == size => {
+ p.run(Io::Last(
+ &match last {
+ Buf::A => a!(),
+ Buf::B => b!(),
+ }[..read],
+ ))?;
+ }
+ _ => match last {
+ Buf::A => {
+ let (a, b) = self.buffer.split_at_mut(self.buffer_size as usize);
+ read = p.run(Io::Middle(&a[..read], b))?;
+ last = Buf::B;
+ }
+ Buf::B => {
+ let (a, b) = self.buffer.split_at_mut(self.buffer_size as usize);
+ read = p.run(Io::Middle(&b[..read], a))?;
+ last = Buf::A;
+ }
+ },
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..b888a3c
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,36 @@
+#![allow(non_camel_case_types)]
+
+use std::io::Write;
+
+use crate::{processor::Io, Process};
+
+impl std::fmt::Debug for echo {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "echo {}",
+ String::from_utf8_lossy(self.what).escape_debug()
+ )
+ }
+}
+pub struct echo {
+ what: &'static [u8],
+ written: usize,
+}
+
+unsafe impl Process for echo {
+ fn run(&mut self, on: Io) -> anyhow::Result<usize> {
+ match on {
+ Io::First(mut w) => {
+ let writ = w.write(&self.what[..self.written])?;
+ self.written += writ;
+ Ok(writ)
+ }
+ Io::Middle(..) | Io::Last(_) => unreachable!(),
+ }
+ }
+
+ fn done(&mut self, _: Option<bool>) -> anyhow::Result<bool> {
+ Ok(self.written == self.what.len())
+ }
+}