use std::array::IntoIter;
use std::env::temp_dir;
use std::io::Write;
use std::fs::OpenOptions;
use std::collections::HashMap;
use std::iter::FromIterator;
use std::process::Command;
use crate::error::RadError;
use crate::arg_parser::{ArgParser, GreedyState};
use crate::consts::{MAIN_CALLER, TEMP_FILE};
use regex::Regex;
use crate::utils::Utils;
use crate::processor::Processor;
use crate::formatter::Formatter;
use lipsum::lipsum;
use lazy_static::lazy_static;
type MacroType = fn(&str, bool ,&mut Processor) -> Result<String, RadError>;
lazy_static!{
pub static ref ITER: Regex = Regex::new(r"\$:").unwrap();
}
#[derive(Clone)]
pub struct BasicMacro {
macros : HashMap<String, MacroType>,
}
impl BasicMacro {
pub fn new() -> Self {
let map = HashMap::from_iter(IntoIter::new([
("regex".to_owned(), BasicMacro::regex_sub as MacroType),
("eval".to_owned(), BasicMacro::eval as MacroType),
("trim".to_owned(), BasicMacro::trim as MacroType),
("chomp".to_owned(), BasicMacro::chomp as MacroType).to_owned(),
("comp".to_owned(), BasicMacro::compress as MacroType).to_owned(),
("lipsum".to_owned(), BasicMacro::placeholder as MacroType).to_owned(),
("time".to_owned(), BasicMacro::time as MacroType).to_owned(),
("date".to_owned(), BasicMacro::date as MacroType).to_owned(),
("include".to_owned(), BasicMacro::include as MacroType).to_owned(),
("repeat".to_owned(), BasicMacro::repeat as MacroType).to_owned(),
("syscmd".to_owned(), BasicMacro::syscmd as MacroType).to_owned(),
("ifelse".to_owned(), BasicMacro::ifelse as MacroType).to_owned(),
("ifdef".to_owned(), BasicMacro::ifdef as MacroType).to_owned(),
("foreach".to_owned(), BasicMacro::foreach as MacroType).to_owned(),
("forloop".to_owned(), BasicMacro::forloop as MacroType).to_owned(),
("undef".to_owned(), BasicMacro::undefine_call as MacroType).to_owned(),
("rename".to_owned(), BasicMacro::rename_call as MacroType).to_owned(),
("append".to_owned(), BasicMacro::append as MacroType).to_owned(),
("from".to_owned(), BasicMacro::from_data as MacroType).to_owned(),
("table".to_owned(), BasicMacro::table as MacroType).to_owned(),
("len".to_owned(), BasicMacro::len as MacroType).to_owned(),
("tr".to_owned(), BasicMacro::translate as MacroType).to_owned(),
("sub".to_owned(), BasicMacro::substring as MacroType).to_owned(),
("pause".to_owned(), BasicMacro::pause as MacroType).to_owned(),
("tempout".to_owned(), BasicMacro::temp as MacroType).to_owned(),
("tempin".to_owned(), BasicMacro::temp_include as MacroType).to_owned(),
("pipe".to_owned(), BasicMacro::pipe as MacroType).to_owned(),
("-".to_owned(), BasicMacro::get_pipe as MacroType).to_owned(),
("*".to_owned(), BasicMacro::get_pipe_literal as MacroType).to_owned(),
]));
Self { macros : map }
}
pub fn contains(&self, name: &str) -> bool {
self.macros.contains_key(name)
}
pub fn call(&self, name : &str, args: &str,greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(func) = self.macros.get(name) {
let result = func(args, greedy, processor)?;
Ok(result)
} else {
Ok(String::new())
}
}
pub fn undefine(&mut self, name: &str) {
self.macros.remove(name);
}
pub fn rename(&mut self, name: &str, target: &str) {
let func = self.macros.remove(name).unwrap();
self.macros.insert(target.to_owned(), func);
}
fn time(_: &str, _: bool, _ : &mut Processor) -> Result<String, RadError> {
Ok(format!("{}", chrono::offset::Local::now().format("%H:%M:%S")))
}
fn date(_: &str, _: bool, _ : &mut Processor) -> Result<String, RadError> {
Ok(format!("{}", chrono::offset::Local::now().format("%Y-%m-%d")))
}
fn regex_sub(args: &str, greedy: bool, _: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 3, greedy) {
let source= &args[0];
let match_expr= &args[1];
let substitution= &args[2];
let reg = Regex::new(&format!(r"{}", match_expr))?;
let result = reg.replace_all(source, substitution); Ok(result.to_string())
} else {
Err(RadError::InvalidArgument("Regex sub requires three arguments"))
}
}
fn eval(args: &str, greedy: bool,_: &mut Processor ) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
let formula = &args[0];
let result = evalexpr::eval(formula)?;
Ok(result.to_string())
} else {
Err(RadError::InvalidArgument("Eval requires an argument"))
}
}
fn trim(args: &str, greedy: bool, _: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
Utils::trim(&args[0])
} else {
Err(RadError::InvalidArgument("Trim requires an argument"))
}
}
fn chomp(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
let source = &args[0];
let reg = Regex::new(&format!(r"{0}\s*{0}", &processor.newline))?;
let result = reg.replace_all(source, &format!("{0}{0}", &processor.newline));
Ok(result.to_string())
} else {
Err(RadError::InvalidArgument("Chomp requires an argument"))
}
}
fn compress(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
let source = &args[0];
let result = Utils::trim(&BasicMacro::chomp(source,greedy, processor)?)?;
Ok(result.to_string())
} else {
Err(RadError::InvalidArgument("Compress requires an argument"))
}
}
fn placeholder(args: &str, greedy: bool,_: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
let word_count = &args[0];
if let Ok(count) = Utils::trim(word_count)?.parse::<usize>() {
Ok(lipsum(count))
} else {
Err(RadError::InvalidArgument("Lipsum needs a number bigger or equal to 0 (unsigned integer)"))
}
} else {
Err(RadError::InvalidArgument("Placeholder requires an argument"))
}
}
fn include(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
let file_path = std::path::Path::new(&args[0]);
Ok(processor.from_file(file_path, true)?)
} else {
Err(RadError::InvalidArgument("Include requires an argument"))
}
}
fn repeat(args: &str, greedy: bool,_: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let repeat_count;
if let Ok(count) = Utils::trim(&args[0])?.parse::<usize>() {
repeat_count = count;
} else {
return Err(RadError::InvalidArgument("Repeat needs a number bigger or equal to 0 (unsigned integer)"));
}
let repeat_object = &args[1];
let mut repeated = String::new();
for _ in 0..repeat_count {
repeated.push_str(&repeat_object);
}
Ok(repeated)
} else {
Err(RadError::InvalidArgument("Repeat requires two arguments"))
}
}
fn syscmd(args: &str, greedy: bool,_: &mut Processor) -> Result<String, RadError> {
if let Some(args_content) = ArgParser::args_with_len(args, 1, greedy) {
let source = &args_content[0];
let arg_vec = ArgParser::args_to_vec(&source, ' ', GreedyState::None);
let output = if cfg!(target_os = "windows") {
Command::new("cmd")
.arg("/C")
.args(arg_vec)
.output()
.expect("failed to execute process")
.stdout
} else {
let sys_args = if arg_vec.len() > 1 { &arg_vec[1..] } else { &[] };
Command::new(&arg_vec[0])
.args(sys_args)
.output()
.expect("failed to execute process")
.stdout
};
Ok(String::from_utf8(output)?)
} else {
Err(RadError::InvalidArgument("Syscmd requires an argument"))
}
}
fn ifelse(args: &str, greedy: bool, _: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let boolean = &args[0];
let if_state = &args[1];
let trimmed_cond = Utils::trim(boolean)?;
if let Ok(cond) = trimmed_cond.parse::<bool>() {
if cond { return Ok(if_state.to_owned()); }
} else if let Ok(number) = trimmed_cond.parse::<i32>() {
if number != 0 { return Ok(if_state.to_owned()); }
} else {
return Err(RadError::InvalidArgument("Ifelse requires either true/false or zero/nonzero integer."))
}
if args.len() >= 3 {
let else_state = &args[2];
return Ok(else_state.to_owned());
}
Ok(String::new())
} else {
Err(RadError::InvalidArgument("ifelse requires an argument"))
}
}
fn ifdef(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
let name = &args[0];
let map = processor.get_map();
if map.basic.contains(name) || map.custom.contains_key(name) {
Ok("true".to_owned())
} else {
Ok("false".to_owned())
}
} else {
Err(RadError::InvalidArgument("Ifdef requires an argument"))
}
}
fn undefine_call(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
let name = &args[0];
processor.map.undefine(name);
Ok("".to_owned())
} else {
Err(RadError::InvalidArgument("Undefine requires an argument"))
}
}
fn foreach(args: &str, greedy: bool, _: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let mut sums = String::new();
let target = &args[1]; let loopable = &args[0];
for value in loopable.split(',') {
sums.push_str(&ITER.replace_all(target, value));
}
Ok(sums)
} else {
Err(RadError::InvalidArgument("Foreach requires two argument"))
}
}
fn forloop(args: &str, greedy: bool, _: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 3, greedy) {
let mut sums = String::new();
let expression = &args[2];
let min: usize;
let max: usize;
if let Ok(num) = Utils::trim(&args[0])?.parse::<usize>() {
min = num;
} else { return Err(RadError::InvalidArgument("Forloop's min value should be non zero positive integer")); }
if let Ok(num) = Utils::trim(&args[1])?.parse::<usize>() {
max = num
} else { return Err(RadError::InvalidArgument("Forloop's min value should be non zero positive integer")); }
for value in min..=max {
sums.push_str(&ITER.replace_all(expression, &value.to_string()));
}
Ok(sums)
} else {
Err(RadError::InvalidArgument("Forloop requires two argument"))
}
}
fn from_data(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let macro_data = &args[0];
let macro_name = &Utils::trim(&args[1])?;
let result = Formatter::csv_to_macros(macro_name, macro_data, &processor.newline)?;
let result = processor.parse_chunk(0, &MAIN_CALLER.to_owned(), &result)?;
Ok(result)
} else {
Err(RadError::InvalidArgument("From requires two arguments"))
}
}
fn table(args: &str, greedy: bool, p: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let table_format = &args[0]; let csv_content = &args[1];
let result = Formatter::csv_to_table(table_format, csv_content, &p.newline)?;
Ok(result)
} else {
Err(RadError::InvalidArgument("Table requires two arguments"))
}
}
fn pipe(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
processor.pipe_value = args[0].to_owned();
}
Ok(String::new())
}
fn get_pipe(_: &str, _: bool, processor: &mut Processor) -> Result<String, RadError> {
let out = processor.pipe_value.clone();
processor.pipe_value.clear();
Ok(out)
}
fn get_pipe_literal(_: &str, _: bool, processor: &mut Processor) -> Result<String, RadError> {
let out = format!("\\*{}*\\",processor.pipe_value);
processor.pipe_value.clear();
Ok(out)
}
fn len(args: &str, _: bool, _: &mut Processor) -> Result<String, RadError> {
Ok(args.chars().count().to_string())
}
fn rename_call(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let target = &args[0];
let new = &args[1];
processor.map.rename(target, new);
Ok(String::new())
} else {
Err(RadError::InvalidArgument("Rename requires two arguments"))
}
}
fn append(args: &str, greedy: bool, processor: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let name = &args[0];
let target = &args[1];
processor.map.append(name, target);
Ok(String::new())
} else {
Err(RadError::InvalidArgument("Append requires two arguments"))
}
}
fn translate(args: &str, greedy: bool, _: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 3, greedy) {
let mut source = args[0].clone();
let target = &args[1].chars().collect::<Vec<char>>();
let destination = &args[2].chars().collect::<Vec<char>>();
if target.len() != destination.len() {
return Err(RadError::InvalidArgument("Tr's replacment should have same length of texts"));
}
for i in 0..target.len() {
source = source.replace(target[i], &destination[i].to_string());
}
Ok(source)
} else {
Err(RadError::InvalidArgument("Tr requires two arguments"))
}
}
fn substring(args: &str, greedy: bool, _: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let source = &args[2];
let mut min: Option<usize> = None;
let mut max: Option<usize> = None;
let start = Utils::trim(&args[0])?;
let end = Utils::trim(&args[1])?;
if let Ok(num) = start.parse::<usize>() {
min.replace(num);
} else {
if start.len() != 0 {
return Err(RadError::InvalidArgument("Sub's min value should be non zero positive integer or empty value"));
}
}
if let Ok(num) = end.parse::<usize>() {
max.replace(num);
} else {
if end.len() != 0 {
return Err(RadError::InvalidArgument("Sub's max value should be non zero positive integer or empty value"));
}
}
Ok(Utils::utf8_substring(source, min, max))
} else {
Err(RadError::InvalidArgument("Sub requires some arguments"))
}
}
fn pause(args: &str, greedy: bool, processor : &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 1, greedy) {
let arg = &args[0];
if let Ok(value) =Utils::is_arg_true(arg) {
if value {
processor.paused = true;
} else {
processor.paused = false;
}
Ok(String::new())
}
else {
Err(RadError::InvalidArgument("Pause requires either true/false or zero/nonzero integer."))
}
} else {
Err(RadError::InvalidArgument("Pause requires an argument"))
}
}
fn temp(args: &str, greedy: bool, _: &mut Processor) -> Result<String, RadError> {
if let Some(args) = ArgParser::args_with_len(args, 2, greedy) {
let truncate = &args[0];
let content = &args[1];
if let Ok(value) = Utils::is_arg_true(truncate) {
let file = temp_dir().join(TEMP_FILE);
let mut temp_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(value)
.open(file)
.unwrap();
temp_file.write_all(content.as_bytes())?;
Ok(String::new())
} else {
Err(RadError::InvalidArgument("Temp requires either true/false or zero/nonzero integer."))
}
} else {
Err(RadError::InvalidArgument("Temp requires an argument"))
}
}
fn temp_include(_: &str, _: bool, processor: &mut Processor) -> Result<String, RadError> {
let file_path = temp_dir().join(TEMP_FILE);
Ok(processor.from_file(&file_path, true)?)
}
}