//! The Rush shell. use env::job; /// Attempt to execute a program, rush expression or builtin. pub fn exec (prog: impl exec::Exec) -> Result> { 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 (&self, env: &mut E) -> Result<()> where E: Env; } impl Exec for F where F: Fn (&mut dyn Env) -> Result<()> { fn exec (&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(&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) -> 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 for Program { fn as_ref (&self) -> &std::path::Path { std::path::Path::new(&self.0) } } impl Exec for Program { fn exec (&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::().unwrap(); /// ``` /// /// Parsing an unknown builtin name yields an error. /// /// ```should_panic /// # use rush::exec::Builtin; /// "sjdkgfhgfkfg" /// .parse::() /// .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 { 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 (&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::().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::() { 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 (self, env: &E) -> Result 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; /// Get the current working directory. fn working_dir (&self) -> Result; /// 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 { 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 , parent: &'parent mut E, } impl Env for Scope<'_, E> { fn working_dir (&self) -> Result { 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 { 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 { self.bindings .get(name) .cloned() .or_else(|| { self.parent.get(name) }) } } /// An iterator of arguments. #[derive(Default)] pub struct Args (Vec); impl FromIterator for Args { fn from_iter > (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 { if self.0.len() > 0 { Some (self.0.remove(0)) } else { None } } } /// A completely empty environment. pub struct Pure { bindings: HashMap , cwd: std::path::PathBuf, } impl Pure { pub fn init (cwd: impl AsRef) -> Pure { Pure { cwd: cwd.as_ref().to_owned(), bindings: HashMap::new(), } } } impl Env for Pure { fn args (&self) -> Args { std::iter::empty::().collect() } fn bind (&mut self, name: &str, value: &str) { self.bindings.insert(name.to_string(), value.to_string()); } fn get (&self, name: &str) -> Option { self.bindings.get(name).cloned() } fn working_dir (&self) -> Result { 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 { std::env::var(name).ok() } fn working_dir (&self) -> Result { 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 { _t: PhantomData, } impl Job { /// Attempt to send this job to the background. pub fn bg (self) -> Result> { todo!() } } impl Job { /// Attempt to pull this job to the foreground. pub fn fg (self) -> Result> { todo!() } } } } pub use err::{ Error, Result, }; mod err { /// A global union type of errors produced anywhere within the Rush shell. #[derive(Debug)] pub enum Error { Io (std::io::Error) } impl From for Error { fn from (err: std::io::Error) -> Self { Error::Io (err) } } /// A less verbose way to use the [`Result`](std::result::Result) type with /// an [`Error`]. pub type Result = std::result::Result; }