extern crate ansi_term;
extern crate nix;
mod props;
use ansi_term::Colour;
use std::path::{Path, PathBuf};
use std::thread::sleep;
use std::time::{Duration};
use crate::config;
use props::RuntimeProps;
use crate::shell::proc::ShellState;
use crate::shell::{Shell};
use crate::shell::unixsignal::UnixSignal;
use crate::translator::ioprocessor::IOProcessor;
use crate::utils::console;
use crate::utils::file;
pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Option<String>, history_file: Option<PathBuf>) -> u8 {
let mut props: RuntimeProps = RuntimeProps::new(true, config, processor);
let (shell, args): (String, Vec<String>) = resolve_shell(&props.config, shell);
let mut shell: Shell = match Shell::start(shell, args, &props.config.prompt_config) {
Ok(sh) => sh,
Err(err) => {
print_err(
String::from(format!("Could not start shell: {}", err)),
props.config.output_config.translate_output,
&props.processor,
);
return 255;
}
};
if let Some(history_file) = history_file.clone() {
match file::read_lines(history_file.clone()) {
Ok(lines) => shell.history.load(lines),
Err(err) => print_err(
String::from(format!("Could not load history from '{}': {}", history_file.display(), err)),
props.config.output_config.translate_output,
&props.processor,
)
}
};
while props.get_last_state() != ShellState::Terminated {
let current_state: ShellState = shell.get_state();
if current_state != props.get_last_state() {
props.update_state(current_state);
}
if props.get_state_changed() && current_state == ShellState::Idle {
shell.refresh_env();
console::print(format!("{} ", shell.get_promptline(&props.processor)));
props.report_state_changed_notified();
} else if props.get_state_changed() {
props.report_state_changed_notified();
}
if let Some(ev) = console::read() {
props.handle_input_event(ev, &mut shell);
};
let new_state = shell.get_state();
if new_state != props.get_last_state() {
props.update_state(new_state);
}
read_from_shell(&mut shell, &props.config, &props.processor);
sleep(Duration::from_nanos(100));
}
if let Some(history_file) = history_file {
let lines: Vec<String> = shell.history.dump();
if let Err(err) = file::write_lines(history_file.clone(), lines) {
print_err(
String::from(format!("Could not write history to '{}': {}", history_file.display(), err)),
props.config.output_config.translate_output,
&props.processor,
);
}
};
match shell.stop() {
Ok(rc) => rc,
Err(err) => {
print_err(format!("Could not stop shell: {}", err), props.config.output_config.translate_output, &props.processor);
255
}
}
}
pub fn run_command(mut command: String, processor: IOProcessor, config: config::Config, shell: Option<String>) -> u8 {
let mut props: RuntimeProps = RuntimeProps::new(false, config, processor);
let (shell, args): (String, Vec<String>) = resolve_shell(&props.config, shell);
let mut shell: Shell = match Shell::start(shell, args, &props.config.prompt_config) {
Ok(sh) => sh,
Err(err) => {
print_err(
String::from(format!("Could not start shell: {}", err)),
props.config.output_config.translate_output,
&props.processor,
);
return 255;
}
};
while command.ends_with('\n') {
command.pop();
}
while command.ends_with(';') {
command.pop();
}
command.push_str("; exit $?\n");
if let Err(err) = shell.write(command) {
print_err(
String::from(format!("Could not start shell: {}", err)),
props.config.output_config.translate_output,
&props.processor,
);
return 255;
}
let _ = shell.write(String::from("\n"));
loop {
if let Some(ev) = console::read() {
props.handle_input_event(ev, &mut shell);
};
read_from_shell(&mut shell, &props.config, &props.processor);
if shell.get_state() == ShellState::Terminated {
break;
}
sleep(Duration::from_nanos(100));
}
match shell.stop() {
Ok(rc) => rc,
Err(err) => {
print_err(format!("Could not stop shell: {}", err), props.config.output_config.translate_output, &props.processor);
255
}
}
}
pub fn run_file(file: String, processor: IOProcessor, config: config::Config, shell: Option<String>) -> u8 {
let file_path: &Path = Path::new(file.as_str());
let lines: Vec<String> = match file::read_lines(file_path) {
Ok(lines) => lines,
Err(_) => {
print_err(format!("{}: No such file or directory", file), config.output_config.translate_output, &processor);
return 255
}
};
let command: String = script_lines_to_string(&lines);
run_command(command, processor, config, shell)
}
fn read_from_shell(shell: &mut Shell, config: &config::Config, processor: &IOProcessor) {
if let Ok((out, err)) = shell.read() {
if out.is_some() {
print_out(out.unwrap(), config.output_config.translate_output, &processor);
}
if err.is_some() {
print_err(err.unwrap().to_string(), config.output_config.translate_output, &processor);
}
}
}
fn resolve_shell(config: &config::Config, shellopt: Option<String>) -> (String, Vec<String>) {
match shellopt {
Some(sh) => (sh, vec![]),
None => (config.shell_config.exec.clone(), config.shell_config.args.clone())
}
}
fn script_lines_to_string(lines: &Vec<String>) -> String {
let mut command: String = String::new();
for line in lines.iter() {
if line.starts_with("#") {
continue;
}
if line.len() == 0 {
continue;
}
command.push_str(line);
if ! line.ends_with(";") {
command.push(';');
}
}
command
}
fn resolve_command(argv: &mut Vec<String>, config: &config::Config) {
match config.get_alias(&argv[0]) {
Some(resolved) => argv[0] = resolved,
None => {}
};
}
fn print_err(err: String, to_cyrillic: bool, processor: &IOProcessor) {
match to_cyrillic {
true => eprintln!("{}", Colour::Red.paint(processor.text_to_cyrillic(&err))),
false => eprintln!("{}", Colour::Red.paint(err)),
};
}
fn print_out(out: String, to_cyrillic: bool, processor: &IOProcessor) {
match to_cyrillic {
true => console::println(format!("{}", processor.text_to_cyrillic(&out))),
false => console::println(format!("{}", out)),
};
}
#[allow(dead_code)]
fn shellsignal_to_signal(sig: u8) -> Option<UnixSignal> {
match sig {
3 => Some(UnixSignal::Sigint),
26 => Some(UnixSignal::Sigstop),
_ => None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use crate::translator::ioprocessor::IOProcessor;
use crate::translator::new_translator;
use crate::translator::lang::Language;
use std::collections::HashMap;
use std::time::Duration;
use std::thread::sleep;
#[test]
fn test_runtime_read_from_shell() {
let mut cfg: Config = Config::default();
cfg.output_config.translate_output = true;
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
let mut shell: Shell = Shell::start(String::from("sh"), vec![], &cfg.prompt_config).unwrap();
sleep(Duration::from_millis(500));
let _ = shell.write(String::from("echo 4\n"));
sleep(Duration::from_millis(100));
read_from_shell(&mut shell, &cfg, &iop);
cfg.output_config.translate_output = false;
let _ = shell.write(String::from("echo 5\n"));
sleep(Duration::from_millis(100));
read_from_shell(&mut shell, &cfg, &iop);
cfg.output_config.translate_output = true;
let _ = shell.write(String::from("poropero\n"));
sleep(Duration::from_millis(100));
read_from_shell(&mut shell, &cfg, &iop);
cfg.output_config.translate_output = false;
let _ = shell.write(String::from("poropero\n"));
sleep(Duration::from_millis(100));
read_from_shell(&mut shell, &cfg, &iop);
sleep(Duration::from_millis(500));
assert!(shell.stop().is_ok());
sleep(Duration::from_millis(500));
}
#[test]
fn test_runtime_resolve_shell() {
let mut cfg: Config = Config::default();
cfg.shell_config.args = vec![String::from("-i")];
assert_eq!(resolve_shell(&cfg, None), (String::from("bash"), vec![String::from("-i")]));
assert_eq!(resolve_shell(&cfg, Some(String::from("fish"))), (String::from("fish"), vec![]));
}
#[test]
fn test_runtime_script_lines_to_command() {
let lines: Vec<String> = vec![String::from("#!/bin/bash"), String::from(""), String::from("echo 4"), String::from("#this is a comment"), String::from("cat /tmp/output;")];
assert_eq!(script_lines_to_string(&lines), String::from("echo 4;cat /tmp/output;"));
}
#[test]
fn test_runtime_resolve_command() {
let mut alias_cfg: HashMap<String, String> = HashMap::new();
alias_cfg.insert(String::from("ll"), String::from("ls -l"));
let cfg: Config = Config {
language: String::from(""),
shell_config: config::ShellConfig::default(),
alias: alias_cfg,
output_config: config::OutputConfig::default(),
prompt_config: config::PromptConfig::default()
};
let mut argv: Vec<String> = vec![String::from("ll"), String::from("/tmp/")];
resolve_command(&mut argv, &cfg);
assert_eq!(*argv.get(0).unwrap(), String::from("ls -l"));
let mut argv: Vec<String> = vec![String::from("du"), String::from("-hs")];
resolve_command(&mut argv, &cfg);
assert_eq!(*argv.get(0).unwrap(), String::from("du"));
}
#[test]
fn test_runtime_print() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
print_out(String::from("Hello"), true, &iop);
print_out(String::from("Hello"), false, &iop);
print_err(String::from("Hello"), true, &iop);
print_err(String::from("Hello"), false, &iop);
}
#[test]
fn test_runtime_shellsignal() {
assert_eq!(shellsignal_to_signal(3).unwrap(), UnixSignal::Sigint);
assert_eq!(shellsignal_to_signal(26).unwrap(), UnixSignal::Sigstop);
assert!(shellsignal_to_signal(255).is_none());
}
}