283 lines
6.8 KiB
Rust
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;
|