rush/src/env.rs

378 lines
8.2 KiB
Rust

//! Defines tools for interacting with the environment
//! of the shell.
use super::{ Error, Result, exec, };
use std::{
io::Write,
collections::HashMap,
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 <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>;
/// Get a bound value from the env.
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>;
/// Get the current working directory.
fn working_dir (&self) -> Result<std::path::PathBuf>;
/// Change the working directory.
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>;
/// Write to stdout.
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString;
/// Search the `PATH` variable for the `query`. The default implementation
/// is derived from [`Env::get`].
fn search <S> (&self, query: S) -> Option<exec::Program>
where
S: AsRef<str>
{
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.as_ref() {
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 <A> (self, args: A) -> SetArgs<Self>
where
Self: Sized,
A: IntoIterator<Item = String>,
{
SetArgs {
parent: self,
args: Args::from_iter(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 <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>
{
self.parent.set_working_dir(path)
}
fn search <S> (&self, query: S) -> Option<exec::Program>
where
S: AsRef<str>
{
self.parent.search(query)
}
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString
{
self.parent.stdout(data)
}
fn args (&self) -> Args {
self.args.clone()
}
fn bind <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>,
{
self.parent.bind(name, value);
}
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>
{
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 <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>
{
self.parent.set_working_dir(path)
}
fn search <S> (&self, query: S) -> Option<exec::Program>
where
S: AsRef<str>
{
self.parent.search(query)
}
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString
{
self.parent.stdout(data)
}
fn args (&self) -> Args {
self.parent.args()
}
fn bind <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>,
{
self.bindings.insert(
name.as_ref().to_string(),
value.as_ref().to_string()
);
}
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>
{
self.bindings
.get(name.as_ref())
.cloned()
.or_else(|| {
self.parent.get(name)
})
}
}
/// An iterator of arguments.
#[derive(Default, Clone)]
pub struct Args {
args: Vec<String>,
cur: usize
}
impl<S: ToString> FromIterator<S> for Args {
fn from_iter <T: IntoIterator<Item = S>> (iter: T) -> Args {
Args {
args: iter
.into_iter()
.map(|s| s.to_string())
.collect(),
cur: 0
}
}
}
impl Iterator for Args {
type Item = String;
fn next (&mut self) -> Option<Self::Item> {
let Args { args, cur } = self;
let val = args.get(*cur)?;
*cur += 1;
Some (val.clone())
}
}
/// 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 <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>,
{
self.bindings.insert(
name.as_ref().to_string(),
value.as_ref().to_string()
);
}
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>
{
self.bindings.get(name.as_ref()).cloned()
}
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString
{
self.buf += &data.to_string();
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 <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>
{
let path = path.as_ref();
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 <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>,
{
std::env::set_var(name.as_ref(), value.as_ref());
}
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>
{
std::env::var(name.as_ref()).ok()
}
fn working_dir (&self) -> Result<std::path::PathBuf> {
std::env::current_dir()
.map_err(Error::Io)
}
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>
{
let path = path.as_ref();
std::env::set_current_dir(path)
.map_err(Error::Io)
}
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString
{
std::io::stdout().write(
data.to_string()
.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;