Skip to content

Commit

Permalink
Rework documentation and naming
Browse files Browse the repository at this point in the history
  • Loading branch information
InAnYan committed Nov 19, 2024
1 parent c1fda75 commit 67a6ff5
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 96 deletions.
76 changes: 36 additions & 40 deletions module/core/format_tools/src/format/output_format/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,27 +94,18 @@ impl Records
INSTANCE.get_or_init( || Records::default() )
}

/// Calculate how much space is needed in order to generate an output with this output formatter
/// It will be impossible to render tables smaller than the result of `min_width`.
///
/// Is is the sum of:
/// - Length of `row_prefix`.
/// - Length of `row_postfix`.
/// - Length of `cell_prefix` and `cell_postfix` multiplied by 2.
/// - Length of `cell_separator`
/// - Just 2.
///
/// 2 here is used as a constant because `output_format::Records` will generate tables only with
/// two columns (key and value).
/// Calculate how much space is minimally needed in order to generate an output with this output formatter.
/// It will be impossible to render tables smaller than the result of `min_width()`.
///
/// This function is similar to `output_format::Table::min_width`, but it does not contain a
/// `column_count` as it always equal to 2, and it aslo uses the `output_format::Table`
/// `column_count` as it always equal to 2, and it aslo uses the `output_format::Records`
/// style parameters.
pub fn min_width
(
&self,
) -> usize
{
// 2 is used here, because `Records` displays 2 columns: keys and values.
self.row_prefix.chars().count()
+ self.row_postfix.chars().count()
+ 2 * ( self.cell_postfix.chars().count() + self.cell_prefix.chars().count() )
Expand Down Expand Up @@ -194,31 +185,31 @@ impl TableOutputFormat for Records
}

// 2 because there are only 2 columns: key and value.
let allowed_cell_space = if self.max_width == 0 { 0 } else { self.max_width - self.min_width() + 2 };
let max_columns_width = if self.max_width == 0 { 0 } else { self.max_width - self.min_width() + 2 };

let field_names : Vec< ( Cow< 'data, str >, [ usize; 2 ] ) > = x.header().collect();

write!( c.buf, "{}", self.table_prefix )?;

let mut actual_entries = 0;
let mut printed_tables_count = 0;

for ( ientry_descriptor, entry_descriptor ) in x.row_descriptors.iter().enumerate()
for ( itable_descriptor, table_descriptor ) in x.row_descriptors.iter().enumerate()
{
if !entry_descriptor.vis || ( x.has_header && ientry_descriptor == 0 )
if !table_descriptor.vis || ( x.has_header && itable_descriptor == 0 )
{
continue;
}

if actual_entries > 0
if printed_tables_count > 0
{
write!( c.buf, "{}", self.table_separator )?;
}

actual_entries += 1;
printed_tables_count += 1;

writeln!( c.buf, " = {}", entry_descriptor.irow )?;
writeln!( c.buf, " = {}", table_descriptor.irow )?;

let wrapped_text = text_wrap( &field_names, &x.data[ ientry_descriptor ], allowed_cell_space );
let wrapped_text = text_wrap( &field_names, &x.data[ itable_descriptor ], max_columns_width );

for ( irow, ( key, value ) ) in wrapped_text.data.iter().enumerate()
{
Expand Down Expand Up @@ -261,8 +252,8 @@ impl TableOutputFormat for Records
#[ derive( Debug ) ]
struct WrappedInputExtract< 'data >
{
/// Tabular data for display, as `Records` only show 2 columns, we used a tuple here
/// instead of a vector.
/// Tabular data for display. Because `Records` only show 2 columns, we used a tuple
/// here instead of a vector.
data : Vec< ( &'data str, &'data str ) >,

/// Width of key column.
Expand All @@ -272,38 +263,43 @@ struct WrappedInputExtract< 'data >
value_width : usize,
}

/// Convert `InputExtract` data to properly wrapped table that is suitable for displaying.
/// `InputExtract` contains logical data of the table but it does not perform wrapping of
/// the cells (as wrapped text will be represented by new rows).
/// Wrap cells in `InputExtract`.
///
/// `InputExtract` contains cells with full content, so it represents the logical
/// structure of the table.
///
/// `WrappedInputExtract` wraps original cells to smaller cells. The resulting data
/// is more low-level and corresponds to the table that will be actually printed to
/// the console (or other output type).
///
/// Wrapping is controlled by `allowed_column_space` parameter.
/// `allowed_cell_space` is the size space that is allowed to be occupied by columns.
/// Wrapping is controlled by `max_columns_width` parameter.
/// `max_columns_width` is the size space that is allowed to be occupied by columns.
/// It equals to maximum table width minus lengthes of visual elements (prefixes,
/// postfixes, separators, etc.).
///
/// The function will perform wrapping and shrink the columns so that they occupy not
/// more than `allowed_cell_space`.
/// more than `max_columns_width`.
///
/// When you use this function, do not forget that it accepts column space, but not the
/// maximum width of the table. It means that to calculate allowed space you need to subtract
/// lengthes of visual elements (prefixes, postfixes, separators, etc.) from the maximum width.
/// If `max_columns_width` is equal to 0, then no wrapping will be performed.
fn text_wrap<'data>
(
keys : &'data Vec< ( Cow< 'data, str >, [ usize; 2 ] ) >,
values : &'data Vec< ( Cow< 'data, str >, [ usize; 2 ] ) >,
allowed_cell_space : usize,
max_columns_width : usize,
)
-> WrappedInputExtract< 'data >
{
let mut data = Vec::new();
let mut key_width = width_calculate( keys );
let mut value_width = width_calculate( values );

let orig_cell_space = key_width + value_width;
let orig_columns_width = key_width + value_width;

if allowed_cell_space != 0 && orig_cell_space > allowed_cell_space
if max_columns_width != 0 && orig_columns_width > max_columns_width
{
let factor = ( allowed_cell_space as f32 ) / ( orig_cell_space as f32 );
let factor = ( max_columns_width as f32 ) / ( orig_columns_width as f32 );
key_width = ( ( key_width as f32 ) * factor ).round() as usize;
value_width = allowed_cell_space - key_width;
value_width = max_columns_width - key_width;
}

for i in 0..values.len()
Expand Down Expand Up @@ -331,14 +327,14 @@ fn text_wrap<'data>
}
}

/// Calculate how much space will a column of cells occupy without wrapping.
/// Calculate how much width a column of cells occupy without wrapping.
fn width_calculate< 'data >
(
vec : &'data Vec< ( Cow< 'data, str >, [ usize; 2 ] ) >
column : &'data Vec< ( Cow< 'data, str >, [ usize; 2 ] ) >
)
-> usize
{
vec.iter().map( |k|
column.iter().map( |k|
{
string::lines( k.0.as_ref() ).map( |l| l.chars().count() ).max().unwrap_or( 0 )
} ).max().unwrap_or( 0 )
Expand Down
83 changes: 38 additions & 45 deletions module/core/format_tools/src/format/output_format/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,17 +163,9 @@ impl Table

}

/// Calculate how much space is needed in order to generate a table output with the specified
/// Calculate how much space is minimally needed in order to generate a table output with the specified
/// number of columns. It will be impossible to render table smaller than the result of
/// `min_width`.
///
/// Is is the sum of:
/// - Length of `row_prefix`.
/// - Length of `row_postfix`.
/// - Length of `cell_prefix` and `cell_postfix` multiplied by column count.
/// - Length of `cell_separator` multiplied by `column_count - 1`.
/// - Count of columns (multiplied by 1, because at least one cell should be available to render
/// meaningful information).
/// `min_width()`.
///
/// This function is similar to `output_format::Records::min_width`, but it contains a `column_count`
/// parameter, and it aslo uses the `output_format::Table` style parameters.
Expand Down Expand Up @@ -205,47 +197,42 @@ impl TableOutputFormat for Table

let column_count = x.col_descriptors.len();

let unchangable_width = self.row_prefix.chars().count()
+ self.row_postfix.chars().count()
+ column_count * ( self.cell_postfix.chars().count() + self.cell_prefix.chars().count() )
+ if column_count == 0 { 0 } else { ( column_count - 1 ) * self.cell_separator.chars().count() };

if self.max_width != 0 && ( unchangable_width + column_count > self.max_width )
if self.max_width != 0 && ( self.min_width( column_count ) > self.max_width )
{
return Err( fmt::Error );
}

let orig_column_space = x.col_descriptors.iter().map( |c| c.width ).sum::<usize>();
let orig_columns_width = x.col_descriptors.iter().map( |c| c.width ).sum::<usize>();
let visual_elements_width = self.min_width( column_count ) - column_count;

let wrapped_text = wrap_text( &x, if self.max_width == 0 { 0 } else { self.max_width - unchangable_width }, orig_column_space );
let wrapped_text = wrap_text( &x, if self.max_width == 0 { 0 } else { self.max_width - visual_elements_width }, orig_columns_width );

let new_column_space = wrapped_text.col_widthes.iter().sum::<usize>();
let new_columns_widthes = wrapped_text.col_widthes.iter().sum::<usize>();
let new_row_width = new_columns_widthes + visual_elements_width;

let new_row_width = new_column_space + unchangable_width;

let mut actual_rows = 0;
let mut printed_row_count = 0;

for row in wrapped_text.data.iter()
{
if actual_rows == wrapped_text.first_row_height && x.has_header && self.delimitting_header
if printed_row_count == wrapped_text.first_row_height && x.has_header && self.delimitting_header
{
write!( c.buf, "{}", row_separator )?;
write!( c.buf, "{}", h.repeat( new_row_width ) )?;
}

if actual_rows > 0
if printed_row_count > 0
{
write!( c.buf, "{}", row_separator )?;
}

actual_rows += 1;
printed_row_count += 1;

write!( c.buf, "{}", row_prefix )?;

for ( icol, col ) in row.iter().enumerate()
{
let cell_width = col.wrap_width;
let col_width = wrapped_text.col_widthes[ icol ];
let cell_wrapped_width = col.wrap_width;
let column_width = wrapped_text.col_widthes[ icol ];
let slice_width = col.content.chars().count();

if icol > 0
Expand All @@ -255,8 +242,8 @@ impl TableOutputFormat for Table

write!( c.buf, "{}", cell_prefix )?;

let lspaces = ( col_width - cell_width ) / 2;
let rspaces = ( ( col_width - cell_width ) as f32 / 2 as f32 ).round() as usize + cell_width - slice_width;
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;

if lspaces > 0
{
Expand Down Expand Up @@ -320,47 +307,53 @@ struct WrappedInputExtract< 'data >
///
/// The first case seems to be properly formatted, while the second case took centering
/// too literally. That is why `wrap_width` is introduced, and additional spaces to the
/// right side will be included in the output formatter.
/// right side will be included by the output formatter.
#[ derive( Debug ) ]
struct WrappedCell< 'data >
{
wrap_width : usize,
content : Cow< 'data, str >
}

/// Convert `InputExtract` data to properly wrapped table that is suitable for displaying.
/// `InputExtract` contains logical data of the table but it does not perform wrapping of
/// the cells (as wrapped text will be represented by new rows).
/// Wrap cells in `InputExtract`.
///
/// `InputExtract` contains cells with full content, so it represents the logical
/// structure of the table.
///
/// `WrappedInputExtract` wraps original cells to smaller cells. The resulting data
/// is more low-level and corresponds to the table that will be actually printed to
/// the console (or other output type).
///
/// Wrapping is controlled by `limit_column_space` and `orig_column_space` parameters.
/// `orig_column_space` is the size occupied column widthes of original tabular data.
/// `limit_column_space` is the size space that is allowed to be occupied by columns.
/// Wrapping is controlled by `max_columns_width` and `orig_columns_width` parameters.
/// `max_columns_width` is the size space that is allowed to be occupied by columns.
/// It equals to maximum table width minus lengthes of visual elements (prefixes,
/// postfixes, separators, etc.).
/// `orig_columns_width` is the sum of column widthes of cells without wrapping (basically,
/// the sum of widthes of column descriptors in `InputExtract`).
///
/// The function will perform wrapping and shrink the columns so that they occupy not
/// more than `limit_column_space`.
/// more than `max_columns_width`.
///
/// When you use this function, do not forget that it accepts column space, but not the
/// maximum width of the table. It means that to calculate allowed space you need to subtract
/// lengthes of visual elements (prefixes, postfixes, separators, etc.) from the maximum width.
/// If `max_columns_width` is equal to 0, then no wrapping will be performed.
fn wrap_text< 'data >
(
x : &'data InputExtract< 'data >,
limit_column_space : usize,
orig_column_space : usize,
max_columns_width : usize,
orig_columns_width : usize,
)
-> WrappedInputExtract< 'data >
{
let mut first_row_height = 0;
let mut new_data = Vec::new();
let mut col_widthes = Vec::new();

if limit_column_space == 0 || limit_column_space >= orig_column_space
if max_columns_width == 0 || max_columns_width >= orig_columns_width
{
col_widthes.extend( x.col_descriptors.iter().map( |d| d.width ) );
}
else
{
let shrink_factor: f32 = ( limit_column_space as f32 ) / ( orig_column_space as f32 );
let shrink_factor : f32 = ( max_columns_width as f32 ) / ( orig_columns_width as f32 );

for ( icol, col ) in x.col_descriptors.iter().enumerate()
{
Expand All @@ -371,7 +364,7 @@ fn wrap_text< 'data >

let col_width_to_put = if icol == x.col_descriptors.len() - 1
{
limit_column_space - col_widthes.iter().sum::<usize>()
max_columns_width - col_widthes.iter().sum::<usize>()
}
else
{
Expand Down
22 changes: 11 additions & 11 deletions module/core/format_tools/tests/inc/format_table_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,15 +351,15 @@ fn test_width_limiting()
{
use the_module::string;

for width in min_width()..max_width()
for max_width in min_width()..max_width()
{
println!("width: {}", width);
println!("max_width: {}", max_width);

let test_objects = test_object::test_objects_gen();
let as_table = AsTable::new( &test_objects );

let mut format = output_format::Table::default();
format.max_width = width;
format.max_width = max_width;

let mut output = String::new();
let printer = print::Printer::with_format( &format );
Expand All @@ -371,24 +371,24 @@ fn test_width_limiting()

for line in string::lines( &output )
{
assert_eq!( width, line.chars().count() );
assert_eq!( max_width, line.chars().count() );
}
}
}

#[ test ]
fn test_error_on_unsatisfiable_limit()
{
// 0 is a special value that signifies no limit.
for width in 1..( min_width() )
// 0 is a special value that signifies no limit. Therefore, the lower bound is 1.
for max_width in 1..( min_width() )
{
println!( "width: {}", width );
println!( "max_width: {}", max_width );

let test_objects = test_object::test_objects_gen();
let as_table = AsTable::new( &test_objects );

let mut format = output_format::Table::default();
format.max_width = width;
format.max_width = max_width;

let mut output = String::new();
let printer = print::Printer::with_format( &format );
Expand All @@ -408,15 +408,15 @@ fn test_table_not_grows()
let expected_width = max_width();

// The upper bound was chosen arbitrarily.
for width in ( expected_width + 1 )..500
for max_width in ( expected_width + 1 )..500
{
println!( "width: {}", width );
println!( "max_width: {}", max_width );

let test_objects = test_object::test_objects_gen();
let as_table = AsTable::new( &test_objects );

let mut format = output_format::Table::default();
format.max_width = width;
format.max_width = max_width;

let mut output = String::new();
let printer = print::Printer::with_format( &format );
Expand Down

0 comments on commit 67a6ff5

Please sign in to comment.