diff --git a/module/core/format_tools/src/format/output_format.rs b/module/core/format_tools/src/format/output_format.rs index 1bf58b75e6..971b413ec5 100644 --- a/module/core/format_tools/src/format/output_format.rs +++ b/module/core/format_tools/src/format/output_format.rs @@ -40,10 +40,7 @@ mod private InputExtract, Context, }; - use core:: - { - fmt, - }; + use core::fmt; //= @@ -136,10 +133,7 @@ pub mod own }; #[ doc( inline ) ] - pub use private:: - { - vector_table_write, - }; + pub use private::vector_table_write; } @@ -160,10 +154,7 @@ pub mod exposed pub use super::super::output_format; #[ doc( inline ) ] - pub use private:: - { - TableOutputFormat, - }; + pub use private::TableOutputFormat; } diff --git a/module/core/format_tools/src/format/output_format/records.rs b/module/core/format_tools/src/format/output_format/records.rs index 1c89f34038..3be07a9e83 100644 --- a/module/core/format_tools/src/format/output_format/records.rs +++ b/module/core/format_tools/src/format/output_format/records.rs @@ -28,10 +28,7 @@ use print:: Context, }; use std::borrow::Cow; -use core:: -{ - fmt, -}; +use core::fmt; use std::sync::OnceLock; /// A struct representing the list of records( rows ) output format. diff --git a/module/core/format_tools/src/format/output_format/table.rs b/module/core/format_tools/src/format/output_format/table.rs index 42f2afe16f..035d1efbca 100644 --- a/module/core/format_tools/src/format/output_format/table.rs +++ b/module/core/format_tools/src/format/output_format/table.rs @@ -16,10 +16,7 @@ use print:: InputExtract, Context, }; -use core:: -{ - fmt, -}; +use core::fmt; use std::sync::OnceLock; /// A struct representing the classic table output format. @@ -261,8 +258,8 @@ impl TableOutputFormat for Table write!( c.buf, "{}", cell_prefix )?; - let lspaces = ( column_width - cell_wrapped_width ) / 2; - let rspaces = ( ( column_width - cell_wrapped_width ) as f32 / 2 as f32 ).round() as usize + cell_wrapped_width - slice_width; + let lspaces = column_width.saturating_sub( cell_wrapped_width ) / 2; + let rspaces = ( ( column_width.saturating_sub( cell_wrapped_width ) as f32 / 2 as f32 ) ).round() as usize + cell_wrapped_width.saturating_sub(slice_width); if lspaces > 0 { diff --git a/module/core/format_tools/src/format/string.rs b/module/core/format_tools/src/format/string.rs index ee34e9e718..250f6b5af4 100644 --- a/module/core/format_tools/src/format/string.rs +++ b/module/core/format_tools/src/format/string.rs @@ -74,7 +74,7 @@ mod private for line in lines( text ) { height += 1; - let line_length = line.chars().count(); + let line_length = line.as_bytes().len(); if line_length > width { width = line_length; @@ -244,39 +244,65 @@ mod private } } - impl< 'a > Iterator for LinesWithLimit< 'a > + impl< 'a > Iterator for LinesWithLimit< 'a > { type Item = &'a str; - fn next( &mut self ) -> Option< Self::Item > + fn next( &mut self ) -> Option< Self::Item > { - if self.cur.is_none() || self.cur.is_some_and( str::is_empty ) + loop { - self.cur = self.lines.next(); - } - - match self.cur - { - None => return None, - - Some( cur ) => + let s = match self.cur { - if self.limit_width == 0 + Some( st ) if !st.is_empty() => st, + + _ => { - self.cur = None; - Some( cur ) + let next_line = self.lines.next()?; + if next_line.is_empty() + { + self.cur = None; + return Some( "" ); + } + else + { + self.cur = Some( next_line ); + continue; + } } - else + }; + + if self.limit_width == 0 + { + self.cur = None; + return Some( s ); + } + + let mut boundary_byte_index = s.len(); + let mut char_count = 0; + for ( byte_i, _ch ) in s.char_indices() + { + if char_count == self.limit_width { - let (chunk, rest) = cur.split_at(self.limit_width.min(cur.len())); - self.cur = Some( rest ); - - Some(chunk) + boundary_byte_index = byte_i; + break; } + char_count += 1; } + + let chunk = &s[ ..boundary_byte_index ]; + let rest = &s[ boundary_byte_index.. ]; + + match rest.is_empty() + { + true => self.cur = None, + false => self.cur = Some( rest ) + }; + + return Some( chunk ); } } - } +} } diff --git a/module/core/format_tools/tests/inc/format_records_test.rs b/module/core/format_tools/tests/inc/format_records_test.rs index 77b8de7364..386bb51d2e 100644 --- a/module/core/format_tools/tests/inc/format_records_test.rs +++ b/module/core/format_tools/tests/inc/format_records_test.rs @@ -63,6 +63,57 @@ fn basic() // +#[ test ] +fn unicode() +{ + let test_objects = test_object::test_objects_gen_2_languages(); + + let as_table = AsTable::new( &test_objects ); + + let mut output = String::new(); + let format = output_format::Records::default(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + println!( "{}", &output ); + + let exp = r#" = 1 +│ id │ Доміно │ +│ created_at │ 100 │ +│ file_ids │ [ │ +│ │ "файл1", │ +│ │ "файл2", │ +│ │ ] │ +│ tools │ [ │ +│ │ { │ +│ │ "тулз1": "значення1", │ +│ │ }, │ +│ │ { │ +│ │ "тулз2": "значення2", │ +│ │ }, │ +│ │ ] │ + = 2 +│ id │ File │ +│ created_at │ 120 │ +│ file_ids │ [ │ +│ │ "file1", │ +│ │ "file2", │ +│ │ ] │ +│ tools │ [ │ +│ │ { │ +│ │ "tools1": "value1", │ +│ │ }, │ +│ │ { │ +│ │ "tools1": "value2", │ +│ │ }, │ +│ │ ] │"#; + a_id!( output.as_str(), exp ); + +} + +// + #[ test ] fn custom_format() { diff --git a/module/core/format_tools/tests/inc/format_table_test.rs b/module/core/format_tools/tests/inc/format_table_test.rs index 945696f572..9adcba28a0 100644 --- a/module/core/format_tools/tests/inc/format_table_test.rs +++ b/module/core/format_tools/tests/inc/format_table_test.rs @@ -470,4 +470,66 @@ fn max_width() -> usize } 0 +} + +#[ test ] +fn ukrainian_chars() +{ + let test_objects = test_object::test_objects_gen_with_unicode(); + let as_table = AsTable::new( &test_objects ); + + let mut output = String::new(); + let mut context = print::Context::new( &mut output, Default::default() ); + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + println!( "{}", &output ); + + let exp = r#"│ id │ created_at │ file_ids │ tools │ +─────────────────────────────────────────────────────────────────────────────────────────────────────── +│ Доміно │ 100 │ [ │ │ +│ │ │ "файл1", │ │ +│ │ │ "файл2", │ │ +│ │ │ ] │ │ +│ Інший юнікод │ 120 │ [] │ [ │ +│ │ │ │ { │ +│ │ │ │ "тулз1": "значення1", │ +│ │ │ │ }, │ +│ │ │ │ { │ +│ │ │ │ "тулз2": "значення2", │ +│ │ │ │ }, │ +│ │ │ │ ] │"#; + a_id!( output.as_str(), exp ); +} + +#[ test ] +fn ukrainian_and_english_chars() +{ + let test_objects = test_object::test_objects_gen_2_languages(); + let as_table = AsTable::new( &test_objects ); + + let mut output = String::new(); + let mut context = print::Context::new( &mut output, Default::default() ); + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + println!( "{}", &output ); + + let exp = r#"│ id │ created_at │ file_ids │ tools │ +──────────────────────────────────────────────────────────────────────────────────────────── +│ Доміно │ 100 │ [ │ [ │ +│ │ │ "файл1", │ { │ +│ │ │ "файл2", │ "тулз1": "значення1", │ +│ │ │ ] │ }, │ +│ │ │ │ { │ +│ │ │ │ "тулз2": "значення2", │ +│ │ │ │ }, │ +│ │ │ │ ] │ +│ File │ 120 │ [ │ [ │ +│ │ │ "file1", │ { │ +│ │ │ "file2", │ "tools1": "value1", │ +│ │ │ ] │ }, │ +│ │ │ │ { │ +│ │ │ │ "tools1": "value2", │ +│ │ │ │ }, │ +│ │ │ │ ] │"#; + a_id!( output.as_str(), exp ); } \ No newline at end of file diff --git a/module/core/format_tools/tests/inc/test_object.rs b/module/core/format_tools/tests/inc/test_object.rs index 70c702d035..019b3eb9d2 100644 --- a/module/core/format_tools/tests/inc/test_object.rs +++ b/module/core/format_tools/tests/inc/test_object.rs @@ -207,10 +207,83 @@ pub fn test_objects_gen_with_unicode() -> Vec< TestObject > [ TestObject { - id : "Юнікод".to_string(), + id : "Доміно".to_string(), created_at : 100, - file_ids : vec![], + file_ids : vec![ "файл1".to_string(), "файл2".to_string() ], tools : None, + }, + TestObject + { + id : "Інший юнікод".to_string(), + created_at : 120, + file_ids : vec![], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "тулз1".to_string(), "значення1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "тулз2".to_string(), "значення2".to_string() ); + map + } + ] + ), + } + ] +} + +pub fn test_objects_gen_2_languages() -> Vec< TestObject > +{ + vec! + [ + TestObject + { + id : "Доміно".to_string(), + created_at : 100, + file_ids : vec![ "файл1".to_string(), "файл2".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "тулз1".to_string(), "значення1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "тулз2".to_string(), "значення2".to_string() ); + map + } + ] + ), + }, + TestObject + { + id : "File".to_string(), + created_at : 120, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tools1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tools1".to_string(), "value2".to_string() ); + map + } + ] + ), } ] } \ No newline at end of file