use super::config::{FloatFormat, FormatterOptions};
use crate::command::{CompositeValue, Parameter, Value};
pub struct Formatters;
impl Formatters {
pub fn format_number(num: &i64, options: &FormatterOptions) -> String {
let fmt = options.number_format.to_string();
if fmt.is_empty() {
return num.to_string();
}
let (prefix, radix) = match fmt.chars().last() {
Some('x') | Some('X') => ("0x", 16),
Some('o') => ("0o", 8),
Some('b') => ("0b", 2),
_ => return num.to_string(),
};
let spec = &fmt[..fmt.len() - 1];
let target_width: usize = if spec.starts_with('0') && spec.len() > 1 {
spec[1..].parse().ok().unwrap_or(0)
} else {
0
};
let unprefixed = match radix {
16 => format!("{:x}", num),
8 => format!("{:o}", num),
2 => format!("{:b}", num),
_ => return num.to_string(),
};
let content = if target_width > 0 {
let pad_len = if target_width > prefix.len() {
target_width - prefix.len()
} else {
0
};
if pad_len > unprefixed.len() {
format!("{:>width$}", unprefixed, width = pad_len)
} else {
unprefixed
}
} else {
unprefixed
};
format!("{}{}", prefix, content)
}
pub fn format_float(f: &f64, options: &FormatterOptions) -> String {
match &options.float_format {
FloatFormat::Default => f.to_string(),
FloatFormat::Fixed(precision) => {
let p = precision.unwrap_or(6);
format!("{:.p$}", f, p = p)
}
FloatFormat::Scientific => format!("{:e}", f),
FloatFormat::General(precision) => {
let p = precision.unwrap_or(6);
format!("{:.*}", p, f)
}
FloatFormat::Custom(fmt) => Self::apply_custom_float_format(f, fmt),
}
}
fn apply_custom_float_format(f: &f64, fmt: &str) -> String {
if fmt.is_empty() {
return f.to_string();
}
let mut precision = None;
let mut specifier = 'f';
let mut prefix = String::new();
let mut chars = fmt.chars().peekable();
while let Some(c) = chars.next() {
if c == '.' {
let mut prec_str = String::new();
while let Some(&next_c) = chars.peek() {
if next_c.is_ascii_digit() {
prec_str.push(chars.next().unwrap());
} else {
break;
}
}
if !prec_str.is_empty() {
precision = Some(prec_str.parse::<usize>().unwrap_or(6));
} else {
precision = Some(6);
}
} else if c == 'e' || c == 'E' {
specifier = c;
break;
} else if c == '+' || c == ' ' || c == '#' || c == '0' {
prefix.push(c);
}
}
let result = match specifier {
'e' | 'E' => {
if let Some(p) = precision {
format!("{:.1$e}", f, p)
} else {
format!("{:e}", f)
}
}
_ => {
if let Some(p) = precision {
format!("{:.1$}", f, p)
} else {
format!("{}", f)
}
}
};
if prefix.is_empty() {
result
} else {
let sign = if result.starts_with('-') {
"-"
} else {
""
};
let abs_result = result.trim_start_matches('-');
format!("{}{}{}", sign, prefix, abs_result)
}
}
pub fn is_valid_variable_name(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
let first_char = chars.next().unwrap();
if !first_char.is_ascii_alphabetic() && first_char != '_' {
return false;
}
for c in chars {
if !c.is_ascii_alphanumeric() && c != '_' {
return false;
}
}
true
}
pub fn format_string(s: &str, options: &FormatterOptions) -> String {
let needs_quotes = options.force_quotes_for_vars || !Self::is_valid_variable_name(s);
if needs_quotes {
let mut result = String::with_capacity(s.len() + 2);
result.push('"');
for c in s.chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
c => result.push(c),
}
}
result.push('"');
result
} else {
s.to_string()
}
}
pub fn format_composite_value(value: &CompositeValue, options: &FormatterOptions) -> String {
match value {
CompositeValue::Single(val) => {
format!("({})", Self::format_value(val, options))
}
CompositeValue::List(values) => {
let mut result = "(".to_string();
let mut first = true;
for val in values {
if !first {
result.push(',');
if !options.compact {
result.push(' ');
}
}
result.push_str(&Self::format_value(val, options));
first = false;
}
result.push(')');
result
}
CompositeValue::Dict(entries) => {
let mut result = "(".to_string();
let mut first = true;
for (key, val) in entries {
if !first {
result.push(',');
if !options.compact {
result.push(' ');
}
}
result.push_str(key);
result.push(':');
if !options.compact {
result.push(' ');
}
result.push_str(&Self::format_value(val, options));
first = false;
}
result.push(')');
result
}
}
}
pub fn format_value(value: &Value, options: &FormatterOptions) -> String {
match value {
Value::Int(i) => Self::format_number(i, options),
Value::Float(f) => Self::format_float(f, options),
Value::Bool(b) => b.to_string(),
Value::String(s) => Self::format_string(s, options),
}
}
pub fn format_parameter(param: &Parameter, options: &FormatterOptions) -> String {
let param_text = match param {
Parameter::Basic(value) => Self::format_value(value, options),
Parameter::Composite(name, composite_value) => {
format!(
"{}{}",
name,
Self::format_composite_value(composite_value, options)
)
}
};
param_text.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{command::{CompositeValue, Parameter, Value}, writer::NumberFormat};
#[test]
fn test_format_number() {
let options = FormatterOptions::default();
let result = Formatters::format_number(&42, &options);
assert_eq!(result, "42");
let options = FormatterOptions {
number_format: NumberFormat::Hex,
..Default::default()
};
let result = Formatters::format_number(&255, &options);
assert_eq!(result, "0xff");
let options = FormatterOptions {
number_format: NumberFormat::Octal,
..Default::default()
};
let result = Formatters::format_number(&63, &options);
assert_eq!(result, "0o77");
let options = FormatterOptions {
number_format: NumberFormat::Binary,
..Default::default()
};
let result = Formatters::format_number(&7, &options);
assert_eq!(result, "0b111");
let options = FormatterOptions::default();
let result = Formatters::format_number(&-42, &options);
assert_eq!(result, "-42");
let options = FormatterOptions {
number_format: NumberFormat::Hex,
..Default::default()
};
let result = Formatters::format_number(&-255, &options);
assert_eq!(result, "0xffffffffffffff01");
}
#[test]
fn test_is_valid_variable_name() {
assert!(Formatters::is_valid_variable_name("valid_name"));
assert!(Formatters::is_valid_variable_name("_valid"));
assert!(Formatters::is_valid_variable_name("valid123"));
assert!(Formatters::is_valid_variable_name("a"));
assert!(Formatters::is_valid_variable_name("A"));
assert!(!Formatters::is_valid_variable_name("123invalid"));
assert!(!Formatters::is_valid_variable_name("invalid-name"));
assert!(!Formatters::is_valid_variable_name("invalid name"));
assert!(!Formatters::is_valid_variable_name("invalid.name"));
assert!(!Formatters::is_valid_variable_name(""));
assert!(!Formatters::is_valid_variable_name("!invalid"));
assert!(!Formatters::is_valid_variable_name("invalid!"));
}
#[test]
fn test_format_string() {
let options = FormatterOptions::default();
let result = Formatters::format_string("valid_name", &options);
assert_eq!(result, "valid_name");
let result = Formatters::format_string("invalid-name", &options);
assert_eq!(result, "\"invalid-name\"");
let result = Formatters::format_string("with_spaces", &options);
assert_eq!(result, "with_spaces");
let options = FormatterOptions {
force_quotes_for_vars: true,
..Default::default()
};
let result = Formatters::format_string("valid_name", &options);
assert_eq!(result, "\"valid_name\"");
}
#[test]
fn test_format_composite_value() {
let options = FormatterOptions::default();
let single_value = CompositeValue::Single(Value::Int(42));
let result = Formatters::format_composite_value(&single_value, &options);
assert_eq!(result, "(42)");
let list_value = CompositeValue::List(vec![
Value::Int(1),
Value::String("two".to_string()),
Value::Int(3),
]);
let result = Formatters::format_composite_value(&list_value, &options);
assert_eq!(result, "(1, two, 3)");
let options_compact = FormatterOptions {
compact: true,
..Default::default()
};
let result = Formatters::format_composite_value(&list_value, &options_compact);
assert_eq!(result, "(1,two,3)");
let dict_entries = vec![
("key1".to_string(), Value::Int(1)),
("key2".to_string(), Value::String("value2".to_string())),
];
let dict_value = CompositeValue::Dict(dict_entries);
let result = Formatters::format_composite_value(&dict_value, &options);
assert_eq!(result, "(key1: 1, key2: value2)");
let result = Formatters::format_composite_value(&dict_value, &options_compact);
assert_eq!(result, "(key1:1,key2:value2)");
}
#[test]
fn test_format_value() {
let options = FormatterOptions::default();
let result = Formatters::format_value(&Value::Int(42), &options);
assert_eq!(result, "42");
let result = Formatters::format_value(&Value::Float(3.14), &options);
assert_eq!(result, "3.14");
let result = Formatters::format_value(&Value::String("test".to_string()), &options);
assert_eq!(result, "test");
let result =
Formatters::format_value(&Value::String("test-with-dash".to_string()), &options);
assert_eq!(result, "\"test-with-dash\"");
let result = Formatters::format_value(&Value::Int(-42), &options);
assert_eq!(result, "-42");
}
#[test]
fn test_format_parameter() {
let options = FormatterOptions::default();
let basic_param = Parameter::from(42);
let result = Formatters::format_parameter(&basic_param, &options);
assert_eq!(result, "42");
let basic_param = Parameter::from("test");
let result = Formatters::format_parameter(&basic_param, &options);
assert_eq!(result, "test");
let composite_param = Parameter::Composite(
"test_name".to_string(),
CompositeValue::Single(Value::Int(42)),
);
let result = Formatters::format_parameter(&composite_param, &options);
assert_eq!(result, "test_name(42)");
let composite_param = Parameter::Composite(
"list_param".to_string(),
CompositeValue::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
let result = Formatters::format_parameter(&composite_param, &options);
assert_eq!(result, "list_param(1, 2, 3)");
let dict_entries = vec![("key".to_string(), Value::String("value".to_string()))];
let composite_param =
Parameter::Composite("dict_param".to_string(), CompositeValue::Dict(dict_entries));
let result = Formatters::format_parameter(&composite_param, &options);
assert_eq!(result, "dict_param(key: value)");
}
#[test]
fn test_format_value_with_number_formats() {
let hex_options = FormatterOptions {
number_format: NumberFormat::Hex,
..Default::default()
};
let result = Formatters::format_value(&Value::Int(255), &hex_options);
assert_eq!(result, "0xff");
let oct_options = FormatterOptions {
number_format: NumberFormat::Octal,
..Default::default()
};
let result = Formatters::format_value(&Value::Int(63), &oct_options);
assert_eq!(result, "0o77");
let bin_options = FormatterOptions {
number_format: NumberFormat::Binary,
..Default::default()
};
let result = Formatters::format_value(&Value::Int(7), &bin_options);
assert_eq!(result, "0b111");
}
#[test]
fn test_format_float() {
let options = FormatterOptions::default();
let result = Formatters::format_float(&3.14159, &options);
assert_eq!(result, "3.14159");
let options = FormatterOptions {
float_format: FloatFormat::Fixed(Some(2)),
..Default::default()
};
let result = Formatters::format_float(&3.14159, &options);
assert_eq!(result, "3.14");
let options = FormatterOptions {
float_format: FloatFormat::Fixed(Some(4)),
..Default::default()
};
let result = Formatters::format_float(&3.14159, &options);
assert_eq!(result, "3.1416");
let options = FormatterOptions {
float_format: FloatFormat::Fixed(None),
..Default::default()
};
let result = Formatters::format_float(&3.1415926535, &options);
assert_eq!(result, "3.141593");
let options = FormatterOptions {
float_format: FloatFormat::Scientific,
..Default::default()
};
let result = Formatters::format_float(&0.001, &options);
assert_eq!(result, "1e-3");
let options = FormatterOptions {
float_format: FloatFormat::Custom(".3f".to_string()),
..Default::default()
};
let result = Formatters::format_float(&3.14159, &options);
assert_eq!(result, "3.142");
let options = FormatterOptions {
float_format: FloatFormat::Custom("+.0f".to_string()),
..Default::default()
};
let result = Formatters::format_float(&3.7, &options);
assert_eq!(result, "+4");
let options = FormatterOptions {
float_format: FloatFormat::Custom(".2e".to_string()),
..Default::default()
};
let result = Formatters::format_float(&3.14159, &options);
assert_eq!(result, "3.14e0");
}
#[test]
fn test_format_value_with_float_formats() {
let fixed_options = FormatterOptions {
float_format: FloatFormat::Fixed(Some(2)),
..Default::default()
};
let result = Formatters::format_value(&Value::Float(3.14159), &fixed_options);
assert_eq!(result, "3.14");
let sci_options = FormatterOptions {
float_format: FloatFormat::Scientific,
..Default::default()
};
let result = Formatters::format_value(&Value::Float(0.001), &sci_options);
assert_eq!(result, "1e-3");
let custom_options = FormatterOptions {
float_format: FloatFormat::Custom("+.0f".to_string()),
..Default::default()
};
let result = Formatters::format_value(&Value::Float(3.7), &custom_options);
assert_eq!(result, "+4");
}
}