Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions src/builtins.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use crate::helpers::escape_singlequotes;
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;
Expand All @@ -9,14 +13,17 @@ 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<String, String>,
args: Vec<String>,
) -> bool {
pub fn alias(shell: &Rc<RefCell<Shell>>, args: Vec<String>) -> 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 {
Expand All @@ -27,9 +34,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;
Expand Down Expand Up @@ -67,7 +93,7 @@ pub fn set(args: Vec<String>, shell: &Rc<RefCell<Shell>>) -> bool {
true
}

pub fn unalias(aliases: &mut BTreeMap<String, String>, args: Vec<String>) -> bool {
pub fn unalias(aliases: &mut BTreeMap<String, Option<Cmd>>, args: Vec<String>) -> bool {
if args.is_empty() {
eprintln!("unalias: usage: unalias [-a] name [name ...]");
false
Expand Down
8 changes: 7 additions & 1 deletion src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Result>,
Expand Down Expand Up @@ -38,7 +39,8 @@ pub struct Shell {
positional: Vec<String>,
name: String,
pub vars: HashMap<String, String>,
pub aliases: BTreeMap<String, String>,
// The value type is Optional because an alias can be defined but empty
pub aliases: BTreeMap<String, Option<Cmd>>,
}

impl Shell {
Expand Down Expand Up @@ -231,3 +233,7 @@ impl Fd {
self.get_stdout()
}
}

pub fn escape_singlequotes(s: &str) -> String {
s.replace("'", r"\'")
}
43 changes: 40 additions & 3 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<Cmd>, Box<Cmd>),
Expand All @@ -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<RefCell<Fd>>,
Expand Down Expand Up @@ -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<String>,
Expand All @@ -81,6 +100,24 @@ impl Simple {
fn add_env(&mut self, map: HashMap<String, String>) {
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, escape_singlequotes(v)));
}
}

result.push_str(&self.cmd);

for arg in &self.args {
result.push_str(&format!(" '{}'", escape_singlequotes(arg)));
}

result
}
}

// The parser struct. Keeps track of current location in a peekable iter of tokens
Expand Down
12 changes: 3 additions & 9 deletions src/runner.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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),
Expand Down