use crate::commands::{cmd_with_output, piped_cmd, return_shellcommand, PipedShellCommand, Redirection, ShellCommand};
use sflib::ensure_directory;
use std::env::var as env_var;
use std::path::PathBuf;
#[cfg(feature = "readline")]
use std::process::exit;
#[cfg(feature = "readline")]
use rustyline::{error::ReadlineError, Editor};
#[cfg(not(feature = "readline"))]
use std::io::Write;
pub struct ShellState {
pub args: Vec<String>,
pub prompt: String,
pub user: String,
pub home: String,
pub na: String,
pub share_dir: String,
pub cd_prev_dir: Option<PathBuf>,
pub config_dir: String,
pub config: String,
pub history_file: String,
pub edit_mode: String,
pub bell_style: String,
pub history_auto_add_lines: bool,
pub history_size: usize,
pub history_spaces_ignored: bool,
}
fn get_time(format: &str) -> String {
#[cfg(not(feature = "time"))]
{
format.to_string()
}
#[cfg(feature = "time")]
{
let date = chrono::Local::now();
date.format(format).to_string()
}
}
pub fn process_input(shell_state: &mut ShellState, input: &str) {
if input.is_empty() {
return;
}
let command = ShellCommand::new(input);
ShellCommand::run(shell_state, command);
}
#[cfg(feature = "readline")]
pub fn run_loop(rl: &mut Editor<()>, mut shell_state: ShellState) {
loop {
let prompt = rl.readline(&ShellState::eval_prompt(&mut shell_state));
match prompt {
Ok(line) => {
rl.add_history_entry(line.as_str());
if line.starts_with("exit") {
if line.contains(' ') {
let input = line.split(' ').collect::<Vec<&str>>()[1];
rl.save_history(&shell_state.history_file).unwrap();
exit(input.parse::<i32>().unwrap_or(0));
} else {
rl.save_history(&shell_state.history_file).unwrap();
exit(0);
}
}
process_input(&mut shell_state, &line);
}
Err(ReadlineError::Interrupted) => {
continue;
}
Err(ReadlineError::Eof) => {
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
}
}
}
rl.save_history(&shell_state.history_file).unwrap();
}
#[cfg(not(feature = "readline"))]
pub fn run_loop(mut shell_state: ShellState) {
loop {
let prompt = ShellState::eval_prompt(&mut shell_state);
print!("{}", prompt);
std::io::stdout().flush().unwrap();
let input = crate::parse_input("interactive");
process_input(&mut shell_state, &input);
}
}
impl ShellState {
pub fn init() -> ShellState {
let args = std::env::args().collect();
let prompt = env_var("PROMPT").unwrap_or_else(|_| String::from("F<GREEN>B<BLACK>%{b}%{u}[crust]-[%{CL}]:%{re} "));
let user_command = return_shellcommand(String::from("whoami"), Vec::new(), Redirection::NoOp);
let user = env_var("USER").unwrap_or_else(|_| cmd_with_output(&user_command)).trim().to_string();
let home = env_var("HOME").unwrap_or_else(|_| ["/home/", user.as_str()].concat());
let na = String::from("no args");
let share_dir = [&home, "/.local/share/crust"].concat();
let cd_prev_dir = None;
let config_dir = [&home, "/.config/crust/"].concat();
let config = [&config_dir, "config"].concat();
let history_file = [&share_dir, "/crust.history"].concat();
let shell_state = ShellState {
args,
prompt,
user,
home,
na,
share_dir,
cd_prev_dir,
config_dir,
config,
history_file,
edit_mode: String::from("emacs"),
bell_style: String::from("nothing"),
history_auto_add_lines: true,
history_size: 500,
history_spaces_ignored: true,
};
ensure_directory(&shell_state.share_dir, true).unwrap();
ensure_directory(&shell_state.config_dir, true).unwrap();
shell_state
}
pub fn eval_prompt(&mut self) -> String {
let mut evaled_prompt = self.prompt.clone();
let commands = crate::prompt::get_commands_from_input(&self.prompt);
let mut command_output: String;
for command in commands {
if command.args.contains(&String::from("|")) {
command_output = piped_cmd(&PipedShellCommand::from(&command));
} else {
command_output = cmd_with_output(&command);
}
evaled_prompt = evaled_prompt.replace(
format!("%({})", command.to_string().trim()).as_str(),
command_output.trim(),
);
}
let files = crate::prompt::get_files_from_input(&evaled_prompt);
for file in files {
evaled_prompt = evaled_prompt.replace(
format!("%[{}]", file.to_string().trim()).as_str(),
crate::builtins::cat::cat(&[file]).trim(),
);
}
evaled_prompt = crate::prompt::parse_prompt_effects(&evaled_prompt);
let substitutions = vec!["%{CL}", "%{CS}", "%{D}", "%{H}", "%{T12}", "%{T24}", "%{U}", "\\n"];
for to_subst in substitutions {
let mut subst = String::new();
match to_subst {
"%{CL}" => subst = std::env::current_dir().unwrap().display().to_string(),
"%{CS}" => {
let cwd = std::env::current_dir().unwrap().display().to_string();
subst = cwd.split("/").collect::<Vec<&str>>()[cwd.split("/").count() - 1].to_string();
},
"%{D}" => subst = get_time("%a %b %d"),
"%{H}" => subst = self.home.clone(),
"%{T12}" => subst = get_time("%I:%M %p").to_string(),
"%{T24}" => subst = get_time("%H:%M").to_string(),
"%{U}" => subst = self.user.clone(),
"\\n" => subst = '\n'.to_string(), _ => (),
}
evaled_prompt = evaled_prompt.replace(to_subst, &subst);
}
evaled_prompt
}
}
pub fn tokenize(input: &str) -> Vec<String> {
input.chars().map(|t| t.to_string()).collect::<Vec<String>>()
}
fn push_to_vec(from_vec: &mut Vec<String>, to_vec: &mut Vec<String>) {
let element = from_vec.concat();
if element.is_empty() {
return;
}
to_vec.push(element);
from_vec.clear();
}
pub fn lex_tokenized_input(input: &str) -> Vec<String> {
let tokenized_vec = tokenize(input);
let mut lexed_vec: Vec<String> = Vec::new();
let mut tmp_vec: Vec<String> = Vec::new();
let mut quoted_vec: Vec<String> = Vec::new();
let mut quoted = false;
let mut quotes_ran = false;
for (idx, character) in tokenized_vec.iter().enumerate() {
match character.as_str() {
"\"" | "'" => {
if quotes_ran {
push_to_vec(&mut quoted_vec, &mut lexed_vec);
quoted = false;
quotes_ran = false;
} else {
quoted = true;
quotes_ran = true;
}
}
" " => {
if quoted {
quoted_vec.push(character.to_string());
} else {
push_to_vec(&mut tmp_vec, &mut lexed_vec);
}
}
_ => {
if quoted {
quoted_vec.push(character.to_string());
} else {
tmp_vec.push(character.to_string());
if idx == tokenized_vec.len() - 1 {
push_to_vec(&mut tmp_vec, &mut lexed_vec);
}
}
}
}
}
lexed_vec
}