pwner 0.1.8

Pwner is a Process Owner crate that allows ergonomic access to child processes
Documentation
struct Process {
    name: String,
    process: pwner::tokio::Duplex,
}

enum Command {
    List,
    Quit,
    Print(usize),
    Send(usize, String),
    Attach(usize),
    Run(String),
    Kill(usize),
    ProcessNotFound,
    Unknown,
}

fn parse(command: &str, children: &[Process]) -> Command {
    let mut args = command.split(' ');

    if let Some(first) = args.next() {
        match first {
            "l" => Command::List,
            "q" => Command::Quit,
            "p" => args
                .next()
                .and_then(|a| a.parse::<usize>().ok())
                .filter(|i| i < &children.len())
                .map_or(Command::ProcessNotFound, Command::Print),
            "a" => args
                .next()
                .and_then(|a| a.parse::<usize>().ok())
                .filter(|i| i < &children.len())
                .map_or(Command::ProcessNotFound, Command::Attach),
            "k" => args
                .next()
                .and_then(|a| a.parse::<usize>().ok())
                .filter(|i| i < &children.len())
                .map_or(Command::ProcessNotFound, Command::Kill),
            "s" => {
                if let Some(index) = args
                    .next()
                    .and_then(|a| a.parse::<usize>().ok())
                    .filter(|i| i < &children.len())
                {
                    if let Some(first) = args.next() {
                        let mut payload = String::from(first);
                        args.for_each(|arg| {
                            payload.push(' ');
                            payload.push_str(arg);
                        });
                        payload.push('\n');
                        Command::Send(index, payload)
                    } else {
                        Command::Unknown
                    }
                } else {
                    Command::ProcessNotFound
                }
            }
            "r" => {
                if let Some(command) = args.next() {
                    let mut payload = String::from(command);
                    args.for_each(|arg| {
                        payload.push(' ');
                        payload.push_str(arg);
                    });
                    return Command::Run(payload);
                }
                Command::Unknown
            }
            _ => Command::Unknown,
        }
    } else {
        Command::Unknown
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let mut children = Vec::<Process>::new();

    loop {
        use std::io::Write;
        use tokio::io::AsyncBufReadExt;
        use tokio::io::AsyncReadExt;

        print!("> ");
        let _ = std::io::stdout().flush();
        let mut buffer = String::new();
        let mut reader = tokio::io::BufReader::new(tokio::io::stdin());
        match reader.read_line(&mut buffer).await {
            Ok(bytes) => {
                if bytes == 0 {
                    break;
                }
            }
            Err(_) => break,
        }
        buffer.remove(buffer.len() - 1);

        match parse(&buffer, &children) {
            Command::List => children
                .iter()
                .enumerate()
                .for_each(|(i, process)| println!("{} - {}", i, process.name)),
            Command::Run(cmd) => {
                use pwner::Spawner;

                if let Ok(child) = tokio::process::Command::new(&cmd).spawn_owned() {
                    children.push(Process {
                        name: cmd,
                        process: child,
                    });
                } else {
                    eprintln!("Could not start \"{}\"", cmd);
                }
            }
            Command::Kill(index) => {
                children.remove(index);
            }
            Command::Quit => break,
            Command::Print(index) => {
                let process = &mut children[index].process;
                process.read_from(pwner::tokio::ReadSource::Both);

                let mut buffer = [0_u8; 1024];
                while let Ok(Ok(bytes)) = tokio::time::timeout(
                    std::time::Duration::from_millis(100),
                    process.read(&mut buffer),
                )
                .await
                {
                    if let Ok(string) = std::str::from_utf8(&buffer[..bytes]) {
                        print!("{}", string);
                    }
                }
                let _ = std::io::stdout().flush();
            }
            Command::Send(index, payload) => {
                use tokio::io::AsyncWriteExt;
                if children[index]
                    .process
                    .write(payload.as_bytes())
                    .await
                    .is_err()
                {
                    eprintln!("Could not send to child");
                }
            }
            Command::Attach(index) => {
                use tokio::io::{AsyncReadExt, AsyncWriteExt};

                let process = &mut children[index];

                let (stdin, stdout, stderr) = process.process.pipes();
                let mut stdin_buffer = [0u8; 1024];
                let mut stdout_buffer = [0_u8; 1024];
                let mut stderr_buffer = [0_u8; 1024];

                let mut input = tokio::io::stdin();
                println!("{} ----------", process.name);
                loop {
                    tokio::select! {
                        result = input.read(&mut stdin_buffer) => {
                            if let Ok(bytes @ 1..=1024) = result {
                                if stdin.write_all(&stdin_buffer[..bytes]).await.is_err() {
                                    break;
                                }
                            } else {
                                break;
                            }
                        },
                        Ok(bytes) = stdout.read(&mut stdout_buffer) => {
                            if bytes == 0 {
                                println!();
                            } else if let Ok(string) = std::str::from_utf8(&stdout_buffer[..bytes - 1]) {
                                println!("{}", string);
                            }
                        },
                        Ok(bytes) = stderr.read(&mut stderr_buffer) => {
                            if bytes == 0 {
                                eprintln!();
                            } else if let Ok(string) = std::str::from_utf8(&stderr_buffer[..bytes - 1]) {
                                eprintln!("{}", string);
                            }
                        },
                    }
                }
                println!("---------- {}", process.name);
            }
            Command::ProcessNotFound => eprintln!("Process is not running"),
            Command::Unknown => eprintln!("Unrecognized command"),
        }
    }
}