Skip to content

Commit

Permalink
make shell variable and env variable more POSIX
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Wang committed Sep 16, 2018
1 parent 3a0cb99 commit 0bd2b8c
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 67 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -5,3 +5,4 @@ target/
# cargo fmt backups
*.bk
*.txt
*.sh
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,7 @@
## v0.8.3

- Fixed path completion issue when contains spaces.
- Fixed environment variable manipulating issues

## v0.8.2

Expand Down
5 changes: 2 additions & 3 deletions Makefile
@@ -1,12 +1,11 @@
run:
@rustc -V
cargo update
cargo build
./target/debug/cicada

update:
cargo update

install:
cargo update
cargo build --release
cp target/release/cicada /usr/local/bin/

Expand Down
70 changes: 57 additions & 13 deletions src/execute.rs
Expand Up @@ -7,6 +7,8 @@ use std::os::unix::process::CommandExt;
use std::path::Path;
use std::process::{Command, Output, Stdio};

use regex::Regex;

use libc;
use nix::sys::signal;
use nix::unistd::pipe;
Expand Down Expand Up @@ -121,12 +123,46 @@ pub fn run_procs(sh: &mut shell::Shell, line: &str, tty: bool) -> i32 {
status
}

fn drain_env_tokens(tokens: &mut Vec<(String, String)>) -> HashMap<String, String> {
let mut envs: HashMap<String, String> = HashMap::new();
let mut n = 0;
for (sep, text) in tokens.iter() {
if !sep.is_empty() || !tools::re_contains(text, r"^([a-zA-Z0-9_]+)=(.*)$") {
break;
}

let re;
match Regex::new(r"^([a-zA-Z0-9_]+)=(.*)$") {
Ok(x) => {
re = x;
}
Err(e) => {
println_stderr!("Regex new: {:?}", e);
return envs;
}
}
for cap in re.captures_iter(text) {
let name = cap[1].to_string();
let value = parsers::parser_line::unquote(&cap[2]);
envs.insert(name, value);
}

n += 1;
}
if n > 0 {
tokens.drain(0..n);
}
envs
}

pub fn run_proc(sh: &mut shell::Shell, line: &str, tty: bool) -> i32 {
let mut envs = HashMap::new();
let cmd_line = tools::remove_envs_from_line(line, &mut envs);
let mut tokens = parsers::parser_line::cmd_to_tokens(line);
let envs = drain_env_tokens(&mut tokens);

let mut tokens = parsers::parser_line::cmd_to_tokens(&cmd_line);
if tokens.is_empty() {
for (name, value) in envs.iter() {
sh.set_env(name, value);
}
return 0;
}
let cmd = tokens[0].1.clone();
Expand All @@ -136,7 +172,7 @@ pub fn run_proc(sh: &mut shell::Shell, line: &str, tty: bool) -> i32 {
return builtins::cd::run(sh, &tokens);
}
if cmd == "export" {
return builtins::export::run(sh, &cmd_line);
return builtins::export::run(sh, line);
}
if cmd == "vox" {
return builtins::vox::run(sh, &tokens);
Expand Down Expand Up @@ -501,14 +537,15 @@ pub fn run_pipeline(
(status, term_given, output)
}

pub fn run(line: &str) -> Result<CommandResult, &str> {
let mut envs = HashMap::new();
let mut cmd_line = tools::remove_envs_from_line(line, &mut envs);
let sh = shell::Shell::new();
tools::pre_handle_cmd_line(&sh, &mut cmd_line);

let mut tokens = parsers::parser_line::cmd_to_tokens(&cmd_line);
fn run_with_shell<'a, 'b>(sh: &'a mut shell::Shell, line: &'b str) -> Result<CommandResult, &'b str> {
let mut line2 = String::from(line);
tools::pre_handle_cmd_line(&sh, &mut line2);
let mut tokens = parsers::parser_line::cmd_to_tokens(&line2);
let envs = drain_env_tokens(&mut tokens);
if tokens.is_empty() {
for (name, value) in envs.iter() {
sh.set_env(name, value);
}
return Ok(CommandResult::new());
}

Expand Down Expand Up @@ -555,12 +592,18 @@ pub fn run(line: &str) -> Result<CommandResult, &str> {
}
}

pub fn run(line: &str) -> Result<CommandResult, &str> {
let mut sh = shell::Shell::new();
return run_with_shell(&mut sh, line);
}

#[cfg(test)]
mod tests {
use super::run;
use super::run_with_shell;
use super::run_calc_float;
use super::run_calc_int;
use super::tools;
use super::shell;

#[test]
fn test_run_calc_float() {
Expand All @@ -585,6 +628,7 @@ mod tests {
let file = BufReader::new(&f);
let mut input = String::new();
let mut expected_stdout = String::new();
let mut sh = shell::Shell::new();
for (num, l) in file.lines().enumerate() {
let line = l.unwrap();
match num % 3 {
Expand All @@ -594,7 +638,7 @@ mod tests {
1 => {
expected_stdout = line.clone();
}
2 => match run(&input) {
2 => match run_with_shell(&mut sh, &input) {
Ok(c) => {
let ptn = if expected_stdout.is_empty() {
r"^$"
Expand Down
29 changes: 27 additions & 2 deletions src/parsers/parser_line.rs
Expand Up @@ -231,7 +231,8 @@ pub fn cmd_to_tokens(line: &str) -> Vec<(String, String)> {
}

if sep.is_empty() {
if c == '\'' || c == '"' {
let is_an_env = tools::re_contains(&token, r"^[a-zA-Z0-9_]+=.*$");
if !is_an_env && (c == '\'' || c == '"') {
sep = c.to_string();
continue;
}
Expand Down Expand Up @@ -460,6 +461,18 @@ pub fn is_valid_input(line: &str) -> bool {
true
}

pub fn unquote(text: &str) -> String {
let mut new_str = String::from(text);
for &c in ['"', '\''].iter() {
if text.starts_with(c) && text.ends_with(c) {
new_str.remove(0);
new_str.pop();
break;
}
}
new_str
}

#[cfg(test)]
mod tests {
use super::cmd_to_tokens;
Expand Down Expand Up @@ -532,7 +545,7 @@ mod tests {
),
(
"export FOO=\"`date` and `go version`\"",
vec![("", "export"), ("\"", "FOO=`date` and `go version`")],
vec![("", "export"), ("", "FOO=\"`date` and `go version`\"")],
),
("ps|wc", vec![("", "ps"), ("", "|"), ("", "wc")]),
(
Expand Down Expand Up @@ -603,6 +616,18 @@ mod tests {
"echo 123'foo bar'",
vec![("", "echo"), ("\'", "123foo bar")],
),
(
"echo --author=\"Hugo Wang <w@mitnk.com>\"",
vec![("", "echo"), ("\"", "--author=Hugo Wang <w@mitnk.com>")],
),
(
"Foo=\"abc\" sh foo.sh",
vec![("", "Foo=\"abc\""), ("", "sh"), ("", "foo.sh")],
),
(
"Foo=\"a b c\" ./foo.sh",
vec![("", "Foo=\"a b c\""), ("", "./foo.sh")],
),
];
for (left, right) in v {
println!("\ninput: {:?}", left);
Expand Down
31 changes: 28 additions & 3 deletions src/shell.rs
Expand Up @@ -13,6 +13,7 @@ use tools::{self, clog};
#[derive(Debug, Clone)]
pub struct Shell {
pub alias: HashMap<String, String>,
pub envs: HashMap<String, String>,
pub cmd: String,
pub previous_dir: String,
pub previous_cmd: String,
Expand All @@ -23,13 +24,33 @@ impl Shell {
pub fn new() -> Shell {
Shell {
alias: HashMap::new(),
envs: HashMap::new(),
cmd: String::new(),
previous_dir: String::new(),
previous_cmd: String::new(),
previous_status: 0,
}
}

pub fn set_env(&mut self, name: &str, value: &str) {
if let Ok(_) = env::var(name) {
env::set_var(name, value);
} else {
self.envs.insert(name.to_string(), value.to_string());
}
}

pub fn get_env(&self, name: &str) -> Option<String> {
match self.envs.get(name) {
Some(x) => {
Some(x.to_string())
}
None => {
None
}
}
}

pub fn add_alias(&mut self, name: &str, value: &str) {
self.alias.insert(name.to_string(), value.to_string());
}
Expand Down Expand Up @@ -190,10 +211,14 @@ pub fn extend_env_blindly(sh: &Shell, token: &str) -> String {
let val = libc::getpid();
result.push_str(format!("{}{}", _head, val).as_str());
}
} else if let Ok(val) = env::var(_key) {
} else if let Ok(val) = env::var(&_key) {
result.push_str(format!("{}{}", _head, val).as_str());
} else {
result.push_str(&_head);
if let Some(val) = sh.get_env(&_key) {
result.push_str(format!("{}{}", _head, val).as_str());
} else {
result.push_str(&_head);
}
}
}
if _tail.is_empty() {
Expand Down Expand Up @@ -264,7 +289,7 @@ mod tests {

let mut s = String::from("export FOO=\"`date` and `go version`\"");
extend_env(&sh, &mut s);
assert_eq!(s, "export \"FOO=`date` and `go version`\"");
assert_eq!(s, "export FOO=\"`date` and `go version`\"");

let mut s = String::from("foo is XX${CICADA_NOT_EXIST}XX");
extend_env(&sh, &mut s);
Expand Down
58 changes: 13 additions & 45 deletions src/tools.rs
@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::fs::OpenOptions;
Expand Down Expand Up @@ -210,16 +209,25 @@ pub fn needs_extend_home(line: &str) -> bool {

pub fn wrap_sep_string(sep: &str, s: &str) -> String {
let mut _token = String::new();
let mut meet_subsep = false;
let mut met_subsep = false;
// let set previous_subsep to any char except '`' or '"'
let mut previous_subsep = 'N';
for c in s.chars() {
// handle cmds like: export DIR=`brew --prefix openssl`/include
if c == '`' {
meet_subsep = !meet_subsep;
// or like: export foo="hello world"
if sep.is_empty() && (c == '`' || c == '"') {
if !met_subsep {
met_subsep = true;
previous_subsep = c;
} else if c == previous_subsep {
met_subsep = false;
previous_subsep = 'N';
}
}
if c.to_string() == sep {
_token.push('\\');
}
if c == ' ' && sep.is_empty() && !meet_subsep {
if c == ' ' && sep.is_empty() && !met_subsep {
_token.push('\\');
}
_token.push(c);
Expand Down Expand Up @@ -575,22 +583,6 @@ pub fn extend_alias(sh: &shell::Shell, line: &str) -> String {
result
}

pub fn remove_envs_from_line(line: &str, envs: &mut HashMap<String, String>) -> String {
let mut result = line.to_string();

while let Some(x) = libs::re::find_first_group(r"^( *[a-zA-Z][a-zA-Z0-9_]+=[^ ]*)", &result) {
let v: Vec<&str> = x.split('=').collect();
if v.len() != 2 {
println_stderr!("remove envs error");
break;
}
envs.insert(v[0].to_string(), v[1].to_string());
result = result.trim().replace(&x, "").trim().to_owned();
}

result
}

pub fn create_fd_from_file(file_name: &str, append: bool) -> Result<Stdio, String> {
let mut oos = OpenOptions::new();
if append {
Expand All @@ -617,11 +609,9 @@ mod tests {
use super::extend_bandband;
use super::is_alias;
use super::needs_extend_home;
use super::remove_envs_from_line;
use super::should_do_dollar_command_extension;
use super::should_do_dot_command_extension;
use shell;
use std::collections::HashMap;

#[test]
fn dots_test() {
Expand Down Expand Up @@ -744,28 +734,6 @@ mod tests {
assert_eq!(extend_alias(&sh, "ls a\\.b"), "ls -G a.b");
}

#[test]
fn test_remove_envs_from_line() {
let line = "foo=1 echo hi";
let mut envs = HashMap::new();
assert_eq!(remove_envs_from_line(line, &mut envs), "echo hi");
assert_eq!(envs["foo"], "1");

let line = "foo=1 bar=2 echo hi";
let mut envs = HashMap::new();
assert_eq!(remove_envs_from_line(line, &mut envs), "echo hi");
assert_eq!(envs["foo"], "1");
assert_eq!(envs["bar"], "2");

let line = "foo=1 bar=2 baz=3 bbq=4 cicada -c 'abc'";
let mut envs = HashMap::new();
assert_eq!(remove_envs_from_line(line, &mut envs), "cicada -c 'abc'");
assert_eq!(envs["foo"], "1");
assert_eq!(envs["bar"], "2");
assert_eq!(envs["baz"], "3");
assert_eq!(envs["bbq"], "4");
}

#[test]
fn test_extend_bandband() {
let mut sh = shell::Shell::new();
Expand Down

0 comments on commit 0bd2b8c

Please sign in to comment.