mod error;
mod local;
use futures::future::BoxFuture;
use tokio::sync::mpsc;
pub use error::{FsError, SpawnError};
pub use local::LocalEnv;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AccessMode {
Exists,
Read,
ReadWrite,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FileStat {
pub is_dir: bool,
pub is_file: bool,
pub size: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SpawnRequest {
pub program: String,
pub args: Vec<String>,
pub cwd: String,
pub env: Vec<(String, String)>,
pub stdin: Option<Vec<u8>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ExitStatus {
pub code: Option<i32>,
pub signal: Option<i32>,
}
impl ExitStatus {
#[must_use]
pub fn shell_exit_code(self) -> i32 {
match (self.code, self.signal) {
(Some(code), _) => code,
(None, Some(signal)) => 128 + signal,
(None, None) => 1,
}
}
}
pub trait ProcessControl: Send {
fn pid(&self) -> Option<u32>;
fn kill_tree(&self);
fn wait(&mut self) -> BoxFuture<'_, std::io::Result<ExitStatus>>;
fn close_output(&mut self);
}
pub struct SpawnedProcess {
pub output: mpsc::UnboundedReceiver<Vec<u8>>,
pub control: Box<dyn ProcessControl>,
}
impl std::fmt::Debug for SpawnedProcess {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SpawnedProcess")
.field("pid", &self.control.pid())
.finish_non_exhaustive()
}
}
pub trait ExecutionEnv: Send + Sync {
fn access<'a>(&'a self, path: &'a str, mode: AccessMode) -> BoxFuture<'a, Result<(), FsError>>;
fn read_file<'a>(&'a self, path: &'a str) -> BoxFuture<'a, Result<Vec<u8>, FsError>>;
fn read_prefix<'a>(
&'a self,
path: &'a str,
max: usize,
) -> BoxFuture<'a, Result<Vec<u8>, FsError>>;
fn write_file<'a>(
&'a self,
path: &'a str,
data: &'a [u8],
) -> BoxFuture<'a, Result<(), FsError>>;
fn mkdir_p<'a>(&'a self, path: &'a str) -> BoxFuture<'a, Result<(), FsError>>;
fn stat<'a>(&'a self, path: &'a str) -> BoxFuture<'a, Result<FileStat, FsError>>;
fn read_dir<'a>(&'a self, path: &'a str) -> BoxFuture<'a, Result<Vec<String>, FsError>>;
fn realpath<'a>(&'a self, path: &'a str) -> BoxFuture<'a, Result<String, FsError>>;
fn spawn(&self, request: SpawnRequest) -> BoxFuture<'_, Result<SpawnedProcess, SpawnError>>;
fn is_local(&self) -> bool;
}
pub async fn path_exists(env: &dyn ExecutionEnv, path: &str) -> bool {
env.access(path, AccessMode::Exists).await.is_ok()
}