Restructuring + added the SetArgs Env
This commit is contained in:
parent
6b9c73cc03
commit
ab11cf6f74
7 changed files with 586 additions and 535 deletions
282
src/env.rs
Normal file
282
src/env.rs
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
//! 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;
|
1
src/env/history.rs
vendored
Normal file
1
src/env/history.rs
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
//! History control.
|
1
src/env/io.rs
vendored
Normal file
1
src/env/io.rs
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
//! Interaction with stdio and the file system.
|
39
src/env/job.rs
vendored
Normal file
39
src/env/job.rs
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
//! Job scheduling.
|
||||||
|
|
||||||
|
use super::Result;
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use sealed::Sealed;
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An empty type used to tag a [`Job`] as running in the background.
|
||||||
|
pub enum Stopped {}
|
||||||
|
impl Sealed for Stopped {}
|
||||||
|
|
||||||
|
/// An empty type used to tag a [`Job`] as running in the foreground.
|
||||||
|
pub enum Running {}
|
||||||
|
impl Sealed for Running {}
|
||||||
|
|
||||||
|
/// Represents a running process, either in the foreground
|
||||||
|
/// or in the background.
|
||||||
|
pub struct Job <State: Sealed> {
|
||||||
|
_t: PhantomData<State>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Job<Running> {
|
||||||
|
|
||||||
|
/// Attempt to send this job to the background.
|
||||||
|
pub fn bg (self) -> Result<Job<Stopped>> { todo!() }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Job<Stopped> {
|
||||||
|
|
||||||
|
/// Attempt to pull this job to the foreground.
|
||||||
|
pub fn fg (self) -> Result<Job<Running>> { todo!() }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
28
src/eval.rs
Normal file
28
src/eval.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
//! The Rush scripting language.
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Result,
|
||||||
|
env::Env,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The value of an expression.
|
||||||
|
pub enum Value {
|
||||||
|
String (String)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An identifier.
|
||||||
|
pub struct Name (String);
|
||||||
|
|
||||||
|
/// Evaluation of expressions in the Rush scripting language.
|
||||||
|
pub trait Eval {
|
||||||
|
|
||||||
|
/// Evaluates the code in the context of the given [`Env`].
|
||||||
|
fn eval <E> (self, env: &E) -> Result<Value>
|
||||||
|
where
|
||||||
|
E: Env;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator of parsed code generated from an iterator of raw
|
||||||
|
/// code fragments.
|
||||||
|
pub struct Parser;
|
231
src/exec.rs
Normal file
231
src/exec.rs
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
//! Execution of commands, builtins and code.
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Result,
|
||||||
|
env::Env,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Execute the code in the given environment.
|
||||||
|
pub trait Exec {
|
||||||
|
|
||||||
|
/// Run the executable.
|
||||||
|
fn exec <E> (&self, env: &mut E) -> Result<()>
|
||||||
|
where
|
||||||
|
E: Env;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Exec for F
|
||||||
|
where
|
||||||
|
F: Fn (&mut dyn Env) -> Result<()>
|
||||||
|
{
|
||||||
|
fn exec <E> (&self, env: &mut E) -> Result<()>
|
||||||
|
where
|
||||||
|
E: Env
|
||||||
|
{
|
||||||
|
self (env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shell command.
|
||||||
|
pub enum Command {
|
||||||
|
Program (Program),
|
||||||
|
Builtin (Builtin),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Exec for Command {
|
||||||
|
fn exec<E>(&self, env: &mut E) -> Result<()>
|
||||||
|
where
|
||||||
|
E: Env
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Command::Builtin (builtin) => builtin.exec(env),
|
||||||
|
Command::Program (program) => program.exec(env),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A path to an executable file representing an external program.
|
||||||
|
pub struct Program (std::ffi::OsString);
|
||||||
|
|
||||||
|
impl Program {
|
||||||
|
|
||||||
|
/// Create a new [`Program`] struct.
|
||||||
|
pub (super) fn new (str: impl AsRef<std::ffi::OsStr>) -> Program {
|
||||||
|
Program (str.as_ref().to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the command name as an [`OsStr`](std::ffi::OsStr)
|
||||||
|
pub fn as_os_str (&self) -> &std::ffi::OsStr {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the [`Path`](std::path::Path) of the file the name references.
|
||||||
|
pub fn as_path (&self) -> &std::path::Path {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<std::path::Path> for Program {
|
||||||
|
fn as_ref (&self) -> &std::path::Path {
|
||||||
|
std::path::Path::new(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Exec for Program {
|
||||||
|
fn exec <E> (&self, env: &mut E) -> Result<()>
|
||||||
|
where
|
||||||
|
E: Env
|
||||||
|
{
|
||||||
|
use ptyprocess::PtyProcess;
|
||||||
|
use std::io::{ BufRead, Read };
|
||||||
|
|
||||||
|
let mut cmd = std::process::Command::new(self.as_os_str());
|
||||||
|
|
||||||
|
cmd.current_dir(env.working_dir()?);
|
||||||
|
cmd.args(env.args());
|
||||||
|
|
||||||
|
let proc = PtyProcess::spawn(cmd)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut pty = proc
|
||||||
|
.get_pty_handle()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
while let Ok (true) = proc.is_alive() {
|
||||||
|
let mut buf = String::new();
|
||||||
|
pty.read_to_string(&mut buf);
|
||||||
|
env.stdout(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Represents the case that a string fails to parse to a [`Builtin`]
|
||||||
|
/// because the associated builtin does not exist. Valid builtin names
|
||||||
|
/// are listed in the [`builtin`] module.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NotFound;
|
||||||
|
|
||||||
|
/// A shell builtin. See also the [`builtin`] module.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// The only way to get a `Builtin` is through [`FromStr`].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use rush::exec::Builtin;
|
||||||
|
/// "type".parse::<Builtin>().unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Parsing an unknown builtin name yields an error.
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # use rush::exec::Builtin;
|
||||||
|
/// "sjdkgfhgfkfg"
|
||||||
|
/// .parse::<Builtin>()
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`FromStr`]: std::str::FromStr
|
||||||
|
pub struct Builtin (fn (&mut dyn Env) -> Result<()>);
|
||||||
|
|
||||||
|
impl std::str::FromStr for Builtin {
|
||||||
|
type Err = NotFound;
|
||||||
|
|
||||||
|
fn from_str (s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let ret = Builtin (match s {
|
||||||
|
"disown" => builtin::disown,
|
||||||
|
"type" => builtin::r#type,
|
||||||
|
"exit" => builtin::exit,
|
||||||
|
"pwd" => builtin::pwd,
|
||||||
|
"fg" => builtin::fg,
|
||||||
|
"bg" => builtin::bg,
|
||||||
|
"cd" => builtin::cd,
|
||||||
|
_ => return Err (NotFound)
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok (ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Exec for Builtin {
|
||||||
|
fn exec <E> (&self, env: &mut E) -> Result<()>
|
||||||
|
where
|
||||||
|
E: Env
|
||||||
|
{
|
||||||
|
self.0.exec(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod builtin {
|
||||||
|
|
||||||
|
//! Shell builtins.
|
||||||
|
|
||||||
|
use super::{ Env, Result, };
|
||||||
|
|
||||||
|
/// Change the current working directory.
|
||||||
|
pub fn cd (env: &mut dyn Env) -> Result<()> {
|
||||||
|
let arg = env.args().next().unwrap();
|
||||||
|
env.set_working_dir(arg.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print the current working directory.
|
||||||
|
pub fn pwd (env: &mut dyn Env) -> Result<()> {
|
||||||
|
let cwd = env.working_dir()?;
|
||||||
|
env.stdout(format!("{}", cwd.display()).as_str());
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the program. Optionally takes an exit code.
|
||||||
|
pub fn exit (env: &mut dyn Env) -> Result<()> {
|
||||||
|
if let Some (code) = env.args().next().and_then(|s| s.parse::<i32>().ok()) {
|
||||||
|
std::process::exit(code)
|
||||||
|
} else {
|
||||||
|
std::process::exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display information about the type of a command.
|
||||||
|
pub fn r#type (env: &mut dyn Env) -> Result<()> {
|
||||||
|
use crate::exec::Builtin;
|
||||||
|
|
||||||
|
for arg in env.args() {
|
||||||
|
let output = if let Ok (_) = arg.parse::<Builtin>() {
|
||||||
|
format!("{} is a shell builtin", arg)
|
||||||
|
} else if let Some (path) = env.search(&arg) {
|
||||||
|
format!("{} is {}", arg, path.as_path().display())
|
||||||
|
} else {
|
||||||
|
format!("type: {}: not found", arg)
|
||||||
|
};
|
||||||
|
|
||||||
|
env.stdout((output + "\n").as_str())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a job to the background. See also [`Job::bg`].
|
||||||
|
///
|
||||||
|
/// [`Job::bg`]: crate::job::Job::bg
|
||||||
|
pub fn bg (_: &mut dyn Env) -> Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bring a job to the foreground. See also [`Job::fg`].
|
||||||
|
///
|
||||||
|
/// [`Job::fg`]: crate::job::Job::fg
|
||||||
|
pub fn fg (_: &mut dyn Env) -> Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disown all background jobs.
|
||||||
|
pub fn disown (_: &mut dyn Env) -> Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
539
src/lib.rs
539
src/lib.rs
|
@ -1,5 +1,9 @@
|
||||||
//! The Rush shell.
|
//! The Rush shell.
|
||||||
|
|
||||||
|
pub mod env;
|
||||||
|
pub mod exec;
|
||||||
|
pub mod eval;
|
||||||
|
|
||||||
use env::job;
|
use env::job;
|
||||||
|
|
||||||
/// Attempt to execute a program, rush expression or builtin.
|
/// Attempt to execute a program, rush expression or builtin.
|
||||||
|
@ -7,541 +11,6 @@ pub fn exec (prog: impl exec::Exec) -> Result<job::Job<job::Running>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod exec {
|
|
||||||
|
|
||||||
//! Execution of commands, builtins and code.
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
Result,
|
|
||||||
env::Env,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Execute the code in the given environment.
|
|
||||||
pub trait Exec {
|
|
||||||
|
|
||||||
/// Run the executable.
|
|
||||||
fn exec <E> (&self, env: &mut E) -> Result<()>
|
|
||||||
where
|
|
||||||
E: Env;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F> Exec for F
|
|
||||||
where
|
|
||||||
F: Fn (&mut dyn Env) -> Result<()>
|
|
||||||
{
|
|
||||||
fn exec <E> (&self, env: &mut E) -> Result<()>
|
|
||||||
where
|
|
||||||
E: Env
|
|
||||||
{
|
|
||||||
self (env)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A shell command.
|
|
||||||
pub enum Command {
|
|
||||||
Program (Program),
|
|
||||||
Builtin (Builtin),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Exec for Command {
|
|
||||||
fn exec<E>(&self, env: &mut E) -> Result<()>
|
|
||||||
where
|
|
||||||
E: Env
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Command::Builtin (builtin) => builtin.exec(env),
|
|
||||||
Command::Program (program) => program.exec(env),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A path to an executable file representing an external program.
|
|
||||||
pub struct Program (std::ffi::OsString);
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
|
|
||||||
/// Create a new [`Program`] struct.
|
|
||||||
pub (super) fn new (str: impl AsRef<std::ffi::OsStr>) -> Program {
|
|
||||||
Program (str.as_ref().to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the command name as an [`OsStr`](std::ffi::OsStr)
|
|
||||||
pub fn as_os_str (&self) -> &std::ffi::OsStr {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the [`Path`](std::path::Path) of the file the name references.
|
|
||||||
pub fn as_path (&self) -> &std::path::Path {
|
|
||||||
self.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<std::path::Path> for Program {
|
|
||||||
fn as_ref (&self) -> &std::path::Path {
|
|
||||||
std::path::Path::new(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Exec for Program {
|
|
||||||
fn exec <E> (&self, env: &mut E) -> Result<()>
|
|
||||||
where
|
|
||||||
E: Env
|
|
||||||
{
|
|
||||||
use ptyprocess::PtyProcess;
|
|
||||||
use std::io::{ BufRead, Read };
|
|
||||||
|
|
||||||
let mut cmd = std::process::Command::new(self.as_os_str());
|
|
||||||
|
|
||||||
cmd.current_dir(env.working_dir()?);
|
|
||||||
cmd.args(env.args());
|
|
||||||
|
|
||||||
let proc = PtyProcess::spawn(cmd)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut pty = proc
|
|
||||||
.get_pty_handle()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
while let Ok (true) = proc.is_alive() {
|
|
||||||
let mut buf = String::new();
|
|
||||||
pty.read_to_string(&mut buf);
|
|
||||||
println!("{}", buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok (())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Represents the case that a string fails to parse to a [`Builtin`]
|
|
||||||
/// because the associated builtin does not exist. Valid builtin names
|
|
||||||
/// are listed in the [`builtin`] module.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NotFound;
|
|
||||||
|
|
||||||
/// A shell builtin. See also the [`builtin`] module.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// The only way to get a `Builtin` is through [`FromStr`].
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use rush::exec::Builtin;
|
|
||||||
/// "type".parse::<Builtin>().unwrap();
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Parsing an unknown builtin name yields an error.
|
|
||||||
///
|
|
||||||
/// ```should_panic
|
|
||||||
/// # use rush::exec::Builtin;
|
|
||||||
/// "sjdkgfhgfkfg"
|
|
||||||
/// .parse::<Builtin>()
|
|
||||||
/// .unwrap();
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [`FromStr`]: std::str::FromStr
|
|
||||||
pub struct Builtin (fn (&mut dyn Env) -> Result<()>);
|
|
||||||
|
|
||||||
impl std::str::FromStr for Builtin {
|
|
||||||
type Err = NotFound;
|
|
||||||
|
|
||||||
fn from_str (s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let ret = Builtin (match s {
|
|
||||||
"disown" => builtin::disown,
|
|
||||||
"type" => builtin::r#type,
|
|
||||||
"exit" => builtin::exit,
|
|
||||||
"pwd" => builtin::pwd,
|
|
||||||
"fg" => builtin::fg,
|
|
||||||
"bg" => builtin::bg,
|
|
||||||
"cd" => builtin::cd,
|
|
||||||
_ => return Err (NotFound)
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok (ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Exec for Builtin {
|
|
||||||
fn exec <E> (&self, env: &mut E) -> Result<()>
|
|
||||||
where
|
|
||||||
E: Env
|
|
||||||
{
|
|
||||||
self.0.exec(env)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod builtin {
|
|
||||||
|
|
||||||
//! Shell builtins.
|
|
||||||
|
|
||||||
use super::{ Env, Result, };
|
|
||||||
|
|
||||||
/// Change the current working directory.
|
|
||||||
pub fn cd (env: &mut dyn Env) -> Result<()> {
|
|
||||||
let arg = env.args().next().unwrap();
|
|
||||||
env.set_working_dir(arg.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print the current working directory.
|
|
||||||
pub fn pwd (env: &mut dyn Env) -> Result<()> {
|
|
||||||
let cwd = env.working_dir()?;
|
|
||||||
println!("{}", cwd.display());
|
|
||||||
Ok (())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop the program. Optionally takes an exit code.
|
|
||||||
pub fn exit (env: &mut dyn Env) -> Result<()> {
|
|
||||||
if let Some (code) = env.args().next().and_then(|s| s.parse::<i32>().ok()) {
|
|
||||||
std::process::exit(code)
|
|
||||||
} else {
|
|
||||||
std::process::exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Display information about the type of a command.
|
|
||||||
pub fn r#type (env: &mut dyn Env) -> Result<()> {
|
|
||||||
use crate::exec::Builtin;
|
|
||||||
|
|
||||||
for arg in env.args() {
|
|
||||||
if let Ok (_) = arg.parse::<Builtin>() {
|
|
||||||
println!("{} is a shell builtin", arg);
|
|
||||||
} else if let Some (path) = env.search(&arg) {
|
|
||||||
println!("{} is {}", arg, path.as_path().display());
|
|
||||||
} else {
|
|
||||||
println!("type: {}: not found", arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok (())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a job to the background. See also [`Job::bg`].
|
|
||||||
///
|
|
||||||
/// [`Job::bg`]: crate::job::Job::bg
|
|
||||||
pub fn bg (_: &mut dyn Env) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bring a job to the foreground. See also [`Job::fg`].
|
|
||||||
///
|
|
||||||
/// [`Job::fg`]: crate::job::Job::fg
|
|
||||||
pub fn fg (_: &mut dyn Env) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Disown all background jobs.
|
|
||||||
pub fn disown (_: &mut dyn Env) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod eval {
|
|
||||||
|
|
||||||
//! The Rush scripting language.
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
Result,
|
|
||||||
env::Env,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The value of an expression.
|
|
||||||
pub enum Value {
|
|
||||||
String (String)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An identifier.
|
|
||||||
pub struct Name (String);
|
|
||||||
|
|
||||||
/// Evaluation of expressions in the Rush scripting language.
|
|
||||||
pub trait Eval {
|
|
||||||
|
|
||||||
/// Evaluates the code in the context of the given [`Env`].
|
|
||||||
fn eval <E> (self, env: &E) -> Result<Value>
|
|
||||||
where
|
|
||||||
E: Env;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator of parsed code generated from an iterator of raw
|
|
||||||
/// code fragments.
|
|
||||||
pub struct Parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod env {
|
|
||||||
|
|
||||||
//! Defines tools for interacting with the environment
|
|
||||||
//! of the shell.
|
|
||||||
|
|
||||||
use super::{ Error, Result, exec, };
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
iter::FromIterator,
|
|
||||||
collections::HashMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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<()>;
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 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)]
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pure {
|
|
||||||
pub fn init (cwd: impl AsRef<std::path::Path>) -> Pure {
|
|
||||||
Pure {
|
|
||||||
cwd: cwd.as_ref().to_owned(),
|
|
||||||
bindings: HashMap::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 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialize the default environment by inheriting from the environment of
|
|
||||||
/// the shell process.
|
|
||||||
pub fn inherit () -> impl Env {
|
|
||||||
Inherit {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod history {
|
|
||||||
|
|
||||||
//! History control.
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod io {
|
|
||||||
|
|
||||||
//! Interaction with stdio and the file system.
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod job {
|
|
||||||
|
|
||||||
//! Job scheduling.
|
|
||||||
|
|
||||||
use super::Result;
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use sealed::Sealed;
|
|
||||||
mod sealed {
|
|
||||||
pub trait Sealed {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An empty type used to tag a [`Job`] as running in the background.
|
|
||||||
pub enum Stopped {}
|
|
||||||
impl Sealed for Stopped {}
|
|
||||||
|
|
||||||
/// An empty type used to tag a [`Job`] as running in the foreground.
|
|
||||||
pub enum Running {}
|
|
||||||
impl Sealed for Running {}
|
|
||||||
|
|
||||||
/// Represents a running process, either in the foreground
|
|
||||||
/// or in the background.
|
|
||||||
pub struct Job <State: Sealed> {
|
|
||||||
_t: PhantomData<State>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Job<Running> {
|
|
||||||
|
|
||||||
/// Attempt to send this job to the background.
|
|
||||||
pub fn bg (self) -> Result<Job<Stopped>> { todo!() }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Job<Stopped> {
|
|
||||||
|
|
||||||
/// Attempt to pull this job to the foreground.
|
|
||||||
pub fn fg (self) -> Result<Job<Running>> { todo!() }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use err::{
|
pub use err::{
|
||||||
Error,
|
Error,
|
||||||
Result,
|
Result,
|
||||||
|
|
Loading…
Reference in a new issue