use crate::arg_parser::{ArgParser, GreedyState};
use crate::auth::AuthType;
use crate::consts::{ESR, LOREM_WIDTH, LOREM, LOREM_SOURCE};
use crate::error::RadError;
use crate::formatter::Formatter;
#[cfg(feature = "hook")]
use crate::hookmap::HookType;
use crate::logger::WarningType;
use crate::models::MacroType;
use crate::models::{
Behaviour, ExtMacroBody, ExtMacroBuilder, FlowControl, ProcessInput, RadResult, RelayTarget,
};
use crate::processor::Processor;
use crate::utils::Utils;
#[cfg(feature = "cindex")]
use cindex::OutOption;
use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashMap;
use std::fs::{OpenOptions, canonicalize};
use std::io::Write;
use std::iter::FromIterator;
use std::path::{Path, PathBuf};
use std::process::Command;
lazy_static! {
static ref CLRF_MATCH: Regex = Regex::new(r#"\r\n"#).unwrap();
static ref CHOMP_MATCH: Regex = Regex::new(r#"\n\s*\n"#).expect("Failed to crate chomp regex");
static ref NUM_MATCH: Regex = Regex::new(r#"[+-]?([\d]*[.])?\d+"#).expect("Failed to crate number regex");
}
pub(crate) type FunctionMacroType = fn(&str, &mut Processor) -> RadResult<Option<String>>;
#[derive(Clone)]
pub(crate) struct FunctionMacroMap {
pub(crate) macros: HashMap<String, FMacroSign>,
}
impl FunctionMacroMap {
pub fn empty() -> Self {
Self {
macros: HashMap::new(),
}
}
pub fn new() -> Self {
#[allow(unused_mut)]
let mut map = HashMap::from_iter(IntoIterator::into_iter([
(
"-".to_owned(),
FMacroSign::new("-", ESR, Self::get_pipe, Some("Get piped value".to_string())),
),
(
"append".to_owned(),
FMacroSign::new("append", ["a_macro_name", "a_content"], Self::append, Some("Append content to a macro".to_string())),
),
(
"arr".to_owned(),
FMacroSign::new("arr", ["a_values"], Self::array, Some("Convert spaced array into comma array".to_string())),
),
(
"assert".to_owned(),
FMacroSign::new("assert", ["a_lvalue", "a_rvalue"], Self::assert, Some("Comopare two statements".to_string())),
),
(
"ceil".to_owned(),
FMacroSign::new("ceil", ["a_number"], Self::get_ceiling, Some("Get ceiling of the number".to_string())),
),
(
"chomp".to_owned(),
FMacroSign::new("chomp", ["a_content"], Self::chomp, Some("Remove duplicate newlines from content".to_string())),
),
(
"clear".to_owned(),
FMacroSign::new("clear", ["a_content"], Self::clear, Some("Clear volatile macros".to_string())),
),
(
"comp".to_owned(),
FMacroSign::new("comp", ["a_content"], Self::compress, Some("Apply trim and chomp to content".to_string())),
),
(
"count".to_owned(),
FMacroSign::new("count", ["a_array"], Self::count, Some("Get count of array".to_string())),
),
(
"countw".to_owned(),
FMacroSign::new("countw", ["a_array"], Self::count_word, Some("Get count of words".to_string())),
),
(
"countl".to_owned(),
FMacroSign::new("countl", ["a_content"], Self::count_lines, Some("Get count of lines".to_string())),
),
(
"dnl".to_owned(),
FMacroSign::new("dnl", ESR, Self::deny_newline, Some("Deny next newline.".to_string())),
),
(
"declare".to_owned(),
FMacroSign::new("declare", ["a_macro_names"], Self::declare, Some("Declare multiple variables separated by comma".to_string())),
),
(
"docu".to_owned(),
FMacroSign::new("docu", ["a_macro_name", "a_content"], Self::document, Some("Append documents to a macro".to_string())),
),
(
"enl".to_owned(),
FMacroSign::new("enl", ESR, Self::escape_newline, None),
),
(
"escape".to_owned(),
FMacroSign::new("escape", ESR, Self::escape, None),
),
(
"exit".to_owned(),
FMacroSign::new("exit", ESR, Self::exit, None),
),
(
"floor".to_owned(),
FMacroSign::new("floor", ["a_number"], Self::get_floor, None),
),
(
"fold".to_owned(),
FMacroSign::new("fold", ["a_content"], Self::fold, None),
),
(
"foldl".to_owned(),
FMacroSign::new("foldl", ["a_content"], Self::fold_line, None),
),
(
"grep".to_owned(),
FMacroSign::new("grep", ["a_regex", "a_content"], Self::grep, None),
),
(
"halt".to_owned(),
FMacroSign::new("halt", ESR, Self::halt_relay, None),
),
(
"head".to_owned(),
FMacroSign::new("head", ["a_count", "a_content"], Self::head, None),
),
(
"headl".to_owned(),
FMacroSign::new("headl", ["a_count", "a_content"], Self::head_line, None),
),
(
"hygiene".to_owned(),
FMacroSign::new("hygiene", ["a_hygiene?"], Self::toggle_hygiene, None),
),
(
"index".to_owned(),
FMacroSign::new("index", ["a_index", "a_array"], Self::index_array, None),
),
(
"len".to_owned(),
FMacroSign::new("len", ["a_string"], Self::len, None),
),
(
"let".to_owned(),
FMacroSign::new(
"let",
["a_macro_name", "a_value"],
Self::bind_to_local,
None,
),
),
(
"lipsum".to_owned(),
FMacroSign::new("lipsum", ["a_word_count"], Self::lipsum_words, None),
),
(
"lower".to_owned(),
FMacroSign::new("lower", ["a_text"], Self::lower, None),
),
(
"max".to_owned(),
FMacroSign::new("max", ["a_array"], Self::get_max, None),
),
(
"min".to_owned(),
FMacroSign::new("min", ["a_array"], Self::get_min, None),
),
(
"name".to_owned(),
FMacroSign::new("name", ["a_path"], Self::get_name, None),
),
(
"nassert".to_owned(),
FMacroSign::new("nassert", ["a_lvalue", "a_rvalue"], Self::assert_ne, None),
),
(
"not".to_owned(),
FMacroSign::new("not", ["a_boolean"], Self::not, None),
),
(
"num".to_owned(),
FMacroSign::new("num", ["a_text"], Self::get_number, None),
),
(
"nl".to_owned(),
FMacroSign::new("nl", ESR, Self::newline, None),
),
(
"panic".to_owned(),
FMacroSign::new("panic", ["a_msg"], Self::manual_panic, None),
),
(
"parent".to_owned(),
FMacroSign::new("parent", ["a_path"], Self::get_parent, None),
),
(
"path".to_owned(),
FMacroSign::new("path", ["a_paths"], Self::merge_path, None),
),
(
"pause".to_owned(),
FMacroSign::new("pause", ["a_pause?"], Self::pause, None),
),
(
"pipe".to_owned(),
FMacroSign::new("pipe", ["a_value"], Self::pipe, None),
),
(
"pipeto".to_owned(),
FMacroSign::new("pipe", ["a_pipe_name", "a_value"], Self::pipe_to, None),
),
(
"prec".to_owned(),
FMacroSign::new("prec", ["a_value", "a_precision"], Self::prec, None),
),
(
"relay".to_owned(),
FMacroSign::new("relay", ["a_type", "a_target+"], Self::relay, None),
),
(
"rev".to_owned(),
FMacroSign::new("rev", ["a_array?"], Self::reverse_array, None),
),
(
"regex".to_owned(),
FMacroSign::new(
"regex",
["a_source", "a_match", "a_substitution"],
Self::regex_sub,
None,
),
),
(
"rename".to_owned(),
FMacroSign::new(
"rename",
["a_macro_name", "a_new_name"],
Self::rename_call,
None,
),
),
(
"repeat".to_owned(),
FMacroSign::new("repeat", ["a_count", "a_source"], Self::repeat, None),
),
(
"repl".to_owned(),
FMacroSign::new("repl", ["a_macro_name", "a_new_value"], Self::replace, None),
),
(
"sep".to_owned(),
FMacroSign::new("sep", ["separator", "a_array"], Self::separate_array, None),
),
(
"sort".to_owned(),
FMacroSign::new("sort", ["a_values"], Self::sort_array, None),
),
(
"sortl".to_owned(),
FMacroSign::new("sortl", ["a_values"], Self::sort_lines, None),
),
(
"static".to_owned(),
FMacroSign::new(
"static",
["a_macro_name", "a_value"],
Self::define_static,
None,
),
),
(
"strip".to_owned(),
FMacroSign::new(
"tail",
["a_count", "a_direction", "a_content"],
Self::strip,
None,
),
),
(
"stripl".to_owned(),
FMacroSign::new(
"taill",
["a_count", "a_direction", "a_content"],
Self::strip_line,
None,
),
),
(
"sub".to_owned(),
FMacroSign::new(
"sub",
["a_start_index", "a_end_index", "a_source"],
Self::substring,
None,
),
),
(
"tail".to_owned(),
FMacroSign::new("tail", ["a_count", "a_content"], Self::tail, None),
),
(
"taill".to_owned(),
FMacroSign::new("taill", ["a_count", "a_content"], Self::tail_line, None),
),
(
"table".to_owned(),
FMacroSign::new("table", ["a_table_form", "a_csv_value"], Self::table, None),
),
(
"tr".to_owned(),
FMacroSign::new(
"tr",
["a_source", "a_matches", "a_substitutions"],
Self::translate,
None,
),
),
(
"trim".to_owned(),
FMacroSign::new("trim", ["a_content"], Self::trim, None),
),
(
"triml".to_owned(),
FMacroSign::new("triml", ["a_content"], Self::triml, None),
),
(
"undef".to_owned(),
FMacroSign::new("undef", ["a_macro_name"], Self::undefine_call, None),
),
(
"upper".to_owned(),
FMacroSign::new("upper", ["a_text"], Self::capitalize, None),
),
(
"define".to_owned(),
FMacroSign::new("define", ESR, Self::define_type, None),
),
]));
#[cfg(not(feature = "wasm"))]
{
map.insert(
"env".to_owned(),
FMacroSign::new("env", ["a_env_name"], Self::get_env, None),
);
map.insert(
"envset".to_owned(),
FMacroSign::new("envset", ["a_env_name", "a_env_value"], Self::set_env, None),
);
map.insert(
"abs".to_owned(),
FMacroSign::new("abs", ["a_path"], Self::absolute_path, None),
);
map.insert(
"syscmd".to_owned(),
FMacroSign::new("syscmd", ["a_command"], Self::syscmd, None),
);
map.insert(
"read".to_owned(),
FMacroSign::new("read", ["a_filename"], Self::read_from_file, None),
);
map.insert(
"tempin".to_owned(),
FMacroSign::new("tempin", ["a_tempin"], Self::temp_include, None),
);
map.insert(
"tempout".to_owned(),
FMacroSign::new("tempout", ["a_tempout"], Self::temp_out, None),
);
map.insert(
"tempto".to_owned(),
FMacroSign::new("tempto", ["a_filename"], Self::set_temp_target, None),
);
map.insert(
"include".to_owned(),
FMacroSign::new("include", ["a_filename"], Self::include, None),
);
map.insert(
"fileout".to_owned(),
FMacroSign::new(
"fileout",
["a_truncate?", "a_filename", "a_content"],
Self::file_out,
None,
),
);
}
#[cfg(feature = "cindex")]
{
map.insert(
"regcsv".to_owned(),
FMacroSign::new(
"regcsv",
["a_table_name", "a_table"],
Self::cindex_register,
None,
),
);
map.insert(
"dropcsv".to_owned(),
FMacroSign::new("dropcsv", ["a_table_name"], Self::cindex_drop, None),
);
map.insert(
"query".to_owned(),
FMacroSign::new("query", ["a_query"], Self::cindex_query, None),
);
map.insert(
"queries".to_owned(),
FMacroSign::new("queries", ["a_query"], Self::cindex_query_list, None),
);
}
#[cfg(feature = "chrono")]
{
map.insert(
"time".to_owned(),
FMacroSign::new("time", ESR, Self::time, None),
);
map.insert(
"date".to_owned(),
FMacroSign::new("date", ESR, Self::date, None),
);
map.insert(
"tarray".to_owned(),
FMacroSign::new("tarray", ["a_second"], Self::tarray, None),
);
map.insert(
"hms".to_owned(),
FMacroSign::new("hms", ["a_second"], Self::hms, None),
);
}
#[cfg(feature = "evalexpr")]
{
map.insert(
"eval".to_owned(),
FMacroSign::new("eval", ["a_expression"], Self::eval, None),
);
map.insert(
"evalk".to_owned(),
FMacroSign::new("evalk", ["a_expression"], Self::eval_keep, None),
);
}
#[cfg(feature = "textwrap")]
map.insert(
"wrap".to_owned(),
FMacroSign::new("wrap", ["a_width", "a_content"], Self::wrap, None),
);
#[cfg(feature = "hook")]
{
map.insert(
"hookon".to_owned(),
FMacroSign::new(
"hookon",
["a_macro_type", "a_target_name"],
Self::hook_enable,
None,
),
);
map.insert(
"hookoff".to_owned(),
FMacroSign::new(
"hookoff",
["a_macro_type", "a_target_name"],
Self::hook_disable,
None,
),
);
}
{
map.insert(
"update".to_owned(),
FMacroSign::new("update", ["a_text"], Self::update_storage, None),
);
map.insert(
"extract".to_owned(),
FMacroSign::new("extract", ESR, Self::extract_storage, None),
);
}
Self { macros: map }
}
pub(crate) fn new_ext_macro(&mut self, ext: ExtMacroBuilder) {
if let Some(ExtMacroBody::Function(mac_ref)) = ext.macro_body {
let sign = FMacroSign::new(&ext.macro_name, &ext.args, mac_ref, ext.macro_desc);
self.macros.insert(ext.macro_name, sign);
}
}
pub fn contains(&self, name: &str) -> bool {
self.macros.contains_key(name)
}
pub fn get_func(&self, name: &str) -> Option<&FunctionMacroType> {
if let Some(sig) = self.macros.get(name) {
Some(&sig.logic)
} else {
None
}
}
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, _: &mut Processor) -> RadResult<Option<String>> {
Ok(Some(format!(
"{}",
chrono::offset::Local::now().format("%H:%M:%S")
)))
}
#[cfg(feature = "chrono")]
fn tarray(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let seconds = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"Could not convert given value \"{}\" into a number",
args[0]
))
})?;
let hour = seconds / 3600;
let minute = seconds % 3600 / 60;
let second = seconds % 3600 % 60;
let mut arr = second.to_string();
if minute != 0 {
arr.push_str(",");
arr.push_str(&minute.to_string());
}
if hour != 0 {
arr.push_str(",");
arr.push_str(&hour.to_string());
}
Ok(Some(arr))
} else {
Err(RadError::InvalidArgument(
"tarray requires an argument".to_owned(),
))
}
}
#[cfg(feature = "chrono")]
fn hms(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let seconds = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"Could not convert given value \"{}\" into a number",
args[0]
))
})?;
let hour = seconds / 3600;
let minute = seconds % 3600 / 60;
let second = seconds % 3600 % 60;
let time = format!("{:02}:{:02}:{:02}", hour, minute, second);
Ok(Some(time))
} else {
Err(RadError::InvalidArgument(
"hms sub requires an argument".to_owned(),
))
}
}
#[cfg(feature = "chrono")]
fn date(_: &str, _: &mut Processor) -> RadResult<Option<String>> {
Ok(Some(format!(
"{}",
chrono::offset::Local::now().format("%Y-%m-%d")
)))
}
fn regex_sub(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
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, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let formula = &args[0];
let result = evalexpr::eval(formula)?;
Ok(Some(result.to_string()))
} else {
Err(RadError::InvalidArgument(
"Eval requires an argument".to_owned(),
))
}
}
#[cfg(feature = "evalexpr")]
fn eval_keep(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let formula = Utils::trim(&args[0]);
let result = format!("{} = {}", formula, evalexpr::eval(&formula)?);
Ok(Some(result))
} else {
Err(RadError::InvalidArgument(
"Eval requires an argument".to_owned(),
))
}
}
fn not(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
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, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
Ok(Some(Utils::trim(&args[0])))
} else {
Err(RadError::InvalidArgument(
"Trim requires an argument".to_owned(),
))
}
}
fn triml(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let mut lines = String::new();
let mut iter = args[0].lines().peekable();
while let Some(line) = iter.next() {
lines.push_str(&Utils::trim(line));
if let Some(_) = iter.peek() {
lines.push_str(&p.state.newline);
}
}
Ok(Some(lines))
} else {
Err(RadError::InvalidArgument(
"Trim requires an argument".to_owned(),
))
}
}
fn chomp(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let source = &args[0];
let lf_converted = &*CLRF_MATCH.replace_all(source, "\n");
let chomp_result = &*CHOMP_MATCH
.replace_all(lf_converted, format!("{0}{0}", &processor.state.newline));
Ok(Some(chomp_result.to_string()))
} else {
Err(RadError::InvalidArgument(
"Chomp requires an argument".to_owned(),
))
}
}
fn compress(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let source = &args[0];
let result = Utils::trim(&FunctionMacroMap::chomp(source, processor)?.unwrap());
Ok(Some(result))
} else {
Err(RadError::InvalidArgument(
"Compress requires an argument".to_owned(),
))
}
}
fn lipsum_words(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let word_count = &args[0];
if let Ok(count) = Utils::trim(word_count).parse::<usize>() {
if count <= *LOREM_WIDTH {
Ok(Some(LOREM[0..count].join(" ")))
} else {
let mut lorem = String::new();
let loop_amount = count / *LOREM_WIDTH;
let remnant = count % *LOREM_WIDTH;
for _ in 0..loop_amount {
lorem.push_str(LOREM_SOURCE);
}
lorem.push_str(&LOREM[0..remnant].join(" "));
Ok(Some(lorem))
}
} else {
Err(RadError::InvalidArgument(format!("Lipsum needs a number bigger or equal to 0 (unsigned integer) but given \"{}\"", word_count)))
}
} else {
Err(RadError::InvalidArgument(
"Lipsum requires an argument".to_owned(),
))
}
}
fn include(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("include", AuthType::FIN, processor)? {
return Ok(None);
}
let args = ArgParser::new().args_to_vec(args, ',', GreedyState::Never);
if args.len() >= 1 {
let raw = Utils::trim(&args[0]);
let mut file_path = PathBuf::from(&raw);
if let ProcessInput::File(path) = &processor.state.current_input {
if file_path.is_relative() {
file_path = path.parent().unwrap().join(file_path);
}
}
if file_path.is_file() {
let canonic = file_path.canonicalize()?;
Self::check_include_sanity(processor, &file_path, &canonic)?;
processor.set_sandbox(true);
if args.len() >= 2 {
let raw_include = Utils::is_arg_true(&args[1])?;
if raw_include {
processor.state.paused = true;
}
}
let chunk
= processor.from_file_as_chunk(&file_path)?;
processor.state.paused = false; processor.set_sandbox(false);
processor.state.input_stack.remove(&canonic); 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 check_include_sanity(processor: &Processor,file_path: &Path, canonic: &Path) -> RadResult<()> {
if let ProcessInput::File(path) = &processor.state.current_input {
if path.canonicalize()? == canonic {
return Err(RadError::InvalidArgument(format!(
"You cannot include self while including a file : \"{}\"",
&path.display()
)));
}
}
if processor.state.input_stack.contains(canonic) {
return Err(RadError::InvalidArgument(format!(
"You cannot include self while including a file : \"{}\"",
&file_path.display()
)));
}
if let Some(RelayTarget::File(target)) = &processor.state.relay.last() {
if target.path.canonicalize()? == file_path.canonicalize()? {
return Err(RadError::InvalidArgument(format!(
"You cannot include relay target while relaying to the file : \"{}\"",
&target.path.display()
)));
}
}
Ok(())
}
fn read_from_file(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("read", AuthType::FIN, processor)? {
return Ok(None);
}
if processor.on_cache() {
return Err(RadError::UnallowedMacroExecution(
"Nested read is not allowed".to_owned(),
));
}
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let arg = &Utils::trim(&args[0]);
let file_path = Path::new(arg);
let canonic = canonicalize(file_path)?;
Self::check_include_sanity(processor, file_path, &canonic)?;
processor.enable_cache(true)?;
processor.set_sandbox(true);
processor.from_file(file_path)?;
processor.flush_cache()?;
processor.set_sandbox(false);
Ok(None)
} else {
Err(RadError::InvalidArgument(
"Read requires an argument".to_owned(),
))
}
}
fn repeat(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
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, p: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("syscmd", AuthType::CMD, p)? {
return Ok(None);
}
if let Some(args_content) = ArgParser::new().args_with_len(args, 1) {
let source = &args_content[0];
let arg_vec = source.split_whitespace().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, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let name = Utils::trim(&args[0]);
if processor.contains_macro(&name, MacroType::Any) {
processor.undefine_macro(&name, MacroType::Any);
} 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(),
))
}
}
fn define_type(_: &str, _: &mut Processor) -> RadResult<Option<String>> {
Ok(None)
}
fn array(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
let parsed = ArgParser::new().args_to_vec(args, ',', GreedyState::Never);
if parsed.len() == 0 {
Err(RadError::InvalidArgument(
"Array requires an argument".to_owned(),
))
} else {
let separater = if parsed.len() >= 2 {
&parsed[1] } else {
" "
}; let mut vec = parsed[0].split(separater).collect::<Vec<&str>>();
if parsed.len() == 3 {
let reg = Regex::new(&parsed[2])?;
vec = vec.into_iter().filter(|&item| reg.is_match(item)).collect();
}
let joined = vec.join(",");
Ok(Some(joined))
}
}
fn assert(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
if args[0] == args[1] {
p.track_assertion(true)?;
Ok(None)
} else {
p.track_assertion(false)?;
Err(RadError::AssertFail)
}
} else {
Err(RadError::InvalidArgument(
"Assert requires two arguments".to_owned(),
))
}
}
fn assert_ne(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
if args[0] != args[1] {
p.track_assertion(true)?;
Ok(None)
} else {
p.track_assertion(false)?;
Err(RadError::AssertFail)
}
} else {
Err(RadError::InvalidArgument(
"Assert_ne requires two arguments".to_owned(),
))
}
}
fn table(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let table_format = &args[0]; let csv_content = &args[1];
let result = Formatter::csv_to_table(table_format, csv_content, &p.state.newline)?;
Ok(Some(result))
} else {
Err(RadError::InvalidArgument(
"Table requires two arguments".to_owned(),
))
}
}
fn pipe(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
processor.state.add_pipe(None, args[0].to_owned());
}
Ok(None)
}
fn pipe_to(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
processor.state.add_pipe(Some(&args[0]), args[1].to_owned());
} else {
return Err(RadError::InvalidArgument(
"pipeto requires two arguments".to_owned(),
));
}
Ok(None)
}
fn get_env(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
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.state.behaviour == Behaviour::Strict {
p.log_warning(
&format!("Env : \"{}\" is not defined.", args),
WarningType::Sanity,
)?;
}
Ok(None)
}
}
fn set_env(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("envset", AuthType::ENV, p)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let name = &args[0];
let value = &args[1];
if p.state.behaviour == Behaviour::Strict && std::env::var(name).is_ok() {
return Err(RadError::InvalidArgument(format!(
"You cannot override environment variable in strict mode. Failed to set \"{}\"",
name
)));
}
std::env::set_var(name, value);
Ok(None)
} else {
Err(RadError::InvalidArgument(
"Envset requires two arguments".to_owned(),
))
}
}
fn manual_panic(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
Err(RadError::ManualPanic(args.to_string()))
}
fn escape(_: &str, processor: &mut Processor) -> RadResult<Option<String>> {
processor.state.flow_control = FlowControl::Escape;
Ok(None)
}
fn exit(_: &str, processor: &mut Processor) -> RadResult<Option<String>> {
processor.state.flow_control = FlowControl::Exit;
Ok(None)
}
fn merge_path(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
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, p: &mut Processor) -> RadResult<Option<String>> {
Ok(Some(p.state.newline.to_owned()))
}
fn deny_newline(_: &str, p: &mut Processor) -> RadResult<Option<String>> {
p.state.deny_newline = true;
Ok(None)
}
fn escape_newline(_: &str, p: &mut Processor) -> RadResult<Option<String>> {
p.state.escape_newline = true;
Ok(None)
}
fn get_name(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
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 absolute_path(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("abs", AuthType::FIN, p)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let path = p.get_current_dir()?.join(&args[0]);
let canonic = std::fs::canonicalize(path)?.to_str().unwrap().to_owned();
Ok(Some(canonic))
} else {
Err(RadError::InvalidArgument(
"Abs requires an argument".to_owned(),
))
}
}
fn get_parent(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
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(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
let pipe = if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let name = Utils::trim(&args[0]);
if name.is_empty() {
let out = processor
.state
.get_pipe("-")
.unwrap_or(String::new())
.clone();
Some(out)
} else {
if let Some(pipe) = processor.state.get_pipe(&args[0]) {
Some(pipe.clone())
} else {
None
}
}
} else {
let out = processor
.state
.get_pipe("-")
.unwrap_or(String::new())
.clone();
Some(out)
};
Ok(pipe)
}
fn len(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
Ok(Some(args.chars().count().to_string()))
}
fn rename_call(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let target = &args[0];
let new = &args[1];
if processor.contains_macro(target, MacroType::Any) {
processor.rename_macro(target, new, MacroType::Any);
} 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, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let name = &args[0];
let target = &args[1];
if processor.contains_macro(name, MacroType::Runtime) {
processor.append_macro(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, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
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, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
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(),
))
}
}
#[cfg(not(feature = "wasm"))]
fn temp_out(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("tempout", AuthType::FOUT, p)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let content = &args[0];
if let Some(file) = p.get_temp_file() {
file.write_all(content.as_bytes())?;
} else {
return Err(RadError::InvalidExecution(
"You cannot use temp related macros in environment where fin/fout is not supported".to_owned(),
));
}
Ok(None)
} else {
Err(RadError::InvalidArgument(
"Tempout requires an argument".to_owned(),
))
}
}
fn file_out(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("fileout", AuthType::FOUT, p)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
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 head(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let count = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"Head requires positive integer number but got \"{}\"",
&args[0]
))
})?;
let content = &args[1].chars().collect::<Vec<_>>();
let length = *count.min(&content.len());
Ok(Some(content[0..length].iter().collect()))
} else {
Err(RadError::InvalidArgument(
"head requires two argument".to_owned(),
))
}
}
fn head_line(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let count = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"Headl requires positive integer number but got \"{}\"",
&args[0]
))
})?;
let lines = Utils::full_lines(args[1].as_bytes())
.map(|line| line.unwrap())
.collect::<Vec<String>>();
let length = *count.min(&lines.len());
Ok(Some(lines[0..length].concat()))
} else {
Err(RadError::InvalidArgument(
"headl requires two argument".to_owned(),
))
}
}
fn tail(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let count = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"tail requires positive integer number but got \"{}\"",
&args[0]
))
})?;
let content = &args[1].chars().collect::<Vec<_>>();
let length = *count.min(&content.len());
Ok(Some(
content[content.len() - length..content.len()]
.iter()
.collect(),
))
} else {
Err(RadError::InvalidArgument(
"tail requires two argument".to_owned(),
))
}
}
fn tail_line(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let count = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"taill requires positive integer number but got \"{}\"",
&args[0]
))
})?;
let lines = Utils::full_lines(args[1].as_bytes())
.map(|line| line.unwrap())
.collect::<Vec<String>>();
let length = *count.min(&lines.len());
Ok(Some(lines[lines.len() - length..lines.len()].concat()))
} else {
Err(RadError::InvalidArgument(
"taill requires two argument".to_owned(),
))
}
}
fn strip(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
let count = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"strip requires positive integer number but got \"{}\"",
&args[0]
))
})?;
let variant = &args[1];
let content = &args[2].chars().collect::<Vec<_>>();
let length = *count.min(&content.len());
match variant.to_lowercase().as_str() {
"head" => Ok(Some(content[length..].iter().collect())),
"tail" => Ok(Some(content[..content.len() - length].iter().collect())),
_ => {
return Err(RadError::InvalidArgument(format!(
"Strip reqruies either head or tail but given \"{}\"",
variant
)))
}
}
} else {
Err(RadError::InvalidArgument(
"strip requires three argument".to_owned(),
))
}
}
fn strip_line(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
let count = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"stripl requires positive integer number but got \"{}\"",
&args[0]
))
})?;
let variant = &args[1];
let lines = Utils::full_lines(args[2].as_bytes())
.map(|line| line.unwrap())
.collect::<Vec<String>>();
let length = *count.min(&lines.len());
match variant.to_lowercase().as_str() {
"head" => Ok(Some(lines[length..].concat())),
"tail" => Ok(Some(lines[..lines.len() - length].concat())),
_ => {
return Err(RadError::InvalidArgument(format!(
"Stripl reqruies either head or tail but given \"{}\"",
variant
)))
}
}
} else {
Err(RadError::InvalidArgument(
"stripl requires two argument".to_owned(),
))
}
}
fn sort_array(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let count = args[0].as_str();
let content = &mut args[1].split(',').collect::<Vec<&str>>();
match count.to_lowercase().as_str() {
"asec" => content.sort(),
"desc" => {
content.sort();
content.reverse()
}
_ => {
return Err(RadError::InvalidArgument(format!(
"Sort requires either asec or desc but given \"{}\"",
count
)))
}
}
Ok(Some(content.join(",")))
} else {
Err(RadError::InvalidArgument(
"sort requires two argument".to_owned(),
))
}
}
fn sort_lines(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let count = args[0].as_str();
let content = &mut args[1].lines().collect::<Vec<&str>>();
match count.to_lowercase().as_str() {
"asec" => content.sort(),
"desc" => {
content.sort();
content.reverse()
}
_ => {
return Err(RadError::InvalidArgument(format!(
"Sortl requires either asec or desc but given \"{}\"",
count
)))
}
}
Ok(Some(content.join(&p.state.newline)))
} else {
Err(RadError::InvalidArgument(
"sortl requires two argument".to_owned(),
))
}
}
fn index_array(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let index = &args[0].parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"index requires positive integer number but got \"{}\"",
&args[0]
))
})?;
let content = &mut args[1].split(',').collect::<Vec<&str>>();
if &content.len() <= index {
return Err(RadError::InvalidArgument(format!(
"Index \"{}\" is bigger than content's length \"{}\"",
index,
content.len()
)));
}
Ok(Some(content[*index].to_owned()))
} else {
Err(RadError::InvalidArgument(
"index requires two argument".to_owned(),
))
}
}
fn fold(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let content = &mut args[0].split(',').collect::<Vec<&str>>();
Ok(Some(content.join("")))
} else {
Err(RadError::InvalidArgument(
"fold requires an argument".to_owned(),
))
}
}
fn fold_line(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let content = &mut args[0].lines().collect::<Vec<&str>>();
Ok(Some(content.join("")))
} else {
Err(RadError::InvalidArgument(
"foldl requires an argument".to_owned(),
))
}
}
fn grep(args: &str, p: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let expr = Regex::new(args[0].as_str())?;
let content = args[1].lines().collect::<Vec<&str>>();
let grepped = content
.into_iter()
.filter(|l| expr.is_match(l))
.collect::<Vec<&str>>()
.join(&p.state.newline);
Ok(Some(grepped))
} else {
Err(RadError::InvalidArgument(
"grep requires two argument".to_owned(),
))
}
}
fn count(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let array_count = &args[0].split(',').collect::<Vec<_>>().len();
Ok(Some(array_count.to_string()))
} else {
Err(RadError::InvalidArgument(
"count requires an argument".to_owned(),
))
}
}
fn count_word(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let array_count = &args[0].split_whitespace().collect::<Vec<_>>().len();
Ok(Some(array_count.to_string()))
} else {
Err(RadError::InvalidArgument(
"countw requires an argument".to_owned(),
))
}
}
fn count_lines(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let line_count = &args[0].lines().collect::<Vec<_>>().len();
Ok(Some(line_count.to_string()))
} else {
Err(RadError::InvalidArgument(
"countl requires an argument".to_owned(),
))
}
}
#[cfg(not(feature = "wasm"))]
fn temp_include(_: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("tempin", AuthType::FIN, processor)? {
return Ok(None);
}
let file = processor.get_temp_path().display();
let chunk = Self::include(&file.to_string(), processor)?;
Ok(chunk)
}
fn relay(args_src: &str, p: &mut Processor) -> RadResult<Option<String>> {
let args: Vec<&str> = args_src.split(',').collect();
if args.len() == 0 {
return Err(RadError::InvalidArgument(
"relay at least requires an argument".to_owned(),
));
}
p.log_warning(
&format!("Relaying text content to \"{}\"", args_src),
WarningType::Security,
)?;
let raw_type = args[0];
let relay_type = match raw_type {
#[cfg(not(feature = "wasm"))]
"temp" => {
if !Utils::is_granted("relay", AuthType::FOUT, p)? {
return Ok(None);
}
RelayTarget::Temp
}
#[cfg(not(feature = "wasm"))]
"file" => {
use crate::models::FileTarget;
if !Utils::is_granted("relay", AuthType::FOUT, p)? {
return Ok(None);
}
if args.len() == 1 {
return Err(RadError::InvalidArgument(
"relay requires second argument as file name for file relaying".to_owned(),
));
}
let mut file_target = FileTarget::empty();
file_target.set_path(Path::new(args[1]));
RelayTarget::File(file_target)
}
"macro" => {
if args.len() == 1 {
return Err(RadError::InvalidArgument(
"relay requires second argument as macro name for macro relaying"
.to_owned(),
));
}
if !p.contains_macro(args[1], MacroType::Runtime) {
return Err(RadError::InvalidMacroName(format!(
"Cannot relay to non-exsitent macro or non-runtime macro \"{}\"",
args[1]
)));
}
RelayTarget::Macro(args[1].to_owned())
}
_ => {
return Err(RadError::InvalidArgument(format!(
"Given type \"{}\" is not a valid relay target",
args[0]
)))
}
};
p.state.relay.push(relay_type);
Ok(None)
}
fn halt_relay(_: &str, p: &mut Processor) -> RadResult<Option<String>> {
p.state.relay.pop();
Ok(None)
}
#[cfg(not(feature = "wasm"))]
fn set_temp_target(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("tempto", AuthType::FOUT, processor)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
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 get_number(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let src = Utils::trim(&args[0]);
let captured = NUM_MATCH
.captures(&src)
.ok_or(RadError::InvalidArgument(format!(
"No digits to extract from \"{}\"",
src
)))?;
if let Some(num) = captured.get(0) {
Ok(Some(num.as_str().to_owned()))
} else {
Err(RadError::InvalidArgument(format!(
"No digits to extract from \"{}\"",
src
)))
}
} else {
Err(RadError::InvalidArgument(
"num requires an argument".to_owned(),
))
}
}
fn capitalize(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let src = Utils::trim(&args[0]);
Ok(Some(src.to_uppercase()))
} else {
Err(RadError::InvalidArgument(
"cap requires an argument".to_owned(),
))
}
}
fn lower(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let src = Utils::trim(&args[0]);
Ok(Some(src.to_lowercase()))
} else {
Err(RadError::InvalidArgument(
"cap requires an argument".to_owned(),
))
}
}
fn get_max(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let content = Utils::trim(&args[0]);
if content.is_empty() {
return Err(RadError::InvalidArgument(
"max requires an array to process but given empty value".to_owned(),
));
}
let max = content.split(',').max().unwrap();
Ok(Some(max.to_string()))
} else {
Err(RadError::InvalidArgument(
"cap requires an argument".to_owned(),
))
}
}
fn get_min(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let content = Utils::trim(&args[0]);
if content.is_empty() {
return Err(RadError::InvalidArgument(
"min requires an array to process but given empty value".to_owned(),
));
}
let max = content.split(',').min().unwrap();
Ok(Some(max.to_string()))
} else {
Err(RadError::InvalidArgument(
"cap requires an argument".to_owned(),
))
}
}
fn get_ceiling(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let number = Utils::trim(&args[0]).parse::<f64>().map_err(|_| {
RadError::InvalidArgument(format!(
"Could not convert given value \"{}\" into a floating point number",
args[0]
))
})?;
Ok(Some(number.ceil().to_string()))
} else {
Err(RadError::InvalidArgument(
"ceil requires an argument".to_owned(),
))
}
}
fn get_floor(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let number = Utils::trim(&args[0]).parse::<f64>().map_err(|_| {
RadError::InvalidArgument(format!(
"Could not convert given value \"{}\" into a floating point number",
args[0]
))
})?;
Ok(Some(number.floor().to_string()))
} else {
Err(RadError::InvalidArgument(
"floor requires an argument".to_owned(),
))
}
}
fn prec(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let number = Utils::trim(&args[0]).parse::<f64>().map_err(|_| {
RadError::InvalidArgument(format!(
"Could not convert given value \"{}\" into a floating point number",
args[0]
))
})?;
let precision = Utils::trim(&args[1]).parse::<usize>().map_err(|_| {
RadError::InvalidArgument(format!(
"Could not convert given value \"{}\" into a precision",
args[1]
))
})?;
let decimal_precision = 10.0f64.powi(precision as i32);
let converted = f64::trunc(number * decimal_precision) / decimal_precision;
let formatted = format!("{:.1$}", converted, precision);
Ok(Some(formatted))
} else {
Err(RadError::InvalidArgument(
"ceil requires an argument".to_owned(),
))
}
}
fn reverse_array(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if args.is_empty() {
Err(RadError::InvalidArgument(
"rev requires an argument".to_owned(),
))
} else {
let reversed = args.split(',').rev().collect::<Vec<&str>>().join(",");
Ok(Some(reversed))
}
}
fn declare(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
let names = ArgParser::new().args_to_vec(args, ',', GreedyState::Never);
let runtime_rules = names
.iter()
.map(|name| (Utils::trim(name), "", ""))
.collect::<Vec<(String, &str, &str)>>();
for (name, _, _) in runtime_rules.iter() {
if processor.contains_macro(&name, MacroType::Any) {
if processor.state.behaviour == Behaviour::Strict {
return Err(RadError::InvalidMacroName(format!(
"Declaring a macro with a name already existing : \"{}\"",
name
)));
} else {
processor.log_warning(
&format!(
"Declaring a macro with a name already existing : \"{}\"",
name
),
WarningType::Sanity,
)?;
}
}
}
processor.add_runtime_rules(runtime_rules)?;
Ok(None)
}
fn document(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let macro_name = &args[0];
let content = &args[1];
if !processor.set_documentation(macro_name, content)
&& processor.state.behaviour == Behaviour::Strict
{
processor.log_error(&format!("No such macro \"{}\" to document", macro_name))?;
}
Ok(None)
} else {
Err(RadError::InvalidArgument(
"Docu requires two argument".to_owned(),
))
}
}
fn bind_to_local(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let name = Utils::trim(&args[0]);
let value = Utils::trim(&args[1]);
processor.add_new_local_macro(1, &name, &value);
Ok(None)
} else {
Err(RadError::InvalidArgument(
"Let requires two argument".to_owned(),
))
}
}
fn clear(_: &str, processor: &mut Processor) -> RadResult<Option<String>> {
processor.clear_volatile();
Ok(None)
}
fn toggle_hygiene(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
if let Ok(value) = Utils::is_arg_true(&args[0]) {
processor.toggle_hygiene(value);
Ok(None)
}
else {
Err(RadError::InvalidArgument(format!(
"hygiene requires either true/false or zero/nonzero integer, but given \"{}\"",
args[0]
)))
}
} else {
Err(RadError::InvalidArgument(
"hygiene requires an argument".to_owned(),
))
}
}
fn pause(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
if let Ok(value) = Utils::is_arg_true(&args[0]) {
processor.state.paused = value;
Ok(None)
}
else {
Err(RadError::InvalidArgument(format!(
"Pause requires either true/false or zero/nonzero integer, but given \"{}\"",
args[0]
)))
}
} else {
Err(RadError::InvalidArgument(
"Pause requires an argument".to_owned(),
))
}
}
fn define_static(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let name = &args[0];
let value = &args[1];
if processor.contains_macro(&name, MacroType::Any) {
if processor.state.behaviour == Behaviour::Strict {
return Err(RadError::InvalidMacroName(format!(
"Creating a static macro with a name already existing : \"{}\"",
name
)));
} else {
processor.log_warning(
&format!(
"Creating a static macro with a name already existing : \"{}\"",
name
),
WarningType::Sanity,
)?;
}
}
processor.add_static_rules(vec![(&name, &value)])?;
Ok(None)
} else {
Err(RadError::InvalidArgument(
"Static requires two argument".to_owned(),
))
}
}
fn separate_array(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let separator = &args[0];
let array = &args[1];
let mut array = array.split(',').into_iter();
let mut splited = String::new();
if let Some(first) = array.next() {
splited.push_str(first);
for item in array {
splited.push_str(&format!("{}{}", separator, item));
}
}
Ok(Some(splited))
} else {
Err(RadError::InvalidArgument(
"sep requires two argument".to_owned(),
))
}
}
fn replace(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let name = &args[0];
let target = &args[1];
if !processor.replace_macro(&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(),
))
}
}
#[cfg(feature = "hook")]
fn hook_enable(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let hook_type = HookType::from_str(&args[0])?;
let index = &args[1];
processor.hook_map.switch_hook(hook_type, index, true)?;
Ok(None)
} else {
Err(RadError::InvalidArgument(
"hookon requires two arguments".to_owned(),
))
}
}
#[cfg(feature = "hook")]
fn hook_disable(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let hook_type = HookType::from_str(&args[0])?;
let index = &args[1];
processor.hook_map.switch_hook(hook_type, index, false)?;
Ok(None)
} else {
Err(RadError::InvalidArgument(
"hookoff requires two arguments".to_owned(),
))
}
}
#[cfg(feature = "textwrap")]
fn wrap(args: &str, _: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let width = Utils::trim(&args[0]).parse::<usize>()?;
let content = &args[1];
let result = textwrap::fill(content, width);
Ok(Some(result))
} else {
Err(RadError::InvalidArgument(
"Wrap requires two arguments".to_owned(),
))
}
}
fn update_storage(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
let args = ArgParser::new().args_to_vec(args, ',', GreedyState::Never);
if let Some(storage) = processor.storage.as_mut() {
if let Err(err) = storage.update(&args) {
return Err(RadError::StorageError(format!("Update error : {}", err)));
}
} else {
processor.log_warning("Empty storage, update didn't trigger", WarningType::Sanity)?;
}
Ok(None)
}
fn extract_storage(_: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(storage) = processor.storage.as_mut() {
match storage.extract(false) {
Err(err) => Err(RadError::StorageError(format!("Update error : {}", err))),
Ok(value) => {
if let Some(output) = value {
Ok(Some(output.into_printable()))
} else {
Ok(None)
}
}
}
} else {
Err(RadError::StorageError(String::from("Empty storage")))
}
}
#[cfg(feature = "cindex")]
fn cindex_register(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
if processor.indexer.contains_table(&args[0]) {
return Err(RadError::InvalidArgument(format!(
"Cannot register exsiting table : \"{}\"",
args[0]
)));
}
processor
.indexer
.add_table_fast(&args[0], args[1].as_bytes())?;
Ok(None)
} else {
Err(RadError::InvalidArgument(
"regcsv requires two arguments".to_owned(),
))
}
}
#[cfg(feature = "cindex")]
fn cindex_drop(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
processor.indexer.drop_table(&args[0]);
Ok(None)
} else {
Err(RadError::InvalidArgument(
"regcsv requires two arguments".to_owned(),
))
}
}
#[cfg(feature = "cindex")]
fn cindex_query(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let mut value = String::new();
processor
.indexer
.index_raw(&Utils::trim(&args[0]), OutOption::Value(&mut value))?;
Ok(Some(Utils::trim(&value)))
} else {
Err(RadError::InvalidArgument(
"query requires an argument".to_owned(),
))
}
}
#[cfg(feature = "cindex")]
fn cindex_query_list(args: &str, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 1) {
let mut value = String::new();
for raw in args[0].split(';') {
if raw.is_empty() {
continue;
}
processor
.indexer
.index_raw(&Utils::trim(raw), OutOption::Value(&mut value))?;
}
Ok(Some(Utils::trim(&value)))
} else {
Err(RadError::InvalidArgument(
"queries requires an argument".to_owned(),
))
}
}
}
#[derive(Clone)]
pub(crate) struct FMacroSign {
name: String,
args: Vec<String>,
pub logic: FunctionMacroType,
#[allow(dead_code)]
pub desc: Option<String>,
}
impl FMacroSign {
pub fn new(
name: &str,
args: impl IntoIterator<Item = impl AsRef<str>>,
logic: FunctionMacroType,
desc: Option<String>,
) -> Self {
let args = args
.into_iter()
.map(|s| s.as_ref().to_owned())
.collect::<Vec<String>>();
Self {
name: name.to_owned(),
args,
logic,
desc,
}
}
}
impl std::fmt::Display for FMacroSign {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut inner = self
.args
.iter()
.fold(String::new(), |acc, arg| acc + &arg + ",");
inner.pop();
write!(f, "${}({})", self.name, inner)
}
}
#[cfg(feature = "signature")]
impl From<&FMacroSign> for crate::sigmap::MacroSignature {
fn from(bm: &FMacroSign) -> Self {
Self {
variant: crate::sigmap::MacroVariant::Function,
name: bm.name.to_owned(),
args: bm.args.to_owned(),
expr: bm.to_string(),
desc: bm.desc.clone(),
}
}
}