use std::borrow::ToOwned;
use std::ffi::{CStr, CString};
use std::fs;
use std::io::{self, Read};
use anyhow::{anyhow, Context};
use encoding::label as enc_label;
use encoding::DecoderTrap;
use once_cell::sync::Lazy;
use crate::defs::{ConfgetError, Config, FileData};
#[cfg(feature = "ini-nom")]
pub mod ini_nom;
#[cfg(feature = "ini-regex")]
pub mod ini_re;
pub type DataRead = (FileData, String);
pub trait Backend<'cfg> {
fn from_config(config: &'cfg Config) -> Result<Self, ConfgetError>
where
Self: Sized;
fn read_file(&self) -> Result<DataRead, ConfgetError>;
}
static DEFAULT_ENCODING: Lazy<Result<String, String>> = Lazy::new(|| {
let langinfo_cstr = {
let empty_str = CString::from_vec_with_nul(vec![0])
.map_err(|err| format!("Could not build an empty C-style string: {err}"))?;
// SAFETY: `once_cell::sync` should prevent data races.
if unsafe { libc::setlocale(libc::LC_CTYPE, empty_str.as_c_str().as_ptr()) }.is_null() {
return Err("setlocale() failed".to_owned());
}
// SAFETY: `once_cell::sync` should prevent data races.
let langinfo_ptr = unsafe { libc::nl_langinfo(libc::CODESET) };
if langinfo_ptr.is_null() {
return Err("nl_langinfo(CODESET) returned a null pointer".to_owned());
}
// SAFETY: we just validated it.
unsafe { CStr::from_ptr(langinfo_ptr) }
};
match langinfo_cstr.to_str() {
Ok(enc_ref) => {
if enc_ref.is_empty() {
Err("nl_langinfo(CODESET) returned an empty string".to_owned())
} else {
Ok(enc_ref.to_owned())
}
}
Err(err) => Err(format!(
"nl_langinfo(CODESET) returned a non-UTF-8 string: {err}"
)),
}
});
fn get_file_lines(filename: &str, encoding: &str) -> Result<Vec<String>, ConfgetError> {
let encoding_name = if encoding.is_empty() {
DEFAULT_ENCODING
.as_ref()
.map_err(|err| ConfgetError::Internal((*err).clone()))?
} else {
encoding
};
let enc_ref = enc_label::encoding_from_whatwg_label(encoding_name)
.with_context(|| {
format!(
"Unsupported encoding '{escaped}'",
escaped = encoding_name.escape_debug()
)
})
.map_err(ConfgetError::ReadData)?;
let raw = if filename == "-" {
let mut buf = vec![];
io::stdin()
.lock()
.read_to_end(&mut buf)
.context("Could not read from the standard input")
.map_err(ConfgetError::ReadData)?;
buf
} else {
fs::read(filename)
.with_context(|| {
format!(
"Could not read from the '{escaped}' file",
escaped = filename.escape_debug()
)
})
.map_err(ConfgetError::ReadData)?
};
let contents = enc_ref.decode(&raw, DecoderTrap::Strict).map_err(|err| {
ConfgetError::ReadData(anyhow!(
"Could not decode the {} input bytes using the '{}' encoding: {}",
raw.len(),
encoding_name,
err
))
})?;
Ok(contents.lines().map(ToOwned::to_owned).collect())
}