use crate::ported::zsh_h::features;
use crate::utils::unmetafy;
use crate::zsh_h::module;
use std::ffi::CStr;
use std::sync::{Mutex, OnceLock};
#[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(); 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 }
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",
];
static MODULE_FEATURES: OnceLock<Mutex<features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features>) -> Vec<String> {
vec!["p:langinfo".to_string()]
}
fn handlefeatures(_m: *const module, _f: &Mutex<features>, enables: &mut Option<Vec<i32>>) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(features {
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() {
let _g = crate::test_util::global_state_lock();
assert!(NL_NAMES.contains(&"CODESET"));
assert!(NL_NAMES.contains(&"D_T_FMT"));
}
#[cfg(unix)]
#[test]
fn getlanginfo_codeset_is_some() {
let _g = crate::test_util::global_state_lock();
assert!(getlanginfo("CODESET").is_some());
}
#[test]
fn getlanginfo_invalid_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(getlanginfo("INVALID_NAME").is_none());
}
#[cfg(unix)]
#[test]
fn liitem_codeset_resolves() {
let _g = crate::test_util::global_state_lock();
assert!(liitem("CODESET").is_some());
assert!(liitem("DOES_NOT_EXIST").is_none());
}
#[cfg(unix)]
#[test]
fn scanlanginfo_emits_items() {
let _g = crate::test_util::global_state_lock();
let v = scanlanginfo();
assert!(!v.is_empty());
assert!(v.iter().any(|(k, _)| k == "CODESET"));
}
#[test]
fn nl_names_covers_canonical_locale_items() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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);
}
#[test]
fn langinfo_corpus_codeset_is_known() {
let _g = crate::test_util::global_state_lock();
assert!(
liitem("CODESET").is_some(),
"CODESET is a POSIX-standard nl_item key"
);
}
#[test]
fn langinfo_corpus_day_1_is_known() {
let _g = crate::test_util::global_state_lock();
assert!(
liitem("DAY_1").is_some(),
"DAY_1 is a POSIX-standard nl_item key"
);
}
#[test]
fn langinfo_corpus_radixchar_is_known() {
let _g = crate::test_util::global_state_lock();
assert!(liitem("RADIXCHAR").is_some());
}
#[test]
fn langinfo_corpus_unknown_key_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(liitem("BOGUS_NOT_REAL_KEY").is_none());
assert!(liitem("").is_none());
}
#[test]
fn langinfo_corpus_getlanginfo_codeset_nonempty() {
let _g = crate::test_util::global_state_lock();
let r = getlanginfo("CODESET");
assert!(r.is_some(), "CODESET resolves to a string");
let s = r.unwrap();
assert!(!s.is_empty(), "CODESET non-empty (e.g. 'UTF-8'), got {s:?}");
}
#[test]
fn langinfo_corpus_scanlanginfo_returns_entries() {
let _g = crate::test_util::global_state_lock();
let entries = scanlanginfo();
assert!(
!entries.is_empty(),
"scanlanginfo should return some entries"
);
}
#[test]
#[cfg(unix)]
fn liitem_canonical_posix_names_resolve() {
let _g = crate::test_util::global_state_lock();
assert!(liitem("CODESET").is_some(), "CODESET is POSIX-required");
assert!(liitem("D_FMT").is_some(), "D_FMT is POSIX-required");
assert!(liitem("T_FMT").is_some(), "T_FMT is POSIX-required");
}
#[test]
#[cfg(unix)]
fn liitem_is_deterministic() {
let _g = crate::test_util::global_state_lock();
for name in &["CODESET", "BOGUS_XYZ", "", "AM_STR"] {
let first = liitem(name);
for _ in 0..5 {
assert_eq!(liitem(name), first, "{:?} must be pure", name);
}
}
}
#[test]
#[cfg(unix)]
fn getlanginfo_empty_name_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(getlanginfo("").is_none());
}
#[test]
#[cfg(unix)]
fn getlanginfo_unknown_name_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(getlanginfo("zzz_never_a_real_key").is_none());
}
#[test]
#[cfg(unix)]
fn scanlanginfo_includes_codeset() {
let _g = crate::test_util::global_state_lock();
let entries = scanlanginfo();
let has_codeset = entries.iter().any(|(k, _)| k == "CODESET");
assert!(has_codeset, "POSIX CODESET must appear in scan output");
}
#[test]
#[cfg(unix)]
fn scanlanginfo_is_deterministic_for_static_locale() {
let _g = crate::test_util::global_state_lock();
let a = scanlanginfo();
let b = scanlanginfo();
assert_eq!(a, b, "two consecutive scans must agree");
}
#[test]
fn langinfo_setup_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(setup_(std::ptr::null()), 0);
}
#[test]
fn langinfo_boot_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(boot_(std::ptr::null()), 0);
}
#[cfg(unix)]
#[test]
fn liitem_empty_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(liitem("").is_none(), "empty name → None");
}
#[cfg(unix)]
#[test]
fn liitem_is_deterministic_full_sweep() {
let _g = crate::test_util::global_state_lock();
for s in ["CODESET", "DAY_1", "MON_1", "RADIXCHAR", "__unknown_xyz__"] {
let first = liitem(s);
for _ in 0..3 {
assert_eq!(liitem(s), first, "liitem({:?}) must be deterministic", s);
}
}
}
#[cfg(unix)]
#[test]
fn getlanginfo_returns_option_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<String> = getlanginfo("CODESET");
}
#[cfg(unix)]
#[test]
fn getlanginfo_deterministic_for_codeset() {
let _g = crate::test_util::global_state_lock();
let first = getlanginfo("CODESET");
for _ in 0..3 {
assert_eq!(
getlanginfo("CODESET"),
first,
"getlanginfo('CODESET') must be deterministic"
);
}
}
#[test]
fn scanlanginfo_returns_vec_tuple_type() {
let _g = crate::test_util::global_state_lock();
let _: Vec<(String, String)> = scanlanginfo();
}
#[cfg(unix)]
#[test]
fn scanlanginfo_full_sweep_deterministic() {
let _g = crate::test_util::global_state_lock();
let first = scanlanginfo();
for _ in 0..3 {
assert_eq!(
scanlanginfo(),
first,
"scanlanginfo must be fully deterministic"
);
}
}
#[test]
fn langinfo_cleanup_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(cleanup_(std::ptr::null()), 0);
}
#[test]
fn langinfo_finish_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(finish_(std::ptr::null()), 0);
}
#[cfg(unix)]
#[test]
fn scanlanginfo_entries_are_ascii_keys() {
let _g = crate::test_util::global_state_lock();
let entries = scanlanginfo();
for (k, _v) in &entries {
assert!(k.is_ascii(), "key {:?} must be ASCII", k);
}
}
#[test]
fn langinfo_setup_cleanup_round_trip_safe() {
let _g = crate::test_util::global_state_lock();
assert_eq!(setup_(std::ptr::null()), 0);
assert_eq!(cleanup_(std::ptr::null()), 0);
assert_eq!(setup_(std::ptr::null()), 0);
assert_eq!(cleanup_(std::ptr::null()), 0);
}
#[cfg(unix)]
#[test]
fn liitem_empty_string_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(liitem("").is_none(), "empty langinfo item must be None");
}
#[cfg(unix)]
#[test]
fn liitem_nonsense_name_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(
liitem("___definitely_not_a_langinfo_item_xyz___").is_none(),
"unknown langinfo name must be None"
);
}
#[cfg(unix)]
#[test]
fn liitem_codeset_returns_some() {
let _g = crate::test_util::global_state_lock();
assert!(
liitem("CODESET").is_some(),
"CODESET must resolve on every POSIX libc"
);
}
#[cfg(unix)]
#[test]
fn getlanginfo_empty_name_returns_none_alt() {
let _g = crate::test_util::global_state_lock();
assert!(
getlanginfo("").is_none(),
"empty langinfo name must yield None"
);
}
#[cfg(unix)]
#[test]
fn getlanginfo_nonsense_name_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(getlanginfo("___bogus_langinfo_xyz___").is_none());
}
#[cfg(unix)]
#[test]
fn scanlanginfo_no_empty_keys() {
let _g = crate::test_util::global_state_lock();
for (k, _v) in scanlanginfo() {
assert!(!k.is_empty(), "no scanlanginfo entry may have empty key");
}
}
#[cfg(unix)]
#[test]
fn scanlanginfo_no_duplicate_keys() {
let _g = crate::test_util::global_state_lock();
let entries = scanlanginfo();
let mut seen = std::collections::HashSet::new();
for (k, _) in &entries {
assert!(
seen.insert(k.clone()),
"duplicate langinfo key {:?} in scanlanginfo output",
k
);
}
}
#[cfg(unix)]
#[test]
fn scanlanginfo_keys_are_uppercase_ascii() {
let _g = crate::test_util::global_state_lock();
for (k, _) in scanlanginfo() {
assert!(
k.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'),
"langinfo key {:?} must be uppercase ASCII + digits + underscore",
k
);
}
}
#[test]
fn langinfo_setup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = setup_(std::ptr::null());
}
#[test]
fn langinfo_features_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let mut v: Vec<String> = Vec::new();
let _: i32 = features_(std::ptr::null(), &mut v);
}
#[test]
fn langinfo_boot_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = boot_(std::ptr::null());
}
#[test]
fn langinfo_setup_idempotent_alt_pin() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(setup_(std::ptr::null()), 0);
}
}
#[test]
fn langinfo_cleanup_idempotent_alt_pin() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(cleanup_(std::ptr::null()), 0);
}
}
#[test]
fn langinfo_finish_idempotent_alt_pin() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(finish_(std::ptr::null()), 0);
}
}
#[test]
fn langinfo_cleanup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = cleanup_(std::ptr::null());
}
#[test]
fn langinfo_finish_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = finish_(std::ptr::null());
}
#[test]
fn liitem_empty_name_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(liitem("").is_none(), "empty name must yield None");
}
#[test]
fn liitem_unknown_name_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(
liitem("__never_real_li_item_xyz__").is_none(),
"unknown name must yield None"
);
}
#[test]
fn getlanginfo_empty_name_returns_none_alt_2() {
let _g = crate::test_util::global_state_lock();
assert!(getlanginfo("").is_none(), "empty name must yield None");
}
#[test]
fn getlanginfo_deterministic_for_same_input() {
let _g = crate::test_util::global_state_lock();
let a = getlanginfo("CODESET");
let b = getlanginfo("CODESET");
assert_eq!(a, b, "getlanginfo(CODESET) must be deterministic");
}
#[test]
fn scanlanginfo_returns_vec_tuple_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Vec<(String, String)> = scanlanginfo();
}
#[test]
fn scanlanginfo_deterministic_repeated_calls() {
let _g = crate::test_util::global_state_lock();
let a = scanlanginfo();
let b = scanlanginfo();
assert_eq!(a, b, "scanlanginfo must be deterministic");
}
#[test]
fn langinfo_enables_with_some_non_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let mut e: Option<Vec<i32>> = Some(vec![1, 2, 3]);
let _ = enables_(std::ptr::null(), &mut e);
}
#[test]
fn langinfo_setup_boot_finish_chain_returns_zero_each() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
assert_eq!(setup_(null), 0);
assert_eq!(boot_(null), 0);
assert_eq!(finish_(null), 0);
}
}