use std::array::IntoIter;
use std::io::Write;
use std::fs::OpenOptions;
use std::collections::HashMap;
use std::iter::FromIterator;
use std::path::{PathBuf,Path};
use std::process::Command;
use crate::error::RadError;
use crate::arg_parser::{ArgParser, GreedyState};
use regex::Regex;
use crate::utils::Utils;
use crate::processor::Processor;
use crate::auth::AuthType;
#[cfg(feature = "csv")]
use crate::formatter::Formatter;
#[cfg(feature = "lipsum")]
use lipsum::lipsum;
pub type MacroType = fn(&str, bool ,&mut Processor) -> Result<Option<String>, RadError>;
#[derive(Clone)]
pub struct BasicMacro {
macros : HashMap<String, MacroType>,
}
impl BasicMacro {
pub fn empty() -> Self {
Self {
macros: HashMap::new(),
}
}
pub fn new() -> Self {
#[allow(unused_mut)]
let mut map = HashMap::from_iter(IntoIter::new([
("-".to_owned(), BasicMacro::get_pipe as MacroType),
("append".to_owned(), BasicMacro::append as MacroType),
("bind".to_owned(), BasicMacro::bind_to_local as MacroType),
("chomp".to_owned(), BasicMacro::chomp as MacroType),
("comp".to_owned(), BasicMacro::compress as MacroType),
("env".to_owned(), BasicMacro::get_env as MacroType),
("fileout".to_owned(), BasicMacro::file_out as MacroType),
("global".to_owned(), BasicMacro::global as MacroType),
("include".to_owned(), BasicMacro::include as MacroType),
("len".to_owned(), BasicMacro::len as MacroType),
("name".to_owned(), BasicMacro::get_name as MacroType),
("not".to_owned(), BasicMacro::not as MacroType),
("nl".to_owned(), BasicMacro::newline as MacroType),
("parent".to_owned(), BasicMacro::get_parent as MacroType),
("path".to_owned(), BasicMacro::merge_path as MacroType),
("paths".to_owned(), BasicMacro::merge_path_vec as MacroType),
("pipe".to_owned(), BasicMacro::pipe as MacroType),
("read".to_owned(), BasicMacro::read as MacroType),
("redir".to_owned(), BasicMacro::temp_redirect as MacroType),
("regex".to_owned(), BasicMacro::regex_sub as MacroType),
("rename".to_owned(), BasicMacro::rename_call as MacroType),
("repeat".to_owned(), BasicMacro::repeat as MacroType),
("repl".to_owned(), BasicMacro::replace as MacroType),
("sub".to_owned(), BasicMacro::substring as MacroType),
("syscmd".to_owned(), BasicMacro::syscmd as MacroType),
("tempin".to_owned(), BasicMacro::temp_include as MacroType),
("tempout".to_owned(), BasicMacro::temp_out as MacroType),
("tempto".to_owned(), BasicMacro::set_temp_target as MacroType),
("tr".to_owned(), BasicMacro::translate as MacroType),
("trim".to_owned(), BasicMacro::trim as MacroType),
("undef".to_owned(), BasicMacro::undefine_call as MacroType),
]));
#[cfg(feature = "csv")]
{
map.insert("from".to_owned(), BasicMacro::from_data as MacroType);
map.insert("table".to_owned(), BasicMacro::table as MacroType);
}
#[cfg(feature = "chrono")]
{
map.insert("time".to_owned(), BasicMacro::time as MacroType);
map.insert("date".to_owned(), BasicMacro::date as MacroType);
}
#[cfg(feature = "lipsum")]
map.insert("lipsum".to_owned(), BasicMacro::placeholder as MacroType);
#[cfg(feature = "evalexpr")]
map.insert("eval".to_owned(), BasicMacro::eval as MacroType);
Self { macros : map }
}
pub fn add_new_rule(&mut self, name: &str, macro_ref: MacroType) {
self.macros.insert(name.to_owned(), macro_ref);
}
pub fn contains(&self, name: &str) -> bool {
self.macros.contains_key(name)
}
pub fn get(&self, name: &str) -> Option<&MacroType> {
self.macros.get(name)
}
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);
}
#[cfg(feature = "chrono")]
fn time(_: &str, _: bool, _ : &mut Processor) -> Result<Option<String>, RadError> {
Ok(Some(format!("{}", chrono::offset::Local::now().format("%H:%M:%S"))))
}
#[cfg(feature = "chrono")]
fn date(_: &str, _: bool, _ : &mut Processor) -> Result<Option<String>, RadError> {
Ok(Some(format!("{}", chrono::offset::Local::now().format("%Y-%m-%d"))))
}
fn regex_sub(args: &str, greedy: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().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(Some(result.to_string()))
} else {
Err(RadError::InvalidArgument("Regex sub requires three arguments".to_owned()))
}
}
#[cfg(feature = "evalexpr")]
fn eval(args: &str, greedy: bool,_: &mut Processor ) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
let formula = &args[0];
let result = evalexpr::eval(formula)?;
Ok(Some(result.to_string()))
} else {
Err(RadError::InvalidArgument("Eval requires an argument".to_owned()))
}
}
fn not(args: &str, greedy: bool,_: &mut Processor ) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
let args = &args[0];
if let Ok(value) = Utils::is_arg_true(args) {
Ok(Some((!value).to_string()))
} else {
Err(RadError::InvalidArgument(format!("Not requires either true/false or zero/nonzero integer but given \"{}\"", args)))
}
} else {
Err(RadError::InvalidArgument("Not requires an argument".to_owned()))
}
}
fn trim(args: &str, greedy: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
Ok(Some(Utils::trim(&args[0])))
} else {
Err(RadError::InvalidArgument("Trim requires an argument".to_owned()))
}
}
fn chomp(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().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(Some(result.to_string()))
} else {
Err(RadError::InvalidArgument("Chomp requires an argument".to_owned()))
}
}
fn compress(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
let source = &args[0];
let result = Utils::trim(&BasicMacro::chomp(source,greedy, processor)?.unwrap());
Ok(Some(result))
} else {
Err(RadError::InvalidArgument("Compress requires an argument".to_owned()))
}
}
#[cfg(feature = "lipsum")]
fn placeholder(args: &str, greedy: bool,_: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
let word_count = &args[0];
if let Ok(count) = Utils::trim(word_count).parse::<usize>() {
Ok(Some(lipsum(count)))
} else {
Err(RadError::InvalidArgument(format!("Lipsum needs a number bigger or equal to 0 (unsigned integer) but given \"{}\"", word_count)))
}
} else {
Err(RadError::InvalidArgument("Placeholder requires an argument".to_owned()))
}
}
fn include(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("include", AuthType::FIN,processor)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
let raw = Utils::trim(&args[0]);
let mut file_path = PathBuf::from(&raw);
if processor.current_input != "stdin" && file_path.is_relative() {
file_path = PathBuf::from(&processor.current_input).parent().unwrap().join(file_path);
}
if file_path.is_file() {
processor.set_sandbox();
let chunk = processor.from_file_as_chunk(file_path)?;
Ok(chunk)
} else {
let formatted = format!("File path : \"{}\" doesn't exist or not a file", file_path.display());
Err(RadError::InvalidArgument(formatted))
}
} else {
Err(RadError::InvalidArgument("Include requires an argument".to_owned()))
}
}
fn read(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("read", AuthType::FIN,processor)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
let raw = Utils::trim(&args[0]);
let mut file_path = PathBuf::from(&raw);
if processor.current_input != "stdin" && file_path.is_relative() {
file_path = PathBuf::from(&processor.current_input).parent().unwrap().join(file_path);
}
if file_path.is_file() {
processor.set_sandbox();
processor.from_file(file_path)?;
Ok(None)
} else {
let formatted = format!("File path : \"{}\" doesn't exist or not a file", file_path.display());
Err(RadError::InvalidArgument(formatted))
}
} else {
Err(RadError::InvalidArgument("Include requires an argument".to_owned()))
}
}
fn repeat(args: &str, greedy: bool,_: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().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(format!("Repeat needs a number bigger or equal to 0 (unsigned integer) but given \"{}\"", &args[0])));
}
let repeat_object = &args[1];
let mut repeated = String::new();
for _ in 0..repeat_count {
repeated.push_str(&repeat_object);
}
Ok(Some(repeated))
} else {
Err(RadError::InvalidArgument("Repeat requires two arguments".to_owned()))
}
}
fn syscmd(args: &str, _: bool,p: &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("syscmd", AuthType::CMD,p)? {
return Ok(None);
}
if let Some(args_content) = ArgParser::new().args_with_len(args, 1, true) {
let source = &args_content[0];
let arg_vec = source.split(' ').collect::<Vec<&str>>();
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(Some(String::from_utf8(output)?))
} else {
Err(RadError::InvalidArgument("Syscmd requires an argument".to_owned()))
}
}
fn undefine_call(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
let name = Utils::trim(&args[0]);
let map = processor.get_map();
if map.contains(&name) {
map.undefine(&name);
} else {
processor.log_error(&format!("Macro \"{}\" doesn't exist, therefore cannot undefine", name))?;
}
Ok(None)
} else {
Err(RadError::InvalidArgument("Undefine requires an argument".to_owned()))
}
}
#[cfg(feature = "csv")]
fn from_data(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 2, greedy) {
let macro_name = Utils::trim(&args[0]);
let macro_data = &args[1];
let result = Formatter::csv_to_macros(¯o_name, macro_data, &processor.newline)?;
let result = processor.parse_chunk_args(0, "", &result)?;
Ok(Some(result))
} else {
Err(RadError::InvalidArgument("From requires two arguments".to_owned()))
}
}
#[cfg(feature = "csv")]
fn table(args: &str, greedy: bool, p: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().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(Some(result))
} else {
Err(RadError::InvalidArgument("Table requires two arguments".to_owned()))
}
}
fn pipe(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
processor.pipe_value = args[0].to_owned();
}
Ok(None)
}
fn global(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 2, greedy) {
let name = &args[0];
let value = &args[1];
if processor.get_map().contains(name) {
processor.log_warning(&format!("Creating a global with a name already existing : \"{}\"", name))?;
}
processor.add_custom_rules(vec![(name,"",value)]);
}
Ok(None)
}
fn bind_to_local(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 2, greedy) {
let name = &args[0];
let value = &args[1];
processor.get_map().new_local(1, name, value);
if processor.get_map().contains(name) {
processor.log_warning(&format!("Creating a binding with a name already existing : \"{}\"", name))?;
}
}
Ok(None)
}
fn get_env(args: &str, _: bool, p : &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("env", AuthType::ENV,p)? {
return Ok(None);
}
if let Ok(out) = std::env::var(args) {
Ok(Some(out))
} else {
if p.strict {
p.log_warning(&format!("Env : \"{}\" is not defined.", args))?;
}
Ok(None)
}
}
fn merge_path(args: &str, greedy: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 2, greedy) {
let target = Utils::trim(&args[0]);
let added = Utils::trim(&args[1]);
let out = format!("{}",&std::path::Path::new(&target).join(&added).display());
Ok(Some(out))
} else {
Err(RadError::InvalidArgument("Path macro needs two arguments".to_owned()))
}
}
fn merge_path_vec(args: &str, _: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
let vec = ArgParser::new().args_to_vec(args, ',', GreedyState::Never);
let out = vec
.iter()
.map(|s| Utils::trim(s))
.collect::<PathBuf>();
if let Some(value) = out.to_str() {
Ok(Some(value.to_owned()))
} else {
Err(RadError::InvalidArgument(format!("Invalid path : {}", out.display())))
}
}
fn newline(_: &str, _: bool, p: &mut Processor) -> Result<Option<String>, RadError> {
Ok(Some(p.newline.to_owned()))
}
fn get_name(args: &str, _: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, false) {
let path = Path::new(&args[0]);
if let Some(name) = path.file_name() {
if let Some(value) = name.to_str() {
return Ok(Some(value.to_owned()));
}
}
Err(RadError::InvalidArgument(format!("Invalid path : {}", path.display())))
} else {
Err(RadError::InvalidArgument("name requires an argument".to_owned()))
}
}
fn get_parent(args: &str, _: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 1, false) {
let path = Path::new(&args[0]);
if let Some(name) = path.parent() {
if let Some(value) = name.to_str() {
return Ok(Some(value.to_owned()));
}
}
Err(RadError::InvalidArgument(format!("Invalid path : {}", path.display())))
} else {
Err(RadError::InvalidArgument("parent requires an argument".to_owned()))
}
}
fn get_pipe(_: &str, _: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
let out = processor.pipe_value.clone();
Ok(Some(out))
}
fn len(args: &str, _: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
Ok(Some(args.chars().count().to_string()))
}
fn rename_call(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 2, greedy) {
let target = &args[0];
let new = &args[1];
let map = processor.get_map();
if map.contains(target) {
processor.get_map().rename(target, new);
} else {
processor.log_error(&format!("Macro \"{}\" doesn't exist, therefore cannot rename", target))?;
}
Ok(None)
} else {
Err(RadError::InvalidArgument("Rename requires two arguments".to_owned()))
}
}
fn append(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 2, greedy) {
let name = &args[0];
let target = &args[1];
let map = processor.get_map();
if map.custom.contains_key(name) {
map.append(name, target);
} else {
processor.log_error(&format!("Macro \"{}\" doesn't exist", name))?;
}
Ok(None)
} else {
Err(RadError::InvalidArgument("Append requires two arguments".to_owned()))
}
}
fn translate(args: &str, greedy: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().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(format!("Tr's replacment should have same length of texts while given \"{:?}\" and \"{:?}\"", target, destination)));
}
for i in 0..target.len() {
source = source.replace(target[i], &destination[i].to_string());
}
Ok(Some(source))
} else {
Err(RadError::InvalidArgument("Tr requires three arguments".to_owned()))
}
}
fn substring(args: &str, greedy: bool, _: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 3, 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(format!("Sub's min value should be non zero positive integer or empty value but given \"{}\"", start)));
}
}
if let Ok(num) = end.parse::<usize>() {
max.replace(num);
} else {
if end.len() != 0 {
return Err(RadError::InvalidArgument(format!("Sub's max value should be non zero positive integer or empty value but given \"{}\"", end)));
}
}
Ok(Some(Utils::utf8_substring(source, min, max)))
} else {
Err(RadError::InvalidArgument("Sub requires three arguments".to_owned()))
}
}
fn temp_out(args: &str, greedy: bool, p: &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("tempout", AuthType::FOUT,p)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
let content = &args[0];
p.get_temp_file().write_all(content.as_bytes())?;
Ok(None)
} else {
Err(RadError::InvalidArgument("Tempout requires an argument".to_owned()))
}
}
fn file_out(args: &str, greedy: bool, p: &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("fileout", AuthType::FOUT,p)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 3, greedy) {
let truncate = &args[0];
let file_name = &args[1];
let content = &args[2];
if let Ok(truncate) = Utils::is_arg_true(truncate) {
let file = std::env::current_dir()?.join(file_name);
let mut target_file;
if truncate {
target_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(file)
.unwrap();
} else {
if !file.is_file() {
return Err(RadError::InvalidArgument(format!("Failed to read \"{}\". Fileout without truncate option needs exsiting file",file.display())));
}
target_file = OpenOptions::new()
.append(true)
.open(file)
.unwrap();
}
target_file.write_all(content.as_bytes())?;
Ok(None)
} else {
Err(RadError::InvalidArgument(format!("Fileout requires either true/false or zero/nonzero integer but given \"{}\"", truncate)))
}
} else {
Err(RadError::InvalidArgument("Fileout requires three argument".to_owned()))
}
}
fn temp_include(_: &str, _: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("tempin", AuthType::FIN,processor)? {
return Ok(None);
}
let file = processor.get_temp_path().to_owned();
processor.set_sandbox();
let chunk = processor.from_file_as_chunk(&file)?;
Ok(chunk)
}
fn temp_redirect(args: &str, _: bool, p: &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("redir", AuthType::FOUT,p)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 1, false) {
let toggle = if let Ok(toggle) =Utils::is_arg_true(&args[0]) {
toggle
} else {
return Err(RadError::InvalidArgument(format!("Redir's agument should be valid boolean value but given \"{}\"", &args[0])));
};
p.redirect = toggle;
Ok(None)
} else {
Err(RadError::InvalidArgument("Redir requires an argument".to_owned()))
}
}
fn set_temp_target(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if !Utils::is_granted("tempto", AuthType::FOUT,processor)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 1, greedy) {
processor.set_temp_file(&PathBuf::from(std::env::temp_dir()).join(&args[0]));
Ok(None)
} else {
Err(RadError::InvalidArgument("Temp requires an argument".to_owned()))
}
}
fn replace(args: &str, greedy: bool, processor: &mut Processor) -> Result<Option<String>, RadError> {
if let Some(args) = ArgParser::new().args_with_len(args, 2, greedy) {
let name = args[0].as_str();
let target = args[1].as_str();
if !processor.get_map().replace(name, target) {
return Err(RadError::InvalidArgument(format!("{} doesn't exist, thus cannot replace it's content", name)))
}
Ok(None)
} else {
Err(RadError::InvalidArgument("Replace requires two arguments".to_owned()))
}
}
}