use std::ffi::CStr;
#[cfg(unix)]
static NL_TABLE: &[(&str, libc::nl_item)] = &[ ("CODESET", libc::CODESET), ("D_T_FMT", libc::D_T_FMT),
("D_FMT", libc::D_FMT), ("T_FMT", libc::T_FMT),
("RADIXCHAR", libc::RADIXCHAR), ("THOUSEP", libc::THOUSEP),
("YESEXPR", libc::YESEXPR), ("NOEXPR", libc::NOEXPR),
#[cfg(target_os = "linux")]
("CRNCYSTR", libc::CRNCYSTR),
("ABDAY_1", libc::ABDAY_1), ("ABDAY_2", libc::ABDAY_2),
("ABDAY_3", libc::ABDAY_3), ("ABDAY_4", libc::ABDAY_4),
("ABDAY_5", libc::ABDAY_5), ("ABDAY_6", libc::ABDAY_6),
("ABDAY_7", libc::ABDAY_7),
("DAY_1", libc::DAY_1), ("DAY_2", libc::DAY_2),
("DAY_3", libc::DAY_3), ("DAY_4", libc::DAY_4),
("DAY_5", libc::DAY_5), ("DAY_6", libc::DAY_6),
("DAY_7", libc::DAY_7),
("ABMON_1", libc::ABMON_1), ("ABMON_2", libc::ABMON_2),
("ABMON_3", libc::ABMON_3), ("ABMON_4", libc::ABMON_4),
("ABMON_5", libc::ABMON_5), ("ABMON_6", libc::ABMON_6),
("ABMON_7", libc::ABMON_7), ("ABMON_8", libc::ABMON_8),
("ABMON_9", libc::ABMON_9), ("ABMON_10", libc::ABMON_10),
("ABMON_11", libc::ABMON_11), ("ABMON_12", libc::ABMON_12),
("MON_1", libc::MON_1), ("MON_2", libc::MON_2),
("MON_3", libc::MON_3), ("MON_4", libc::MON_4),
("MON_5", libc::MON_5), ("MON_6", libc::MON_6),
("MON_7", libc::MON_7), ("MON_8", libc::MON_8),
("MON_9", libc::MON_9), ("MON_10", libc::MON_10),
("MON_11", libc::MON_11), ("MON_12", libc::MON_12),
("T_FMT_AMPM", libc::T_FMT_AMPM), ("AM_STR", libc::AM_STR),
("PM_STR", libc::PM_STR), ("ERA", libc::ERA),
("ERA_D_FMT", libc::ERA_D_FMT), ("ERA_D_T_FMT", libc::ERA_D_T_FMT),
("ERA_T_FMT", libc::ERA_T_FMT), ("ALT_DIGITS", libc::ALT_DIGITS),
];
#[cfg(unix)]
pub fn liitem(name: &str) -> Option<libc::nl_item> { NL_TABLE.iter().find(|(n, _)| *n == name).map(|(_, v)| *v) }
#[cfg(not(unix))]
#[allow(unused_variables)]
pub fn liitem(name: &str) -> Option<i32> { None
}
#[cfg(unix)]
pub fn getlanginfo(name: &str) -> Option<String> { let mut buf = name.as_bytes().to_vec(); crate::ported::utils::unmetafy(&mut buf); let nameu = std::str::from_utf8(&buf).ok()?;
let elem = liitem(nameu)?; unsafe {
let ptr = libc::nl_langinfo(elem); if ptr.is_null() {
return None; }
let s = CStr::from_ptr(ptr).to_string_lossy().into_owned();
if s.is_empty() {
return None;
}
Some(s) }
}
#[cfg(not(unix))]
pub fn getlanginfo(_name: &str) -> Option<String> { None
}
pub fn scanlanginfo() -> Vec<(String, String)> { let mut out = Vec::new();
for &name in NL_NAMES { if let Some(v) = getlanginfo(name) { out.push((name.to_string(), v)); }
}
out
}
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 { 0 }
use crate::ported::zsh_h::module;
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 { *features = featuresarray(m, module_features());
0 }
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 { handlefeatures(m, module_features(), enables) }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 { 0 }
pub fn cleanup_(m: *const module) -> i32 { setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0 }
pub static NL_NAMES: &[&str] = &[ "CODESET", "D_T_FMT", "D_FMT", "T_FMT",
"RADIXCHAR", "THOUSEP", "YESEXPR", "NOEXPR", "CRNCYSTR",
"ABDAY_1", "ABDAY_2", "ABDAY_3", "ABDAY_4",
"ABDAY_5", "ABDAY_6", "ABDAY_7",
"DAY_1", "DAY_2", "DAY_3", "DAY_4", "DAY_5", "DAY_6", "DAY_7",
"ABMON_1", "ABMON_2", "ABMON_3", "ABMON_4", "ABMON_5", "ABMON_6",
"ABMON_7", "ABMON_8", "ABMON_9", "ABMON_10", "ABMON_11", "ABMON_12",
"MON_1", "MON_2", "MON_3", "MON_4", "MON_5", "MON_6",
"MON_7", "MON_8", "MON_9", "MON_10", "MON_11", "MON_12",
"T_FMT_AMPM", "AM_STR", "PM_STR",
"ERA", "ERA_D_FMT", "ERA_D_T_FMT", "ERA_T_FMT", "ALT_DIGITS",
];
use crate::ported::zsh_h::features as features_t;
use std::sync::{Mutex, OnceLock};
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["p:langinfo".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
bn_list: None,
bn_size: 0,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 1,
n_abstract: 0,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nl_names_includes_codeset() {
assert!(NL_NAMES.contains(&"CODESET"));
assert!(NL_NAMES.contains(&"D_T_FMT"));
}
#[cfg(unix)]
#[test]
fn getlanginfo_codeset_is_some() {
assert!(getlanginfo("CODESET").is_some());
}
#[test]
fn getlanginfo_invalid_returns_none() {
assert!(getlanginfo("INVALID_NAME").is_none());
}
#[cfg(unix)]
#[test]
fn liitem_codeset_resolves() {
assert!(liitem("CODESET").is_some());
assert!(liitem("DOES_NOT_EXIST").is_none());
}
#[cfg(unix)]
#[test]
fn scanlanginfo_emits_items() {
let v = scanlanginfo();
assert!(!v.is_empty());
assert!(v.iter().any(|(k, _)| k == "CODESET"));
}
#[test]
fn nl_names_covers_canonical_locale_items() {
for required in [
"CODESET", "D_T_FMT", "D_FMT", "T_FMT", "T_FMT_AMPM",
"AM_STR", "PM_STR", "DAY_1", "DAY_7", "ABDAY_1", "MON_1",
"MON_12", "RADIXCHAR", "THOUSEP", "YESEXPR", "NOEXPR",
] {
assert!(NL_NAMES.contains(&required),
"NL_NAMES missing {} — port table truncated?", required);
}
}
#[test]
fn nl_names_entries_are_uppercase_identifiers() {
for &n in NL_NAMES {
assert!(!n.is_empty(), "empty entry in NL_NAMES");
assert!(n.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'),
"NL_NAMES entry {:?} contains non-uppercase chars", n);
assert!(!n.starts_with(|c: char| c.is_ascii_digit()),
"NL_NAMES entry {:?} starts with a digit", n);
}
}
#[test]
fn nl_names_has_no_duplicates() {
let unique: std::collections::HashSet<_> = NL_NAMES.iter().copied().collect();
assert_eq!(unique.len(), NL_NAMES.len(),
"duplicate entry in NL_NAMES");
}
#[cfg(unix)]
#[test]
fn scanlanginfo_keys_are_subset_of_nl_names() {
for (k, _) in scanlanginfo() {
assert!(NL_NAMES.contains(&k.as_str()),
"scanlanginfo emitted {:?} which is not in NL_NAMES", k);
}
}
#[cfg(unix)]
#[test]
fn getlanginfo_is_case_sensitive() {
assert!(getlanginfo("CODESET").is_some());
assert!(getlanginfo("codeset").is_none(),
"getlanginfo must be case-sensitive per the C source's strcmp lookup");
}
#[test]
fn module_lifecycle_shims_all_return_zero() {
let m: *const crate::ported::zsh_h::module = std::ptr::null();
assert_eq!(setup_(m), 0);
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
}