rush/src/env.rs

283 lines
6.8 KiB
Rust

//! Defines tools for interacting with the environment
//! of the shell.
use super::{ Error, Result, exec, };
use std::{collections::HashMap, io::Write, iter::FromIterator};
/// An environment used to [evaluate expressions](mod@super::eval) or
/// [execute programs](mod@super::exec).
pub trait Env {
/// Get the arguments passed to the environment.
fn args (&self) -> Args;
/// Bind a value to a name.
fn bind (&mut self, name: &str, value: &str);
/// Get a bound value from the env.
fn get (&self, name: &str) -> Option<String>;
/// Get the current working directory.
fn working_dir (&self) -> Result<std::path::PathBuf>;
/// Change the working directory.
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()>;
/// Write to stdout.
fn stdout (&mut self, data: &str) -> Result<()>;
/// Search the `PATH` variable for the `query`. The default implementation
/// is derived from [`Env::get`].
fn search (&self, query: &str) -> Option<exec::Program> {
self.get("PATH")?
.split(':')
.filter_map(|path| {
std::fs::read_dir(path).ok()
})
.flat_map(|dir| {
dir.filter_map(|entry| entry.ok())
})
.map(|file| file.path())
.find_map(|path| {
if path.file_name()? == query {
Some (path)
} else {
None
}
})
.map(exec::Program::new)
}
/// Create a scoped environment to isolate variable
/// bindings.
fn scope (&mut self) -> Scope<'_, Self>
where
Self: Sized
{
Scope {
bindings: HashMap::new(),
parent: self,
}
}
/// Create a new environment that inherits this environments'
/// behavior, except the args of this env are overwritten with
/// `args`.
fn set_args (self, args: Args) -> SetArgs<Self>
where
Self: Sized
{
SetArgs {
parent: self,
args,
}
}
}
/// Overwrites the arguments in the wrapped [`Env`].
pub struct SetArgs<E: Env> {
parent: E,
args: Args,
}
impl<E: Env> Env for SetArgs<E> {
fn working_dir (&self) -> Result<std::path::PathBuf> {
self.parent.working_dir()
}
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
self.parent.set_working_dir(path)
}
fn search (&self, query: &str) -> Option<exec::Program> {
self.parent.search(query)
}
fn stdout (&mut self, data: &str) -> Result<()> {
self.parent.stdout(data)
}
fn args (&self) -> Args {
self.args.clone()
}
fn bind (&mut self, name: &str, value: &str) {
self.parent.bind(name, value);
}
fn get (&self, name: &str) -> Option<String> {
self.parent.get(name)
}
}
/// A one-way barrier for variable bindings. Things executed in
/// a `Scope` can read the parent's bindings and shadow them, but
/// not mutate them.
pub struct Scope <'parent, E: Env> {
bindings: HashMap <String, String>,
parent: &'parent mut E,
}
impl<E: Env> Env for Scope<'_, E> {
fn working_dir (&self) -> Result<std::path::PathBuf> {
self.parent.working_dir()
}
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
self.parent.set_working_dir(path)
}
fn search (&self, query: &str) -> Option<exec::Program> {
self.parent.search(query)
}
fn stdout (&mut self, data: &str) -> Result<()> {
self.parent.stdout(data)
}
fn args (&self) -> Args {
self.parent.args()
}
fn bind (&mut self, name: &str, value: &str) {
self.bindings.insert(name.to_string(), value.to_string());
}
fn get (&self, name: &str) -> Option<String> {
self.bindings
.get(name)
.cloned()
.or_else(|| {
self.parent.get(name)
})
}
}
/// An iterator of arguments.
#[derive(Default, Clone)]
pub struct Args (Vec<String>);
impl<S: ToString> FromIterator<S> for Args {
fn from_iter <T: IntoIterator<Item = S>> (iter: T) -> Args {
Args (
iter.into_iter()
.map(|s| s.to_string())
.collect()
)
}
}
impl Iterator for Args {
type Item = String;
fn next (&mut self) -> Option<Self::Item> {
if self.0.len() > 0 {
Some (self.0.remove(0))
} else {
None
}
}
}
/// A completely empty environment.
pub struct Pure {
bindings: HashMap <String, String>,
cwd: std::path::PathBuf,
buf: String,
}
impl Pure {
pub fn init (cwd: impl AsRef<std::path::Path>) -> Pure {
Pure {
cwd: cwd.as_ref().to_owned(),
bindings: HashMap::new(),
buf: String::new(),
}
}
}
impl Env for Pure {
fn args (&self) -> Args {
std::iter::empty::<String>().collect()
}
fn bind (&mut self, name: &str, value: &str) {
self.bindings.insert(name.to_string(), value.to_string());
}
fn get (&self, name: &str) -> Option<String> {
self.bindings.get(name).cloned()
}
fn stdout (&mut self, data: &str) -> Result<()> {
self.buf += data;
Ok (())
}
fn working_dir (&self) -> Result<std::path::PathBuf> {
if self.cwd.exists() {
Ok (self.cwd.clone())
} else {
use std::io::ErrorKind;
Err (Error::Io (ErrorKind::NotFound.into()))
}
}
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
if path.is_dir() && path.exists() {
Ok (self.cwd = path.to_owned())
} else {
use std::io::ErrorKind;
Err (Error::Io (ErrorKind::InvalidInput.into()))
}
}
}
/// The default global environment, which inherits from the environment of the
/// shell process.
pub struct Inherit {}
impl Env for Inherit {
fn args (&self) -> Args {
std::env::args().skip(1).collect()
}
fn bind (&mut self, name: &str, value: &str) {
std::env::set_var(name, value);
}
fn get (&self, name: &str) -> Option<String> {
std::env::var(name).ok()
}
fn working_dir (&self) -> Result<std::path::PathBuf> {
std::env::current_dir()
.map_err(Error::Io)
}
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
std::env::set_current_dir(path)
.map_err(Error::Io)
}
fn stdout (&mut self, data: &str) -> Result<()> {
std::io::stdout().write(data.as_bytes())?;
Ok (())
}
}
/// Initialize the default environment by inheriting from the environment of
/// the shell process.
pub fn inherit () -> impl Env {
Inherit {}
}
pub mod history;
pub mod job;
pub mod io;