use std::ops::{Bound, RangeBounds};
use crate::errors::TransformerError;
use crate::VAR_TRANSFORM_SEP_CHAR;
use lazy_static::lazy_static;
use regex::Regex;
use titlecase::titlecase;
pub fn apply_tranformers(val: &str, transformations: &str) -> Result<String, TransformerError> {
let mut val: String = val.to_string();
for tstr in transformations.split(VAR_TRANSFORM_SEP_CHAR) {
if tstr.is_empty() {
continue;
}
let (name, args) = tstr.split_once('(').ok_or(TransformerError::InvalidSyntax(
tstr.to_string(),
"No opening paranthesis".to_string(),
))?;
let args: Vec<&str> = args
.strip_suffix(')')
.ok_or(TransformerError::InvalidSyntax(
tstr.to_string(),
"No closing paranthesis".to_string(),
))?
.split(',')
.collect();
val = match name {
"f" => float_format(&val, args)?,
"case" => string_case(&val, args)?,
"calc" => calc(&val, args)?,
"count" => count(&val, args)?,
"repl" => replace(&val, args)?,
"take" => take(&val, args)?,
"trim" => trim(&val, args)?,
"comma" => comma(&val, args)?,
"group" => group(&val, args)?,
"q" => quote(&val, args)?,
_ => {
return Err(TransformerError::UnknownTranformer(
name.to_string(),
val.to_string(),
))
}
};
}
Ok(val)
}
pub fn bound(b: Bound<&usize>, lower: bool) -> Option<usize> {
match b {
Bound::Unbounded => None,
Bound::Included(v) => Some(*v),
Bound::Excluded(v) => Some(if lower { v + 1 } else { v - 1 }),
}
}
fn check_arguments_len<R: RangeBounds<usize>>(
func_name: &'static str,
req: R,
given: usize,
) -> Result<(), TransformerError> {
if req.contains(&given) {
Ok(())
} else {
match (
bound(req.start_bound(), true),
bound(req.end_bound(), false),
) {
(None, Some(r)) => Err(TransformerError::TooManyArguments(func_name, r, given)),
(Some(r), None) => Err(TransformerError::TooFewArguments(func_name, r, given)),
(Some(r1), Some(r2)) => {
if given < r1 {
Err(TransformerError::TooFewArguments(func_name, r1, given))
} else {
Err(TransformerError::TooManyArguments(func_name, r2, given))
}
}
_ => Ok(()),
}
}
}
pub fn float_format(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "f";
check_arguments_len(func_name, 1..=1, args.len())?;
let format = args[0];
let val = val
.parse::<f64>()
.map_err(|_| TransformerError::InvalidValueType(func_name, "float"))?;
let mut start = 0usize;
let mut decimal = 6usize;
if let Some((d, f)) = format.split_once('.') {
if !d.is_empty() {
start = d.parse().map_err(|_| {
TransformerError::InvalidArgumentType(func_name, d.to_string(), "uint")
})?;
}
if f.is_empty() {
decimal = 0;
} else {
decimal = f.parse().map_err(|_| {
TransformerError::InvalidArgumentType(func_name, f.to_string(), "uint")
})?;
}
} else if !format.is_empty() {
decimal = format.parse().map_err(|_| {
TransformerError::InvalidArgumentType(func_name, format.to_string(), "uint")
})?;
}
Ok(format!("{0:1$.2$}", val, start, decimal))
}
pub fn string_case(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "case";
check_arguments_len(func_name, 1..=1, args.len())?;
let format = args[0];
match format.to_lowercase().as_str() {
"up" => Ok(val.to_uppercase()),
"down" => Ok(val.to_lowercase()),
"title" => Ok(titlecase(val)),
"proper" => Ok({
let mut c = val.chars();
match c.next() {
None => String::new(),
Some(f) => {
f.to_uppercase().collect::<String>() + c.as_str().to_lowercase().as_str()
}
}
}),
_ => Err(TransformerError::InvalidArgumentType(
func_name,
format.to_string(),
"{up;down;proper;title}",
)),
}
}
lazy_static! {
static ref CALC_NUMBERS: Regex = Regex::new("[0-9.]+").unwrap();
}
pub fn calc(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "calc";
check_arguments_len(func_name, 1.., args.len())?;
let val: f64 = val
.parse()
.map_err(|_| TransformerError::InvalidValueType(func_name, "float"))?;
let mut results: Vec<String> = Vec::new();
for expr in args {
let mut last_match = 0usize;
let mut result = val;
for cap in CALC_NUMBERS.captures_iter(expr) {
let m = cap.get(0).unwrap();
let curr_val = m.as_str().parse().map_err(|_| {
TransformerError::InvalidArgumentType(func_name, m.as_str().to_string(), "float")
})?;
if m.start() == 0 {
result = curr_val;
} else {
match &expr[last_match..m.start()] {
"+" => result += curr_val,
"-" => result -= curr_val,
"/" => result /= curr_val,
"*" => result *= curr_val,
"^" => result = result.powf(curr_val),
s => {
return Err(TransformerError::InvalidArgumentType(
func_name,
s.to_string(),
"{+,-,*,/,^}",
))
}
};
}
last_match = m.end();
}
results.push(result.to_string());
}
Ok(results.join(","))
}
pub fn count(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "count";
check_arguments_len(func_name, 1.., args.len())?;
let counts: Vec<String> = args
.iter()
.map(|sep| val.matches(sep).count().to_string())
.collect();
Ok(counts.join(","))
}
pub fn replace(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "replace";
check_arguments_len(func_name, 2..=2, args.len())?;
Ok(val.replace(args[0], args[1]))
}
pub fn take(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "take";
check_arguments_len(func_name, 2..=3, args.len())?;
let n: usize = args[1].parse().map_err(|_| {
TransformerError::InvalidArgumentType(func_name, args[1].to_string(), "uint")
})?;
let spl = if args.len() == 2 {
val.split(args[0]).nth(n - 1)
} else {
val.splitn(
args[2].parse().map_err(|_| {
TransformerError::InvalidArgumentType(func_name, args[1].to_string(), "int")
})?,
args[0],
)
.nth(n - 1)
};
Ok(spl.unwrap_or("").to_string())
}
pub fn trim(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "trim";
check_arguments_len(func_name, .., args.len())?;
if args.is_empty() {
return Ok(val.trim().to_string());
}
let mut val = val;
for arg in args {
val = val.trim_matches(|c| arg.contains(c))
}
Ok(val.to_string())
}
pub fn comma(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "comma";
check_arguments_len(func_name, 1.., args.len())?;
let mut args: Vec<usize> = args
.iter()
.map(|s| {
s.parse().map_err(|_| {
TransformerError::InvalidArgumentType(func_name, s.to_string(), "uint")
})
})
.rev()
.collect::<Result<Vec<usize>, TransformerError>>()?;
let last = args[0];
let mut i = args.pop().unwrap();
let mut result = vec![];
let val: Vec<char> = val.replace(',', "").chars().rev().collect();
for c in val {
if i == 0 {
i = args.pop().unwrap_or(last);
result.push(',');
}
result.push(c);
i -= 1;
}
result.reverse();
let result: String = result.into_iter().collect();
Ok(result)
}
pub fn group(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "group";
check_arguments_len(func_name, 2.., args.len())?;
let sep = args[0];
let mut args: Vec<usize> = args[1..]
.iter()
.map(|s| {
s.parse().map_err(|_| {
TransformerError::InvalidArgumentType(func_name, s.to_string(), "uint")
})
})
.rev()
.collect::<Result<Vec<usize>, TransformerError>>()?;
let last = args[0];
let mut i = args.pop().unwrap();
let mut result = vec![];
let val: Vec<char> = val.replace(sep, "").chars().rev().collect();
for c in val {
if i == 0 {
i = args.pop().unwrap_or(last);
for c in sep.chars().rev() {
result.push(c);
}
}
result.push(c);
i -= 1;
}
result.reverse();
let result: String = result.into_iter().collect();
Ok(result)
}
pub fn quote(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
let func_name = "quote";
check_arguments_len(func_name, ..=2, args.len())?;
Ok(if args.is_empty() {
format!("{:?}", val)
} else if args.len() == 1 {
if args[0].is_empty() {
format!("{:?}", val)
} else {
format!(
"{0}{1}{0}",
args[0],
val.replace(args[0], &format!("\\{}", args[0]))
)
}
} else {
format!(
"{}{}{}",
args[0],
val.replace(args[0], &format!("\\{}", args[0]))
.replace(args[1], &format!("\\{}", args[1])),
args[1]
)
})
}