use core::marker::PhantomData;
use core::slice;
use core::str;
use arrayvec::ArrayString;
use libc::{self, c_char};
use crate::constants::{MAX_INF_LEN, MAX_MIN_LEN, MAX_NAN_LEN};
use crate::errors::Error;
use crate::format::utils::{InfinityStr, MinusSignStr, NanStr};
use crate::format::{Format, Grouping, Locale};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
pub struct Environment {
dec: char,
grp: Grouping,
inf: ArrayString<[u8; MAX_INF_LEN]>,
min: ArrayString<[u8; MAX_MIN_LEN]>,
nan: ArrayString<[u8; MAX_NAN_LEN]>,
sep: Option<char>,
}
impl Environment {
pub fn new() -> Result<Environment, Error> {
let empty_slice = &['\0' as c_char];
let _ = unsafe { libc::setlocale(libc::LC_MONETARY, empty_slice.as_ptr()) };
let ptr = unsafe { libc::localeconv() };
if ptr.is_null() {
return Err(Error::c("C function 'localeconv' returned a null pointer"));
}
let lconv: &libc::lconv = unsafe { ptr.as_ref() }.unwrap();
let dec_ptr = SafePointer::new(lconv.mon_decimal_point)?;
let grp_ptr = SafePointer::new(lconv.mon_grouping)?;
let min_ptr = SafePointer::new(lconv.negative_sign)?;
let sep_ptr = SafePointer::new(lconv.mon_thousands_sep)?;
let maybe_dec = dec_ptr.as_char()?;
let grp = grp_ptr.as_grouping()?;
let min = min_ptr.as_str()?;
let maybe_sep = sep_ptr.as_char()?;
let environment = Environment {
dec: maybe_dec.unwrap_or_else(|| '.'),
grp,
inf: ArrayString::from(Locale::en.infinity()).unwrap(),
min: ArrayString::from(min).map_err(|_| Error::capacity(MAX_MIN_LEN))?,
nan: ArrayString::from(Locale::en.nan()).unwrap(),
sep: maybe_sep,
};
Ok(environment)
}
pub fn decimal(&self) -> char {
self.dec
}
pub fn grouping(&self) -> Grouping {
self.grp
}
pub fn infinity(&self) -> &str {
&self.inf
}
pub fn minus_sign(&self) -> &str {
&self.min
}
pub fn nan(&self) -> &str {
&self.nan
}
pub fn separator(&self) -> Option<char> {
self.sep
}
pub fn set_infinity<S>(&mut self, value: S) -> Result<(), Error>
where
S: AsRef<str>,
{
let s = value.as_ref();
self.inf = ArrayString::from(s).map_err(|_| Error::capacity(MAX_INF_LEN))?;
Ok(())
}
pub fn set_nan<S>(&mut self, value: S) -> Result<(), Error>
where
S: AsRef<str>,
{
let s = value.as_ref();
self.nan = ArrayString::from(s).map_err(|_| Error::capacity(MAX_NAN_LEN))?;
Ok(())
}
}
impl Format for Environment {
fn decimal(&self) -> char {
self.decimal()
}
fn grouping(&self) -> Grouping {
self.grouping()
}
fn infinity(&self) -> InfinityStr<'_> {
InfinityStr::new(self.infinity()).unwrap()
}
fn minus_sign(&self) -> MinusSignStr<'_> {
MinusSignStr::new(self.minus_sign()).unwrap()
}
fn nan(&self) -> NanStr<'_> {
NanStr::new(self.nan()).unwrap()
}
fn separator(&self) -> Option<char> {
self.separator()
}
}
struct SafePointer<'a> {
ptr: *const c_char,
phantom: PhantomData<&'a ()>,
}
impl<'a> SafePointer<'a> {
fn new(ptr: *const c_char) -> Result<SafePointer<'a>, Error> {
if ptr.is_null() {
return Err(Error::c("received a null pointer"));
}
Ok(SafePointer {
ptr,
phantom: PhantomData,
})
}
fn as_char(&self) -> Result<Option<char>, Error> {
let len = unsafe { libc::strlen(self.ptr) };
let s = unsafe { slice::from_raw_parts(self.ptr as *const u8, len) };
let s = str::from_utf8(s)
.map_err(|_| Error::c("could not parse data returned from C into utf-8"))?;
if s.chars().count() > 1 {
return Err(Error::c(
"received C string of length greater than 1 when C string of length 1 was expected",
));
}
Ok(s.chars().next())
}
fn as_grouping(&self) -> Result<Grouping, Error> {
let len = unsafe { libc::strlen(self.ptr) };
let s = unsafe { slice::from_raw_parts(self.ptr as *const u8, len) };
match s {
[3, 3] => Ok(Grouping::Standard),
[3, 2] => Ok(Grouping::Indian),
[] => Ok(Grouping::Posix),
_ => Err(Error::c("received unexpected grouping code from C")),
}
}
fn as_str(&self) -> Result<&str, Error> {
let len = unsafe { libc::strlen(self.ptr) };
let s = unsafe { slice::from_raw_parts(self.ptr as *const u8, len) };
let s = str::from_utf8(s)
.map_err(|_| Error::c("could not parse data returned from C into utf-8"))?;
if s.len() > MAX_MIN_LEN {
return Err(Error::capacity(MAX_MIN_LEN));
}
Ok(s)
}
}