summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2022-03-07 01:54:56 -0500
committerJesse Luehrs <doy@tozt.net>2022-03-07 01:54:56 -0500
commit3c478958c77eb00367513b21200d432333a887aa (patch)
treeafd626281f58e5b3a13686981c449140d19c2542
parent0a36ee5f441260f13beda17eb5482e6bd0f9eb8c (diff)
downloadnbsh-3c478958c77eb00367513b21200d432333a887aa.tar.gz
nbsh-3c478958c77eb00367513b21200d432333a887aa.zip
basic implementation of aliases
-rw-r--r--Cargo.lock41
-rw-r--r--Cargo.toml2
-rw-r--r--src/config.rs25
-rw-r--r--src/dirs.rs9
-rw-r--r--src/main.rs2
-rw-r--r--src/parse/ast.rs43
-rw-r--r--src/parse/mod.rs17
-rw-r--r--src/parse/test_ast.rs16
-rw-r--r--src/runner/mod.rs46
9 files changed, 180 insertions, 21 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ce566bc..2137155 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -260,6 +260,26 @@ dependencies = [
]
[[package]]
+name = "directories"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -831,6 +851,7 @@ dependencies = [
"bytes",
"clap",
"console-subscriber",
+ "directories",
"futures-util",
"git2",
"glob",
@@ -849,6 +870,7 @@ dependencies = [
"tokio",
"tokio-stream",
"tokio-util 0.7.0",
+ "toml",
"unicode-width",
"users",
"vt100",
@@ -1222,6 +1244,16 @@ dependencies = [
]
[[package]]
+name = "redox_users"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+]
+
+[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1539,6 +1571,15 @@ dependencies = [
]
[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "tonic"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 0009c08..3a2f0d9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,7 @@ anyhow = "1.0.55"
bincode = "1.3.3"
bytes = "1.1.0"
clap = { version = "3.1.5", features = ["wrap_help", "derive"] }
+directories = "4.0.1"
futures-util = "0.3.21"
git2 = { version = "0.14.1", default-features = false }
glob = "0.3.0"
@@ -28,6 +29,7 @@ time = { version = "0.3.7", features = ["formatting", "parsing"] }
tokio = { version = "1.17.0", features = ["full"] }
tokio-stream = "0.1.8"
tokio-util = { version = "0.7.0", features = ["io"] }
+toml = "0.5.8"
unicode-width = "0.1.9"
users = "0.11.0"
vt100 = "0.15.1"
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..08fa002
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,25 @@
+use crate::prelude::*;
+
+#[derive(serde::Deserialize, Default, Debug)]
+pub struct Config {
+ aliases:
+ std::collections::HashMap<std::path::PathBuf, crate::parse::ast::Exe>,
+}
+
+impl Config {
+ pub fn load() -> Result<Self> {
+ let file = crate::dirs::config_file();
+ if std::fs::metadata(&file).is_ok() {
+ Ok(toml::from_slice(&std::fs::read(&file)?)?)
+ } else {
+ Ok(Self::default())
+ }
+ }
+
+ pub fn alias_for(
+ &self,
+ path: &std::path::Path,
+ ) -> Option<&crate::parse::ast::Exe> {
+ self.aliases.get(path)
+ }
+}
diff --git a/src/dirs.rs b/src/dirs.rs
new file mode 100644
index 0000000..45674e3
--- /dev/null
+++ b/src/dirs.rs
@@ -0,0 +1,9 @@
+pub fn config_file() -> std::path::PathBuf {
+ config_dir().join("config.toml")
+}
+
+fn config_dir() -> std::path::PathBuf {
+ let project_dirs =
+ directories::ProjectDirs::from("", "", "nbsh").unwrap();
+ project_dirs.config_dir().to_path_buf()
+}
diff --git a/src/main.rs b/src/main.rs
index 03440e1..d6b2725 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,6 +16,8 @@
// just get a compilation failure
#![allow(clippy::future_not_send)]
+mod config;
+mod dirs;
mod env;
mod format;
mod info;
diff --git a/src/parse/ast.rs b/src/parse/ast.rs
index 9c99907..92c04ab 100644
--- a/src/parse/ast.rs
+++ b/src/parse/ast.rs
@@ -117,14 +117,14 @@ impl Pipeline {
}
#[derive(Debug, Clone, PartialEq, Eq)]
-struct Exe {
+pub struct Exe {
exe: Word,
args: Vec<Word>,
redirects: Vec<Redirect>,
}
impl Exe {
- async fn eval(self, env: &Env) -> Result<super::Exe> {
+ pub async fn eval(self, env: &Env) -> Result<super::Exe> {
let exe = self.exe.eval(env).await?;
assert_eq!(exe.len(), 1); // TODO
let exe = &exe[0];
@@ -152,6 +152,15 @@ impl Exe {
})
}
+ pub fn parse(s: &str) -> Result<Self, super::Error> {
+ Ok(Self::build_ast(
+ Shell::parse(Rule::exe, s)
+ .map_err(|e| super::Error::new(s.to_string(), e))?
+ .next()
+ .unwrap(),
+ ))
+ }
+
fn build_ast(pair: pest::iterators::Pair<Rule>) -> Self {
assert!(matches!(pair.as_rule(), Rule::subshell | Rule::exe));
if matches!(pair.as_rule(), Rule::subshell) {
@@ -206,6 +215,36 @@ impl Exe {
}
}
+impl<'de> serde::Deserialize<'de> for Exe {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ struct Visitor;
+ impl<'de> serde::de::Visitor<'de> for Visitor {
+ type Value = Exe;
+
+ fn expecting(
+ &self,
+ f: &mut std::fmt::Formatter,
+ ) -> std::fmt::Result {
+ f.write_str("a command")
+ }
+
+ fn visit_str<E>(
+ self,
+ value: &str,
+ ) -> std::result::Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ Exe::parse(value).map_err(serde::de::Error::custom)
+ }
+ }
+ deserializer.deserialize_string(Visitor)
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Word {
parts: Vec<WordPart>,
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 9663086..e2b7ec0 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -11,7 +11,7 @@ impl Pipeline {
}
}
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Exe {
exe: std::path::PathBuf,
args: Vec<String>,
@@ -27,6 +27,16 @@ impl Exe {
&self.args
}
+ pub fn append(&mut self, other: Self) {
+ let Self {
+ exe: _exe,
+ args,
+ redirects,
+ } = other;
+ self.args.extend(args);
+ self.redirects.extend(redirects);
+ }
+
pub fn redirects(&self) -> &[Redirect] {
&self.redirects
}
@@ -107,10 +117,7 @@ pub struct Error {
impl Error {
fn new(input: String, e: pest::error::Error<ast::Rule>) -> Self {
- Self {
- input,
- e,
- }
+ Self { input, e }
}
}
diff --git a/src/parse/test_ast.rs b/src/parse/test_ast.rs
index 94798a3..4ac75ec 100644
--- a/src/parse/test_ast.rs
+++ b/src/parse/test_ast.rs
@@ -164,6 +164,16 @@ macro_rules! eval_eq {
}};
}
+macro_rules! deserialize_eq {
+ ($line:literal, $parsed:expr) => {{
+ use serde::de::IntoDeserializer as _;
+ use serde::Deserialize as _;
+ let exe: Result<_, serde::de::value::Error> =
+ Exe::deserialize($line.into_deserializer());
+ assert_eq!(exe.unwrap(), $parsed);
+ }};
+}
+
macro_rules! eval_fails {
($line:literal, $env:expr) => {{
let ast = Commands::parse($line).unwrap();
@@ -486,3 +496,9 @@ async fn test_eval_glob() {
eval_fails!("echo *.doesnotexist", env);
eval_fails!("echo *.{toml,doesnotexist}", env);
}
+
+#[test]
+fn test_deserialize() {
+ deserialize_eq!("foo", e!(w!("foo")));
+ deserialize_eq!("foo bar baz", e!(w!("foo"), w!("bar"), w!("baz")));
+}
diff --git a/src/runner/mod.rs b/src/runner/mod.rs
index ea55b34..6fcd28e 100644
--- a/src/runner/mod.rs
+++ b/src/runner/mod.rs
@@ -73,7 +73,8 @@ pub async fn main(
shell_write: &mut Option<tokio::fs::File>,
) -> Result<i32> {
let mut env = Env::new_from_env()?;
- run_commands(commands, &mut env, shell_write).await?;
+ let config = crate::config::Config::load()?;
+ run_commands(commands, &mut env, &config, shell_write).await?;
let status = env.latest_status();
write_event(shell_write, Event::Exit(env)).await?;
@@ -86,6 +87,7 @@ pub async fn main(
async fn run_commands(
commands: String,
env: &mut Env,
+ config: &crate::config::Config,
shell_write: &mut Option<tokio::fs::File>,
) -> Result<()> {
let commands = crate::parse::ast::Commands::parse(&commands)?;
@@ -96,7 +98,8 @@ async fn run_commands(
match &commands[pc] {
crate::parse::ast::Command::Pipeline(pipeline) => {
if stack.should_execute() {
- run_pipeline(pipeline.clone(), env, shell_write).await?;
+ run_pipeline(pipeline.clone(), env, config, shell_write)
+ .await?;
}
pc += 1;
}
@@ -107,7 +110,8 @@ async fn run_commands(
}
if should {
let status = env.latest_status();
- run_pipeline(pipeline.clone(), env, shell_write).await?;
+ run_pipeline(pipeline.clone(), env, config, shell_write)
+ .await?;
if let Some(Frame::If(should, found)) = stack.top_mut() {
*should = env.latest_status().success();
if *should {
@@ -127,7 +131,8 @@ async fn run_commands(
}
if should {
let status = env.latest_status();
- run_pipeline(pipeline.clone(), env, shell_write).await?;
+ run_pipeline(pipeline.clone(), env, config, shell_write)
+ .await?;
if let Some(Frame::While(should, _)) = stack.top_mut() {
*should = env.latest_status().success();
} else {
@@ -187,8 +192,13 @@ async fn run_commands(
*should = false;
} else if let Some(pipeline) = pipeline {
let status = env.latest_status();
- run_pipeline(pipeline.clone(), env, shell_write)
- .await?;
+ run_pipeline(
+ pipeline.clone(),
+ env,
+ config,
+ shell_write,
+ )
+ .await?;
*should = env.latest_status().success();
if *should {
*found = true;
@@ -231,6 +241,7 @@ async fn run_commands(
async fn run_pipeline(
pipeline: crate::parse::ast::Pipeline,
env: &mut Env,
+ config: &crate::config::Config,
shell_write: &mut Option<tokio::fs::File>,
) -> Result<()> {
write_event(shell_write, Event::RunPipeline(pipeline.span())).await?;
@@ -248,9 +259,21 @@ async fn run_pipeline(
io.set_stderr(stderr);
let pwd = env.pwd().to_path_buf();
- let pipeline = pipeline.eval(env).await?;
let interactive = shell_write.is_some();
- let (children, pg) = spawn_children(pipeline, env, &io, interactive)?;
+ let pipeline = pipeline.eval(env).await?;
+ let mut exes: Vec<_> = pipeline.into_exes().collect();
+ for exe in &mut exes {
+ if let Some(alias) = config.alias_for(exe.exe()) {
+ let mut new = alias.clone().eval(env).await?;
+ new.append(exe.clone());
+ *exe = new;
+ }
+ }
+ let cmds = exes
+ .into_iter()
+ .map(|exe| Command::new(exe, io.clone()))
+ .collect();
+ let (children, pg) = spawn_children(cmds, env, interactive)?;
let status = wait_children(children, pg, shell_write).await;
if interactive {
sys::set_foreground_pg(nix::unistd::getpid())?;
@@ -275,15 +298,10 @@ async fn write_event(
}
fn spawn_children(
- pipeline: crate::parse::Pipeline,
+ mut cmds: Vec<Command>,
env: &Env,
- io: &builtins::Io,
interactive: bool,
) -> Result<(Vec<Child>, Option<nix::unistd::Pid>)> {
- let mut cmds: Vec<_> = pipeline
- .into_exes()
- .map(|exe| Command::new(exe, io.clone()))
- .collect();
for i in 0..(cmds.len() - 1) {
let (r, w) = sys::pipe()?;
cmds[i].stdout(w);