rusht 1.3.0

Shell commands written in Rust
Documentation
use ::std::collections::HashSet;
use ::std::env::current_dir;
use ::std::io::stdin;
use ::std::io::Read;
use ::std::path::PathBuf;
use ::std::thread::spawn;

use ::log::debug;
use log::warn;

use crate::common::{fail, CommandArgs, Task};

pub fn create_tasks(
    line_reader: impl FnOnce() -> Vec<String>,
    base_cmd: CommandArgs,
    working_dir: Option<String>,
    lines_with: Option<String>,
    stdin: Option<String>,
    unique: bool,
    ignore_stdin: bool,
) -> Vec<Task> {
    let cmd = base_cmd.unpack();
    let new_tasks = if let Some(templ) = lines_with {
        assert!(!templ.is_empty());
        let mut has_placeholder = cmd.iter().any(|part| part.contains(&templ));
        if !has_placeholder {
            if let Some(cwd) = &working_dir {
                has_placeholder |= cwd.contains(&templ);
            }
        }
        if !has_placeholder {
            if let Some(sin) = &stdin {
                has_placeholder |= sin.contains(&templ);
            }
        }
        if !has_placeholder {
            fail(format!("did not filter template string '{}' in task, working dir or stdin (cmd={} ; stdin={:?} ; cwd={:?})",
                    templ, cmd.join(" "), &stdin, &working_dir));
        }
        debug!("going to read stdin lines");
        let mut seen: HashSet<&String> = HashSet::new();
        line_reader()
            .iter()
            .filter(|line| !unique || seen.insert(line))
            .map(|input| {
                task_from_template(&cmd, input, &templ, working_dir.as_ref(), stdin.as_ref())
            })
            .collect()
    } else {
        if ! ignore_stdin {
            spawn(stdin_ignored_warning);
        }
        let working_dir = working_dir
            .map(PathBuf::from)
            .unwrap_or_else(|| current_dir().unwrap());
        if let Some(sin) = &stdin {
            if sin.contains("{}") {
                warn!("--stdin contains a default placeholder '{{}}' but --lines/--lines-with are not active so it will not be replaced");
            }
        }
        vec![Task::new_split(cmd, working_dir, stdin)]
    };
    debug!("finished constructing {} new tasks", new_tasks.len());
    new_tasks
}

fn task_from_template(
    cmd: &[String],
    input: &str,
    templ: &str,
    working_dir: Option<&String>,
    stdin: Option<&String>,
) -> Task {
    let parts = cmd.iter().map(|part| part.replace(templ, input)).collect();
    let working_dir = match working_dir {
        Some(dir) => PathBuf::from(dir.replace(templ, input))
            .canonicalize()
            .expect("failed to get absolute path for working directory"),
        None => current_dir().unwrap(),
    };
    let stdin = if let Some(sin) = stdin {
        Some(sin.replace(templ, input))
    } else {
        None
    };
    Task::new_split(parts, working_dir, stdin)
}

fn stdin_ignored_warning() {
    let mut buffer = [0u8; 1];
    if let Err(err) = stdin().lock().read(&mut buffer) {
        debug!("failed to read stdin, error {}", err)
    }
    if !buffer.is_empty() {
        eprintln!("found data on stdin, but --lines(-with) not given, so it will be ignored")
    }
}