Skip to main content

rustpython_host_env/
locale.rs

1use alloc::vec::Vec;
2use core::{ffi::CStr, ptr};
3
4pub use libc::{LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME};
5
6#[cfg(all(unix, not(any(target_os = "ios", target_os = "redox"))))]
7pub use libc::LC_MESSAGES;
8
9#[cfg(all(
10    unix,
11    not(any(target_os = "ios", target_os = "android", target_os = "redox"))
12))]
13pub use libc::{
14    ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, ABMON_1, ABMON_2, ABMON_3,
15    ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12, ALT_DIGITS,
16    AM_STR, CODESET, CRNCYSTR, D_FMT, D_T_FMT, DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7,
17    ERA, ERA_D_FMT, ERA_D_T_FMT, ERA_T_FMT, MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8,
18    MON_9, MON_10, MON_11, MON_12, NOEXPR, PM_STR, RADIXCHAR, T_FMT, T_FMT_AMPM, THOUSEP, YESEXPR,
19};
20
21#[cfg(windows)]
22#[repr(C)]
23struct RawLconv {
24    decimal_point: *mut libc::c_char,
25    thousands_sep: *mut libc::c_char,
26    grouping: *mut libc::c_char,
27    int_curr_symbol: *mut libc::c_char,
28    currency_symbol: *mut libc::c_char,
29    mon_decimal_point: *mut libc::c_char,
30    mon_thousands_sep: *mut libc::c_char,
31    mon_grouping: *mut libc::c_char,
32    positive_sign: *mut libc::c_char,
33    negative_sign: *mut libc::c_char,
34    int_frac_digits: libc::c_char,
35    frac_digits: libc::c_char,
36    p_cs_precedes: libc::c_char,
37    p_sep_by_space: libc::c_char,
38    n_cs_precedes: libc::c_char,
39    n_sep_by_space: libc::c_char,
40    p_sign_posn: libc::c_char,
41    n_sign_posn: libc::c_char,
42}
43
44#[cfg(windows)]
45unsafe extern "C" {
46    fn localeconv() -> *mut RawLconv;
47}
48
49#[cfg(unix)]
50use libc::localeconv;
51
52#[derive(Debug, Clone)]
53pub struct LocaleConv {
54    pub decimal_point: Vec<u8>,
55    pub thousands_sep: Vec<u8>,
56    pub grouping: Vec<libc::c_char>,
57    pub int_curr_symbol: Vec<u8>,
58    pub currency_symbol: Vec<u8>,
59    pub mon_decimal_point: Vec<u8>,
60    pub mon_thousands_sep: Vec<u8>,
61    pub mon_grouping: Vec<libc::c_char>,
62    pub positive_sign: Vec<u8>,
63    pub negative_sign: Vec<u8>,
64    pub int_frac_digits: libc::c_char,
65    pub frac_digits: libc::c_char,
66    pub p_cs_precedes: libc::c_char,
67    pub p_sep_by_space: libc::c_char,
68    pub n_cs_precedes: libc::c_char,
69    pub n_sep_by_space: libc::c_char,
70    pub p_sign_posn: libc::c_char,
71    pub n_sign_posn: libc::c_char,
72}
73
74fn copy_cstr(ptr: *const libc::c_char) -> Vec<u8> {
75    if ptr.is_null() {
76        Vec::new()
77    } else {
78        unsafe { CStr::from_ptr(ptr) }.to_bytes().to_vec()
79    }
80}
81
82fn copy_grouping(ptr: *const libc::c_char) -> Vec<libc::c_char> {
83    if ptr.is_null() {
84        return Vec::new();
85    }
86    let mut out = Vec::new();
87    let mut cur = ptr;
88    unsafe {
89        while ![0, libc::c_char::MAX].contains(&*cur) {
90            out.push(*cur);
91            cur = cur.add(1);
92        }
93    }
94    out
95}
96
97/// Every byte of a NUL-terminated C string, `CHAR_MAX` included.
98///
99/// `localeconv().grouping` spells a "repeat last group" / "stop" terminator
100/// as `CHAR_MAX`. Stopping at that value drops the distinction; this reader
101/// keeps it, the way a NUL-only C-string walk does.
102///
103/// # Safety
104///
105/// `ptr` must be null or point to a NUL-terminated C string that remains valid
106/// for the duration of the call.
107pub unsafe fn charp2bytes(ptr: *const libc::c_char) -> Vec<u8> {
108    let mut out = Vec::new();
109    if !ptr.is_null() {
110        let mut cur = ptr;
111        unsafe {
112            while *cur != 0 {
113                out.push(*cur as u8);
114                cur = cur.add(1);
115            }
116        }
117    }
118    out
119}
120
121/// Decimal point, thousands separator and grouping of the current locale,
122/// as the raw bytes `localeconv()` reports. Grouping keeps a `CHAR_MAX`
123/// terminator when the C locale spells one.
124pub fn localeconv_numeric() -> (Vec<u8>, Vec<u8>, Vec<u8>) {
125    let lc = unsafe { localeconv() };
126    if lc.is_null() {
127        return (b".".to_vec(), Vec::new(), Vec::new());
128    }
129    unsafe {
130        (
131            charp2bytes((*lc).decimal_point),
132            charp2bytes((*lc).thousands_sep),
133            charp2bytes((*lc).grouping),
134        )
135    }
136}
137
138pub fn localeconv_data() -> LocaleConv {
139    let lc = unsafe { localeconv() };
140    unsafe {
141        LocaleConv {
142            decimal_point: copy_cstr((*lc).decimal_point),
143            thousands_sep: copy_cstr((*lc).thousands_sep),
144            grouping: copy_grouping((*lc).grouping),
145            int_curr_symbol: copy_cstr((*lc).int_curr_symbol),
146            currency_symbol: copy_cstr((*lc).currency_symbol),
147            mon_decimal_point: copy_cstr((*lc).mon_decimal_point),
148            mon_thousands_sep: copy_cstr((*lc).mon_thousands_sep),
149            mon_grouping: copy_grouping((*lc).mon_grouping),
150            positive_sign: copy_cstr((*lc).positive_sign),
151            negative_sign: copy_cstr((*lc).negative_sign),
152            int_frac_digits: (*lc).int_frac_digits,
153            frac_digits: (*lc).frac_digits,
154            p_cs_precedes: (*lc).p_cs_precedes,
155            p_sep_by_space: (*lc).p_sep_by_space,
156            n_cs_precedes: (*lc).n_cs_precedes,
157            n_sep_by_space: (*lc).n_sep_by_space,
158            p_sign_posn: (*lc).p_sign_posn,
159            n_sign_posn: (*lc).n_sign_posn,
160        }
161    }
162}
163
164pub fn strcoll(string1: &CStr, string2: &CStr) -> libc::c_int {
165    unsafe { libc::strcoll(string1.as_ptr(), string2.as_ptr()) }
166}
167
168pub fn strxfrm(string: &CStr, _initial_len: usize) -> Vec<u8> {
169    let len = unsafe { libc::strxfrm(ptr::null_mut(), string.as_ptr(), 0) };
170    let mut buff = vec![0u8; len + 1];
171    unsafe {
172        libc::strxfrm(buff.as_mut_ptr() as _, string.as_ptr(), buff.len());
173    }
174    buff.truncate(len);
175    buff
176}
177
178pub fn setlocale(category: i32, locale: Option<&CStr>) -> Option<Vec<u8>> {
179    let result = unsafe {
180        match locale {
181            None => libc::setlocale(category, ptr::null()),
182            Some(locale) => libc::setlocale(category, locale.as_ptr()),
183        }
184    };
185    (!result.is_null()).then(|| unsafe { CStr::from_ptr(result) }.to_bytes().to_vec())
186}
187
188#[cfg(windows)]
189pub fn acp() -> u32 {
190    unsafe { windows_sys::Win32::Globalization::GetACP() }
191}
192
193#[cfg(windows)]
194pub fn user_default_lcid() -> u32 {
195    unsafe { windows_sys::Win32::Globalization::GetUserDefaultLCID() }
196}
197
198#[cfg(windows)]
199pub const LOCALE_SISO639LANGNAME: u32 = 0x0000_0059;
200#[cfg(windows)]
201pub const LOCALE_SISO3166CTRYNAME: u32 = 0x0000_005A;
202
203/// `GetLocaleInfoW` into a 16-unit buffer, the size `_getdefaultlocale` uses.
204#[cfg(windows)]
205pub fn locale_info(lcid: u32, lctype: u32) -> Option<String> {
206    use windows_sys::Win32::Globalization::GetLocaleInfoW;
207
208    let mut buffer = [0u16; 16];
209    let len = unsafe { GetLocaleInfoW(lcid, lctype, buffer.as_mut_ptr(), buffer.len() as i32) };
210    if len <= 1 {
211        None
212    } else {
213        Some(String::from_utf16_lossy(&buffer[..len as usize - 1]))
214    }
215}
216
217#[cfg(windows)]
218fn wide_cstr(units: &[u16]) -> alloc::borrow::Cow<'_, [u16]> {
219    if units.last() == Some(&0) {
220        alloc::borrow::Cow::Borrowed(units)
221    } else {
222        let mut owned = units.to_vec();
223        owned.push(0);
224        alloc::borrow::Cow::Owned(owned)
225    }
226}
227
228#[cfg(windows)]
229pub fn wcscoll(s1: &[u16], s2: &[u16]) -> i32 {
230    unsafe extern "C" {
231        fn wcscoll(s1: *const u16, s2: *const u16) -> i32;
232    }
233    let s1 = wide_cstr(s1);
234    let s2 = wide_cstr(s2);
235    unsafe { wcscoll(s1.as_ptr(), s2.as_ptr()) }
236}
237
238#[cfg(windows)]
239pub fn wcsxfrm(src: &[u16]) -> Vec<u16> {
240    unsafe extern "C" {
241        fn wcsxfrm(dst: *mut u16, src: *const u16, count: usize) -> usize;
242    }
243    let src = wide_cstr(src);
244    let needed = unsafe { wcsxfrm(core::ptr::null_mut(), src.as_ptr(), 0) };
245    let mut dst = vec![0u16; needed + 1];
246    let written = unsafe { wcsxfrm(dst.as_mut_ptr(), src.as_ptr(), dst.len()) };
247    dst.truncate(written);
248    dst
249}
250
251#[cfg(windows)]
252pub fn decode_ansi_bytes(bytes: &[u8]) -> Option<String> {
253    use core::ptr;
254    use windows_sys::Win32::Globalization::{CP_ACP, MultiByteToWideChar};
255
256    if bytes.is_empty() {
257        return Some(String::new());
258    }
259    let len_i32 = i32::try_from(bytes.len()).ok()?;
260
261    let len =
262        unsafe { MultiByteToWideChar(CP_ACP, 0, bytes.as_ptr(), len_i32, ptr::null_mut(), 0) };
263    if len <= 0 {
264        return None;
265    }
266    let mut wide = vec![0u16; len as usize];
267    unsafe {
268        MultiByteToWideChar(CP_ACP, 0, bytes.as_ptr(), len_i32, wide.as_mut_ptr(), len);
269    }
270    Some(String::from_utf16_lossy(&wide))
271}
272
273#[cfg(all(
274    unix,
275    not(any(target_os = "ios", target_os = "android", target_os = "redox"))
276))]
277pub fn nl_langinfo_codeset() -> Option<Vec<u8>> {
278    let codeset = unsafe { libc::nl_langinfo(libc::CODESET) };
279    (!codeset.is_null()).then(|| unsafe { CStr::from_ptr(codeset) }.to_bytes().to_vec())
280}