Skip to content

Instantly share code, notes, and snippets.

@andurilan
Created September 1, 2018 07:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andurilan/08054750b52a105ab7424a8f63f6ce64 to your computer and use it in GitHub Desktop.
Save andurilan/08054750b52a105ab7424a8f63f6ce64 to your computer and use it in GitHub Desktop.
pls.plus
#![allow(unused_macros)]
use std::
{
collections::{ HashMap, HashSet },
env::{ self },
error::{Error},
fs::{ self, File, OpenOptions, read_dir },
io::{ self, Error as IO_Error, Read, Write },
mem::{ self },
os::unix::
{
io::{ FromRawFd, IntoRawFd },
process::{ CommandExt },
},
path::{ Path, PathBuf },
process::{ self, Command as Processor, Output, Stdio },
str::{ self, FromStr },
sync::{ Arc },
};
use super::
{
errno::{ errno },
exec::{ self },
glob::{ self },
libc::{ self },
linefeed::
{
Interface::{ self },
complete::{ Completer, Completion },
prompter::{ Prompter },
ReadResult::{ self },
terminal::{ DefaultTerminal, Terminal },
},
nix::{ sys::signal, unistd::pipe },
nom::{ digit, IResult },
regex::{ self, Regex },
sqlite::{ self, State },
};
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 )
} );
}
#[macro_use] macro_rules! matches
{
( $val:expr, default => $c:expr, $( $( $p:pat )|+ => $b:expr ),+ ) => {
{
let val = $val;
let mut matched = false;
$(
#[allow(unreachable_patterns)]
match val
{
$($p)|+ => { $b; matched = true; },
_ => (),
} )+
if !matched { $c }
} };
($val:expr, $($($p:pat)|+ => $b:expr),+) => {
{
let val = $val;
$(
#[allow(unreachable_patterns)]
match val
{
$($p)|+ => { $b; },
_ => (),
}
)+
} };
}
macro_rules! when
{
($var:ident in $val:expr, ever => $c:expr, $($($p:pat)|+ => $b:expr),+) => {
{
for $var in $val.iter()
{
let mut matched = false;
let var = *$var;
$(
#[allow(unreachable_patterns)]
match var
{
$( $p )|+ => { $b; matched = true; },
_ => (),
}
)+
if !matched { $c }
}
} };
($var:ident in $val:expr, $($($p:pat)|+ => $b:expr),+) => {
{
for $var in $val.iter()
{
let var = *$var;
$(
#[allow(unreachable_patterns)]
match var
{
$( $p )|+ => { $b; },
_ => (),
}
)+
}
} };
}
macro_rules! fоr
{
( $var:ident in $val:expr, $($($p:pat)|+ => $b:expr),+ ) => {
{
for $var in $val.iter()
{
let var = *$var;
#[allow(unreachable_patterns)]
match var
{
$( $( $p )|+ => { $b; }),+
_ => (),
}
}
} };
}
macro_rules! constructing
{
( $strυct:literal )=>({ println!("installer::constructing( {} )::", $strυct ); } );
( $strυct:literal, $from:literal )=>({ println!("installer::constructing( {}, from {} )::", $strυct, $from ); } );
( $strυct:literal, $from:literal, $msg:literal )=>({ println!("installer::constructing( {}, from {} )::`{}`::", $strυct, $from, $msg ); } );
}
macro_rules! constructed
{
( $strυct:literal )=>({ println!("installer::CONSTRUCTED( {} )::", $strυct ); } );
( $strυct:literal, $from:literal )=>({ println!("installer::CONSTRUCTED( {}, from {} )::", $strυct, $from ); } );
( $strυct:literal, $from:literal, $msg:literal )=>({ println!("installer::CONSTRUCTED( {}, from {} )::` {} `::", $strυct, $from, $msg ); } );
}
macro_rules! functioning
{
( $function:literal )=>({ println!("installer::functioning( {} )::", $function ); } );
( $function:literal, $from:literal )=>({ println!("installer::functioning( {}, from {} )::", $function, $from ); } );
( $function:literal, $from:literal, $msg:literal )=>({ println!("installer::{}( {} )...| {}", $function, $from, $msg ); } );
}
macro_rules! functioned
{
( $function:literal )=>({ println!("installer::FUNCTIONED( {} )::", $function ); } );
( $function:literal, $with:literal )=>({ println!("installer::FUNCTIONED( {}, from {} )::", $function, $with ); } );
( $function:literal, $with:literal, $msg:literal )=>({ println!("installer::{}( {} )...| {}", $function, $with, $msg ); } );
}
named!( parens_i64<i64>, ws!(delimited!(tag!("("), expr_int, tag!(")"))));
named!( parens_f64<f64>, ws!( delimited!( tag!("("), expr_float, tag!(")") ) ) );
named!( factor_f64<f64>, alt!
(
map_res!(
map_res!(
ws!(recognize!(alt!(
delimited!(digit, complete!(tag!(".")), opt!(complete!(digit)))
| delimited!(opt!(digit), complete!(tag!(".")), digit)
| digit
))),
str::from_utf8
),
FromStr::from_str
) | parens_f64
) );
named!( factor_i64<i64>, alt!
( map_res!(
map_res!
(
ws!( recognize!( alt!(
delimited!(digit, complete!(tag!(".")), opt!(complete!(digit))) |
delimited!(opt!(digit), complete!(tag!(".")), digit ) |
digit ) ) ),
str::from_utf8
),
FromStr::from_str ) |
parens_i64
) );
named!( term_i64<i64>, do_parse!
( init: factor_i64 >> res: fold_many0!
( pair!( alt!( tag!("*") | tag!("/") ), factor_i64 ),
init, |
acc, ( op, val ): (&[u8], i64)|
{
if (op[0] as char) == '*' { ( acc * val ) as i64 }
else { (acc / val) as i64 }
}
) >> (res)
) );
named!( term_f64<f64>,
do_parse!
(
init: factor_f64
>> res: fold_many0!(
pair!(alt!(tag!("*") | tag!("/")), factor_f64),
init,
|acc, (op, val): (&[u8], f64)| {
if (op[0] as char) == '*' {
(acc * val) as f64
} else {
(acc / val) as f64
}
}
) >> (res)
) );
named!( pub expr_int <i64>, do_parse!
( init: term_i64 >> res: fold_many0!
(
pair!(alt!(tag!("+") | tag!("-")), term_i64),
init,
| acc, (op, val): (&[u8], i64)|
{
if (op[0] as char) == '+' { (acc + val) as i64 }
else { (acc - val) as i64 }
}
) >> (res)
) );
named!( pub expr_float <f64>, do_parse!
(
init: term_f64 >>
res: fold_many0!(
pair!(alt!(tag!("+") | tag!("-")), term_f64),
init,
|acc, (op, val): (&[u8], f64)| {
if (op[0] as char) == '+' { (acc + val) as f64 } else { (acc - val) as f64 }
}
) >>
(res)
) );
pub fn clog( s: &str )
{
let file;
if let Ok(x) = env::var("INSTALLER_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!("installer::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() ); );
}
const BLUE: &str = "\x01\x1B[34m\x02";
const RED: &str = "\x01\x1B[31m\x02";
const GREEN: &str = "\x01\x1B[32m\x02";
const RESET: &str = "\x01\x1B[0m\x02";
pub type Tokens = Vec<(String, String)>;
pub type Redirection = (String, String, String);
#[derive(Debug)]
pub struct Command
{
pub tokens: Tokens,
pub redirects: Vec<Redirection>,
}
#[derive(Clone, Debug, Default)]
pub struct CommandResult {
pub status: i32,
pub stdout: String,
pub stderr: String,
}
impl CommandResult {
pub fn new() -> CommandResult {
CommandResult {
status: 0,
stdout: String::new(),
stderr: String::new(),
}
}
}
extern "C" fn handle_sigchld( _: i32 )
{
// When handle waitpid here & for commands like `ls | cmd-not-exist`
// will panic: "wait() should either return Ok or panic"
// which I currently don't know how to fix.
/*
unsafe {
let mut stat: i32 = 0;
let ptr: *mut i32 = &mut stat;
let pid = libc::waitpid(-1, ptr, libc::WNOHANG);
}
*/
}
extern "C" { fn gethostname(name: *mut libc::c_char, size: libc::size_t) -> libc::c_int; }
#[derive(Debug, Clone)]
pub struct Agent
{
pub alias: HashMap<String, String>,
pub profile:String,
pub dir: String,
pub previous_dir: String,
pub cmd: String,
pub previous_cmd: String,
pub status: i32,
pub previous_status: i32,
}
impl Agent
{
pub fn new( status:i32 ) -> Agent
{
constructing!( "Agent", "installer::main()", "New Agent" );
let path = env::current_dir().unwrap();
let mut agent = Agent
{
alias: HashMap::new(),
profile:String::new(),
dir: format!("{}", path.display()),
previous_dir: format!("{}", path.display()),
cmd: String::new(),
previous_cmd: String::new(),
status,
previous_status: 0
};
constructing!( "Agent", "installer::main()", "Agent Constructed; Cloning" );
agent.clone()
}
// pub fn comprises( &mut self, from: &str, to: &str ) -> bool
pub fn re_contains( &mut self, line: &str, ptn: &str ) -> bool
{
let re;
match Regex::new( ptn )
{
Ok(x) => { re = x; }
Err(e) =>
{
println!("Regex new: {:?}", e);
return false;
}
}
re.is_match( line )
}
pub fn get_user_home( &mut self ) -> String
{
match env::var("HOME")
{
Ok(x) => x,
Err(e) =>
{
println!("installer: env HOME error: {:?}", e);
String::new()
}
}
}
pub fn add_alias( &mut self, name: &str, value: &str )
{
self.alias.insert( name.to_string(), value.to_string() );
}
pub fn get_alias_content( &mut self, name: &str) -> Option<String>
{
let mut result;
match self.alias.get(name) {
Some(x) => {
result = x.to_string();
}
None => {
result = String::new();
}
}
self.pre_handle_cmd_line( &mut result );
if result.is_empty() { None } else { Some(result) }
}
pub fn library_cmd_to_tokens( &mut self ) -> Agent
{
self.clone()
}
pub fn line_tokens_to_cmd_tokens( &mut self, tokens: &Tokens) -> Vec<Tokens>
{
let mut cmd = Vec::new();
let mut cmds = Vec::new();
for token in tokens {
let sep = &token.0;
let value = &token.1;
if sep.is_empty() && value == "|" {
if cmd.is_empty() {
return Vec::new();
}
cmds.push(cmd.clone());
cmd = Vec::new();
} else {
cmd.push(token.clone());
}
}
if cmd.is_empty() {
return Vec::new();
}
cmds.push(cmd.clone());
cmds
}
pub fn line_cmd_to_with_redirects( &mut self, tokens: &Tokens) -> Result<Command, String>
{
let mut tokens_new = Vec::new();
let mut redirects = Vec::new();
let mut to_be_continued = false;
let mut to_be_continued_s1 = String::new();
let mut to_be_continued_s2 = String::new();
for token in tokens {
let sep = &token.0;
if !sep.is_empty() && !to_be_continued {
tokens_new.push(token.clone());
continue;
}
let word = &token.1;
if to_be_continued {
if sep.is_empty() && word.starts_with('&') {
return Err(String::from("bad redirection syntax near &"));
}
let s3 = format!("{}{}{}", sep, word, sep);
if self.re_contains(&to_be_continued_s1, r"^\d+$") {
if to_be_continued_s1 != "1" && to_be_continued_s1 != "2" {
return Err(String::from("Bad file descriptor #3"));
}
let s1 = to_be_continued_s1.clone();
let s2 = to_be_continued_s2.clone();
redirects.push((s1, s2, s3));
} else {
if to_be_continued_s1 != "" {
tokens_new.push((sep.clone(), to_be_continued_s1.to_string()));
}
redirects.push(("1".to_string(), to_be_continued_s2.clone(), s3));
}
to_be_continued = false;
continue;
}
let ptn1 = r"^([^>]*)(>>?)([^>]+)$";
let ptn2 = r"^([^>]*)(>>?)$";
if !self.re_contains(word, r">") {
tokens_new.push(token.clone());
} else if self.re_contains(word, ptn1) {
let re;
if let Ok(x) = Regex::new(ptn1) {
re = x;
} else {
return Err(String::from("Failed to build Regex"));
}
if let Some(caps) = re.captures(&word) {
let s1 = caps.get(1).unwrap().as_str();
let s2 = caps.get(2).unwrap().as_str();
let s3 = caps.get(3).unwrap().as_str();
if s3.starts_with('&') && s3 != "&1" && s3 != "&2" {
return Err(String::from("Bad file descriptor #1"));
}
if self.re_contains(s1, r"^\d+$") {
if s1 != "1" && s1 != "2" {
return Err(String::from("Bad file descriptor #2"));
}
redirects.push((s1.to_string(), s2.to_string(), s3.to_string()));
} else {
if s1 != "" {
tokens_new.push((sep.clone(), s1.to_string()));
}
redirects.push((String::from("1"), s2.to_string(), s3.to_string()));
}
}
} else if self.re_contains(word, ptn2) {
let re;
if let Ok(x) = Regex::new(ptn2) {
re = x;
} else {
return Err(String::from("Failed to build Regex"));
}
if let Some(caps) = re.captures(&word) {
let s1 = caps.get(1).unwrap().as_str();
let s2 = caps.get(2).unwrap().as_str();
to_be_continued = true;
to_be_continued_s1 = s1.to_string();
to_be_continued_s2 = s2.to_string();
}
}
}
if to_be_continued {
return Err(String::from("redirection syntax error"));
}
Ok(Command {
tokens: tokens_new,
redirects,
})
}
pub fn line_tokens_to_args( &mut self, tokens: &Vec<(String, String)>) -> Vec<String>
{
let mut result = Vec::new();
for s in tokens
{
result.push(s.1.clone());
}
result
}
pub fn lines_to_cmds( &mut self, line: &str) -> Vec<String>
{
// Special characters: http://tldp.org/LDP/abs/html/special-chars.html
let mut result = Vec::new();
let mut sep = String::new();
let mut token = String::new();
let len = line.len();
for (i, c) in line.chars().enumerate() {
if c == '#' {
if sep.is_empty() {
break;
} else {
token.push(c);
continue;
}
}
if c == '\'' || c == '"' || c == '`' {
if sep.is_empty() {
sep.push(c);
token.push(c);
continue;
} else if sep == c.to_string() {
token.push(c);
sep = String::new();
continue;
} else {
token.push(c);
continue;
}
}
if c == '&' || c == '|' {
// needs watch ahead here
if sep.is_empty() {
if i + 1 == len {
// for bg commands, e.g. `ls &`
token.push(c);
continue;
} else {
let c_next;
match line.chars().nth(i + 1) {
Some(x) => c_next = x,
None => {
println!("chars nth error - should never happen");
continue;
}
}
if c_next != c {
token.push(c);
continue;
}
}
}
if sep.is_empty() {
sep.push(c);
continue;
} else if c.to_string() == sep {
let _token = token.trim().to_string();
if !_token.is_empty() {
result.push(_token);
}
token = String::new();
result.push(format!("{}{}", sep, sep));
sep = String::new();
continue;
} else {
token.push(c);
continue;
}
}
if c == ';' {
if sep.is_empty() {
let _token = token.trim().to_string();
if !_token.is_empty() {
result.push(_token);
}
result.push(String::from(";"));
token = String::new();
continue;
} else {
token.push(c);
continue;
}
}
token.push(c);
}
if !token.is_empty() {
result.push(token.trim().to_string());
}
result
}
#[allow(cyclomatic_complexity)]
pub fn line_cmd_to_tokens( &mut self, line: &str ) -> Vec<(String, String)>
{
let mut result = Vec::new();
let mut sep = String::new();
// `sep_second` is for commands like this:
// export DIR=`brew --prefix openssl`/include
// it only could have non-empty value when sep is empty.
let mut sep_second = String::new();
let mut token = String::new();
let mut has_backslash = false;
let mut new_round = true;
let mut skip_next = false;
let count_chars = line.chars().count();
for (i, c) in line.chars().enumerate() {
if skip_next {
skip_next = false;
continue;
}
if c == '\\' && sep != "\'" {
if !has_backslash {
has_backslash = true;
} else {
has_backslash = false;
token.push(c);
}
continue;
}
if new_round {
if c == ' ' {
continue;
} else if c == '"' || c == '\'' || c == '`' {
sep = c.to_string();
} else {
sep = String::new();
// handle inline comments
if c == '#' {
if has_backslash {
has_backslash = false;
token.push(c);
continue;
}
break;
}
if c == '|' {
if i + 1 < count_chars && line.chars().nth(i + 1).unwrap() == '|' {
result.push((String::from(""), "||".to_string()));
skip_next = true;
} else {
result.push((String::from(""), "|".to_string()));
}
new_round = true;
continue;
} else {
token.push(c);
}
}
new_round = false;
continue;
}
if c == '|' && !has_backslash && sep.is_empty() {
result.push((String::from(""), token));
result.push((String::from(""), "|".to_string()));
sep = String::new();
sep_second = String::new();
token = String::new();
new_round = true;
continue;
}
if c == ' ' {
if has_backslash {
has_backslash = false;
token.push(c);
if sep.is_empty() {
sep = String::from("\"");
}
continue;
}
if sep.is_empty() {
if sep_second.is_empty() {
result.push((String::from(""), token));
token = String::new();
new_round = true;
continue;
} else {
token.push(c);
continue;
}
} else {
token.push(c);
continue;
}
}
if c == '\'' || c == '"' || c == '`' {
if has_backslash {
has_backslash = false;
token.push(c);
continue;
}
if sep.is_empty() {
if c == '\'' || c == '"' {
sep = c.to_string();
continue;
}
token.push(c);
if sep_second.is_empty() {
sep_second = c.to_string();
} else if sep_second == c.to_string() {
sep_second = String::new();
}
continue;
} else if sep == c.to_string() {
result.push((c.to_string(), token));
sep = String::new();
sep_second = String::new();
token = String::new();
new_round = true;
continue;
} else {
token.push(c);
}
} else {
if has_backslash {
has_backslash = false;
if sep == "\"" || sep == "'" {
token.push('\\');
}
}
token.push(c);
}
}
if !token.is_empty() {
result.push((sep, token));
}
result
}
pub fn line_to_plain_tokens( &mut self, line: &str ) -> Vec<String>
{
let mut result = Vec::new();
let v = self.line_cmd_to_tokens( line );
for (_, r) in v
{
result.push(r);
}
result
}
pub fn unquote( &mut self, s: &str ) -> String
{
let args = self.line_to_plain_tokens( s );
if args.is_empty()
{
return String::new();
}
args[0].clone()
}
pub fn find_first_group( &mut self, ptn: &str, text: &str) -> Option<String>
{
let re;
match regex::Regex::new(ptn) {
Ok(x) => re = x,
Err(_) => return None,
}
match re.captures( text )
{
Some(caps) =>
{
if let Some(x) = caps.get(1) { return Some(x.as_str().to_owned()); }
}
None => { return None; }
}
None
}
pub fn wrap_sep_string( &mut self, 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)
}
// pub fn numerical( &mut self, line: &str ) -> bool
pub fn is_arithmetic( &mut self, line: &str ) -> bool { self.re_contains(line, r"^[ 0-9\.\(\)\+\-\*/]+$") }
pub fn is_env( &mut self, line: &str ) -> bool
{
self.re_contains( line, r"^ *export +[a-zA-Z0-9_]+=.*$" )
}
pub fn is_alias( &mut self, line: &str ) -> bool
{
self.re_contains( line, r"^ *alias +[a-zA-Z0-9_\.-]+=.*$")
}
#[allow(trivial_regex)]
pub fn extend_bandband( &mut self, line: &mut String)
{
if !self.re_contains(line, r"!!") { return; }
if self.previous_cmd.is_empty() { return; }
let re;
match Regex::new(r"!!")
{
Ok(x) => { re = x; }
Err(e) => { println_stderr!("installer::extend_bandband: Regex new: {:?}", e); return; }
}
let mut replaced = false;
let mut new_line = String::new();
let tokens = self.line_cmd_to_tokens(line);
for (sep, token) in tokens
{
if !sep.is_empty() { new_line.push_str(&sep); }
if self.re_contains(&token, r"!!") && sep != "'"
{
let line2 = token.clone();
let result = re.replace_all(&line2, self.previous_cmd.as_str());
new_line.push_str(&result);
replaced = true;
}
else { new_line.push_str(&token); }
if !sep.is_empty() { new_line.push_str(&sep); }
new_line.push(' ');
}
*line = new_line.trim_right().to_string();
// print full line after extending
if replaced { println!("{}", line); }
}
// pub fn housing( &mut self, line: &str) -> bool
pub fn needs_extend_home( &mut self, line: &str) -> bool {
self.re_contains(line, r"( +~ +)|( +~/)|(^ *~/)|( +~ *$)")
}
pub fn needs_globbing( &mut self, line: &str ) -> bool {
if self.is_arithmetic(line){ return false; }
let re;
if let Ok(x) = Regex::new(r"\*+") { re = x; }
else { return false; }
let tokens = self.line_cmd_to_tokens(line);
for (sep, token) in tokens
{
if !sep.is_empty() { continue; }
if re.is_match(&token) { return true; }
}
false
}
pub fn should_do_brace_command_extension( &mut self, line: &str ) -> bool { self.re_contains(line, r"\$\([^\)]+\)") }
pub fn should_extend_brace( &mut self, line: &str ) -> bool {
self.re_contains(line, r"\{.*,.*\}")
}
pub fn extend_home( &mut self, s: &mut String)
{
if !self.needs_extend_home(s) { return; }
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 = self.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 do_brace_expansion( &mut self, line: &mut String)
{
let _line = line.clone();
let args = self.line_cmd_to_tokens(_line.as_str() );
let mut result: Vec<String> = Vec::new();
for (sep, token) in args
{
if sep.is_empty() && self.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( self.wrap_sep_string(sep.as_str(), _result.join(" ").as_str()) );
}
else { result.push( self.wrap_sep_string(sep.as_str(), token.as_str()) ); }
}
*line = result.join(" ");
}
pub fn do_command_substitution_for_dollar( &mut self, line: &mut String)
{
loop
{
if !self.should_do_brace_command_extension(&line) { break; }
let ptn_cmd = r"\$\(([^\(]+)\)";
let cmd;
match self.find_first_group( ptn_cmd, &line )
{
Some(x) => { cmd = x; }
None => { println_stderr!("installer: no first group"); return; }
}
let _args = self.line_cmd_to_tokens(&cmd);
let (_, _, output) = self.run_pipeline(_args, "", 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!("installer: from_utf8 error"); return; }
}
}
else { println_stderr!("installer: 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();
}
}
pub fn do_command_substitution_for_dot( &mut self, line: &mut String)
{
let tokens = self.line_cmd_to_tokens( &line );
let mut result: Vec<String> = Vec::new();
for (sep, token) in tokens
{
if sep == "`"
{
let _args = self.line_cmd_to_tokens(token.as_str());
let (_, _, output) = self.run_pipeline(_args, "", false, false, true, None);
if let Some(x) = output
{
match String::from_utf8(x.stdout) {
Ok(stdout) => {
let _txt = self.wrap_sep_string(sep.as_str(), stdout.trim());
result.push(_txt);
}
Err(_) => {
println_stderr!("installer: from_utf8 error");
result.push(self.wrap_sep_string(sep.as_str(), token.as_str()));
}
}
}
else { println_stderr!("installer: command error"); result.push( self.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!("installer: re new error"); return; }
if !re.is_match( &token )
{
result.push( self.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 = self.line_cmd_to_tokens(&cap[2]);
let (_, _, output) = self.run_pipeline(_args, "", 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!("installer: from_utf8 error");
result.push( self.wrap_sep_string(sep.as_str(), token.as_str()) );
return;
}
}
}
else
{
println_stderr!("installer: command error: {}", token);
result.push( self.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( self.wrap_sep_string(sep.as_str(), &_item));
}
else { result.push( self.wrap_sep_string(sep.as_str(), token.as_str()) ); }
}
*line = result.join(" ");
}
pub fn do_command_substitution( &mut self, line: &mut String )
{
self.do_command_substitution_for_dot(line);
self.do_command_substitution_for_dollar(line);
}
pub fn extend_glob( &mut self, line: &mut String )
{
if !self.needs_globbing( &line ) { return; }
let _line = line.clone();
// XXX: spliting needs to consider cases like `echo 'a * b'`
let _tokens: Vec<&str> = _line.split(' ').collect();
let mut result: Vec<String> = Vec::new();
for item in &_tokens
{
if !item.contains('*') || 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 !item.starts_with('.') && s.starts_with('.') && !s.contains('/')
{
// skip hidden files, you may need to
// type `ls .*rc` instead of `ls *rc`
continue;
}
result.push(s.into_owned());
is_empty = false;
}
Err(e) => { log!("installer::glob error: {:?}", e); }
}
}
if is_empty { result.push( item.to_string() ); }
}
Err(e) =>
{
println!("installer::glob error: {:?}", e);
result.push(item.to_string());
return;
}
}
}
}
*line = result.join(" ");
}
pub fn in_env( &mut self ) -> bool
{
if let Ok(x) = env::var("VIRTUAL_ENV") { if x != "" { return true; } }
false
}
pub fn get_envs_home( &mut self ) -> String
{
let home_envs;
match env::var("VIRTUALENV_HOME")
{
Ok( x ) => { home_envs = x; }
Err( _ ) => { home_envs = String::new(); }
}
home_envs
}
pub fn list_envs( &mut self ) -> i32
{
let home_envs = self.get_envs_home();
if home_envs == "" { println!("installer::list_env::you need to set VIRTUALENV_HOME to use vox"); return 1; }
if !Path::new(home_envs.as_str()).exists()
{
match fs::create_dir_all(home_envs.as_str())
{
Ok(_) => {}
Err(e) => println!("installer::list_env::fs create_dir_all failed: {:?}", e),
}
}
println!("Envs under: {}", home_envs);
let pdir = home_envs.clone();
if let Ok(list) = read_dir(home_envs)
{
for ent in list
{
if let Ok(ent) = ent
{
let ent_name = ent.file_name();
if let Ok(path) = ent_name.into_string()
{
let full_path = format!("{}/{}/bin/activate", pdir, path);
if !Path::new(full_path.as_str()).exists() { continue; }
println!("{}", path);
}
}
}
}
0
}
pub fn enter_env( &mut self, path: &str ) -> i32
{
if self.in_env() { println!("vox: already in env"); return 1; }
let home_envs = self.get_envs_home();
let full_path = format!("{}/{}/bin/activate", home_envs, path);
if !Path::new(full_path.as_str()).exists() { println!("no such env: {}", full_path); return 1; }
let path_env = format!("{}/{}", home_envs, path);
env::set_var("VIRTUAL_ENV", &path_env);
let mut path_new = String::from("${VIRTUAL_ENV}/bin:$PATH");
self.extend_env( &mut path_new );
env::set_var("PATH", &path_new);
0
}
pub fn exit_env( &mut self ) -> i32
{
if !self.in_env() { println!("installer::vox: not in an env"); return 0; }
let env_path;
match env::var("PATH")
{
Ok(x) => env_path = x,
Err(_) => { println!("installer::vox: cannot read PATH env"); return 1; }
}
let mut _tokens: Vec<&str> = env_path.split(':').collect();
let mut path_virtual_env = String::from("${VIRTUAL_ENV}/bin");
self.extend_env( &mut path_virtual_env );
_tokens.iter()
.position(|&n| n == path_virtual_env)
.map(|e| _tokens.remove(e));
let env_path_new = _tokens.join(":");
env::set_var("PATH", &env_path_new);
env::set_var("VIRTUAL_ENV", "");
0
}
pub fn extend_env_blindly( &mut self, token: &str ) -> String
{
let re;
if let Ok( x ) = Regex::new(r"([^\$]*)\$\{?([A-Za-z0-9\?\$_]+)\}?(.*)") { re = x; }
else{ println!("installer::extend_env_blindly: 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, self.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( &mut self, line: &mut String )
{
let mut result: Vec<String> = Vec::new();
let _line = line.clone();
let args = self.line_cmd_to_tokens( _line.as_str() );
for (sep, token) in args
{
if sep == "`" || sep == "'" { result.push( self.wrap_sep_string( &sep, &token ) ); }
else
{
let _token = self.extend_env_blindly( &token );
result.push( self.wrap_sep_string( &sep, &_token ) );
}
}
*line = result.join(" ");
}
pub fn extend_alias( &mut self, line: &str ) -> String
{
let cmds = self.lines_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 = self.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 self.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 run_calc_int( &mut self, line: &str ) -> Result<i64, String>
{
match expr_int(line.as_bytes()) {
IResult::Done(_, x) => Ok(x),
IResult::Error(e) => Err(e.description().to_owned()),
IResult::Incomplete(_) => Err(String::from("Incomplete arithmetic")),
}
}
pub fn run_calc_float( &mut self, line: &str ) -> Result<f64, String>
{
match expr_float( line.as_bytes() )
{
IResult::Done( _, x ) => Ok( x ),
IResult::Error( e ) => Err( e.description().to_owned() ),
IResult::Incomplete( _ ) => Err( String::from("Incomplete arithmetic") ),
}
}
pub fn env_args_to_command_line( &mut self ) -> 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 remove_envs_from_line( &mut self, line: &str, envs: &mut HashMap<String, String> ) -> String
{
let mut result = line.to_string();
while let Some(x) = self.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 get_user_completer_dir( &mut self ) -> String { format!("{}/_.installer/completers", self.get_user_home() ) }
pub fn get_rc_file( &mut self ) -> String{ format!("{}/{}", self.get_user_home(), ".installerc") }
pub fn pre_handle_cmd_line( &mut self, line: &mut String )
{
self.extend_home(line);
self.do_brace_expansion( line );
self.extend_glob( line );
self.extend_env( line );
self.do_command_substitution( line );
}
pub fn historical( &mut self ) -> ( String, String )
{
( self.get_history_file(), self.get_history_table() )
}
pub fn epoch( &mut self, rl: &mut Interface<DefaultTerminal>)
{
let mut hist_size: usize = 999;
if let Ok(x) = env::var("HISTORY_SIZE") { if let Ok(y) = x.parse::<usize>() { hist_size = y; } }
rl.set_history_size(hist_size);
//let history_table = self.get_history_table(); let hfile = self.get_history_file();
let ( hfile, history_table ) = self.historical();
let path = Path::new(hfile.as_str());
if !path.exists()
{
let _parent;
match path.parent() {
Some(x) => _parent = x,
None => {
println!("installer::epoch: history init - no parent found");
return;
}
}
let parent;
match _parent.to_str()
{
Some(x) => parent = x,
None => {
println!("installer::epoch: parent to_str is None");
return;
}
}
match fs::create_dir_all(parent)
{
Ok(_) => {}
Err(e) => {
println!("installer::epoch: dirs create failed: {:?}", e);
return;
}
}
match fs::File::create(hfile.as_str()) {
Ok(_) => {}
Err(e) => {
println!("installer::epoch: file create failed: {:?}", e);
}
}
}
let mut histories: HashMap<String, bool> = HashMap::new();
match sqlite::open(hfile.clone())
{
Ok(conn) => {
let sql_create = format!(
"
CREATE TABLE IF NOT EXISTS {}
(inp TEXT,
rtn INTEGER,
tsb REAL,
tse REAL,
sessionid TEXT,
out TEXT,
info TEXT
);
",
history_table
);
match conn.execute(sql_create) {
Ok(_) => {}
Err(e) => println_stderr!("installer: sqlite exec error - {:?}", e),
}
if let Ok(x) = env::var("HISTORY_DELETE_DUPS") {
if x == "1" {
self.delete_duplicated_histories();
}
}
let sql_select = format!("SELECT inp FROM {} ORDER BY tsb;", history_table,);
match conn.iterate(sql_select, |pairs| {
for &(_, value) in pairs.iter() {
let inp;
match value {
Some(x) => inp = x,
None => {
println!("installer: sqlite pairs None");
continue;
}
}
let _k = inp.to_string();
if histories.contains_key(&_k) {
continue;
}
histories.insert(_k, true);
rl.add_history(inp.trim().to_string());
}
true
}) {
Ok(_) => {}
Err(e) => println_stderr!("installer: sqlite select error - {:?}", e),
}
}
Err(e) =>{ println_stderr!("installer::epoch: sqlite conn error - {:?}", e); }
}
}
pub fn historic( &mut self, rl: &mut Interface<DefaultTerminal>, line: &str, status: i32, tsb: f64, tse: f64 )
{
self.previous_status = status;
if line == self.previous_cmd { return; }
rl.add_history(line.to_string());
self.previous_cmd = line.to_string();
let ( hfile, history_table ) = self.historical(); //let hfile = self.get_history_file(); let history_table = self.get_history_table();
let conn;
match sqlite::open( hfile )
{
Ok(x) => conn = x,
Err(e) =>
{
println!("installer::historic: sqlite open db error: {:?}", e);
return;
}
}
let sql = format!
(
"INSERT INTO {} (inp, rtn, tsb, tse, sessionid) VALUES('{}', {}, {}, {}, '{}');",
history_table, str::replace( line.trim(), "'", "''" ), status, tsb, tse, "installer"
);
match conn.execute( sql )
{
Ok(_) => {}
Err(e) => println!("installer::historic: failed to save history: {:?}", e),
}
}
pub fn get_history_file( &mut self ) -> String
{
if let Ok( hfile ) = env::var("HISTORY_FILE") { return hfile; }
else if let Ok( d ) = env::var("XDG_DATA_HOME") { return format!("{}/{}", d, "installer/history.sqlite"); }
else { let home = self.get_user_home(); return format!("{}/{}", home, ".local/share/installer/history.sqlite"); }
}
pub fn get_history_table( &mut self ) -> String
{
if let Ok( hfile ) = env::var("HISTORY_TABLE") { return hfile; }
else { return String::from("installer_history"); }
}
pub fn delete_duplicated_histories( &mut self )
{
let ( hfile, history_table ) = self.historical(); //let hfile = self.get_history_file(); let history_table = self.get_history_table();
match sqlite::open( hfile.clone() )
{
Ok( conn ) =>
{
let sql = format!
(
"DELETE FROM {} WHERE rowid NOT IN ( SELECT MAX(rowid) FROM {} GROUP BY inp)",
history_table, history_table
);
match conn.execute( sql )
{
Ok(_) => {}
Err(e) => println_stderr!("installer: sqlite exec error - {:?}", e),
}
}
Err(e) => println_stderr!("installer: sqlite open file error - {:?}", e),
}
}
pub fn list_current_history( &mut self, conn: &sqlite::Connection ) -> i32
{
let history_table = self.get_history_table();
let q = format!( "SELECT inp FROM {} ORDER BY tsb desc limit 20;", history_table );
match conn.prepare( q )
{
Ok(mut statement) =>
{
let mut vec = Vec::new();
loop
{
match statement.next()
{
Ok( x ) =>
{
if let State::Row = x { if let Ok(_x) = statement.read::<String>(0) { vec.push( _x ); } }
else { break; }
}
Err(e) => { println_stderr!("history: statement.next error: {:?}", e); return 1; }
}
}
for (i, elem) in vec.iter().rev().enumerate()
{
println!("{}: {}", i, elem);
}
}
Err(e) => { println_stderr!("history: prepare error - {:?}", e); return 1; }
}
0
}
pub fn search_history( &mut self, conn: &sqlite::Connection, q: &str )
{
let history_table = self.get_history_table();
let q = format!( "SELECT inp FROM {} WHERE inp like '%{}%' ORDER BY tsb desc limit 20;", history_table, q );
match conn.prepare( q )
{
Ok( mut statement ) =>
{
let mut vec = Vec::new();
loop
{
match statement.next()
{
Ok(x) =>
{
if let State::Row = x { if let Ok(_x) = statement.read::<String>(0) { vec.push(_x); } }
else { break; }
}
Err(e) => { println_stderr!("history: statement.next error: {:?}", e); return; }
}
}
for ( i, elem ) in vec.iter().rev().enumerate()
{
println!( "{}: {}", i, elem );
}
}
Err(e) => { println_stderr!( "history: prepare error - {:?}", e ); }
}
}
pub fn history( &mut self, tokens: &Vec<(String, String)>) -> i32
{
let args = self.line_tokens_to_args(&tokens );
let hfile = self.get_history_file();
let path = Path::new(hfile.as_str());
if !path.exists() { println_stderr!("installer::history::no history file."); return 1; }
if let Ok( conn ) = sqlite::open( hfile.clone() )
{
if args.len() == 1 { return self.list_current_history( &conn ); }
else if args.len() == 2 { self.search_history( &conn, args[1].as_str() ); }
else { println_stderr!("installer::history: only take one arg"); }
}
else { println_stderr!("installer::history: history file open error."); return 1; }
0
}
pub fn cd( &mut self, tokens: &Vec<(String, String)>) -> i32
{
let args = self.line_tokens_to_args(&tokens);
if args.len() > 2 { println!("installer::cd::invalid cd command"); return 1; }
let mut current_dir = PathBuf::new();
match env::current_dir()
{
Ok(x) => { current_dir = x; }
Err(e) => { println!("installer::cd::current_dir() failed: {}", e.description()); }
}
let mut str_current_dir = "";
match current_dir.to_str()
{
Some(x) => str_current_dir = x,
None => { println!("installer::cd::current_dir to str failed."); }
}
let mut dir_to = if args.len() == 1
{
let home = self.get_user_home();
home.to_string()
}
else { args[1..].join("") };
if dir_to == "-"
{
if self.previous_dir == "" { println!("installer::cd::no previous dir"); return 0; }
dir_to = self.previous_dir.clone();
}
else if !dir_to.starts_with('/') { dir_to = format!("{}/{}", str_current_dir, dir_to); }
if str_current_dir != dir_to { self.previous_dir = str_current_dir.to_string(); }
match env::set_current_dir(&dir_to)
{
Ok(_) => 0,
Err(e) => { println!("installer::cd: {}", e.description()); 1 }
}
}
pub fn exports( &mut self, line: &str) -> i32
{
if !self.is_env( line )
{
println!("installer::exports: invalid command\nusage: export VARIABLE=VALUE");
return 1;
}
let _line;
if let Ok( re ) = Regex::new(r"^ *export +")
{
if !re.is_match(line)
{
println_stderr!("installer::exports: invalid command?");
return 1;
}
_line = re.replace_all( line, "" );
}
else
{
println_stderr!("installer::exports: re new error");
return 2;
}
let args = self.line_cmd_to_tokens( &_line );
for (sep, token) in args
{
if sep == "`" { continue; }
if let Ok(re) = Regex::new(r" *([a-zA-Z0-9_]+)=(.*) *")
{
if !re.is_match(&token) { continue; }
for cap in re.captures_iter( &token )
{
let mut _value = self.unquote( &cap[2] );
if self.needs_extend_home( &_value )
{
self.extend_home( &mut _value );
}
let value = self.extend_env_blindly( &_value );
env::set_var( &cap[1], &value );
}
}
else { println_stderr!("installer::exports: re new error"); return 2; }
}
0
}
pub fn vox( &mut self, tokens: &Vec<(String, String)>) -> i32
{
let args = self.line_tokens_to_args(&tokens);
if args.len() == 2 && args[1] == "ls" { self.list_envs() }
else if args.len() == 3 && args[1] == "enter" { self.enter_env( args[2].as_str()) }
else if args.len() == 2 && args[1] == "exit" { self.exit_env() }
else
{
println!("vox: invalid command");
println!("usage: vox (ls | enter <env-name> | exit)");
1
}
}
// builtins::exec::run
pub fn does( &mut self, tokens: &Vec<(String, String)> ) -> i32
{
let args = self.line_tokens_to_args( &tokens );
let len = args.len();
if len == 1 { println!("installer::does: invalid command"); return 1; }
let mut cmd = exec::Command::new(&args[1]);
let err = cmd.args(&args[2..len]).exec();
println!("installer::does: exec error: {:?}", err);
0
}
pub fn get_release_value( &mut self, ptn: &str ) -> String
{
let line = format!( "grep -i '{}' /etc/*release* | grep -o '=.*' | tr '\"=' ' '", ptn );
match self.run(&line)
{
Ok( x ) => { return x.stdout.trim().to_string(); }
Err(_) => { return String::new(); }
}
}
pub fn get_hostname( &mut self ) -> 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 get_uname( &mut self ) -> String
{
match self.run("uname")
{
Ok(x) => {
return x.stdout.trim().to_string();
}
Err(_) => {
return String::new();
}
}
}
pub fn get_osx_codename( &mut self ) -> String
{
match self.run("grep -o 'SOFTWARE LICENSE AGREEMENT FOR .*[a-zA-Z]' '/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf' | sed 's/SOFTWARE LICENSE AGREEMENT FOR *//'")
{
Ok(x) => { return x.stdout.trim().to_string(); }
Err(_) => { return String::new(); }
}
}
pub fn get_osx_version( &mut self ) -> String
{
match self.run("sw_vers -productVersion")
{
Ok(x) => { return x.stdout.trim().to_string(); }
Err(_) => { return String::new(); }
}
}
pub fn get_macos_name( &mut self ) -> String
{
let mut os_name = self.get_osx_codename();
let ver = self.get_osx_version();
if !ver.is_empty() {
os_name.push(' ');
os_name.push_str(&ver);
}
os_name
}
pub fn get_other_os_name( &mut self ) -> String
{
let mut name = self.get_release_value("PRETTY_NAME");
if !name.is_empty() { return name; }
name = self.get_release_value("DISTRIB_DESCRIPTION");
if !name.is_empty() { return name; }
name = self.get_release_value("IMAGE_DESCRIPTION");
if !name.is_empty() { return name; }
String::new()
}
pub fn get_os_name( &mut self ) -> String
{
//let uname = get_uname();
if self.get_uname().to_lowercase() == "darwin" { return self.get_macos_name(); }
else { return self.get_other_os_name(); }
}
pub fn informs( &mut self, _tokens: &Vec<(String, String)> ) -> i32
{
const VERSION: &str = env!("CARGO_PKG_VERSION");
println!("Cicada Version: {}", VERSION);
println!("Commit: {}", env!("GIT_HASH"));
let os_name = self.get_os_name();
println!( "OS: {}", os_name );
println!("Built with: {}", env!("BUILD_RUSTC_VERSION"));
println!("Built at: {}", env!("BUILD_DATE"));
0
}
pub fn exits( &mut self, tokens: &Vec<(String, String)>) -> i32
{
if tokens.len() > 2 {
println_stderr!("installer: exit: too many arguments");
return 1;
}
let mut code = 0;
if tokens.len() == 2 {
let _code = &tokens[1].1;
match _code.parse::<i32>() {
Ok(x) => {
code = x;
}
Err(_) => {
println_stderr!("installer: exit: {}: numeric argument required", _code);
code = 255;
}
}
}
process::exit(code);
0
}
pub fn handle_env( &mut self, line: &str )
{
self.exports( line );
}
pub fn handle_alias( &mut self, line: &str )
{
let re;
match Regex::new( r"^ *alias +([a-zA-Z0-9_\.-]+)=(.*)$" ) {
Ok(x) => re = x,
Err(e) => {
println!( "installer::handle_alias: Regex error: {:?}", e );
return;
}
}
for cap in re.captures_iter( line )
{
let name = self.unquote(&cap[1] );
let value = self.unquote(&cap[2] );
self.add_alias(name.as_str(), value.as_str() );
}
}
pub fn handle_line( &mut self, line: &str )
{
if self.is_env( line )
{
self.handle_env( line );
return;
}
if self.is_alias( line )
{
self.handle_alias( line );
return;
}
return;
}
pub fn describes( &mut self, file_name: &str, append: bool) -> Result<Stdio, String>
{
let mut oos = OpenOptions::new();
if append { oos.append(true); }
else
{
oos.write(true);
oos.truncate(true);
}
match oos.create(true).open(file_name)
{
Ok(x) => {
let fd = x.into_raw_fd();
let file_out = unsafe { Stdio::from_raw_fd(fd) };
Ok(file_out)
}
Err(e) => Err(format!("failed to create fd from file: {:?}", e)),
}
}
pub unsafe fn give_terminal_to( &mut self, 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!("installer::give_terminal_to: 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 handle_non_tty( &mut self ) -> Agent
{
let mut buffer = String::new();
let stdin = io::stdin();
let mut handle = stdin.lock();
match handle.read_to_string( &mut buffer )
{
Ok(_) =>
{
log!("installer::handle_non_tty: run non tty command: {}", &buffer);
return self.run_procs( &buffer, false );
}
Err( e ) =>
{
println!("installer::handle_non_tty: io stdin read_to_string failed: {:?}", e);
self.previous_status = self.status; self.status = 1;
return self.clone();
}
}
}
#[allow(cyclomatic_complexity)]
pub fn run_pipeline( &mut self, tokens: Tokens, redirect_from: &str, background: bool, tty: bool, capture_output: bool, envs: Option<HashMap<String, String>>, ) -> (i32, bool, Option<Output>)
{
if background && capture_output { println_stderr!("installer::run_pipeline: cannot capture output of background cmd"); return (1, false, None); }
// the defaults to return
let mut status = 0;
let mut term_given = false;
let mut output = None;
let sig_action = signal::SigAction::new
(
signal::SigHandler::Handler( handle_sigchld ),
signal::SaFlags::empty(),
signal::SigSet::empty(),
);
unsafe
{
match signal::sigaction( signal::SIGCHLD, &sig_action )
{
Ok(_) => {}
Err(e) => println!("sigaction error: {:?}", e),
}
}
let cmds = self.line_tokens_to_cmd_tokens( &tokens );
let length = cmds.len();
// info is built here only for printing log
let mut info = String::new();
for ( i, cmd ) in cmds.iter().enumerate()
{
for item in cmd
{
let sep = &item.0;
let token = &item.1;
info.push_str(sep);
info.push_str(token);
info.push_str(sep);
info.push(' ');
}
if length > 1 && i < length - 1 { info.push_str("| ") }
}
if length == 0 { println!("installer::run_pipeline: invalid command: cmds with empty length"); return (1, false, None); }
let mut pipes = Vec::new();
for _ in 0..length - 1
{
let fds;
match pipe()
{
Ok(x) => fds = x,
Err(e) =>
{
println!("pipe error: {:?}", e);
return (1, false, None);
}
}
pipes.push(fds);
}
if pipes.len() + 1 != length { println!("installer::run_pipeline: invalid command: unmatched pipes count"); return (1, false, None); }
let isatty = if tty { unsafe { libc::isatty(0) == 1 } }
else { false };
let mut i = 0;
let mut pgid: u32 = 0;
let mut children: Vec<u32> = Vec::new();
let mut _envs: HashMap<String, String> = HashMap::new();
if let Some(x) = envs { _envs = x; }
for cmd in &cmds
{
let cmd_new;
match self.line_cmd_to_with_redirects(&cmd )
{
Ok(x) => { cmd_new = x; }
Err(e) => { println!("installer::run_pipeline: cmd_to_with_redirects failed: {:?}", e); return (1, false, None); }
}
let cmd_ = self.line_tokens_to_args( &cmd_new.tokens );
if cmd_.is_empty()
{
println!("installer::run_pipeline: cmd_ is empty");
return (1, false, None);
}
let program = &cmd_[0];
// treat `(ls)` as `ls`
let mut p = Processor::new( program.trim_matches( |c| c == '(' || c == ')' ) );
p.args( &cmd_[1..] );
p.envs( &_envs );
if isatty { p.before_exec( move || {
unsafe
{
if i == 0
{
// set the first process as progress group leader
let pid = libc::getpid();
libc::setpgid( 0, pid );
}
else
{
libc::setpgid( 0, pgid as i32 );
}
}
Ok( ( ) ) } ); }
if i > 0
{
let fds_prev = pipes[i - 1];
let pipe_in = unsafe { Stdio::from_raw_fd( fds_prev.0 ) };
p.stdin( pipe_in );
}
// all processes except the last one need to get stdout piped
if i < length - 1
{
let fds = pipes[i];
let pipe_out = unsafe { Stdio::from_raw_fd(fds.1) };
p.stdout( pipe_out );
}
// capture output of last process if needed.
if i == length - 1 && capture_output
{
p.stdout(Stdio::piped());
p.stderr(Stdio::piped());
}
for item in &cmd_new.redirects
{
let from_ = &item.0;
let op_ = &item.1;
let to_ = &item.2;
if to_ == "&1" && from_ == "2"
{
unsafe
{
if i < length - 1
{
let fds = pipes[i];
let pipe_out = Stdio::from_raw_fd( fds.1 );
p.stderr(pipe_out);
}
else if !capture_output
{
let fd = libc::dup(1);
p.stderr( Stdio::from_raw_fd( fd ) );
}
else
{
// note: capture output with redirections does not
// make much sense
}
}
}
else if to_ == "&2" && from_ == "1"
{
unsafe
{
if i < length - 1 || !capture_output
{
let fd = libc::dup(2);
p.stdout(Stdio::from_raw_fd(fd));
}
else
{
// note: capture output with redirections does not
// make much sense
}
}
}
else
{
let append = op_ == ">>";
match self.describes( to_, append )
{
Ok( fd ) =>
{
if from_ == "1" { p.stdout( fd ); }
else { p.stderr( fd ); }
}
Err(e) => { println_stderr!("installer::run_pipeline: {}", e); return (1, false, None); }
}
}
}
if i == 0 && !redirect_from.is_empty()
{
let path = Path::new( redirect_from );
let display = path.display();
let file = match File::open( &path )
{
Err(why) => panic!("couldn't open {}: {}", display, why.description()),
Ok(file) => file,
};
let fd = file.into_raw_fd();
let file_in = unsafe { Stdio::from_raw_fd(fd) };
p.stdin( file_in );
}
let mut child;
match p.spawn()
{
Ok(x) =>
{
child = x;
if i != length - 1 { children.push( child.id() ); }
}
Err(e) =>
{
println!("{}: {}", program, e.description());
status = 1;
continue;
}
}
if isatty && !background && i == 0
{
pgid = child.id();
unsafe { term_given = self.give_terminal_to( pgid as i32 ); }
}
if !background && i == length - 1
{
if capture_output
{
match child.wait_with_output()
{
Ok( x ) => { output = Some( x ); }
Err( e ) => { println_stderr!("installer::run_pipeline: {:?}", e); output = None; }
}
}
else
{
match child.wait()
{
Ok(ecode) =>
{
if ecode.success() { status = 0; }
else
{
match ecode.code() {
Some(x) => status = x,
None => status = 1,
}
}
}
Err(_) =>
{
match IO_Error::last_os_error().raw_os_error()
{
Some(10) => { status = 0; }
Some(e) => { status = e; }
None => { status = 1; }
}
}
}
}
// ack of the zombies
// FIXME: better wait children in signal handler, but ..
// .. see comments in `handle_sigchld()` above.
for pid in &children
{
unsafe
{
let mut stat: i32 = 0;
let ptr: *mut i32 = &mut stat;
libc::waitpid(*pid as i32, ptr, 0);
}
}
}
i += 1;
}
(status, term_given, output)
}
pub fn run_default_proc( &mut self, line: &str, tty: bool, envs:&mut HashMap<String, String>, tokens:&mut Vec<(String, String)>, cmd:&str) -> i32
{
// for any other situations
functioning!( "Agent::run_default_proc", "...", "For unsupported installation commands" );
let mut background = false;
let mut len = tokens.len();
if len > 1 && tokens[len - 1].1 == "&"
{
background = true;
tokens.pop();
len -= 1;
}
let mut redirect_from = String::new();
let has_redirect_from = tokens.iter().any(|x| x.1 == "<");
if has_redirect_from
{
if let Some(idx) = tokens.iter().position(|x| x.1 == "<")
{
tokens.remove(idx);
len -= 1;
if len > idx
{
redirect_from = tokens.remove(idx).1;
len -= 1;
}
else
{
//println!("installer::run_default_proc: invalid command: cannot get redirect from");
functioned!( "Agent::run_default_proc", "...", "Invalid command; cannot get redirect from command" );
return 1;
}
}
}
if len == 0 { functioned!( "Agent::run_default_proc", "...", "if len == 0" ); return 0; }
let _envs = envs.clone();
let (result, term_given, _) = self.run_pipeline
( tokens.clone(),
redirect_from.as_str(),
background,
tty,
false,
Some(_envs),
);
if term_given
{
unsafe
{
let gid = libc::getpgid(0);
self.give_terminal_to(gid);
}
}
functioned!( "Agent::run_default_proc", "...", "return result" );
return result;
}
pub fn run_proc( &mut self, line: &str, tty: bool ) -> i32
{
let mut envs = HashMap::new();
let cmd_line = self.remove_envs_from_line( line, &mut envs );
let mut tokens = self.line_cmd_to_tokens( &cmd_line );
if tokens.is_empty() { return 0; }
let cmd = tokens[0].1.clone();
match cmd.as_str()
{
"cd"=>{ return self.cd( &tokens ); }
"export"=>{ return self.exports( &cmd_line ); }
"vox"=>{ return self.vox( &tokens ); }
"history"=>{ return self.history( &tokens ); }
"exec"=>{ return self.does( &tokens ); }
"cinfo"=>{ return self.informs( &tokens ); }
"exit"=>{ return self.exits( &tokens ); }
_ => { return self.run_default_proc( line, tty, &mut envs.clone(), &mut tokens.clone(), &cmd.clone() ); }
};
}
pub fn run_procs( &mut self, line: &str, tty: bool ) -> Agent
{
if self.is_arithmetic( line )
{
if line.contains('.')
{
match self.run_calc_float(line)
{
Ok( x ) =>
{
println!("{}", x);
self.previous_status = self.status; self.status = 0;
return self.clone();
}
Err(e) =>
{
println!("installer::run_proc: Error: {}", e);
self.previous_status = self.status; self.status = 1;
return self.clone();
}
}
}
else
{
match self.run_calc_int(line)
{
Ok(x) =>
{
println!("{}", x);
self.previous_status = self.status; self.status = 0;
return self.clone();
}
Err(e) =>
{
println!("installer::run_proc: Error: {}", e);
self.previous_status = self.status; self.status = 1;
return self.clone();
}
}
}
}
let mut cmd_line: String = line.to_string();
self.pre_handle_cmd_line( &mut cmd_line );
cmd_line = self.extend_alias( &cmd_line );
let mut status = 0;
let mut sep = String::new();
for token in self.lines_to_cmds( &cmd_line )
{
if token == ";" || token == "&&" || token == "||"
{
sep = token.clone();
continue;
}
if sep == "&&" && status != 0 { break; }
if sep == "||" && status == 0 { break; }
status = self.run_proc( token.as_str(), tty );
}
self.previous_status = self.status; self.status = status;
self.clone()
}
pub fn green( &mut self, s: &str) -> String {
return format!("{}{}{}", GREEN, s, RESET);
}
pub fn red( &mut self, s: &str) -> String {
return format!("{}{}{}", RED, s, RESET);
}
pub fn blue( &mut self, s: &str) -> String {
return format!("{}{}{}", BLUE, s, RESET);
}
pub fn prompts( &mut self ) -> String
{
let home = self.get_user_home();
let user;
match env::var("USER")
{
Ok(x) => user = x,
Err(e) =>
{
println!("installer::get_prompt: env USER error: {:?}", e);
return String::from("installer >> ");
}
}
let hostname = self.get_hostname();
let _current_dir;
match env::current_dir()
{
Ok(x) => _current_dir = x,
Err(e) =>
{
println!("installer::get_prompt: env current_dir error: {}", e.description());
return format!("({})$ ", self.red("installer: no current dir"));
}
}
let current_dir;
match _current_dir.to_str()
{
Some(x) => current_dir = x,
None => {
println!("installer::get_prompt: to_str error");
return String::from("installer >> ");
}
}
let _tokens: Vec<&str> = current_dir.split('/').collect();
let last;
match _tokens.last()
{
Some(x) => last = x,
None => {
println!("installer::get_prompt: prompt token last error");
return String::from("installer >> ");
}
}
let pwd: String;
if last.is_empty() { pwd = String::from("/"); }
else if current_dir == home { pwd = String::from("~"); }
else { pwd = last.to_string(); }
let mut prompt = if self.status == 0
{
format!(
"{}@{}: {}$ ",
self.green(user.as_str()),
self.green(hostname.as_str()),
self.green(pwd.as_str())
)
} else {
format!(
"{}@{}: {}$ ",
self.red(user.as_str()),
self.red(hostname.as_str()),
self.red(pwd.as_str())
)
};
if let Ok(x) = env::var("VIRTUAL_ENV") {
if x != "" {
let _tokens: Vec<&str> = x.split('/').collect();
let env_name;
match _tokens.last()
{
Some(x) => env_name = x,
None => {
println!("installer::get_prompt: prompt token last error");
return String::from("installer >> ");
}
}
prompt = format!("({}){}", self.blue(env_name), prompt);
}
}
prompt
}
pub fn run(&mut self, line: &str) -> Result<CommandResult, &str>
{
let mut envs = HashMap::new();
let mut cmd_line = self.remove_envs_from_line(line, &mut envs);
//let sh = shell::Shell::new();
self.pre_handle_cmd_line( &mut cmd_line );
let mut tokens = self.line_cmd_to_tokens(&cmd_line);
if tokens.is_empty() { return Ok(CommandResult::new()); }
let mut len = tokens.len();
if len > 1 && tokens[len - 1].1 == "&"
{
tokens.pop();
len -= 1;
}
let mut redirect_from = String::new();
let has_redirect_from = tokens.iter().any(|x| x.1 == "<");
if has_redirect_from {
if let Some(idx) = tokens.iter().position(|x| x.1 == "<") {
tokens.remove(idx);
len -= 1;
if len > idx {
redirect_from = tokens.remove(idx).1;
len -= 1;
} else {
return Err("installer: invalid command: cannot get redirect from");
}
}
}
if len == 0 {
return Ok(CommandResult::new());
}
let (status, _, output) = self.run_pipeline
(
tokens.clone(),
redirect_from.as_str(),
false,
false,
true,
Some(envs),
);
match output
{
Some(x) => Ok(CommandResult {
status,
stdout: String::from_utf8_lossy(&x.stdout).into_owned(),
stderr: String::from_utf8_lossy(&x.stderr).into_owned(),
}),
None => Err("no output"),
}
}
pub fn dispatches( &mut self ) -> Agent
{
let editor = unsafe { libc::isatty(libc::STDOUT_FILENO as i32) } == 0;
if editor {
println!("stdout is tty editor");
} else {
println!("stdout is not tty editor");
}
match true
{
true if env::args().len() > 1 =>
{
println!("if env::args().len() > {:?}", env::args().len() );
let line = self.env_args_to_command_line();
return self.run_procs( &line, false );
}
true if editor =>
{
println!( "installer::dispatches::libc::istty( {:?} )", editor );
return self.handle_non_tty();
}
_=>
{
println!( "installer::dispatches: default" );
let mut rl;
match Interface::new("installer")
{
Ok(x) => rl = x,
Err(e) =>
{
// non-tty will raise errors here
println!("installer::dispatches: linefeed Interface Error: {:?}", e);
return self.clone();
}
}
self.epoch( &mut rl );
/*rl.set_completer( Arc::new(completers::CicadaCompleter {
sh: Arc::new(sh.clone()),
}));*/
let mut status = self.status;
loop
{
let prompt = self.prompts();
match rl.set_prompt(&prompt)
{
Ok(_) => {}
Err(e) => { println!("installer::dispatches: error when setting prompt: {:?}\n", e); }
}
println!("Reading Line");
match rl.read_line()
{
Ok(ReadResult::Input(line)) =>
{
if line.trim() == "" { continue; }
/*
if line=="^C"
{
self.previous_status = self.status; self.status = 2;
return self.clone();
}
*/
let tsb_spec = time::get_time();
let tsb = (tsb_spec.sec as f64) + tsb_spec.nsec as f64 / 1_000_000_000.0;
let mut line = line.clone();
self.extend_bandband( &mut line );
self.run_procs( &line, true );
status = self.status;
let tse_spec = time::get_time();
let tse = (tse_spec.sec as f64) + tse_spec.nsec as f64 / 1_000_000_000.0;
self.historic( &mut rl, &line, status, tsb, tse );
}
Ok(ReadResult::Eof) =>
{
if let Ok(x) = env::var("NO_EXIT_ON_CTRL_D")
{
if x == "1"
{
println!("eol( 1 )");
continue;
}
}
println!("installer::dispatches: exit");
break;
}
Ok(ReadResult::Signal(s)) => { println!("installer::dispatches: readline signal: {:?}", s); }
Err(e) => { println!("installer::dispatches: readline error: {:?}", e); }
}
}
self.clone()
}
}
}
pub fn employs( &mut self ) -> Agent
{
functioning!( "Agent::run_default_proc", "&mut self", "Employ The pls Installer" );
self.profile = self.get_rc_file();
let mut file;
match File::open( &mut self.profile )
{
Ok(x) => file = x,
Err(e) =>
{
println!( "installer: Open Profile Error: {:?}\nCreating New Profile Part @ Place( {} )", e, &mut self.profile );
match File::create( &mut self.profile )
{
Ok( _ ) => { return self.employs(); }
_=>
{
println!( "installer: Create Profile Error: {:?}", e );
return self.clone();
}
}
return self.employs();
}
}
let mut text = String::new();
match file.read_to_string( &mut text )
{
Ok(_) => {}
Err(e) => {
println!("installer: read_to_string error: {:?}", e);
return self.clone();
}
}
for line in text.lines()
{
self.handle_line( line );
}
functioned!( "Agent::employs", "...", "pls Installer Employed" );
self.clone()
}
}
pub use super::{ installer };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment