diff options
| author | Jonas Maier <> | 2026-04-17 17:56:58 +0200 |
|---|---|---|
| committer | Jonas Maier <> | 2026-04-17 17:56:58 +0200 |
| commit | b677713a7caf179b144674ff4c6e6b7171ad1337 (patch) | |
| tree | 073c32bac54fe2b960203ddd307530a5e761ebad | |
| parent | ddd2627360ee7a30b26cd3b7fab19ede55c574d7 (diff) | |
| download | pish-b677713a7caf179b144674ff4c6e6b7171ad1337.tar.gz | |
aliases
| -rw-r--r-- | src/main.rs | 4 | ||||
| -rw-r--r-- | src/parse/mod.rs | 45 | ||||
| -rw-r--r-- | src/run/builtin.rs | 84 | ||||
| -rw-r--r-- | src/run/mod.rs | 60 |
4 files changed, 188 insertions, 5 deletions
diff --git a/src/main.rs b/src/main.rs index 92ce812..5e1d847 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,7 @@ use crate::completion::{PathCache, completion}; use crate::ctrlc::CtrlC; use crate::cursor::{Direction, move_cursor}; use crate::history::HistoryEntry; -use crate::parse::Block; +use crate::parse::{Block, ExpString}; macro_rules! print { ($($x:tt)*) => {{ @@ -83,6 +83,7 @@ pub struct Session { builtins: HashMap<BString, &'static dyn run::Builtin>, vars: HashMap<BString, BString>, funs: HashMap<BString, Block>, + aliases: run::Aliases, socket_running: Option<export_fun::SocketRunning>, path_cache: PathCache, ctrlc: CtrlC, @@ -281,6 +282,7 @@ fn event_loop() { socket_running: None, vars: HashMap::new(), funs: HashMap::new(), + aliases: run::Aliases::new(), path_cache: Default::default(), ctrlc: Default::default(), }; diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 8682f04..184a514 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,4 +1,4 @@ -use crate::BString; +use crate::{BString, bstr}; #[cfg(test)] mod test; @@ -30,6 +30,17 @@ pub trait Expander { type Error; fn expand_var(&mut self, v: BString, default: Option<BString>) -> Res<BString, Self::Error>; fn expand_cmd(&mut self, c: Ast<PostExpansion>) -> Res<BString, Self::Error>; + + type AliasAge; + fn expand_alias( + &mut self, + cmd: &bstr, + age: Option<Self::AliasAge>, + ) -> Res<Option<(Self::AliasAge, Vec<ExpString>)>, Self::Error> { + let _ = cmd; + let _ = age; + Ok(None) + } } #[derive(Debug, Clone, PartialEq)] @@ -857,7 +868,30 @@ pub struct Command<T: Stage> { } impl Command<PreExpansion> { - fn expand<E: Expander>(self, e: &mut E) -> Res<Command<PostExpansion>, E::Error> { + fn full_alias_expansion<E: Expander>(&mut self, e: &mut E) -> Res<(), E::Error> { + self.args.reverse(); + let mut age = None; + + while self.cmd.parts.len() == 1 + && let StringPart::Boring(s) = &self.cmd.parts[0] + { + if let Some((new_age, exp)) = e.expand_alias(&s, age.take())? { + age = Some(new_age); + self.cmd = exp.first().unwrap().clone(); + for e in exp.into_iter().skip(1).rev() { + self.args.push(e); + } + } else { + break; + } + } + self.args.reverse(); + + Ok(()) + } + + fn expand<E: Expander>(mut self, e: &mut E) -> Res<Command<PostExpansion>, E::Error> { + self.full_alias_expansion(e)?; let cmd = self.cmd.expand(e)?; let mut args = Vec::with_capacity(self.args.len()); for arg in self.args.into_iter() { @@ -999,8 +1033,11 @@ pub fn completion_context<E: Expander>(x: &[u8], e: &mut E) -> CompletionContext ast.completion(e) } -trait Parse: Sized { +pub trait Parse: Sized { fn parse(b: &mut Cursor<'_>) -> Result<Self>; + fn parse_from_bytes(x: &[u8]) -> Result<Self> { + Self::parse(&mut Cursor::new(x, ParseMode::Command)) + } } enum ParseMode { @@ -1008,7 +1045,7 @@ enum ParseMode { Completion, } -struct Cursor<'a> { +pub struct Cursor<'a> { buf: &'a [u8], mode: ParseMode, diff --git a/src/run/builtin.rs b/src/run/builtin.rs index f0cb999..47a10c2 100644 --- a/src/run/builtin.rs +++ b/src/run/builtin.rs @@ -447,3 +447,87 @@ impl Builtin for var { } } } + +pub struct alias; +impl Builtin for alias { + fn name(&self) -> &str { + "alias" + } + + fn io( + &self, + session: Arc<Mutex<Session>>, + mut args: &[BString], + _stdin: &mut dyn Read, + stdout: &mut dyn Write, + ) -> Result { + let Some(alias_name) = args.first() else { + writeln!(stdout, "alias: expected alias name")?; + return Err(Error::Exit(-1)); + }; + + args = &args[1..]; + if let Some(b"=") = args.first().map(|x| x.as_slice()) { + args = &args[1..]; + } + + if args.is_empty() { + writeln!(stdout, "alias: expected alias definition")?; + return Err(Error::Exit(-1)); + }; + + let mut parse_fail = false; + let mut alias_args = Vec::new(); + for arg in args { + match <crate::parse::ExpString as crate::parse::Parse>::parse_from_bytes(&arg[..]) { + Ok(parsed) => { + alias_args.push(parsed); + } + Err(err) => { + writeln!( + stdout, + "alias: unparseable argument ({err:?}): {}", + arg.escape_ascii(), + )?; + parse_fail = true; + } + } + } + + if parse_fail { + return Err(Error::Exit(-1)); + } + + session + .lock() + .unwrap() + .aliases + .insert(alias_name.clone(), alias_args); + + Ok(()) + } +} + +pub struct unalias; +impl Builtin for unalias { + fn name(&self) -> &str { + "unalias" + } + + fn io( + &self, + session: Arc<Mutex<Session>>, + args: &[BString], + _stdin: &mut dyn Read, + stdout: &mut dyn Write, + ) -> Result { + if args.len() != 1 { + writeln!(stdout, "unalias: expecting exactly one argument")?; + return Err(Error::Exit(-1)); + } + + session.lock().unwrap().aliases.remove(&args[0]); + + Ok(()) + } +}
\ No newline at end of file diff --git a/src/run/mod.rs b/src/run/mod.rs index 091c662..d52c4e7 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -18,6 +18,7 @@ pub enum ExecError { IO(std::io::Error), ErrorStack(Vec<ExecError>), Panic, + AliasDepthExceeded, } impl ExecError { @@ -59,6 +60,7 @@ impl ExecError { } out } + ExecError::AliasDepthExceeded => "alias depth exceeded".to_string(), } } } @@ -404,6 +406,62 @@ impl parse::Expander for Executor { } Ok(buf) } + + type AliasAge = AliasAge; + + fn expand_alias( + &mut self, + cmd: &bstr, + older_than: Option<AliasAge>, + ) -> Result<Option<(AliasAge, Vec<ExpString>)>, Self::Error> { + Ok(self + .se + .lock() + .unwrap() + .aliases + .get(cmd, older_than.unwrap_or(AliasAge::MAX))) + } +} + +type AliasAge = u32; +type AliasBody = Vec<ExpString>; +type AliasSet = Vec<(AliasAge, AliasBody)>; + +pub struct Aliases { + age: u32, + aliases: HashMap<BString, AliasSet>, +} + +impl Aliases { + pub fn new() -> Self { + Self { + age: 0, + aliases: HashMap::new(), + } + } + + fn insert(&mut self, name: BString, body: AliasBody) { + let age = self.age; + self.age += 1; + let new = (age, body); + if let Some(entry) = self.aliases.get_mut(&name) { + entry.push(new); + } else { + self.aliases.insert(name, vec![new]); + } + } + + fn get(&mut self, name: &bstr, older_than: AliasAge) -> Option<(AliasAge, AliasBody)> { + let Some(alias_set) = self.aliases.get(name) else { + return None; + }; + + alias_set.iter().rev().find(|e| e.0 < older_than).cloned() + } + + fn remove(&mut self, name: &bstr) { + self.aliases.remove(name); + } } fn exec(se: Arc<Mutex<Session>>, ast: Ast<PreExpansion>) -> Result<(), ExecError> { @@ -479,6 +537,8 @@ const BUILTINS: &[&'static dyn Builtin] = &[ &builtin::null, &builtin::var, &builtin::completion, + &builtin::alias, + &builtin::unalias, ]; pub fn builtin_map() -> HashMap<BString, &'static dyn Builtin> { |
