cicada 0.7.2

A simple Unix shell.
Documentation
use std::env;
use errno::errno;
use libc;
use std::collections::HashMap;
use std::mem;

use regex::Regex;

use parsers;
use tools::{self, clog};

pub struct Shell {
    pub alias: HashMap<String, String>,
    pub previous_dir: String,
    pub previous_cmd: String,
    pub previous_status: i32,
}

impl Shell {
    pub fn new() -> Shell {
        Shell {
            alias: HashMap::new(),
            previous_dir: String::new(),
            previous_cmd: String::new(),
            previous_status: 0,
        }
    }

    pub fn add_alias(&mut self, name: &str, value: &str) {
        self.alias.insert(name.to_string(), value.to_string());
    }

    pub fn get_alias_content(&self, name: &str) -> Option<String> {
        let mut result;
        match self.alias.get(name) {
            Some(x) => {
                result = x.to_string();
            }
            None => {
                result = String::new();
            }
        }
        tools::pre_handle_cmd_line(self, &mut result);
        if result.is_empty() {
            None
        } else {
            Some(result)
        }
    }
}

pub unsafe fn give_terminal_to(gid: i32) -> bool {
    let mut mask: libc::sigset_t = mem::zeroed();
    let mut old_mask: libc::sigset_t = mem::zeroed();

    libc::sigemptyset(&mut mask);
    libc::sigaddset(&mut mask, libc::SIGTSTP);
    libc::sigaddset(&mut mask, libc::SIGTTIN);
    libc::sigaddset(&mut mask, libc::SIGTTOU);
    libc::sigaddset(&mut mask, libc::SIGCHLD);

    let rcode = libc::pthread_sigmask(libc::SIG_BLOCK, &mask, &mut old_mask);
    if rcode != 0 {
        log!("failed to call pthread_sigmask");
    }
    let rcode = libc::tcsetpgrp(1, gid);
    let given;
    if rcode == -1 {
        given = false;
        let e = errno();
        let code = e.0;
        log!("Error {}: {}", code, e);
    } else {
        given = true;
    }
    let rcode = libc::pthread_sigmask(libc::SIG_SETMASK, &old_mask, &mut mask);
    if rcode != 0 {
        log!("failed to call pthread_sigmask");
    }
    given
}

pub fn extend_env_blindly(sh: &Shell, token: &str) -> String {
    let re;
    if let Ok(x) = Regex::new(r"([^\$]*)\$\{?([A-Za-z0-9\?\$_]+)\}?(.*)") {
        re = x;
    } else {
        println!("cicada: re new error");
        return String::new();
    }
    if !re.is_match(token) {
        return token.to_string();
    }
    let mut result = String::new();
    let mut _token = token.to_string();
    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() {
                result.push_str(&_token);
            }
            break;
        }
        for cap in re.captures_iter(&_token) {
            _head = cap[1].to_string();
            _tail = cap[3].to_string();
            let _key = cap[2].to_string();
            if _key == "?" {
                result.push_str(format!("{}{}", _head, sh.previous_status).as_str());
            } else if _key == "$" {
                unsafe {
                    let val = libc::getpid();
                    result.push_str(format!("{}{}", _head, val).as_str());
                }
            } else if let Ok(val) = env::var(_key) {
                result.push_str(format!("{}{}", _head, val).as_str());
            } else {
                result.push_str(&_head);
            }
        }
        if _tail.is_empty() {
            break;
        }
        _token = _tail.clone();
    }
    result
}

pub fn extend_env(sh: &Shell, line: &mut String) {
    let mut result: Vec<String> = Vec::new();
    let _line = line.clone();
    let args = parsers::parser_line::cmd_to_tokens(_line.as_str());
    for (sep, token) in args {
        if sep == "`" || sep == "'" {
            result.push(tools::wrap_sep_string(&sep, &token));
        } else {
            let _token = extend_env_blindly(sh, &token);
            result.push(tools::wrap_sep_string(&sep, &_token));
        }
    }
    *line = result.join(" ");
}

#[cfg(test)]
mod tests {
    use super::extend_env;
    use super::Shell;

    #[test]
    fn test_extend_env() {
        let sh = Shell::new();
        let mut s = String::from("echo '$PATH'");
        extend_env(&sh, &mut s);
        assert_eq!(s, "echo '$PATH'");

        let mut s = String::from("echo 'hi $PATH'");
        extend_env(&sh, &mut s);
        assert_eq!(s, "echo 'hi $PATH'");

        let mut s = String::from("echo '\\\''");
        extend_env(&sh, &mut s);
        assert_eq!(s, "echo '\\\''");

        let mut s = String::from("export DIR=`brew --prefix openssl`/include");
        extend_env(&sh, &mut s);
        assert_eq!(s, "export DIR=`brew --prefix openssl`/include");

        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`\"");

        let mut s = String::from("foo is XX${CICADA_NOT_EXIST}XX");
        extend_env(&sh, &mut s);
        assert_eq!(s, "foo is XXXX");

        let mut s = String::from("foo is $CICADA_NOT_EXIST_1 and bar is $CICADA_NOT_EXIST_2.");
        extend_env(&sh, &mut s);
        assert_eq!(s, "foo is  and bar is .");
    }
}