use crate::error::{Error, Result};
pub fn lower(s: &str) -> String {
s.to_ascii_lowercase()
}
pub fn compare(a: &str, b: &str) -> bool {
a.eq_ignore_ascii_case(b)
}
fn is_space(c: char) -> bool {
let v = c as u32;
v == 0x20 || (0x09..=0x0d).contains(&v)
}
pub fn trim(s: &str) -> &str {
s.trim_matches(is_space)
}
pub fn right_trim(s: &str) -> &str {
s.trim_end_matches(is_space)
}
pub fn split(s: &str, sep: char) -> Vec<String> {
if s.is_empty() {
return vec![String::new()];
}
s.split(sep).map(|x| x.to_string()).collect()
}
pub fn join(v: &[String], sep: char) -> String {
let s = format!("{sep} ");
v.join(&s)
}
pub fn split_by_lines(s: &str) -> Vec<String> {
if s.is_empty() {
return vec![String::new()];
}
s.lines().map(|l| l.to_string()).collect()
}
pub fn contain(list: &[String], entry: &str) -> bool {
list.iter().any(|e| compare(e, entry))
}
pub fn remove(list: &mut Vec<String>, entry: &str) -> bool {
if let Some(pos) = list.iter().position(|e| compare(e, entry)) {
list.remove(pos);
true
} else {
false
}
}
pub fn find_in_vec_case_ignore(vec: &[String], s: &str) -> Option<usize> {
vec.iter().position(|e| compare(e, s))
}
pub fn intersect_case_ignore(vec1: &[String], vec2: &[String]) -> Vec<String> {
vec1.iter()
.filter(|v| vec2.iter().any(|w| compare(v, w)))
.cloned()
.collect()
}
fn find_end_of_name(s: &[u8], start: usize, sep: u8) -> Result<usize> {
let mut current = start;
loop {
match s[current.min(s.len())..]
.iter()
.position(|&c| c == b'"' || c == sep)
{
None => return Ok(s.len()),
Some(off) => {
let pos = current + off;
if s[pos] == b'"' {
match s[pos + 1..].iter().position(|&c| c == b'"') {
None => {
return Err(Error::msg(format!(
"The string '{}' is not correctly formatted. It is missing a closing quote.",
String::from_utf8_lossy(s)
)))
}
Some(off2) => current = pos + 1 + off2 + 1,
}
} else {
return Ok(pos);
}
}
}
}
}
pub fn split_string_env_style(s: &str) -> Result<Vec<String>> {
let s = trim(s);
if s.is_empty() {
return Ok(vec![String::new()]);
}
let bytes = s.as_bytes();
let mut out = Vec::new();
let found_comma = s.contains(',');
let found_colon = s.contains(':');
if found_comma || found_colon {
let sep = if found_comma { b',' } else { b':' };
let mut current = 0usize;
while current <= bytes.len() {
let end = find_end_of_name(bytes, current, sep)?;
if end > current {
out.push(String::from_utf8_lossy(&bytes[current..end]).into_owned());
current = end + 1;
} else {
out.push(String::new());
current += 1;
}
}
} else {
out.push(s.to_string());
}
for v in out.iter_mut() {
let t = trim(v).to_string();
if t.len() > 1 && t.starts_with('"') && t.ends_with('"') {
*v = t[1..t.len() - 1].to_string();
} else {
*v = t;
}
}
Ok(out)
}
pub fn split_string_env_style_lossy(s: &str) -> Vec<String> {
split_string_env_style(s).unwrap_or_else(|_| vec![trim(s).to_string()])
}
fn needs_quotes(v: &str) -> bool {
v.contains([',', ':']) && v.len() > 1 && !v.starts_with('"') && !v.ends_with('"')
}
pub fn join_string_env_style(v: &[String]) -> String {
let mut out = String::new();
for (i, e) in v.iter().enumerate() {
if i != 0 {
out.push_str(", ");
}
if needs_quotes(e) {
out.push('"');
out.push_str(e);
out.push('"');
} else {
out.push_str(e);
}
}
out
}
pub fn contains_context_variable_token(s: &str) -> bool {
crate::context::contains_context_variable_token(s)
}
pub fn contains_context_variables(s: &str) -> bool {
crate::context::contains_context_variables(s)
}
pub fn format_g(v: f64, precision: usize) -> String {
if v.is_nan() {
return if v.is_sign_negative() {
"-nan".to_string()
} else {
"nan".to_string()
};
}
if v.is_infinite() {
return if v < 0.0 {
"-inf".to_string()
} else {
"inf".to_string()
};
}
let p = precision.max(1);
if v == 0.0 {
return if v.is_sign_negative() {
"-0".to_string()
} else {
"0".to_string()
};
}
let sci = format!("{:.*e}", p - 1, v);
let (mant, exp) = match sci.split_once('e') {
Some((m, e)) => (m.to_string(), e.parse::<i32>().unwrap_or(0)),
None => (sci.clone(), 0),
};
if exp < -4 || exp >= p as i32 {
let mut m = mant;
if m.contains('.') {
while m.ends_with('0') {
m.pop();
}
if m.ends_with('.') {
m.pop();
}
}
let sign = if exp < 0 { '-' } else { '+' };
format!("{m}e{sign}{:02}", exp.abs())
} else {
let decimals = (p as i32 - 1 - exp).max(0) as usize;
let mut s = format!("{:.*}", decimals, v);
if s.contains('.') {
while s.ends_with('0') {
s.pop();
}
if s.ends_with('.') {
s.pop();
}
}
s
}
}
pub fn fmt_f64(v: f64) -> String {
format_g(v, 6)
}
pub fn fmt_f32(v: f32) -> String {
format_g(v as f64, 6)
}
pub fn double_to_string(v: f64) -> String {
format_g(v, 16)
}
pub fn float_to_string(v: f32) -> String {
format_g(v as f64, 7)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn g_format() {
assert_eq!(format_g(0.2126, 15), "0.2126");
assert_eq!(format_g(1.0, 15), "1");
assert_eq!(format_g(2.2, 15), "2.2");
assert_eq!(format_g(1e-10, 15), "1e-10");
assert_eq!(format_g(123456789.0, 6), "1.23457e+08");
assert_eq!(format_g(0.1f32 as f64, 15), "0.100000001490116");
assert_eq!(format_g(0.1f32 as f64, 7), "0.1");
assert_eq!(format_g(-0.0001, 6), "-0.0001");
assert_eq!(format_g(0.00001, 6), "1e-05");
assert_eq!(format_g(100000.0, 6), "100000");
assert_eq!(format_g(1000000.0, 6), "1e+06");
}
#[test]
fn env_style() {
assert_eq!(split_string_env_style("a, b").unwrap(), vec!["a", "b"]);
assert_eq!(split_string_env_style("a:b").unwrap(), vec!["a", "b"]);
assert_eq!(split_string_env_style("").unwrap(), vec![""]);
assert_eq!(split_string_env_style("a,").unwrap(), vec!["a", ""]);
assert_eq!(
split_string_env_style("\"a, b\", c").unwrap(),
vec!["a, b", "c"]
);
assert!(split_string_env_style("\"a, b").is_err());
assert_eq!(
join_string_env_style(&["a, b".to_string(), "c".to_string()]),
"\"a, b\", c"
);
}
}