- Simplified auth module by removing unused imports and code - Cleaned up shared models by removing unused structs (Organization, User, Bot, etc.) - Updated add-req.sh to comment out unused directories - Modified LLM fallback strategy in README with additional notes about model behaviors The changes focus on removing unused code and improving documentation while maintaining existing functionality. The auth module was significantly reduced by removing redundant code, and similar cleanup was applied to shared models. The build script was adjusted to reflect currently used directories.
304 lines
9.4 KiB
Rust
304 lines
9.4 KiB
Rust
use rhai::{Dynamic, Engine};
|
||
use chrono::{NaiveDateTime, Timelike, Datelike};
|
||
use num_format::{Locale, ToFormattedString};
|
||
use std::str::FromStr;
|
||
|
||
pub fn format_keyword(engine: &mut Engine) {
|
||
engine
|
||
.register_custom_syntax(&["FORMAT", "$expr$", "$expr$"], false, {
|
||
move |context, inputs| {
|
||
let value_dyn = context.eval_expression_tree(&inputs[0])?;
|
||
let pattern_dyn = context.eval_expression_tree(&inputs[1])?;
|
||
|
||
let value_str = value_dyn.to_string();
|
||
let pattern = pattern_dyn.to_string();
|
||
|
||
if let Ok(num) = f64::from_str(&value_str) {
|
||
let formatted = if pattern.starts_with("N") || pattern.starts_with("C") {
|
||
let (prefix, decimals, locale_tag) = parse_pattern(&pattern);
|
||
|
||
let locale = get_locale(&locale_tag);
|
||
let symbol = if prefix == "C" {
|
||
get_currency_symbol(&locale_tag)
|
||
} else {
|
||
""
|
||
};
|
||
|
||
let int_part = num.trunc() as i64;
|
||
let frac_part = num.fract();
|
||
|
||
if decimals == 0 {
|
||
format!("{}{}", symbol, int_part.to_formatted_string(&locale))
|
||
} else {
|
||
let frac_scaled =
|
||
((frac_part * 10f64.powi(decimals as i32)).round()) as i64;
|
||
|
||
let decimal_sep = match locale_tag.as_str() {
|
||
"pt" | "fr" | "es" | "it" | "de" => ",",
|
||
_ => "."
|
||
};
|
||
|
||
format!(
|
||
"{}{}{}{:0width$}",
|
||
symbol,
|
||
int_part.to_formatted_string(&locale),
|
||
decimal_sep,
|
||
frac_scaled,
|
||
width = decimals
|
||
)
|
||
}
|
||
} else {
|
||
match pattern.as_str() {
|
||
"n" => format!("{:.2}", num),
|
||
"F" => format!("{:.2}", num),
|
||
"f" => format!("{}", num),
|
||
"0%" => format!("{:.0}%", num * 100.0),
|
||
_ => format!("{}", num),
|
||
}
|
||
};
|
||
|
||
return Ok(Dynamic::from(formatted));
|
||
}
|
||
|
||
if let Ok(dt) = NaiveDateTime::parse_from_str(&value_str, "%Y-%m-%d %H:%M:%S") {
|
||
let formatted = apply_date_format(&dt, &pattern);
|
||
return Ok(Dynamic::from(formatted));
|
||
}
|
||
|
||
let formatted = apply_text_placeholders(&value_str, &pattern);
|
||
Ok(Dynamic::from(formatted))
|
||
}
|
||
})
|
||
.unwrap();
|
||
}
|
||
|
||
fn parse_pattern(pattern: &str) -> (String, usize, String) {
|
||
let mut prefix = String::new();
|
||
let mut decimals: usize = 2;
|
||
let mut locale_tag = "en".to_string();
|
||
|
||
if pattern.starts_with('C') {
|
||
prefix = "C".to_string();
|
||
} else if pattern.starts_with('N') {
|
||
prefix = "N".to_string();
|
||
}
|
||
|
||
let rest = &pattern[1..];
|
||
let mut num_part = String::new();
|
||
for ch in rest.chars() {
|
||
if ch.is_ascii_digit() {
|
||
num_part.push(ch);
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
if !num_part.is_empty() {
|
||
decimals = num_part.parse().unwrap_or(2);
|
||
}
|
||
|
||
if let Some(start) = pattern.find('[') {
|
||
if let Some(end) = pattern.find(']') {
|
||
if end > start {
|
||
locale_tag = pattern[start + 1..end].to_string();
|
||
}
|
||
}
|
||
}
|
||
|
||
(prefix, decimals, locale_tag)
|
||
}
|
||
|
||
fn get_locale(tag: &str) -> Locale {
|
||
match tag {
|
||
"en" => Locale::en,
|
||
"fr" => Locale::fr,
|
||
"de" => Locale::de,
|
||
"pt" => Locale::pt,
|
||
"it" => Locale::it,
|
||
"es" => Locale::es,
|
||
_ => Locale::en,
|
||
}
|
||
}
|
||
|
||
fn get_currency_symbol(tag: &str) -> &'static str {
|
||
match tag {
|
||
"en" => "$",
|
||
"pt" => "R$ ",
|
||
"fr" | "de" | "es" | "it" => "€",
|
||
_ => "$",
|
||
}
|
||
}
|
||
|
||
fn apply_date_format(dt: &NaiveDateTime, pattern: &str) -> String {
|
||
let mut output = pattern.to_string();
|
||
|
||
let year = dt.year();
|
||
let month = dt.month();
|
||
let day = dt.day();
|
||
let hour24 = dt.hour();
|
||
let minute = dt.minute();
|
||
let second = dt.second();
|
||
let millis = dt.and_utc().timestamp_subsec_millis();
|
||
|
||
output = output.replace("yyyy", &format!("{:04}", year));
|
||
output = output.replace("yy", &format!("{:02}", year % 100));
|
||
output = output.replace("MM", &format!("{:02}", month));
|
||
output = output.replace("M", &format!("{}", month));
|
||
output = output.replace("dd", &format!("{:02}", day));
|
||
output = output.replace("d", &format!("{}", day));
|
||
|
||
output = output.replace("HH", &format!("{:02}", hour24));
|
||
output = output.replace("H", &format!("{}", hour24));
|
||
|
||
let mut hour12 = hour24 % 12;
|
||
if hour12 == 0 { hour12 = 12; }
|
||
output = output.replace("hh", &format!("{:02}", hour12));
|
||
output = output.replace("h", &format!("{}", hour12));
|
||
|
||
output = output.replace("mm", &format!("{:02}", minute));
|
||
output = output.replace("m", &format!("{}", minute));
|
||
|
||
output = output.replace("ss", &format!("{:02}", second));
|
||
output = output.replace("s", &format!("{}", second));
|
||
|
||
output = output.replace("fff", &format!("{:03}", millis));
|
||
|
||
output = output.replace("tt", if hour24 < 12 { "AM" } else { "PM" });
|
||
output = output.replace("t", if hour24 < 12 { "A" } else { "P" });
|
||
|
||
output
|
||
}
|
||
|
||
fn apply_text_placeholders(value: &str, pattern: &str) -> String {
|
||
let mut result = String::new();
|
||
let mut i = 0;
|
||
let chars: Vec<char> = pattern.chars().collect();
|
||
|
||
while i < chars.len() {
|
||
match chars[i] {
|
||
'@' => result.push_str(value),
|
||
'&' => {
|
||
result.push_str(&value.to_lowercase());
|
||
// Handle modifiers
|
||
if i + 1 < chars.len() {
|
||
match chars[i+1] {
|
||
'!' => {
|
||
result.push('!');
|
||
i += 1;
|
||
}
|
||
'>' => {
|
||
i += 1;
|
||
}
|
||
_ => ()
|
||
}
|
||
}
|
||
}
|
||
'>' | '!' => result.push_str(&value.to_uppercase()),
|
||
_ => result.push(chars[i]),
|
||
}
|
||
i += 1;
|
||
}
|
||
|
||
result
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use rhai::Engine;
|
||
|
||
fn create_engine() -> Engine {
|
||
let mut engine = Engine::new();
|
||
format_keyword(&mut engine);
|
||
engine
|
||
}
|
||
|
||
#[test]
|
||
fn test_numeric_formatting_basic() {
|
||
let engine = create_engine();
|
||
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.567 \"n\"").unwrap(),
|
||
"1234.57"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.5 \"F\"").unwrap(),
|
||
"1234.50"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.567 \"f\"").unwrap(),
|
||
"1234.567"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 0.85 \"0%\"").unwrap(),
|
||
"85%"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_numeric_formatting_with_locale() {
|
||
let engine = create_engine();
|
||
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.56 \"N[en]\"").unwrap(),
|
||
"1,234.56"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.56 \"N[pt]\"").unwrap(),
|
||
"1.234,56"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.56 \"N[fr]\"").unwrap(),
|
||
"1 234,56"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_currency_formatting() {
|
||
let engine = create_engine();
|
||
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.56 \"C[en]\"").unwrap(),
|
||
"$1,234.56"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.56 \"C[pt]\"").unwrap(),
|
||
"R$ 1.234,56"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT 1234.56 \"C[fr]\"").unwrap(),
|
||
"€1 234,56"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_date_formatting() {
|
||
let engine = create_engine();
|
||
|
||
let result = engine.eval::<String>("FORMAT \"2024-03-15 14:30:25\" \"yyyy-MM-dd HH:mm:ss\"").unwrap();
|
||
assert_eq!(result, "2024-03-15 14:30:25");
|
||
|
||
let result = engine.eval::<String>("FORMAT \"2024-03-15 14:30:25\" \"dd/MM/yyyy\"").unwrap();
|
||
assert_eq!(result, "15/03/2024");
|
||
|
||
let result = engine.eval::<String>("FORMAT \"2024-03-15 14:30:25\" \"MM/dd/yy\"").unwrap();
|
||
assert_eq!(result, "03/15/24");
|
||
}
|
||
|
||
#[test]
|
||
fn test_text_formatting() {
|
||
let engine = create_engine();
|
||
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT \"hello\" \"Prefix: @\"").unwrap(),
|
||
"Prefix: hello"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT \"HELLO\" \"Result: &!\"").unwrap(),
|
||
"Result: hello!"
|
||
);
|
||
assert_eq!(
|
||
engine.eval::<String>("FORMAT \"hello\" \"RESULT: >\"").unwrap(),
|
||
"RESULT: HELLO"
|
||
);
|
||
}
|
||
}
|