From 9c5c324659508e5e836030bcd6c7dde231279ba5 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Sat, 31 Oct 2020 09:02:25 +0100 Subject: [PATCH 1/2] Pre-parse aliases immediately when defined --- src/builtins.rs | 51 ++++++++++++++++++++++++++++++++++++++----------- src/helpers.rs | 4 +++- src/parser.rs | 41 +++++++++++++++++++++++++++++++++++++-- src/runner.rs | 12 +++--------- 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index d8a9764..606b5ee 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -1,3 +1,6 @@ +use crate::lexer::Lexer; +use crate::parser::Cmd; +use crate::parser::Parser; use regex::Regex; use std::collections::BTreeMap; use std::process::exit as exit_program; @@ -9,14 +12,21 @@ use crate::helpers::Shell; // Unless specified otherwise, if provided multiple arguments while only // accepting one, these use the first argument. Dash does this as well. -pub fn alias( - // Aliases can be added and then printed in the same command - aliases: &mut BTreeMap, - args: Vec, -) -> bool { +fn escape_singlequotes(s: &str) -> String { + s.replace("'", r"\'") +} + +pub fn alias(shell: &Rc>, args: Vec) -> bool { if args.is_empty() { - for (lhs, rhs) in aliases { - println!("alias {}='{}'", lhs, rhs); + for (lhs, rhs) in &shell.borrow_mut().aliases { + println!( + "alias {}='{}'", + lhs, + rhs.as_ref() + .map(|cmd| cmd.to_commandline()) + .map(|cmd| escape_singlequotes(&cmd)) + .unwrap_or("".to_string()) + ); } true } else { @@ -27,9 +37,28 @@ pub fn alias( let caps = assignment_re.captures(&arg).unwrap(); let lhs = &caps[1]; let rhs = &caps[2]; - aliases.insert(lhs.to_string(), rhs.to_string()); - } else if aliases.contains_key(&arg) { - println!("alias {}='{}'", arg, aliases[&arg]); + + let lexer = Lexer::new(rhs, Rc::clone(shell)); + let mut parser = Parser::new(lexer, Rc::clone(shell)); + + if let Ok(substitution) = parser.get() { + shell + .borrow_mut() + .aliases + .insert(lhs.to_string(), Some(substitution)); + } else { + shell.borrow_mut().aliases.insert(lhs.to_string(), None); + } + } else if shell.borrow().aliases.contains_key(&arg) { + println!( + "alias {}='{}'", + arg, + shell.borrow().aliases[&arg] + .as_ref() + .map(|cmd| cmd.to_commandline()) + .map(|cmd| escape_singlequotes(&cmd)) + .unwrap_or("".to_string()) + ); } else { eprintln!("rush: alias: {}: not found", arg); success = false; @@ -67,7 +96,7 @@ pub fn set(args: Vec, shell: &Rc>) -> bool { true } -pub fn unalias(aliases: &mut BTreeMap, args: Vec) -> bool { +pub fn unalias(aliases: &mut BTreeMap>, args: Vec) -> bool { if args.is_empty() { eprintln!("unalias: usage: unalias [-a] name [name ...]"); false diff --git a/src/helpers.rs b/src/helpers.rs index 5cbc492..ccf8b20 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -5,6 +5,7 @@ use std::env; use std::fs::{self, File, OpenOptions}; use std::io::{self, BufRead, BufReader, Write}; use std::process::{Stdio, self}; +use crate::parser::Cmd; // My own, less nasty version of BufRead::lines(). // Returns an Option rather Option, @@ -38,7 +39,8 @@ pub struct Shell { positional: Vec, name: String, pub vars: HashMap, - pub aliases: BTreeMap, + // The value type is Optional because an alias can be defined but empty + pub aliases: BTreeMap>, } impl Shell { diff --git a/src/parser.rs b/src/parser.rs index 27b2525..9da8676 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -16,7 +16,7 @@ use std::process::exit; use std::rc::Rc; use crate::runner::Runner; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum Cmd { Simple(Simple), Pipeline(Box, Box), @@ -26,6 +26,25 @@ pub enum Cmd { Empty, } +impl Cmd { + pub fn to_commandline(&self) -> String { + match self { + Self::Simple(simple) => simple.to_commandline(), + Self::Pipeline(left, right) => { + format!("{} | {}", left.to_commandline(), right.to_commandline()) + } + Self::And(left, right) => { + format!("{} && {}", left.to_commandline(), right.to_commandline()) + } + Self::Or(left, right) => { + format!("{} || {}", left.to_commandline(), right.to_commandline()) + } + Self::Not(cmd) => format!("! {}", cmd.to_commandline()), + Self::Empty => "".to_string(), + } + } +} + // Keeps track of io in one spot before it's put into a command pub struct Io { stdin: Rc>, @@ -56,7 +75,7 @@ impl Io { } // The most basic command - it, its arguments, and its redirections. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Simple { pub cmd: String, pub args: Vec, @@ -81,6 +100,24 @@ impl Simple { fn add_env(&mut self, map: HashMap) { self.env = Some(map); } + + fn to_commandline(&self) -> String { + let mut result = String::new(); + + if let Some(env) = &self.env { + for (k, v) in env { + result.push_str(&format!("{}='{}' ", k, v)); + } + } + + result.push_str(&self.cmd); + + for arg in &self.args { + result.push_str(&format!(" '{}'", arg)); + } + + result + } } // The parser struct. Keeps track of current location in a peekable iter of tokens diff --git a/src/runner.rs b/src/runner.rs index 35c7b96..7db96a5 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,7 +1,5 @@ use crate::builtins; use crate::helpers::{Fd, Shell}; -use crate::lexer::Lexer; -use crate::parser::Parser; use crate::parser::{Cmd, Simple}; use os_pipe::{pipe, PipeReader, PipeWriter}; use std::process::Command; @@ -97,11 +95,7 @@ impl Runner { } fn expand_alias(&self, cmd: Simple) -> Cmd { - let substitution = &self.shell.borrow().aliases[&cmd.cmd]; - let lexer = Lexer::new(substitution, Rc::clone(&self.shell)); - let mut parser = Parser::new(lexer, Rc::clone(&self.shell)); - - if let Ok(expanded) = parser.get() { + if let Some(substitution) = &self.shell.borrow().aliases[&cmd.cmd] { fn move_args(expanding: Cmd, parent: Simple) -> Cmd { match expanding { Cmd::Simple(mut new_simple) => { @@ -141,7 +135,7 @@ impl Runner { } } - move_args(propagate_env(expanded, &cmd), cmd) + move_args(propagate_env(substitution.clone(), &cmd), cmd) } else { let mut cmd = cmd; cmd.cmd = if cmd.args.is_empty() { @@ -187,7 +181,7 @@ impl Runner { fn visit_simple(&self, mut simple: Simple, stdio: CmdMeta) -> bool { self.reconcile_io(&mut simple, stdio); match &simple.cmd[..] { - "alias" => builtins::alias(&mut self.shell.borrow_mut().aliases, simple.args), + "alias" => builtins::alias(&self.shell, simple.args), "exit" => builtins::exit(simple.args), "cd" => builtins::cd(simple.args), "set" => builtins::set(simple.args, &self.shell), From 2ff1de3681996a081bac3f4f51de7c446154c4d8 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Sat, 31 Oct 2020 09:46:54 +0100 Subject: [PATCH 2/2] Escape single quotes in to_commandline() too --- src/builtins.rs | 5 +---- src/helpers.rs | 4 ++++ src/parser.rs | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index 606b5ee..8fc7d8d 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -1,3 +1,4 @@ +use crate::helpers::escape_singlequotes; use crate::lexer::Lexer; use crate::parser::Cmd; use crate::parser::Parser; @@ -12,10 +13,6 @@ use crate::helpers::Shell; // Unless specified otherwise, if provided multiple arguments while only // accepting one, these use the first argument. Dash does this as well. -fn escape_singlequotes(s: &str) -> String { - s.replace("'", r"\'") -} - pub fn alias(shell: &Rc>, args: Vec) -> bool { if args.is_empty() { for (lhs, rhs) in &shell.borrow_mut().aliases { diff --git a/src/helpers.rs b/src/helpers.rs index ccf8b20..c7e5e67 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -233,3 +233,7 @@ impl Fd { self.get_stdout() } } + +pub fn escape_singlequotes(s: &str) -> String { + s.replace("'", r"\'") +} diff --git a/src/parser.rs b/src/parser.rs index 9da8676..83ee5a2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,4 @@ -use crate::helpers::{Fd, Shell}; +use crate::helpers::{escape_singlequotes, Fd, Shell}; use crate::lexer::Token::{self, *}; use crate::lexer::{ Action, @@ -106,14 +106,14 @@ impl Simple { if let Some(env) = &self.env { for (k, v) in env { - result.push_str(&format!("{}='{}' ", k, v)); + result.push_str(&format!("{}='{}' ", k, escape_singlequotes(v))); } } result.push_str(&self.cmd); for arg in &self.args { - result.push_str(&format!(" '{}'", arg)); + result.push_str(&format!(" '{}'", escape_singlequotes(arg))); } result