@@ -222,6 +222,103 @@ pub fn strxfrm(string: &CStr, _initial_len: usize) -> Vec<u8> {
222222 buff
223223}
224224
225+ #[ cfg( target_os = "linux" ) ]
226+ pub use wide_collation:: { CollationString , CollationTransformError } ;
227+
228+ #[ cfg( target_os = "linux" ) ]
229+ mod wide_collation {
230+ use rustpython_wtf8:: { CodePoint , Wtf8 , Wtf8Buf } ;
231+ use std:: io;
232+
233+ unsafe extern "C" {
234+ fn wcscoll ( first : * const libc:: wchar_t , second : * const libc:: wchar_t ) -> libc:: c_int ;
235+ fn wcsxfrm (
236+ destination : * mut libc:: wchar_t ,
237+ source : * const libc:: wchar_t ,
238+ capacity : usize ,
239+ ) -> usize ;
240+ }
241+
242+ /// A NUL-terminated wide string for the locale collation APIs.
243+ pub struct CollationString ( Vec < libc:: wchar_t > ) ;
244+
245+ pub enum CollationTransformError {
246+ Memory ,
247+ Os ( io:: Error ) ,
248+ }
249+
250+ impl CollationString {
251+ /// Preserve Python's lone surrogates and reject embedded NULs.
252+ pub fn new ( string : & Wtf8 ) -> Option < Self > {
253+ let mut wide = Vec :: with_capacity ( string. len ( ) + 1 ) ;
254+ for point in string. code_points ( ) {
255+ let value = point. to_u32 ( ) ;
256+ if value == 0 {
257+ return None ;
258+ }
259+ wide. push ( value as libc:: wchar_t ) ;
260+ }
261+ wide. push ( 0 ) ;
262+ Some ( Self ( wide) )
263+ }
264+
265+ pub fn compare ( & self , other : & Self ) -> libc:: c_int {
266+ // Both buffers retain their terminator and cannot contain interior NULs.
267+ unsafe { wcscoll ( self . 0 . as_ptr ( ) , other. 0 . as_ptr ( ) ) }
268+ }
269+
270+ pub fn transform ( & self ) -> Result < Wtf8Buf , CollationTransformError > {
271+ let mut dummy = [ 0 ] ;
272+ crate :: os:: clear_errno ( ) ;
273+ let needed = unsafe { wcsxfrm ( dummy. as_mut_ptr ( ) , self . 0 . as_ptr ( ) , 1 ) } ;
274+ let errno = crate :: os:: get_errno ( ) ;
275+ if errno != 0 && errno != libc:: ERANGE {
276+ return Err ( CollationTransformError :: Os ( io:: Error :: from_raw_os_error (
277+ errno,
278+ ) ) ) ;
279+ }
280+ let capacity = needed
281+ . checked_add ( 1 )
282+ . ok_or ( CollationTransformError :: Memory ) ?;
283+ let mut wide = Vec :: new ( ) ;
284+ wide. try_reserve_exact ( capacity)
285+ . map_err ( |_| CollationTransformError :: Memory ) ?;
286+ wide. resize ( capacity, 0 ) ;
287+ crate :: os:: clear_errno ( ) ;
288+ let written = unsafe { wcsxfrm ( wide. as_mut_ptr ( ) , self . 0 . as_ptr ( ) , wide. len ( ) ) } ;
289+ let errno = crate :: os:: get_errno ( ) ;
290+ if errno != 0 {
291+ return Err ( CollationTransformError :: Os ( io:: Error :: from_raw_os_error (
292+ errno,
293+ ) ) ) ;
294+ }
295+ // A concurrent locale change can invalidate the size query. Never
296+ // interpret a truncated or unterminated result as a collation key.
297+ if written >= wide. len ( ) {
298+ return Err ( CollationTransformError :: Os ( io:: Error :: from_raw_os_error (
299+ libc:: ERANGE ,
300+ ) ) ) ;
301+ }
302+ wide. truncate ( written) ;
303+
304+ // wcsxfrm emits integer weights, not necessarily Unicode. CPython
305+ // 3.15 splits weights above 0x10000 into two ordered code points.
306+ // Keep individual surrogates rather than decoding as UTF-16.
307+ let mut key = Wtf8Buf :: new ( ) ;
308+ for weight in wide {
309+ let value = weight as u32 ;
310+ if value > 0x10000 {
311+ key. push ( CodePoint :: from_u32 ( ( value >> 16 ) | 0x10000 ) . unwrap ( ) ) ;
312+ key. push ( CodePoint :: from_u32 ( value & 0xffff ) . unwrap ( ) ) ;
313+ } else {
314+ key. push ( CodePoint :: from_u32 ( value) . unwrap ( ) ) ;
315+ }
316+ }
317+ Ok ( key)
318+ }
319+ }
320+ }
321+
225322pub fn setlocale ( category : i32 , locale : Option < & CStr > ) -> Option < Vec < u8 > > {
226323 let result = unsafe {
227324 match locale {
0 commit comments