Restructuring + added the SetArgs Env

This commit is contained in:
Riley Apeldoorn 2021-07-05 22:17:49 +02:00
parent 6b9c73cc03
commit ab11cf6f74
7 changed files with 586 additions and 535 deletions

282
src/env.rs Normal file
View 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
View File

@ -0,0 +1 @@
//! History control.

1
src/env/io.rs vendored Normal file
View File

@ -0,0 +1 @@
//! Interaction with stdio and the file system.

39
src/env/job.rs vendored Normal file
View 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
View 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
View 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!()
}
}

View File

@ -1,5 +1,9 @@
//! The Rush shell.
pub mod env;
pub mod exec;
pub mod eval;
use env::job;
/// 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!()
}
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::{
Error,
Result,