Add _locale module (#4558)

_locale.localeconv and _locale.setlocale

Co-authored-by: Jeong YunWon <jeong@youknowone.org>
This commit is contained in:
John Pham
2023-03-01 03:37:52 +07:00
committed by GitHub
parent 5f7fffbe81
commit af1d46f184
4 changed files with 169 additions and 4 deletions

View File

@@ -381,6 +381,8 @@ class TestEnUSCollation(BaseLocalizedTest, TestCollation):
is_emscripten or is_wasi,
"musl libc issue on Emscripten/WASI, bpo-46390"
)
# TODO: RUSTPYTHON", strcoll has not been implemented
@unittest.expectedFailure
def test_strcoll_with_diacritic(self):
self.assertLess(locale.strcoll('à', 'b'), 0)
@@ -390,6 +392,8 @@ class TestEnUSCollation(BaseLocalizedTest, TestCollation):
is_emscripten or is_wasi,
"musl libc issue on Emscripten/WASI, bpo-46390"
)
# TODO: RUSTPYTHON", strxfrm has not been implemented
@unittest.expectedFailure
def test_strxfrm_with_diacritic(self):
self.assertLess(locale.strxfrm('à'), locale.strxfrm('b'))
@@ -506,8 +510,6 @@ class NormalizeTest(unittest.TestCase):
class TestMiscellaneous(unittest.TestCase):
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_defaults_UTF8(self):
# Issue #18378: on (at least) macOS setting LC_CTYPE to "UTF-8" is
# valid. Furthermore LC_CTYPE=UTF is used by the UTF-8 locale coercing
@@ -544,6 +546,10 @@ class TestMiscellaneous(unittest.TestCase):
if orig_getlocale is not None:
_locale._getdefaultlocale = orig_getlocale
# TODO: RUSTPYTHON
if sys.platform == "win32":
test_defaults_UTF8 = unittest.expectedFailure(test_defaults_UTF8)
def test_getencoding(self):
# Invoke getencoding to make sure it does not cause exceptions.
@@ -565,8 +571,6 @@ class TestMiscellaneous(unittest.TestCase):
self.assertRaises(TypeError, locale.strcoll, "a", None)
self.assertRaises(TypeError, locale.strcoll, b"a", None)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_setlocale_category(self):
locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_TIME)
@@ -577,6 +581,10 @@ class TestMiscellaneous(unittest.TestCase):
# crasher from bug #7419
self.assertRaises(locale.Error, locale.setlocale, 12345)
# TODO: RUSTPYTHON
if sys.platform == "win32":
test_setlocale_category = unittest.expectedFailure(test_setlocale_category)
def test_getsetlocale_issue1813(self):
# Issue #1813: setting and getting the locale under a Turkish locale

View File

@@ -403,6 +403,7 @@ class TypesTests(unittest.TestCase):
self.assertEqual(locale.format_string('%g', x, grouping=True), format(x, 'n'))
self.assertEqual(locale.format_string('%.10g', x, grouping=True), format(x, '.10n'))
@unittest.skip("TODO: RustPython format code n is not integrated with locale")
@run_with_locale('LC_NUMERIC', 'en_US.UTF8')
def test_int__format__locale(self):
# test locale support for __format__ code 'n' for integers

View File

@@ -15,6 +15,7 @@ mod dis;
mod gc;
mod hashlib;
mod json;
mod locale;
mod math;
#[cfg(unix)]
mod mmap;
@@ -159,5 +160,9 @@ pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInit
{
"_uuid" => uuid::make_module,
}
#[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))]
{
"_locale" => locale::make_module,
}
}
}

151
stdlib/src/locale.rs Normal file
View File

@@ -0,0 +1,151 @@
#[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))]
pub(crate) use _locale::make_module;
#[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))]
#[pymodule]
mod _locale {
use rustpython_vm::{
builtins::{PyDictRef, PyIntRef, PyListRef, PyStrRef, PyTypeRef},
convert::ToPyException,
function::OptionalArg,
PyObjectRef, PyResult, VirtualMachine,
};
use std::{
ffi::{CStr, CString},
ptr,
};
#[pyattr]
use libc::{
ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, ABMON_1, ABMON_10, ABMON_11,
ABMON_12, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9,
ALT_DIGITS, AM_STR, CODESET, CRNCYSTR, DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7,
D_FMT, D_T_FMT, ERA, ERA_D_FMT, ERA_D_T_FMT, ERA_T_FMT, LC_ALL, LC_COLLATE, LC_CTYPE,
LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME, MON_1, MON_10, MON_11, MON_12, MON_2, MON_3,
MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, NOEXPR, PM_STR, RADIXCHAR, THOUSEP, T_FMT,
T_FMT_AMPM, YESEXPR,
};
#[pyattr(name = "CHAR_MAX")]
fn char_max(vm: &VirtualMachine) -> PyIntRef {
vm.ctx.new_int(libc::c_char::MAX)
}
unsafe fn copy_grouping(group: *const libc::c_char, vm: &VirtualMachine) -> PyListRef {
let mut group_vec: Vec<PyObjectRef> = Vec::new();
if group.is_null() {
return vm.ctx.new_list(group_vec);
}
let mut ptr = group;
while ![0_i8, libc::c_char::MAX].contains(&*ptr) {
let val = vm.ctx.new_int(*ptr);
group_vec.push(val.into());
ptr = ptr.add(1);
}
// https://github.com/python/cpython/blob/677320348728ce058fa3579017e985af74a236d4/Modules/_localemodule.c#L80
if !group_vec.is_empty() {
group_vec.push(vm.ctx.new_int(0).into());
}
vm.ctx.new_list(group_vec)
}
unsafe fn pystr_from_raw_cstr(vm: &VirtualMachine, raw_ptr: *const libc::c_char) -> PyResult {
let slice = unsafe { CStr::from_ptr(raw_ptr) };
let string = slice
.to_str()
.expect("localeconv always return decodable string");
Ok(vm.new_pyobj(string))
}
#[pyattr(name = "Error", once)]
fn error(vm: &VirtualMachine) -> PyTypeRef {
vm.ctx.new_exception_type(
"locale",
"Error",
Some(vec![vm.ctx.exceptions.exception_type.to_owned()]),
)
}
#[pyfunction]
fn localeconv(vm: &VirtualMachine) -> PyResult<PyDictRef> {
let result = vm.ctx.new_dict();
unsafe {
let lc = libc::localeconv();
macro_rules! set_string_field {
($field:ident) => {{
result.set_item(
stringify!($field),
pystr_from_raw_cstr(vm, (*lc).$field)?,
vm,
)?
}};
}
macro_rules! set_int_field {
($field:ident) => {{
result.set_item(stringify!($field), vm.new_pyobj((*lc).$field), vm)?
}};
}
macro_rules! set_group_field {
($field:ident) => {{
result.set_item(
stringify!($field),
copy_grouping((*lc).$field, vm).into(),
vm,
)?
}};
}
set_group_field!(mon_grouping);
set_group_field!(grouping);
set_int_field!(int_frac_digits);
set_int_field!(frac_digits);
set_int_field!(p_cs_precedes);
set_int_field!(p_sep_by_space);
set_int_field!(n_cs_precedes);
set_int_field!(p_sign_posn);
set_int_field!(n_sign_posn);
set_string_field!(decimal_point);
set_string_field!(thousands_sep);
set_string_field!(int_curr_symbol);
set_string_field!(currency_symbol);
set_string_field!(mon_decimal_point);
set_string_field!(mon_thousands_sep);
set_int_field!(n_sep_by_space);
set_string_field!(positive_sign);
set_string_field!(negative_sign);
}
Ok(result)
}
#[derive(FromArgs)]
struct LocaleArgs {
#[pyarg(any)]
category: i32,
#[pyarg(any, optional)]
locale: OptionalArg<Option<PyStrRef>>,
}
#[pyfunction]
fn setlocale(args: LocaleArgs, vm: &VirtualMachine) -> PyResult {
unsafe {
let result = match args.locale.flatten() {
None => libc::setlocale(args.category, ptr::null()),
Some(locale) => {
let c_locale: CString =
CString::new(locale.as_str()).map_err(|e| e.to_pyexception(vm))?;
libc::setlocale(args.category, c_locale.as_ptr())
}
};
if result.is_null() {
let error = error(vm);
return Err(vm.new_exception_msg(error, String::from("unsupported locale setting")));
}
pystr_from_raw_cstr(vm, result)
}
}
}