use crate::tina::data::AppResult;
use crate::{app_error_from, app_system_error};
use regex::{Captures, Regex};
use std::cmp::{max, min};
const PREFIXES: [&str; 17] = ["y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"];
pub struct NumberFormat {
decimal: char,
group_delimiter: char,
}
#[derive(Debug)]
struct FormatSpec<'a> {
zero: bool,
fill: Option<&'a str>,
align: Option<&'a str>,
sign: Option<&'a str>,
symbol: Option<&'a str>,
width: Option<usize>,
grouping: Option<&'a str>,
precision: Option<i32>,
format_type: Option<&'a str>,
}
impl<'a> From<Captures<'a>> for FormatSpec<'a> {
fn from(c: Captures<'a>) -> Self {
let mut spec = Self {
fill: c.get(1).map(|m| m.as_str()).or(Some(" ")),
align: c.get(2).map(|m| m.as_str()),
sign: c.get(3).map(|m| m.as_str()).or(Some("-")),
symbol: c.get(4).map(|m| m.as_str()),
zero: c.get(5).is_some(),
width: c.get(6).map(|m| m.as_str().parse().unwrap_or(0)).or(Some(0)),
grouping: c.get(7).map(|m| m.as_str()),
precision: c.get(8).map(|m| m.as_str()[1..].parse().unwrap_or(6)).or(Some(6)),
format_type: c.get(9).map(|m| m.as_str()),
};
if spec.zero || (spec.fill.unwrap_or_default() == "0" && spec.align.unwrap_or_default() == "=") {
spec.zero = true;
spec.fill = Some("0");
spec.align = Some("=");
}
if spec.format_type.unwrap_or_default() == "d" {
spec.precision = Some(0);
};
spec
}
}
impl Default for NumberFormat {
fn default() -> Self {
Self::new()
}
}
impl NumberFormat {
pub fn new() -> Self {
Self {
decimal: '.',
group_delimiter: ',',
}
}
#[allow(dead_code)]
fn get_significant_digits(input: &str) -> usize {
let contains_dot = input.contains('.');
let mut dot_counted = false;
let mut insignificant = 0;
for char in input.chars() {
match char {
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => break,
'.' => {
insignificant += 1;
dot_counted = true;
}
_ => insignificant += 1,
}
}
if !contains_dot {
for char in input.chars().rev() {
match char {
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => break,
_ => insignificant += 1,
}
}
}
input.len() - insignificant - (contains_dot && !dot_counted) as usize
}
fn decompose_to_coefficient_and_exponent(&self, value: f64, significant_digits: Option<usize>) -> (String, isize) {
let formatted_value = if significant_digits.is_some() {
let precision = if significant_digits.unwrap_or(0) == 0 {
0
} else {
significant_digits.unwrap_or(0) - 1
};
format!("{:.1$e}", value, precision)
} else {
format!("{:e}", value)
};
let exp_tokens: Vec<&str> = formatted_value.split('e').collect::<Vec<&str>>();
let exponent = exp_tokens[1].parse().unwrap_or(0);
if exp_tokens[0].len() == 1 {
(exp_tokens[0].to_owned(), exponent)
} else {
let dot_idx = exp_tokens[0].chars().position(|c| c == self.decimal).unwrap_or(0);
(format!("{}{}", &exp_tokens[0][..dot_idx], &exp_tokens[0][dot_idx + 1..]), exponent)
}
}
fn format_si_prefix(&self, value: f64, precision: Option<i32>) -> (String, isize) {
let (coefficient, exponent) = self.decompose_to_coefficient_and_exponent(value, precision.map(|p| p as usize));
let prefix_exponent = max(-8, min(8, (exponent as f32 / 3_f32).floor() as isize));
let i: isize = exponent - prefix_exponent * 3 + 1;
let n: isize = coefficient.len() as isize;
if i == n {
(coefficient, prefix_exponent)
} else if i > n {
(format!("{}{}", coefficient, "0".repeat((i - n) as usize)), prefix_exponent)
} else if i > 0 {
(format!("{}{}{}", &coefficient[..i as usize], self.decimal, &coefficient[i as usize..]), prefix_exponent)
} else {
(
format!(
"0{}{}{}",
self.decimal,
"0".repeat(i.unsigned_abs()),
self.decompose_to_coefficient_and_exponent(
value,
precision.and(Some(max(0, precision.map(|p| (p - i.abs() as i32 - 1) as usize).unwrap_or(0))))
)
.0
),
prefix_exponent,
)
}
}
fn parse_pattern<'a>(&self, pattern: &'a str) -> AppResult<FormatSpec<'a>> {
let re = Regex::new(r"^(?:(.)?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([A-Za-z%])?$").map_err(app_error_from!())?;
Ok(FormatSpec::from(re.captures(pattern).ok_or_else(|| crate::app_system_error!("no captures"))?))
}
fn group_value(&self, value: &str, width: usize) -> String {
let mut reversed_chars: Vec<&[char]> = Vec::new();
let input_chars: Vec<char> = value.chars().rev().collect();
let separator: [char; 1] = [self.group_delimiter];
for group in input_chars.chunks(3) {
reversed_chars.push(group);
reversed_chars.push(&separator);
}
reversed_chars.pop();
let grouped: Vec<&char> = reversed_chars.into_iter().flatten().collect();
if width > 0 && grouped.len() > width {
let to_skip = if grouped[width - 1] == &separator[0] {
grouped.len() - width - 1
} else {
grouped.len() - width
};
grouped.into_iter().rev().skip(to_skip).collect::<String>()
} else {
grouped.into_iter().rev().collect::<String>()
}
}
fn get_formatted_exp_value(&self, format_type: &str, value: f64, precision: usize, include_decimal_point: bool) -> String {
let formatted = format!("{:.1$e}", value, precision);
let tokens = formatted.split(format_type).collect::<Vec<&str>>();
let exp_suffix = if &tokens[1][0..1] == "-" {
if tokens[1].len() == 2 {
format!("-0{}", &tokens[1][1..])
} else {
tokens[1].to_owned()
}
} else {
format!("+{:0>2}", &tokens[1])
};
let possible_decimal = if include_decimal_point && precision == 0 {
format_args!("{}", self.decimal).to_string()
} else {
"".to_owned()
};
format!("{}{}{}{}", &tokens[0], possible_decimal, format_type, exp_suffix)
}
fn get_sign_prefix(&self, is_negative: bool, format_spec: &FormatSpec) -> &str {
if is_negative {
"-"
} else if format_spec.sign.unwrap_or("") == "+" {
"+"
} else if format_spec.sign.unwrap_or("") == " " {
" "
} else {
""
}
}
pub fn format<T: Into<f64>>(&self, pattern: &str, input: T) -> AppResult<String> {
let format_spec = self.parse_pattern(pattern)?;
let input_f64: f64 = input.into();
let mut value_is_negative: bool = input_f64.is_sign_negative();
let mut decimal_part = String::new();
let mut si_prefix_exponent: &str = "";
let unit_of_measurement: &str = match format_spec.format_type {
Some("%") => "%",
_ => "",
};
let mut value = match format_spec.format_type {
Some("%") => format!(
"{:.1$}",
input_f64.abs() * 100_f64,
format_spec.precision.ok_or_else(|| app_system_error!("no precision"))? as usize
),
Some("b") => format!("{:#b}", input_f64.abs() as i64)[2..].into(),
Some("o") | Some("O") => format!("{:#o}", input_f64.abs() as i64)[2..].into(),
Some("x") => format!("{:#x}", input_f64.abs() as i64)[2..].into(),
Some("X") => format!("{:#X}", input_f64.abs() as i64)[2..].into(),
Some("f") if format_spec.symbol.unwrap_or_default() == "#" => {
let maybe_decimal = if format_spec.precision.unwrap_or(-1) == 0 {
self.decimal.to_string()
} else {
"".to_string()
};
format!(
"{:.2$}{}",
input_f64.abs(),
maybe_decimal,
format_spec.precision.ok_or_else(|| app_system_error!("no precision"))? as usize
)
}
Some("e") => self.get_formatted_exp_value(
"e",
input_f64.abs(),
format_spec.precision.ok_or_else(|| app_system_error!("no precision"))? as usize,
format_spec.symbol.unwrap_or_default() == "#",
),
Some("E") => self.get_formatted_exp_value(
"E",
input_f64.abs(),
format_spec.precision.ok_or_else(|| app_system_error!("no precision"))? as usize,
format_spec.symbol.unwrap_or_default() == "#",
),
Some("s") => {
let (val, si_prefix) = self.format_si_prefix(input_f64.abs(), format_spec.precision);
si_prefix_exponent = PREFIXES[(8 + si_prefix) as usize];
val
}
_ => format!("{:.1$}", input_f64.abs(), format_spec.precision.ok_or_else(|| app_system_error!("no precision"))? as usize),
};
if format_spec.format_type != Some("x")
&& format_spec.format_type != Some("X")
&& value_is_negative
&& value.parse::<f64>().map_err(app_error_from!())? == 0_f64
&& format_spec.sign.unwrap_or("+") != "+"
{
value_is_negative = false;
}
let sign_prefix = self.get_sign_prefix(value_is_negative, &format_spec);
let leading_part = match format_spec.symbol {
Some("#") => match format_spec.format_type {
Some("b") => "0b",
Some("o") => "0o",
Some("x") => "0x",
Some("O") => "0O",
Some("X") => "0x",
_ => "",
},
_ => "",
};
let chars = value.chars().enumerate();
for (i, c) in chars {
if "0123456789".find(c).is_none() {
decimal_part = value[i..].to_owned();
value = value[..i].to_owned();
break;
}
}
let prefix = format!("{}{}", sign_prefix, leading_part);
let suffix = format!("{}{}{}", decimal_part, si_prefix_exponent, unit_of_measurement);
if format_spec.grouping.is_some() && !format_spec.zero {
value = self.group_value(&value, 0)
}
let length = prefix.len() + value.to_string().len() + suffix.len();
let mut padding = if length < format_spec.width.ok_or_else(|| crate::app_system_error!("no width"))? {
vec![format_spec.fill.unwrap_or(""); format_spec.width.ok_or_else(|| crate::app_system_error!("no width"))? - length].join("")
} else {
"".to_owned()
};
if format_spec.grouping.is_some() && format_spec.zero {
value = self.group_value(
format!("{}{}", &padding, value).as_str(),
if !padding.is_empty() {
format_spec.width.ok_or_else(|| crate::app_system_error!("no width"))? - suffix.len()
} else {
0
},
);
padding = "".to_owned();
};
Ok(match format_spec.align {
Some("<") => format!("{}{}{}{}", prefix, value, suffix, padding),
Some("=") => format!("{}{}{}{}", prefix, padding, value, suffix),
Some("^") => format!("{}{}{}{}{}", &padding[..padding.len() / 2], prefix, value, suffix, &padding[padding.len() / 2..]),
_ => format!("{}{}{}{}", padding, prefix, value, suffix),
})
}
}