Compare commits
2 Commits
ab11cf6f74
...
main
Author | SHA1 | Date |
---|---|---|
Riley Apeldoorn | d366f17ed4 | |
Riley Apeldoorn | 3447bcef80 |
187
src/env.rs
187
src/env.rs
|
@ -3,7 +3,11 @@
|
||||||
|
|
||||||
use super::{ Error, Result, exec, };
|
use super::{ Error, Result, exec, };
|
||||||
|
|
||||||
use std::{collections::HashMap, io::Write, iter::FromIterator};
|
use std::{
|
||||||
|
io::Write,
|
||||||
|
collections::HashMap,
|
||||||
|
iter::FromIterator
|
||||||
|
};
|
||||||
|
|
||||||
/// An environment used to [evaluate expressions](mod@super::eval) or
|
/// An environment used to [evaluate expressions](mod@super::eval) or
|
||||||
/// [execute programs](mod@super::exec).
|
/// [execute programs](mod@super::exec).
|
||||||
|
@ -13,23 +17,35 @@ pub trait Env {
|
||||||
fn args (&self) -> Args;
|
fn args (&self) -> Args;
|
||||||
|
|
||||||
/// Bind a value to a name.
|
/// Bind a value to a name.
|
||||||
fn bind (&mut self, name: &str, value: &str);
|
fn bind <N, V> (&mut self, name: N, value: V)
|
||||||
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
V: AsRef<str>;
|
||||||
|
|
||||||
/// Get a bound value from the env.
|
/// Get a bound value from the env.
|
||||||
fn get (&self, name: &str) -> Option<String>;
|
fn get <N> (&self, name: N) -> Option<String>
|
||||||
|
where
|
||||||
|
N: AsRef<str>;
|
||||||
|
|
||||||
/// Get the current working directory.
|
/// Get the current working directory.
|
||||||
fn working_dir (&self) -> Result<std::path::PathBuf>;
|
fn working_dir (&self) -> Result<std::path::PathBuf>;
|
||||||
|
|
||||||
/// Change the working directory.
|
/// Change the working directory.
|
||||||
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()>;
|
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<std::path::Path>;
|
||||||
|
|
||||||
/// Write to stdout.
|
/// Write to stdout.
|
||||||
fn stdout (&mut self, data: &str) -> Result<()>;
|
fn stdout <R> (&mut self, data: R) -> Result<()>
|
||||||
|
where
|
||||||
|
R: ToString;
|
||||||
|
|
||||||
/// Search the `PATH` variable for the `query`. The default implementation
|
/// Search the `PATH` variable for the `query`. The default implementation
|
||||||
/// is derived from [`Env::get`].
|
/// is derived from [`Env::get`].
|
||||||
fn search (&self, query: &str) -> Option<exec::Program> {
|
fn search <S> (&self, query: S) -> Option<exec::Program>
|
||||||
|
where
|
||||||
|
S: AsRef<str>
|
||||||
|
{
|
||||||
self.get("PATH")?
|
self.get("PATH")?
|
||||||
.split(':')
|
.split(':')
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
|
@ -40,7 +56,7 @@ pub trait Env {
|
||||||
})
|
})
|
||||||
.map(|file| file.path())
|
.map(|file| file.path())
|
||||||
.find_map(|path| {
|
.find_map(|path| {
|
||||||
if path.file_name()? == query {
|
if path.file_name()? == query.as_ref() {
|
||||||
Some (path)
|
Some (path)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -64,13 +80,14 @@ pub trait Env {
|
||||||
/// Create a new environment that inherits this environments'
|
/// Create a new environment that inherits this environments'
|
||||||
/// behavior, except the args of this env are overwritten with
|
/// behavior, except the args of this env are overwritten with
|
||||||
/// `args`.
|
/// `args`.
|
||||||
fn set_args (self, args: Args) -> SetArgs<Self>
|
fn set_args <A> (self, args: A) -> SetArgs<Self>
|
||||||
where
|
where
|
||||||
Self: Sized
|
Self: Sized,
|
||||||
|
A: IntoIterator<Item = String>,
|
||||||
{
|
{
|
||||||
SetArgs {
|
SetArgs {
|
||||||
parent: self,
|
parent: self,
|
||||||
args,
|
args: Args::from_iter(args),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,15 +105,24 @@ impl<E: Env> Env for SetArgs<E> {
|
||||||
self.parent.working_dir()
|
self.parent.working_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
|
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<std::path::Path>
|
||||||
|
{
|
||||||
self.parent.set_working_dir(path)
|
self.parent.set_working_dir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search (&self, query: &str) -> Option<exec::Program> {
|
fn search <S> (&self, query: S) -> Option<exec::Program>
|
||||||
|
where
|
||||||
|
S: AsRef<str>
|
||||||
|
{
|
||||||
self.parent.search(query)
|
self.parent.search(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stdout (&mut self, data: &str) -> Result<()> {
|
fn stdout <R> (&mut self, data: R) -> Result<()>
|
||||||
|
where
|
||||||
|
R: ToString
|
||||||
|
{
|
||||||
self.parent.stdout(data)
|
self.parent.stdout(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,11 +130,18 @@ impl<E: Env> Env for SetArgs<E> {
|
||||||
self.args.clone()
|
self.args.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind (&mut self, name: &str, value: &str) {
|
fn bind <N, V> (&mut self, name: N, value: V)
|
||||||
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
V: AsRef<str>,
|
||||||
|
{
|
||||||
self.parent.bind(name, value);
|
self.parent.bind(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get (&self, name: &str) -> Option<String> {
|
fn get <N> (&self, name: N) -> Option<String>
|
||||||
|
where
|
||||||
|
N: AsRef<str>
|
||||||
|
{
|
||||||
self.parent.get(name)
|
self.parent.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,15 +161,24 @@ impl<E: Env> Env for Scope<'_, E> {
|
||||||
self.parent.working_dir()
|
self.parent.working_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
|
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<std::path::Path>
|
||||||
|
{
|
||||||
self.parent.set_working_dir(path)
|
self.parent.set_working_dir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search (&self, query: &str) -> Option<exec::Program> {
|
fn search <S> (&self, query: S) -> Option<exec::Program>
|
||||||
|
where
|
||||||
|
S: AsRef<str>
|
||||||
|
{
|
||||||
self.parent.search(query)
|
self.parent.search(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stdout (&mut self, data: &str) -> Result<()> {
|
fn stdout <R> (&mut self, data: R) -> Result<()>
|
||||||
|
where
|
||||||
|
R: ToString
|
||||||
|
{
|
||||||
self.parent.stdout(data)
|
self.parent.stdout(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,13 +186,23 @@ impl<E: Env> Env for Scope<'_, E> {
|
||||||
self.parent.args()
|
self.parent.args()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind (&mut self, name: &str, value: &str) {
|
fn bind <N, V> (&mut self, name: N, value: V)
|
||||||
self.bindings.insert(name.to_string(), value.to_string());
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
V: AsRef<str>,
|
||||||
|
{
|
||||||
|
self.bindings.insert(
|
||||||
|
name.as_ref().to_string(),
|
||||||
|
value.as_ref().to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get (&self, name: &str) -> Option<String> {
|
fn get <N> (&self, name: N) -> Option<String>
|
||||||
|
where
|
||||||
|
N: AsRef<str>
|
||||||
|
{
|
||||||
self.bindings
|
self.bindings
|
||||||
.get(name)
|
.get(name.as_ref())
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.parent.get(name)
|
self.parent.get(name)
|
||||||
|
@ -160,15 +212,20 @@ impl<E: Env> Env for Scope<'_, E> {
|
||||||
|
|
||||||
/// An iterator of arguments.
|
/// An iterator of arguments.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct Args (Vec<String>);
|
pub struct Args {
|
||||||
|
args: Vec<String>,
|
||||||
|
cur: usize
|
||||||
|
}
|
||||||
|
|
||||||
impl<S: ToString> FromIterator<S> for Args {
|
impl<S: ToString> FromIterator<S> for Args {
|
||||||
fn from_iter <T: IntoIterator<Item = S>> (iter: T) -> Args {
|
fn from_iter <T: IntoIterator<Item = S>> (iter: T) -> Args {
|
||||||
Args (
|
Args {
|
||||||
iter.into_iter()
|
args: iter
|
||||||
|
.into_iter()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.collect()
|
.collect(),
|
||||||
)
|
cur: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,11 +233,12 @@ impl Iterator for Args {
|
||||||
type Item = String;
|
type Item = String;
|
||||||
|
|
||||||
fn next (&mut self) -> Option<Self::Item> {
|
fn next (&mut self) -> Option<Self::Item> {
|
||||||
if self.0.len() > 0 {
|
let Args { args, cur } = self;
|
||||||
Some (self.0.remove(0))
|
|
||||||
} else {
|
let val = args.get(*cur)?;
|
||||||
None
|
*cur += 1;
|
||||||
}
|
|
||||||
|
Some (val.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,16 +264,29 @@ impl Env for Pure {
|
||||||
std::iter::empty::<String>().collect()
|
std::iter::empty::<String>().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind (&mut self, name: &str, value: &str) {
|
fn bind <N, V> (&mut self, name: N, value: V)
|
||||||
self.bindings.insert(name.to_string(), value.to_string());
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
V: AsRef<str>,
|
||||||
|
{
|
||||||
|
self.bindings.insert(
|
||||||
|
name.as_ref().to_string(),
|
||||||
|
value.as_ref().to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get (&self, name: &str) -> Option<String> {
|
fn get <N> (&self, name: N) -> Option<String>
|
||||||
self.bindings.get(name).cloned()
|
where
|
||||||
|
N: AsRef<str>
|
||||||
|
{
|
||||||
|
self.bindings.get(name.as_ref()).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stdout (&mut self, data: &str) -> Result<()> {
|
fn stdout <R> (&mut self, data: R) -> Result<()>
|
||||||
self.buf += data;
|
where
|
||||||
|
R: ToString
|
||||||
|
{
|
||||||
|
self.buf += &data.to_string();
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +299,12 @@ impl Env for Pure {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
|
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() {
|
if path.is_dir() && path.exists() {
|
||||||
Ok (self.cwd = path.to_owned())
|
Ok (self.cwd = path.to_owned())
|
||||||
} else {
|
} else {
|
||||||
|
@ -247,12 +323,19 @@ impl Env for Inherit {
|
||||||
std::env::args().skip(1).collect()
|
std::env::args().skip(1).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind (&mut self, name: &str, value: &str) {
|
fn bind <N, V> (&mut self, name: N, value: V)
|
||||||
std::env::set_var(name, value);
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
V: AsRef<str>,
|
||||||
|
{
|
||||||
|
std::env::set_var(name.as_ref(), value.as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get (&self, name: &str) -> Option<String> {
|
fn get <N> (&self, name: N) -> Option<String>
|
||||||
std::env::var(name).ok()
|
where
|
||||||
|
N: AsRef<str>
|
||||||
|
{
|
||||||
|
std::env::var(name.as_ref()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn working_dir (&self) -> Result<std::path::PathBuf> {
|
fn working_dir (&self) -> Result<std::path::PathBuf> {
|
||||||
|
@ -260,13 +343,25 @@ impl Env for Inherit {
|
||||||
.map_err(Error::Io)
|
.map_err(Error::Io)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
|
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)
|
std::env::set_current_dir(path)
|
||||||
.map_err(Error::Io)
|
.map_err(Error::Io)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stdout (&mut self, data: &str) -> Result<()> {
|
fn stdout <R> (&mut self, data: R) -> Result<()>
|
||||||
std::io::stdout().write(data.as_bytes())?;
|
where
|
||||||
|
R: ToString
|
||||||
|
{
|
||||||
|
std::io::stdout().write(
|
||||||
|
data.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,191 @@
|
||||||
//! History control.
|
//! History control.
|
||||||
|
|
||||||
|
/// An entry in the [`History`] buffer.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Entry {
|
||||||
|
raw: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
/// Returns `None` if the string does not match the given
|
||||||
|
/// fuzzy pattern. Returns `Some(score)` which can be used
|
||||||
|
/// to rank the results depending on how well they match.
|
||||||
|
pub fn fuzzy_match (&self, s: &str) -> Option<usize> {
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
for c in s.chars() {
|
||||||
|
i = self.raw[i..].find(c)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some (0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the entry contains the substring `s`.
|
||||||
|
pub fn exact_match (&self, s: &str) -> bool {
|
||||||
|
self.raw.contains(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A buffer that keeps track of the shell history.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct History {
|
||||||
|
buf: Vec<Entry>,
|
||||||
|
cfg: Config,
|
||||||
|
cur: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl History {
|
||||||
|
|
||||||
|
/// Create a new empty history buffer with the given configuration.
|
||||||
|
///
|
||||||
|
/// To create one with the default configuration, you can use [`History::default`].
|
||||||
|
pub fn new (cfg: Config) -> History {
|
||||||
|
History {
|
||||||
|
buf: Vec::new(),
|
||||||
|
cur: 0,
|
||||||
|
cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the cursor backwards in time.
|
||||||
|
pub fn prev (&mut self) -> &mut Self {
|
||||||
|
self.back(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the cursor forwards in time.
|
||||||
|
pub fn next (&mut self) -> &mut Self {
|
||||||
|
self.skip(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Time travel backwards.
|
||||||
|
fn back (&mut self, n: usize) -> &mut Self {
|
||||||
|
let History { buf, cur, .. } = self;
|
||||||
|
*cur = if *cur + n >= buf.len() { buf.len() - 1 } else { *cur + n };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Time travel into the "future".
|
||||||
|
fn skip (&mut self, n: usize) -> &mut Self {
|
||||||
|
let History { cur, .. } = self;
|
||||||
|
*cur = if *cur > n { *cur - n } else { 0 };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append the given entry to the history buffer.
|
||||||
|
pub fn append (&mut self, entry: impl Into<Entry>) -> &mut Self {
|
||||||
|
let entry = entry.into();
|
||||||
|
self.buf.push(entry);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the last entry of the buffer, if one exists.
|
||||||
|
pub fn pop (&mut self) -> Option<Entry> {
|
||||||
|
self.buf.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search the command history.
|
||||||
|
pub fn search <'s: 'x, 'x> (&'x self, s: &'s str) -> impl Iterator<Item = &'x Entry> {
|
||||||
|
self.buf.iter()
|
||||||
|
.rev()
|
||||||
|
.skip(self.cur)
|
||||||
|
.filter(move |Entry { raw }| {
|
||||||
|
raw.contains(s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search the command history using the fuzzy algorithm of
|
||||||
|
/// [`fuzzy_match`](Entry::fuzzy_match). The matching entries are
|
||||||
|
/// returned in the order in which they are entered in the buffer,
|
||||||
|
/// reversed. The fuzzy matching score is ignored.
|
||||||
|
pub fn fuzzy_search <'s: 'x, 'x> (&'x self, s: &'s str) -> impl Iterator<Item = &'x Entry> {
|
||||||
|
self.buf.iter()
|
||||||
|
.rev()
|
||||||
|
.skip(self.cur)
|
||||||
|
.filter(move |e| {
|
||||||
|
e.fuzzy_match(&s).is_some()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ToString> From<S> for Entry {
|
||||||
|
fn from (text: S) -> Entry {
|
||||||
|
Entry { raw: text.to_string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for the [`History`] buffer.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
ignore: Vec<String>,
|
||||||
|
ignore_dups: bool,
|
||||||
|
limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
|
||||||
|
/// Create a new config.
|
||||||
|
///
|
||||||
|
/// This is an alias for [`Config::default`].
|
||||||
|
pub fn new () -> Config {
|
||||||
|
Config::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the history to ignore consecutive exactly equal
|
||||||
|
/// history entries.
|
||||||
|
pub fn ignore_dups (mut self) -> Self {
|
||||||
|
self.ignore_dups = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the history size limit.
|
||||||
|
pub fn limit (mut self, limit: usize) -> Self {
|
||||||
|
self.limit = limit;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a [`History`] buffer which uses this configuration.
|
||||||
|
pub fn into_history (self) -> History {
|
||||||
|
History::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default () -> Self {
|
||||||
|
Config {
|
||||||
|
ignore: Vec::new(),
|
||||||
|
ignore_dups: false,
|
||||||
|
limit: 10000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzy_search () {
|
||||||
|
let mut hist = History::default();
|
||||||
|
|
||||||
|
hist.append("z /etc/nixos")
|
||||||
|
.append("exa /home/riley")
|
||||||
|
.append("bat /home/riley/default.nix")
|
||||||
|
.append("exa /etc")
|
||||||
|
.append("nv ~/.config/sway/config")
|
||||||
|
.append("exa /etc/nixos")
|
||||||
|
.append("nv /etc/nixos/configuration.nix");
|
||||||
|
|
||||||
|
assert!(hist.fuzzy_search("cosw").count() == 1);
|
||||||
|
assert!(hist.fuzzy_search("conf").count() == 2);
|
||||||
|
assert!(hist.fuzzy_search("nix").count() == 4);
|
||||||
|
|
||||||
|
// Matches `nv /etc/nixos/configuration.nix` and
|
||||||
|
// `nv ~/.config/sway/config`, in that order.
|
||||||
|
let mut iter = hist.fuzzy_search("cfg");
|
||||||
|
assert!(iter.next().unwrap().raw == "nv /etc/nixos/configuration.nix");
|
||||||
|
assert!(iter.next().unwrap().raw == "nv ~/.config/sway/config");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzy_match () {
|
||||||
|
Entry::from("exa /home/riley").fuzzy_match("exa").unwrap();
|
||||||
|
}
|
||||||
|
|
68
src/exec.rs
68
src/exec.rs
|
@ -15,18 +15,6 @@ pub trait Exec {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
/// A shell command.
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Program (Program),
|
Program (Program),
|
||||||
|
@ -130,20 +118,30 @@ pub struct NotFound;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`FromStr`]: std::str::FromStr
|
/// [`FromStr`]: std::str::FromStr
|
||||||
pub struct Builtin (fn (&mut dyn Env) -> Result<()>);
|
pub struct Builtin (BuiltinName);
|
||||||
|
|
||||||
|
enum BuiltinName {
|
||||||
|
Disown,
|
||||||
|
Type,
|
||||||
|
Exit,
|
||||||
|
Pwd,
|
||||||
|
Fg,
|
||||||
|
Bg,
|
||||||
|
Cd,
|
||||||
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Builtin {
|
impl std::str::FromStr for Builtin {
|
||||||
type Err = NotFound;
|
type Err = NotFound;
|
||||||
|
|
||||||
fn from_str (s: &str) -> Result<Self, Self::Err> {
|
fn from_str (s: &str) -> Result<Self, Self::Err> {
|
||||||
let ret = Builtin (match s {
|
let ret = Builtin (match s {
|
||||||
"disown" => builtin::disown,
|
"disown" => BuiltinName::Disown,
|
||||||
"type" => builtin::r#type,
|
"type" => BuiltinName::Type,
|
||||||
"exit" => builtin::exit,
|
"exit" => BuiltinName::Exit,
|
||||||
"pwd" => builtin::pwd,
|
"pwd" => BuiltinName::Pwd,
|
||||||
"fg" => builtin::fg,
|
"fg" => BuiltinName::Fg,
|
||||||
"bg" => builtin::bg,
|
"bg" => BuiltinName::Bg,
|
||||||
"cd" => builtin::cd,
|
"cd" => BuiltinName::Cd,
|
||||||
_ => return Err (NotFound)
|
_ => return Err (NotFound)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,7 +154,19 @@ impl Exec for Builtin {
|
||||||
where
|
where
|
||||||
E: Env
|
E: Env
|
||||||
{
|
{
|
||||||
self.0.exec(env)
|
use BuiltinName::*;
|
||||||
|
|
||||||
|
let f = match self.0 {
|
||||||
|
Disown => builtin::disown,
|
||||||
|
Type => builtin::r#type,
|
||||||
|
Exit => builtin::exit,
|
||||||
|
Pwd => builtin::pwd,
|
||||||
|
Fg => builtin::fg,
|
||||||
|
Bg => builtin::bg,
|
||||||
|
Cd => builtin::cd,
|
||||||
|
};
|
||||||
|
|
||||||
|
f (env)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,20 +177,20 @@ pub mod builtin {
|
||||||
use super::{ Env, Result, };
|
use super::{ Env, Result, };
|
||||||
|
|
||||||
/// Change the current working directory.
|
/// Change the current working directory.
|
||||||
pub fn cd (env: &mut dyn Env) -> Result<()> {
|
pub fn cd (env: &mut impl Env) -> Result<()> {
|
||||||
let arg = env.args().next().unwrap();
|
let arg = env.args().next().unwrap();
|
||||||
env.set_working_dir(arg.as_ref())
|
env.set_working_dir(arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print the current working directory.
|
/// Print the current working directory.
|
||||||
pub fn pwd (env: &mut dyn Env) -> Result<()> {
|
pub fn pwd (env: &mut impl Env) -> Result<()> {
|
||||||
let cwd = env.working_dir()?;
|
let cwd = env.working_dir()?;
|
||||||
env.stdout(format!("{}", cwd.display()).as_str());
|
env.stdout(format!("{}", cwd.display()).as_str());
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the program. Optionally takes an exit code.
|
/// Stop the program. Optionally takes an exit code.
|
||||||
pub fn exit (env: &mut dyn Env) -> Result<()> {
|
pub fn exit (env: &mut impl Env) -> Result<()> {
|
||||||
if let Some (code) = env.args().next().and_then(|s| s.parse::<i32>().ok()) {
|
if let Some (code) = env.args().next().and_then(|s| s.parse::<i32>().ok()) {
|
||||||
std::process::exit(code)
|
std::process::exit(code)
|
||||||
} else {
|
} else {
|
||||||
|
@ -189,7 +199,7 @@ pub mod builtin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display information about the type of a command.
|
/// Display information about the type of a command.
|
||||||
pub fn r#type (env: &mut dyn Env) -> Result<()> {
|
pub fn r#type (env: &mut impl Env) -> Result<()> {
|
||||||
use crate::exec::Builtin;
|
use crate::exec::Builtin;
|
||||||
|
|
||||||
for arg in env.args() {
|
for arg in env.args() {
|
||||||
|
@ -210,19 +220,19 @@ pub mod builtin {
|
||||||
/// Send a job to the background. See also [`Job::bg`].
|
/// Send a job to the background. See also [`Job::bg`].
|
||||||
///
|
///
|
||||||
/// [`Job::bg`]: crate::job::Job::bg
|
/// [`Job::bg`]: crate::job::Job::bg
|
||||||
pub fn bg (_: &mut dyn Env) -> Result<()> {
|
pub fn bg (_: &mut impl Env) -> Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bring a job to the foreground. See also [`Job::fg`].
|
/// Bring a job to the foreground. See also [`Job::fg`].
|
||||||
///
|
///
|
||||||
/// [`Job::fg`]: crate::job::Job::fg
|
/// [`Job::fg`]: crate::job::Job::fg
|
||||||
pub fn fg (_: &mut dyn Env) -> Result<()> {
|
pub fn fg (_: &mut impl Env) -> Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disown all background jobs.
|
/// Disown all background jobs.
|
||||||
pub fn disown (_: &mut dyn Env) -> Result<()> {
|
pub fn disown (_: &mut impl Env) -> Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue