use crossterm::style::Color;
use super::cargo_cmds::{cargo_asm, cargo_bench, ToolChain};
use super::cargo_cmds::{cargo_fmt, cargo_fmt_file, cargo_run, MAIN_FILE_EXTERN};
use super::highlight::highlight;
use crate::irust::format::{format_check_output, format_err, format_eval_output};
use crate::irust::printer::{PrintQueue, PrinterItem};
use crate::irust::{IRust, IRustError};
use crate::utils::{remove_main, stdout_and_stderr};
const SUCCESS: &str = "Ok!";
macro_rules! success {
() => {{
let mut print_queue = PrintQueue::default();
print_queue.push(PrinterItem::Str(SUCCESS, Color::Blue));
print_queue.add_new_line(1);
Ok(print_queue)
}};
}
macro_rules! print_queue {
($item: expr, $color: expr) => {{
let mut print_queue = PrintQueue::default();
print_queue.push(PrinterItem::String($item, $color));
print_queue.add_new_line(1);
Ok(print_queue)
}};
}
impl IRust {
pub fn parse(&mut self) -> Result<PrintQueue, IRustError> {
match self.buffer.to_string().as_str() {
":help" => self.help(),
":reset" => self.reset(),
":show" => self.show(),
":pop" => self.pop(),
":irust" => self.irust(),
":sync" => self.sync(),
cmd if cmd.starts_with("::") => self.run_cmd(),
cmd if cmd.starts_with(":edit") => self.extern_edit(),
cmd if cmd.starts_with(":add") => self.add_dep(),
cmd if cmd.starts_with(":load") => self.load(),
cmd if cmd.starts_with(":reload") => self.reload(),
cmd if cmd.starts_with(":type") => self.show_type(),
cmd if cmd.starts_with(":del") => self.del(),
cmd if cmd.starts_with(":cd") => self.cd(),
cmd if cmd.starts_with(":color") => self.color(),
cmd if cmd.starts_with(":toolchain") => self.toolchain(),
cmd if cmd.starts_with(":check_statements") => self.check_statements(),
cmd if cmd.starts_with(":time_release") => self.time_release(),
cmd if cmd.starts_with(":time") => self.time(),
cmd if cmd.starts_with(":bench") => self.bench(),
cmd if cmd.starts_with(":asm") => self.asm(),
_ => self.parse_second_order(),
}
}
fn reset(&mut self) -> Result<PrintQueue, IRustError> {
self.repl.reset(self.options.toolchain)?;
success!()
}
fn pop(&mut self) -> Result<PrintQueue, IRustError> {
self.repl.pop();
success!()
}
fn check_statements(&mut self) -> Result<PrintQueue, IRustError> {
const ERROR: &str = "Invalid argument, accepted values are `false` `true`";
let buffer = self.buffer.to_string();
let buffer = buffer.split_whitespace().nth(1).ok_or(ERROR)?;
self.options.check_statements = buffer.parse().map_err(|_| ERROR)?;
success!()
}
fn del(&mut self) -> Result<PrintQueue, IRustError> {
if let Some(line_num) = self.buffer.to_string().split_whitespace().last() {
self.repl.del(line_num)?;
}
success!()
}
fn show(&mut self) -> Result<PrintQueue, IRustError> {
let code: Vec<char> = self.repl.show().chars().collect();
let repl_code = highlight(&code, &self.theme);
Ok(repl_code)
}
fn toolchain(&mut self) -> Result<PrintQueue, IRustError> {
self.options.toolchain = ToolChain::from_str(
self.buffer
.to_string()
.split_whitespace()
.nth(1)
.unwrap_or("?"),
)?;
success!()
}
fn add_dep(&mut self) -> Result<PrintQueue, IRustError> {
let mut dep: Vec<String> = crate::utils::split_args(self.buffer.to_string());
dep.remove(0);
#[cfg(unix)]
for p in dep.iter_mut() {
let path = std::path::Path::new(p);
if path.exists() {
if let Ok(full_path) = path.canonicalize() {
if let Some(full_path) = full_path.to_str() {
*p = full_path.to_string();
}
}
}
}
#[cfg(windows)]
for p in dep.iter_mut() {
if p == "." {
*p = self
.known_paths
.get_cwd()
.to_str()
.ok_or("Error parsing path to dependecy")?
.to_string();
}
}
self.wait_add(self.repl.add_dep(&dep)?, "Add")?;
self.wait_add(self.repl.build(self.options.toolchain)?, "Build")?;
if self.options.check_statements {
self.wait_add(
super::cargo_cmds::cargo_check(self.options.toolchain)?,
"Check",
)?;
}
success!()
}
fn color(&mut self) -> Result<PrintQueue, IRustError> {
let buffer = self.buffer.to_string();
let mut buffer = buffer.split_whitespace().skip(1).peekable();
if buffer.peek() == Some(&"reset") {
self.theme.reset();
return success!();
}
let mut parse = || -> Result<(), IRustError> {
let key = buffer.next().ok_or("Key not specified")?;
let value = buffer.next().ok_or("Value not specified")?;
let mut theme = toml::Value::try_from(&self.theme)?;
*theme
.get_mut(key)
.ok_or_else(|| IRustError::Custom("key doesn't exist".into()))? = value.into();
if super::highlight::theme::theme_color_to_term_color(value).is_none() {
return Err("Value is incorrect".into());
}
self.theme = theme.try_into()?;
Ok(())
};
if let Err(e) = parse() {
return Err(e);
}
success!()
}
fn load(&mut self) -> Result<PrintQueue, IRustError> {
let buffer = self.buffer.to_string();
let path = if let Some(path) = buffer.split_whitespace().nth(1) {
std::path::Path::new(&path).to_path_buf()
} else {
return Err("No path specified").map_err(|e| e.into());
};
self.load_inner(path)
}
fn reload(&mut self) -> Result<PrintQueue, IRustError> {
let path = if let Some(path) = self.known_paths.get_last_loaded_coded_path() {
path
} else {
return Err("No saved path").map_err(|e| e.into());
};
self.load_inner(path)
}
pub fn load_inner(&mut self, path: std::path::PathBuf) -> Result<PrintQueue, IRustError> {
self.known_paths.set_last_loaded_coded_path(path.clone());
self.repl.reset(self.options.toolchain)?;
let path_code = std::fs::read(path)?;
let code = if let Ok(code) = String::from_utf8(path_code) {
code
} else {
return Err("The specified file is not utf8 encoded").map_err(Into::into);
};
let code = cargo_fmt(&code)?;
let code = remove_main(&code);
let (status, output) = self.repl.eval_build(code.clone(), self.options.toolchain)?;
if !status.success() {
Ok(format_err(&output))
} else {
self.repl.insert(code);
success!()
}
}
fn show_type(&mut self) -> Result<PrintQueue, IRustError> {
const TYPE_FOUND_MSG: &str = "expected `()`, found ";
const EMPTY_TYPE_MSG: &str = "dev [unoptimized + debuginfo]";
let variable = self
.buffer
.to_string()
.trim_start_matches(":type")
.to_string();
let mut raw_out = String::new();
let toolchain = self.options.toolchain;
self.repl
.eval_in_tmp_repl(variable, || -> Result<(), IRustError> {
let (_status, out) = cargo_run(false, false, toolchain)?;
raw_out = out;
Ok(())
})?;
let var_type = if raw_out.find(TYPE_FOUND_MSG).is_some() {
raw_out
.lines()
.rev()
.find(|l| l.contains("found"))
.unwrap()
.rsplit("found ")
.next()
.unwrap()
.to_string()
} else if raw_out.find(EMPTY_TYPE_MSG).is_some() {
"()".into()
} else {
"Uknown".into()
};
print_queue!(var_type, self.options.ok_color)
}
fn run_cmd(&mut self) -> Result<PrintQueue, IRustError> {
let buffer = &self.buffer.to_string()[2..];
let mut cmd = buffer.split_whitespace();
let output = stdout_and_stderr(
std::process::Command::new(cmd.next().unwrap_or_default())
.args(&cmd.collect::<Vec<&str>>())
.output()?,
);
print_queue!(output, self.options.shell_color)
}
fn parse_second_order(&mut self) -> Result<PrintQueue, IRustError> {
const FUNCTION_DEF: &str = "fn ";
const ASYNC_FUNCTION_DEF: &str = "async fn ";
const ENUM_DEF: &str = "enum ";
const STRUCT_DEF: &str = "struct ";
const TRAIT_DEF: &str = "trait ";
const IMPL: &str = "impl ";
const PUB: &str = "pub ";
const WHILE: &str = "while ";
const EXTERN: &str = "extern ";
const ATTRIBUTE: &str = "#";
let buffer = self.buffer.to_string();
let buffer = buffer.trim();
if buffer.is_empty() {
Ok(PrintQueue::default())
} else if buffer.ends_with(';')
|| buffer.starts_with(FUNCTION_DEF)
|| buffer.starts_with(ASYNC_FUNCTION_DEF)
|| buffer.starts_with(ENUM_DEF)
|| buffer.starts_with(STRUCT_DEF)
|| buffer.starts_with(TRAIT_DEF)
|| buffer.starts_with(IMPL)
|| buffer.starts_with(ATTRIBUTE)
|| buffer.starts_with(PUB)
|| buffer.starts_with(WHILE)
|| buffer.starts_with(EXTERN)
{
let mut print_queue = PrintQueue::default();
let mut insert_flag = true;
if self.options.check_statements {
if let Some(mut e) = format_check_output(
self.repl
.check(self.buffer.to_string(), self.options.toolchain)?,
) {
print_queue.append(&mut e);
insert_flag = false;
}
}
if insert_flag {
self.repl.insert(self.buffer.to_string());
}
Ok(print_queue)
} else {
let mut outputs = PrintQueue::default();
let (status, out) = self
.repl
.eval(self.buffer.to_string(), self.options.toolchain)?;
if let Some(mut eval_output) = format_eval_output(status, out) {
outputs.append(&mut eval_output);
}
Ok(outputs)
}
}
pub fn sync(&mut self) -> Result<PrintQueue, IRustError> {
match self.repl.update_from_extern_main_file() {
Ok(_) => success!(),
Err(e) => {
self.repl.reset(self.options.toolchain)?;
Err(e)
}
}
}
fn extern_edit(&mut self) -> Result<PrintQueue, IRustError> {
let editor: String = match self.buffer.to_string().split_whitespace().nth(1) {
Some(ed) => ed.to_string(),
None => return Err(IRustError::Custom("No editor specified".to_string())),
};
self.printer.writer.raw.write_with_color(
format!("waiting for {}...", editor),
crossterm::style::Color::Magenta,
)?;
self.repl.write_to_extern()?;
cargo_fmt_file(&*MAIN_FILE_EXTERN);
#[cfg(windows)]
std::process::Command::new("cmd")
.arg("/C")
.arg(editor)
.arg(&*MAIN_FILE_EXTERN)
.spawn()?
.wait()?;
#[cfg(not(windows))]
std::process::Command::new(editor)
.arg(&*MAIN_FILE_EXTERN)
.spawn()?
.wait()?;
self.sync()
}
fn irust(&mut self) -> Result<PrintQueue, IRustError> {
print_queue!(self.ferris(), Color::Red)
}
fn cd(&mut self) -> Result<PrintQueue, IRustError> {
use std::env::*;
let buffer = self.buffer.to_string();
let buffer = buffer
.split(":cd")
.skip(1)
.collect::<String>()
.trim()
.to_string();
match buffer.as_str() {
"" => {
if let Some(dir) = dirs_next::home_dir() {
set_current_dir(dir)?;
}
}
"-" => {
set_current_dir(self.known_paths.get_pwd())?;
}
path => {
let mut dir = current_dir()?;
dir.push(&path);
set_current_dir(dir)?;
}
}
let cwd = current_dir()?;
self.known_paths.update_cwd(cwd.clone());
self.printer
.writer
.raw
.set_title(&format!("IRust: {}", cwd.display()))?;
print_queue!(cwd.display().to_string(), self.options.ok_color)
}
fn time(&mut self) -> Result<PrintQueue, IRustError> {
self.inner_time(":time", false)
}
fn time_release(&mut self) -> Result<PrintQueue, IRustError> {
self.inner_time(":time_release", true)
}
fn inner_time(&mut self, pattern: &str, release: bool) -> Result<PrintQueue, IRustError> {
let buffer = self.buffer.to_string();
let fnn = buffer
.splitn(2, pattern)
.nth(1)
.ok_or("No function specified")?;
if fnn.is_empty() {
return Err("No function specified".into());
}
let time = format!(
"\
use std::time::Instant;
let now = Instant::now();
{};
println!(\"{{:?}}\", now.elapsed());
",
fnn,
);
let toolchain = self.options.toolchain;
let mut raw_out = String::new();
let mut status = None;
self.repl
.eval_in_tmp_repl(time, || -> Result<(), IRustError> {
let (s, out) = cargo_run(true, release, toolchain)?;
raw_out = out;
status = Some(s);
Ok(())
})?;
Ok(format_eval_output(status.unwrap(), raw_out).ok_or("failed to bench function")?)
}
fn bench(&mut self) -> Result<PrintQueue, IRustError> {
self.repl.write()?;
let out = cargo_bench(self.options.toolchain)?.trim().to_owned();
print_queue!(out, self.options.eval_color)
}
fn asm(&mut self) -> Result<PrintQueue, IRustError> {
let buffer = self.buffer.to_string();
let fnn = buffer.strip_prefix(":asm").expect("already checked").trim();
if fnn.is_empty() {
return Err("No function specified".into());
}
self.repl.write_lib()?;
let asm = cargo_asm(fnn, self.options.toolchain)?;
print_queue!(asm, self.options.eval_color)
}
}