From 3c478958c77eb00367513b21200d432333a887aa Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Mon, 7 Mar 2022 01:54:56 -0500 Subject: basic implementation of aliases --- Cargo.lock | 41 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/config.rs | 25 +++++++++++++++++++++++++ src/dirs.rs | 9 +++++++++ src/main.rs | 2 ++ src/parse/ast.rs | 43 +++++++++++++++++++++++++++++++++++++++++-- src/parse/mod.rs | 17 ++++++++++++----- src/parse/test_ast.rs | 16 ++++++++++++++++ src/runner/mod.rs | 46 ++++++++++++++++++++++++++++++++-------------- 9 files changed, 180 insertions(+), 21 deletions(-) create mode 100644 src/config.rs create mode 100644 src/dirs.rs diff --git a/Cargo.lock b/Cargo.lock index ce566bc..2137155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,26 @@ dependencies = [ "generic-array", ] +[[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" @@ -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", @@ -1221,6 +1243,16 @@ dependencies = [ "bitflags", ] +[[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" @@ -1538,6 +1570,15 @@ dependencies = [ "tokio", ] +[[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" 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, +} + +impl Config { + pub fn load() -> Result { + 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, redirects: Vec, } impl Exe { - async fn eval(self, env: &Env) -> Result { + pub async fn eval(self, env: &Env) -> Result { 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 { + 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) -> 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(deserializer: D) -> Result + 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( + self, + value: &str, + ) -> std::result::Result + 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, 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, @@ -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) -> 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, ) -> Result { 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, ) -> 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, ) -> 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, env: &Env, - io: &builtins::Io, interactive: bool, ) -> Result<(Vec, Option)> { - 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); -- cgit v1.2.3