use std::path::Path;
use tokio_util::sync::CancellationToken;
use crate::builtins::write_line;
use crate::interp::Flow;
use crate::io::OutTarget;
pub(crate) async fn sleep(args: &[String], stderr: OutTarget, cancel: &CancellationToken) -> Flow {
let [only] = args else {
write_line(
stderr,
format_args!("sleep: invalid duration: {}", args.join(" ")),
);
return Flow::Next(2);
};
if only.starts_with('-') {
write_line(stderr, format_args!("sleep: invalid option: {only}"));
return Flow::Next(2);
}
let duration = only
.parse::<f64>()
.ok()
.and_then(|seconds| std::time::Duration::try_from_secs_f64(seconds).ok());
let Some(duration) = duration else {
write_line(stderr, format_args!("sleep: invalid duration: {only}"));
return Flow::Next(2);
};
tokio::select! {
() = tokio::time::sleep(duration) => Flow::Next(0),
() = cancel.cancelled() => Flow::Next(130),
}
}
pub(crate) fn test(args: &[String], cwd: &Path, stderr: OutTarget) -> Flow {
finish(evaluate(args, cwd), "test", stderr)
}
pub(crate) fn test_bracket(args: &[String], cwd: &Path, stderr: OutTarget) -> Flow {
match args.split_last() {
Some((last, rest)) if last == "]" => finish(evaluate(rest, cwd), "[", stderr),
_ => {
write_line(stderr, "[: missing closing ]");
Flow::Next(2)
}
}
}
fn finish(result: Result<bool, String>, name: &str, stderr: OutTarget) -> Flow {
match result {
Ok(true) => Flow::Next(0),
Ok(false) => Flow::Next(1),
Err(detail) => {
write_line(stderr, format_args!("{name}: {detail}"));
Flow::Next(2)
}
}
}
fn evaluate(args: &[String], cwd: &Path) -> Result<bool, String> {
if let Some((first, rest)) = args.split_first()
&& first == "!"
{
return evaluate(rest, cwd).map(|result| !result);
}
match args {
[] => Ok(false),
[only] => Ok(!only.is_empty()),
[op, operand] => unary(op, operand, cwd),
[left, op, right] => binary(left, op, right),
_ => Err("too many arguments".to_string()),
}
}
fn unary(op: &str, operand: &str, cwd: &Path) -> Result<bool, String> {
match op {
"-f" => Ok(cwd.join(operand).is_file()),
"-d" => Ok(cwd.join(operand).is_dir()),
"-e" => Ok(cwd.join(operand).exists()),
"-z" => Ok(operand.is_empty()),
"-n" => Ok(!operand.is_empty()),
_ => Err(format!("unknown unary operator: {op}")),
}
}
fn binary(left: &str, op: &str, right: &str) -> Result<bool, String> {
match op {
"=" => Ok(left == right),
"!=" => Ok(left != right),
"-eq" | "-ne" | "-lt" | "-le" | "-gt" | "-ge" => {
let left = parse_integer(left)?;
let right = parse_integer(right)?;
Ok(match op {
"-eq" => left == right,
"-ne" => left != right,
"-lt" => left < right,
"-le" => left <= right,
"-gt" => left > right,
"-ge" => left >= right,
_ => unreachable!("matched above"),
})
}
_ => Err(format!("unknown binary operator: {op}")),
}
}
fn parse_integer(operand: &str) -> Result<i64, String> {
operand
.parse()
.map_err(|_| format!("integer expression expected: {operand}"))
}