378 lines
8.2 KiB
Rust
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;
|