use std::env;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs::OpenOptions;
use std::io::Write;
use time;
use glob;
use regex::Regex;
use libc;
use parsers;
use execute;
use shell;
use libs;
macro_rules! println_stderr {
($fmt:expr) => (
match writeln!(&mut ::std::io::stderr(), concat!($fmt, "\n")) {
Ok(_) => {}
Err(e) => println!("write to stderr failed: {:?}", e)
}
);
($fmt:expr, $($arg:tt)*) => (
match writeln!(&mut ::std::io::stderr(), concat!($fmt, "\n"), $($arg)*) {
Ok(_) => {}
Err(e) => println!("write to stderr failed: {:?}", e)
}
);
}
pub fn clog(s: &str) {
let file;
if let Ok(x) = env::var("CICADA_LOG_FILE") {
file = x;
} else {
return;
}
let mut cfile;
match OpenOptions::new().append(true).create(true).open(&file) {
Ok(x) => cfile = x,
Err(e) => {
println!("clog: open file {} failed: {:?}", &file, e);
return;
}
}
let pid = unsafe { libc::getpid() };
let now = time::now();
let s =
format!(
"[{:04}-{:02}-{:02} {:02}:{:02}:{:02}][{}]{}",
now.tm_year + 1900,
now.tm_mon + 1,
now.tm_mday,
now.tm_hour,
now.tm_min,
now.tm_sec,
pid,
s,
);
match cfile.write_all(s.as_bytes()) {
Ok(_) => {}
Err(e) => {
println!("clog: write_all failed: {:?}", e);
return;
}
}
}
macro_rules! log {
($fmt:expr) => (
clog(concat!($fmt, "\n"));
);
($fmt:expr, $($arg:tt)*) => (
clog(format!(concat!($fmt, "\n"), $($arg)*).as_str());
);
}
pub fn get_user_home() -> String {
match env::var("HOME") {
Ok(x) => x,
Err(e) => {
println!("cicada: env HOME error: {:?}", e);
String::new()
}
}
}
pub fn get_user_completer_dir() -> String {
let home = get_user_home();
format!("{}/.cicada/completers", home)
}
pub fn get_rc_file() -> String {
let home = get_user_home();
format!("{}/{}", home, ".cicadarc")
}
pub fn unquote(s: &str) -> String {
let args = parsers::parser_line::parse_line(s);
if args.is_empty() {
return String::new();
}
args[0].clone()
}
pub fn is_env(line: &str) -> bool {
let re;
if let Ok(x) = Regex::new(r"^ *export +[a-zA-Z0-9_]+=.*$") {
re = x;
} else {
return false;
}
re.is_match(line)
}
fn should_extend_brace(line: &str) -> bool {
let re;
if let Ok(x) = Regex::new(r"\{.*,.*\}") {
re = x;
} else {
return false;
}
re.is_match(line)
}
pub fn extend_home(s: &mut String) {
let v = vec![
r"(?P<head> +)~(?P<tail> +)",
r"(?P<head> +)~(?P<tail>/)",
r"^(?P<head> *)~(?P<tail>/)",
r"(?P<head> +)~(?P<tail> *$)",
];
for item in &v {
let re;
if let Ok(x) = Regex::new(item) {
re = x;
} else {
return;
}
let home = get_user_home();
let ss = s.clone();
let to = format!("$head{}$tail", home);
let result = re.replace_all(ss.as_str(), to.as_str());
*s = result.to_string();
}
}
pub fn needs_extend_home(line: &str) -> bool {
let re;
if let Ok(x) = Regex::new(r"( +~ +)|( +~/)|(^ *~/)|( +~ *$)") {
re = x;
} else {
return false;
}
re.is_match(line)
}
pub fn wrap_sep_string(sep: &str, s: &str) -> String {
let mut _token = String::new();
for c in s.chars() {
if c.to_string() == sep {
_token.push('\\');
}
_token.push(c);
}
format!("{}{}{}", sep, _token, sep)
}
fn do_command_substitution(line: &mut String) {
do_command_substitution_for_dot(line);
do_command_substitution_for_dollar(line);
}
fn should_do_brace_command_extension(line: &str) -> bool {
let ptn = r"\$\([^\)]+\)";
let re;
if let Ok(x) = Regex::new(ptn) {
re = x;
} else {
log!("re build failed");
return false;
}
return re.is_match(line);
}
fn do_command_substitution_for_dollar(line: &mut String) {
loop {
if !should_do_brace_command_extension(&line) {
break;
}
let ptn_cmd = r"\$\(([^\(]+)\)";
let cmd;
match libs::re::find_first_group(ptn_cmd, &line) {
Some(x) => {
cmd = x;
}
None => {
println_stderr!("cicada: no first group");
return;
}
}
let _args = parsers::parser_line::cmd_to_tokens(&cmd);
let (_, _, output) = execute::run_pipeline(_args, "", "", false, false, false, true, None);
let _stdout;
let output_txt;
if let Some(x) = output {
match String::from_utf8(x.stdout) {
Ok(stdout) => {
_stdout = stdout.clone();
output_txt = _stdout.trim();
}
Err(_) => {
println_stderr!("cicada: from_utf8 error");
return;
}
}
} else {
println_stderr!("cicada: command error");
return;
}
let ptn = r"(?P<head>[^\$]*)\$\([^\(]+\)(?P<tail>.*)";
let re;
if let Ok(x) = Regex::new(ptn) {
re = x;
} else {
return;
}
let to = format!("${{head}}{}${{tail}}", output_txt);
let line_ = line.clone();
let result = re.replace(&line_, to.as_str());
*line = result.to_string();
}
}
fn do_command_substitution_for_dot(line: &mut String) {
let tokens = parsers::parser_line::cmd_to_tokens(&line);
let mut result: Vec<String> = Vec::new();
for (sep, token) in tokens {
if sep == "`" {
let _args = parsers::parser_line::cmd_to_tokens(token.as_str());
let (_, _, output) =
execute::run_pipeline(_args, "", "", false, false, false, true, None);
if let Some(x) = output {
match String::from_utf8(x.stdout) {
Ok(stdout) => {
let _txt = wrap_sep_string(sep.as_str(), stdout.trim());
result.push(_txt);
}
Err(_) => {
println_stderr!("cicada: from_utf8 error");
result.push(wrap_sep_string(sep.as_str(), token.as_str()));
}
}
} else {
println_stderr!("cicada: command error");
result.push(wrap_sep_string(sep.as_str(), token.as_str()));
}
} else if sep == "\"" || sep.is_empty() {
let re;
if let Ok(x) = Regex::new(r"^([^`]*)`([^`]+)`(.*)$") {
re = x;
} else {
println_stderr!("cicada: re new error");
return;
}
if !re.is_match(&token) {
result.push(wrap_sep_string(sep.as_str(), token.as_str()));
continue;
}
let mut _token = token.clone();
let mut _item = String::new();
let mut _head = String::new();
let mut _output = String::new();
let mut _tail = String::new();
loop {
if !re.is_match(&_token) {
if !_token.is_empty() {
_item = format!("{}{}", _item, _token);
}
break;
}
for cap in re.captures_iter(&_token) {
_head = cap[1].to_string();
_tail = cap[3].to_string();
let _args = parsers::parser_line::cmd_to_tokens(&cap[2]);
let (_, _, output) =
execute::run_pipeline(_args, "", "", false, false, false, true, None);
if let Some(x) = output {
match String::from_utf8(x.stdout) {
Ok(stdout) => {
_output = stdout.trim().to_string();
}
Err(_) => {
println_stderr!("cicada: from_utf8 error");
result.push(wrap_sep_string(sep.as_str(), token.as_str()));
return;
}
}
} else {
println_stderr!("cicada: command error: {}", token);
result.push(wrap_sep_string(sep.as_str(), token.as_str()));
return;
}
}
_item = format!("{}{}{}", _item, _head, _output);
if _tail.is_empty() {
break;
}
_token = _tail.clone();
}
result.push(wrap_sep_string(sep.as_str(), &_item));
} else {
result.push(wrap_sep_string(sep.as_str(), token.as_str()));
}
}
*line = result.join(" ");
}
pub fn do_brace_expansion(line: &mut String) {
let _line = line.clone();
let args = parsers::parser_line::cmd_to_tokens(_line.as_str());
let mut result: Vec<String> = Vec::new();
for (sep, token) in args {
if sep.is_empty() && should_extend_brace(token.as_str()) {
let mut _prefix = String::new();
let mut _token = String::new();
let mut _result = Vec::new();
let mut only_tail_left = false;
let mut start_sign_found = false;
for c in token.chars() {
if c == '{' {
start_sign_found = true;
continue;
}
if !start_sign_found {
_prefix.push(c);
continue;
}
if only_tail_left {
_token.push(c);
continue;
}
if c == '}' {
if !_token.is_empty() {
_result.push(_token);
_token = String::new();
}
only_tail_left = true;
continue;
}
if c == ',' {
if !_token.is_empty() {
_result.push(_token);
_token = String::new();
}
} else {
_token.push(c);
}
}
for item in &mut _result {
*item = format!("{}{}{}", _prefix, item, _token);
}
result.push(wrap_sep_string(sep.as_str(), _result.join(" ").as_str()));
} else {
result.push(wrap_sep_string(sep.as_str(), token.as_str()));
}
}
*line = result.join(" ");
}
fn needs_globbing(line: &str) -> bool {
if is_arithmetic(line) {
return false;
}
let re;
if let Ok(x) = Regex::new(r"[\*]+") {
re = x;
} else {
return false;
}
let tokens = parsers::parser_line::cmd_to_tokens(line);
for (sep, token) in tokens {
if !sep.is_empty() {
return false;
}
if re.is_match(&token) {
return true;
}
}
false
}
fn extend_glob(line: &mut String) {
let _line = line.clone();
let _tokens: Vec<&str> = _line.split(' ').collect();
let mut result: Vec<String> = Vec::new();
for item in &_tokens {
if item.trim().starts_with('\'') || item.trim().starts_with('"') {
result.push(item.to_string());
} else {
match glob::glob(item) {
Ok(paths) => {
let mut is_empty = true;
for entry in paths {
match entry {
Ok(path) => {
let s = path.to_string_lossy();
if s.starts_with('.') {
continue;
}
result.push(s.into_owned());
is_empty = false;
}
Err(e) => println!("{:?}", e),
}
}
if is_empty {
result.push(item.to_string());
}
}
Err(e) => {
println!("glob error: {:?}", e);
result.push(item.to_string());
return;
}
}
}
}
*line = result.join(" ");
}
pub fn pre_handle_cmd_line(sh: &shell::Shell, line: &mut String) {
if needs_extend_home(line.as_str()) {
extend_home(line);
}
if needs_globbing(line.as_str()) {
extend_glob(line);
}
shell::extend_env(sh, line);
do_command_substitution(line);
do_brace_expansion(line);
}
pub fn env_args_to_command_line() -> String {
let mut result = String::new();
let env_args = env::args();
if env_args.len() <= 1 {
return result;
}
for (i, arg) in env_args.enumerate() {
if i == 0 || arg == "-c" {
continue;
}
result.push_str(arg.as_str());
}
result
}
pub fn is_alias(line: &str) -> bool {
let re;
if let Ok(x) = Regex::new(r"^ *alias +[a-zA-Z0-9_\.-]+=.*$") {
re = x;
} else {
return false;
}
re.is_match(line)
}
extern "C" {
fn gethostname(name: *mut libc::c_char, size: libc::size_t) -> libc::c_int;
}
pub fn get_hostname() -> String {
let len = 255;
let mut buf = Vec::<u8>::with_capacity(len);
let ptr = buf.as_mut_slice().as_mut_ptr();
let err = unsafe { gethostname(ptr as *mut libc::c_char, len as libc::size_t) } as i32;
match err {
0 => {
let real_len;
let mut i = 0;
loop {
let byte = unsafe { *(((ptr as u64) + (i as u64)) as *const u8) };
if byte == 0 {
real_len = i;
break;
}
i += 1;
}
unsafe { buf.set_len(real_len) }
String::from_utf8_lossy(buf.as_slice()).into_owned()
}
_ => String::from("unknown"),
}
}
pub fn is_arithmetic(line: &str) -> bool {
let re;
if let Ok(x) = Regex::new(r"^[ 0-9\.\(\)\+\-\*/]+$") {
re = x;
} else {
println!("regex error for arithmetic");
return false;
}
re.is_match(line)
}
pub fn extend_alias(sh: &shell::Shell, line: &str) -> String {
let cmds = parsers::parser_line::line_to_cmds(line);
let mut seps_cmd: HashSet<&str> = HashSet::new();
seps_cmd.insert(";");
seps_cmd.insert("&&");
seps_cmd.insert("||");
let mut result = String::new();
for (_, cmd) in cmds.iter().enumerate() {
if seps_cmd.contains(cmd.as_str()) {
result.push(' ');
result.push_str(cmd);
result.push(' ');
continue;
}
let tokens = parsers::parser_line::cmd_to_tokens(cmd);
let mut is_cmd = false;
for (i, token) in tokens.iter().enumerate() {
let sep = &token.0;
let arg = &token.1;
if !sep.is_empty() {
is_cmd = false;
result.push(' ');
result.push_str(&sep);
let replace_to = format!("\\{}", sep);
result.push_str(&arg.replace(sep, &replace_to));
result.push_str(&sep);
continue;
}
if i == 0 {
is_cmd = true;
} else if arg == "|" {
result.push(' ');
result.push_str(&arg);
is_cmd = true;
continue;
}
if !is_cmd {
result.push(' ');
result.push_str(&arg);
continue;
}
let extended;
match sh.get_alias_content(arg) {
Some(_extended) => {
extended = _extended;
}
None => {
extended = arg.clone();
}
}
if i > 0 {
result.push(' ');
}
result.push_str(&extended);
is_cmd = false;
}
}
result
}
pub fn remove_envs_from_line(line: &str, envs: &mut HashMap<String, String>) -> String {
let mut result = line.to_string();
loop {
match libs::re::find_first_group(r"^( *[a-zA-Z][a-zA-Z0-9_]+=[^ ]*)", &result) {
Some(x) => {
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();
}
None => {
break;
}
}
}
result
}
#[cfg(test)]
mod tests {
use shell;
use std::collections::HashMap;
use super::needs_extend_home;
use super::needs_globbing;
use super::is_alias;
use super::do_brace_expansion;
use super::do_command_substitution;
use super::should_do_brace_command_extension;
use super::extend_alias;
use super::remove_envs_from_line;
#[test]
fn dots_test() {
assert!(needs_extend_home("ls ~"));
assert!(needs_extend_home("ls ~ "));
assert!(needs_extend_home("cat ~/a.py"));
assert!(needs_extend_home("echo ~"));
assert!(needs_extend_home("echo ~ ~~"));
assert!(needs_extend_home("~/bin/py"));
assert!(!needs_extend_home("echo '~'"));
assert!(!needs_extend_home("echo \"~\""));
assert!(!needs_extend_home("echo ~~"));
}
#[test]
fn test_needs_globbing() {
assert!(needs_globbing("ls *"));
assert!(needs_globbing("ls *.txt"));
assert!(!needs_globbing("2 * 3"));
assert!(!needs_globbing("ls '*.md'"));
assert!(!needs_globbing("ls 'a * b'"));
}
#[test]
fn test_is_alias() {
assert!(is_alias("alias ls='ls -lh'"));
}
#[test]
fn test_do_brace_expansion() {
let mut s = String::from("echo {foo,bar,baz}.txt");
do_brace_expansion(&mut s);
assert_eq!(s, "echo foo.txt bar.txt baz.txt");
let mut s = String::from("echo foo.{txt,py}");
do_brace_expansion(&mut s);
assert_eq!(s, "echo foo.txt foo.py");
let mut s = String::from("echo foo.{cpp,py}.txt");
do_brace_expansion(&mut s);
assert_eq!(s, "echo foo.cpp.txt foo.py.txt");
}
#[test]
fn test_do_command_substitution() {
let mut s = String::from("ls `echo yoo`");
do_command_substitution(&mut s);
assert_eq!(s, "ls `yoo`");
s = String::from("ls `echo yoo` foo `echo hoo`");
do_command_substitution(&mut s);
assert_eq!(s, "ls `yoo` foo `hoo`");
s = String::from("ls $(echo yoo)");
do_command_substitution(&mut s);
assert_eq!(s, "ls yoo");
s = String::from("ls $(echo yoo) foo $(echo hoo)");
do_command_substitution(&mut s);
assert_eq!(s, "ls yoo foo hoo");
}
#[test]
fn test_should_do_brace_command_extension() {
assert!(!should_do_brace_command_extension("ls $HOME"));
assert!(!should_do_brace_command_extension("echo $[pwd]"));
assert!(should_do_brace_command_extension("echo $(pwd)"));
assert!(should_do_brace_command_extension("echo $(pwd) foo"));
assert!(should_do_brace_command_extension("echo $(foo bar)"));
assert!(should_do_brace_command_extension("echo $(echo foo)"));
assert!(should_do_brace_command_extension("$(pwd) foo"));
}
#[test]
fn test_extend_alias() {
let mut sh = shell::Shell::new();
sh.add_alias("ls", "ls -G");
sh.add_alias("wc", "wc -l");
sh.add_alias("grep", "grep -I --color=auto --exclude-dir=.git");
sh.add_alias("tx", "tmux");
assert_eq!(extend_alias(&sh, "ls"), "ls -G");
assert_eq!(extend_alias(&sh, "ls -lh"), "ls -G -lh");
assert_eq!(extend_alias(&sh, "ls | wc"), "ls -G | wc -l");
assert_eq!(
extend_alias(&sh, "ps ax | grep foo"),
"ps ax | grep -I --color=auto --exclude-dir=.git foo"
);
assert_eq!(extend_alias(&sh, "ls | wc | cat"), "ls -G | wc -l | cat");
assert_eq!(extend_alias(&sh, "echo foo | wc"), "echo foo | wc -l");
assert_eq!(
extend_alias(&sh, "echo foo | cat | wc"),
"echo foo | cat | wc -l"
);
assert_eq!(
extend_alias(&sh, "echo foo | wc | cat"),
"echo foo | wc -l | cat"
);
assert_eq!(extend_alias(&sh, "ls || wc"), "ls -G || wc -l");
assert_eq!(extend_alias(&sh, "ls && wc"), "ls -G && wc -l");
assert_eq!(extend_alias(&sh, "ls&&wc"), "ls -G && wc -l");
assert_eq!(extend_alias(&sh, "ls ; wc"), "ls -G ; wc -l");
assert_eq!(extend_alias(&sh, "ls; wc"), "ls -G ; wc -l");
assert_eq!(extend_alias(&sh, "ls;wc"), "ls -G ; wc -l");
assert_eq!(
extend_alias(&sh, "ls&&wc; foo || bar"),
"ls -G && wc -l ; foo || bar"
);
assert_eq!(extend_alias(&sh, "echo 'ls | wc'"), "echo 'ls | wc'");
assert_eq!(extend_alias(&sh, "echo \"ls | wc\""), "echo \"ls | wc\"");
assert_eq!(extend_alias(&sh, "echo `ls | wc`"), "echo `ls | wc`");
assert_eq!(extend_alias(&sh, "tx ls"), "tmux ls");
assert_eq!(
extend_alias(&sh, "awk -F \"[ ,.\\\"]+\""),
"awk -F \"[ ,.\\\"]+\""
);
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");
}
}