8000 Add _locale module (#4558) · RustPython/RustPython@af1d46f · GitHub
[go: up one dir, main page]

Skip to content

Commit af1d46f

Browse files
Add _locale module (#4558)
_locale.localeconv and _locale.setlocale Co-authored-by: Jeong YunWon <jeong@youknowone.org>
1 parent 5f7fffb commit af1d46f

File tree

4 files changed

+169
-4
lines changed

4 files changed

+169
-4
lines changed

Lib/test/test_locale.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ def setUp(self):
381381
is_emscripten or is_wasi,
382382
"musl libc issue on Emscripten/WASI, bpo-46390"
383383
)
384+
# TODO: RUSTPYTHON", strcoll has not been implemented
385+
@unittest.expectedFailure
384386
def test_strcoll_with_diacritic(self):
385387
self.assertLess(locale.strcoll('à', 'b'), 0)
386388

@@ -390,6 +392,8 @@ def test_strcoll_with_diacritic(self):
390392
is_emscripten or is_wasi,
391393
"musl libc issue on Emscripten/WASI, bpo-46390"
392394
)
395+
# TODO: RUSTPYTHON", strxfrm has not been implemented
396+
@unittest.expectedFailure
393397
def test_strxfrm_with_diacritic(self):
394398
self.assertLess(locale.strxfrm('à'), locale.strxfrm('b'))
395399

@@ -506,8 +510,6 @@ def test_japanese(self):
506510

507511

508512
class TestMiscellaneous(unittest.TestCase):
509-
# TODO: RUSTPYTHON
510-
@unittest.expectedFailure
511513
def test_defaults_UTF8(self):
512514
# Issue #18378: on (at least) macOS setting LC_CTYPE to "UTF-8" is
513515
# valid. Furthermore LC_CTYPE=UTF is used by the UTF-8 locale coercing
@@ -544,6 +546,10 @@ def test_defaults_UTF8(self):
544546

545547
if orig_getlocale is not None:
546548
_locale._getdefaultlocale = orig_getlocale
549+
550+
# TODO: RUSTPYTHON
551+
if sys.platform == "win32":
552+
test_defaults_UTF8 = unittest.expectedFailure(test_defaults_UTF8)
547553

548554
def test_getencoding(self):
549555
# Invoke getencoding to make sure it does not cause exceptions.
@@ -565,8 +571,6 @@ def test_strcoll_3303(self):
565571
self.assertRaises(TypeError, locale.strcoll, "a", None)
566572
self.assertRaises(TypeError, locale.strcoll, b"a", None)
567573

568-
# TODO: RUSTPYTHON
569-
@unittest.expectedFailure
570574
def test_setlocale_category(self):
571575
locale.setlocale(locale.LC_ALL)
572576
locale.setlocale(locale.LC_TIME)
@@ -577,6 +581,10 @@ def test_setlocale_category(self):
577581

578582
# crasher from bug #7419
579583
self.assertRaises(locale.Error, locale.setlocale, 12345)
584+
585+
# TODO: RUSTPYTHON
586+
if sys.platform == "win32":
587+
test_setlocale_category = unittest.expectedFailure(test_setlocale_category)
580588

581589
def test_getsetlocale_issue1813(self):
582590
# Issue #1813: setting and getting the locale under a Turkish locale

Lib/test/test_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ def test_float__format__locale(self):
403403
self.assertEqual(locale.format_string('%g', x, grouping=True), format(x, 'n'))
404404
self.assertEqual(locale.format_string('%.10g', x, grouping=True), format(x, '.10n'))
405405

406+
@unittest.skip("TODO: RustPython format code n is not integrated with locale")
406407
@run_with_locale('LC_NUMERIC', 'en_US.UTF8')
407408
def test_int__format__locale(self):
408409
# test locale support for __format__ code 'n' for integers

stdlib/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod dis;
1515
mod gc;
1616
mod hashlib;
1717
mod json;
18+
mod locale;
1819
mod math;
1920
#[cfg(unix)]
2021
mod mmap;
@@ -159,5 +160,9 @@ pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInit
159160
{
160161
"_uuid" => uuid::make_module,
161162
}
163+
#[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))]
164+
{
165+
"_locale" => locale::make_module,
166+
}
162167
}
163168
}

stdlib/src/locale.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))]
2+
pub(crate) use _locale::make_module;
3+
4+
#[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))]
5+
#[pymodule]
6+
mod _locale {
7+
use rustpython_vm::{
8+
builtins::{PyDictRef, PyIntRef, PyListRef, PyStrRef, PyTypeRef},
9+
convert::ToPyException,
10+
function::OptionalArg,
11+
PyObjectRef, PyResult, VirtualMachine,
12+
};
13+
use std::{
14+
ffi::{CStr, CString},
15+
ptr,
16+
};
17+
18+
#[pyattr]
19+
use libc::{
20+
ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, ABMON_1, ABMON_10, ABMON_11,
21+
ABMON_12, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9,
22+
ALT_DIGITS, AM_STR, CODESET, CRNCYSTR, DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7,
23+
D_FMT, D_T_FMT, ERA, ERA_D_FMT, ERA_D_T_FMT, ERA_T_FMT, LC_ALL, LC_COLLATE, LC_CTYPE,
24+
LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME, MON_1, MON_10, MON_11, MON_12, MON_2, MON_3,
25+
MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, NOEXPR, PM_STR, RADIXCHAR, THOUSEP, T_FMT,
26+
T_FMT_AMPM, YESEXPR,
27+
};
28+
29+
#[pyattr(name = "CHAR_MAX")]
30+
fn char_max(vm: &VirtualMachine) -> PyIntRef {
31+
vm.ctx.new_int(libc::c_char::MAX)
32+
}
33+
34+
unsafe fn copy_grouping(group: *const libc::c_char, vm: &VirtualMachine) -> PyListRef {
35+
let mut group_vec: Vec<PyObjectRef> = Vec::new();
36+
if group.is_null() {
37+
return vm.ctx.new_list(group_vec);
38+
}
39+
40+
let mut ptr = group;
41+
while ![0_i8, libc::c_char::MAX].contains(&*ptr) {
42+
let val = vm.ctx.new_int(*ptr);
43+
group_vec.push(val.into());
44+
ptr = ptr.add(1);
45+
}
46+
// https://github.com/python/cpython/blob/677320348728ce058fa3579017e985af74a236d4/Modules/_localemodule.c#L80
47+
if !group_vec.is_empty() {
48+
group_vec.push(vm.ctx.new_int(0).into());
49+
}
50+
vm.ctx.new_list(group_vec)
51+
}
52+
53+
unsafe fn pystr_from_raw_cstr(vm: &VirtualMachine, raw_ptr: *const libc::c_char) -> PyResult {
54+
let slice = unsafe { CStr::from_ptr(raw_ptr) };
55+
let string = slice
56+
.to_str()
57+
.expect("localeconv always return decodable string");
58+
Ok(vm.new_pyobj(string))
59+
}
60+
61+
#[pyattr(name = "Error", once)]
62+
fn error(vm: &VirtualMachine) -> PyTypeRef {
63+
vm.ctx.new_exception_type(
64+
"locale",
65+
"Error",
66+
Some(vec![vm.ctx.exceptions.exception_type.to_owned()]),
67+
)
68+
}
69+
70+
#[pyfunction]
71+
fn localeconv(vm: &VirtualMachine) -> PyResult<PyDictRef> {
72+
let result = vm.ctx.new_dict();
73+
74+
unsafe {
75+
let lc = libc::localeconv();
76+
77+
macro_rules! set_string_field {
78+
($field:ident) => {{
79+
result.set_item(
80+
stringify!($field),
81+
pystr_from_raw_cstr(vm, (*lc).$field)?,
82+
vm,
83+
)?
84+
}};
85+
}
86+
87+
macro_rules! set_int_field {
88+
($field:ident) => {{
89+
result.set_item(stringify!($field), vm.new_pyobj((*lc).$field), vm)?
90+
}};
91+
}
92+
93+
macro_rules! set_group_field {
94+
($field:ident) => {{
95+
result.set_item(
96+
stringify!($field),
97+
copy_grouping((*lc).$field, vm).into(),
98+
vm,
99+
)?
100+
}};
101+
}
102+
103+
set_group_field!(mon_grouping);
104+
set_group_field!(grouping);
105+
set_int_field!(int_frac_digits);
106+
set_int_field!(frac_digits);
107+
set_int_field!(p_cs_precedes);
108+
set_int_field!(p_sep_by_space);
109+
set_int_field!(n_cs_precedes);
110+
set_int_field!(p_sign_posn);
111+
set_int_field!(n_sign_posn);
112+
set_string_field!(decimal_point);
113+
set_string_field!(thousands_sep);
114+
set_string_field!(int_curr_symbol);
115+
set_string_field!(currency_symbol);
116+
set_string_field!(mon_decimal_point);
117+
set_string_field!(mon_thousands_sep);
118+
set_int_field!(n_sep_by_space);
119+
set_string_field!(positive_sign);
120+
set_string_field!(negative_sign);
121+
}
122+
Ok(result)
123+
}
124+
125+
#[derive(FromArgs)]
126+
struct LocaleArgs {
127+
#[pyarg(any)]
128+
category: i32,
129+
#[pyarg(any, optional)]
130+
locale: OptionalArg<Option<PyStrRef>>,
131+
}
132+
133+
#[pyfunction]
134+
fn setlocale(args: LocaleArgs, vm: &VirtualMachine) -> PyResult {
135+
unsafe {
136+
let result = match args.locale.flatten() {
137+
None => libc::setlocale(args.category, ptr::null()),
138+
Some(locale) => {
139+
let c_locale: CString =
140+
CString::new(locale.as_str()).map_err(|e| e.to_pyexception(vm))?;
141+
libc::setlocale(args.category, c_locale.as_ptr())
142+
}
143+
};
144+
if result.is_null() {
145+
let error = error(vm);
146+
return Err(vm.new_exception_msg(error, String::from("unsupported locale setting")));
147+
}
148+
pystr_from_raw_cstr(vm, result)
149+
}
150+
}
151+
}

0 commit comments

Comments
 (0)
0