mirror of
https://github.com/uutils/coreutils
synced 2024-12-18 09:03:14 +00:00
241 lines
11 KiB
Rust
241 lines
11 KiB
Rust
use std::cmp;
|
|
use std::slice::Iter;
|
|
use parse_formats::ParsedFormatterItemInfo;
|
|
use formatteriteminfo::FormatterItemInfo;
|
|
|
|
/// Size in bytes of the max datatype. ie set to 16 for 128-bit numbers.
|
|
const MAX_BYTES_PER_UNIT: usize = 8;
|
|
|
|
/// Contains information to output single output line in human readable form
|
|
pub struct SpacedFormatterItemInfo {
|
|
/// Contains a function pointer to output data, and information about the output format.
|
|
pub formatter_item_info: FormatterItemInfo,
|
|
/// Contains the number of spaces to add to align data with other output formats.
|
|
///
|
|
/// If the corresponding data is a single byte, each entry in this array contains
|
|
/// the number of spaces to insert when outputting each byte. If the corresponding
|
|
/// data is multi-byte, only the fist byte position is used. For example a 32-bit
|
|
/// datatype, could use positions 0, 4, 8, 12, ....
|
|
/// As each block is formatted identically, only the spacing for a single block is set.
|
|
pub spacing: [usize; MAX_BYTES_PER_UNIT],
|
|
/// if set adds a ascii dump at the end of the line
|
|
pub add_ascii_dump: bool,
|
|
}
|
|
|
|
/// Contains information about all output lines.
|
|
pub struct OutputInfo {
|
|
/// The number of bytes of a line.
|
|
pub byte_size_line: usize,
|
|
/// The width of a line in human readable format.
|
|
pub print_width_line: usize,
|
|
|
|
/// The number of bytes in a block. (This is the size of the largest datatype in `spaced_formatters`.)
|
|
pub byte_size_block: usize,
|
|
/// The width of a block in human readable format. (The size of the largest format.)
|
|
pub print_width_block: usize,
|
|
/// All formats.
|
|
spaced_formatters: Vec<SpacedFormatterItemInfo>,
|
|
/// determines if duplicate output lines should be printed, or
|
|
/// skipped with a "*" showing one or more skipped lines.
|
|
pub output_duplicates: bool,
|
|
}
|
|
|
|
|
|
impl OutputInfo {
|
|
/// Returns an iterator over the `SpacedFormatterItemInfo` vector.
|
|
pub fn spaced_formatters_iter(&self) -> Iter<SpacedFormatterItemInfo> {
|
|
self.spaced_formatters.iter()
|
|
}
|
|
|
|
/// Creates a new `OutputInfo` based on the parameters
|
|
pub fn new(line_bytes: usize, formats: &[ParsedFormatterItemInfo], output_duplicates: bool) -> OutputInfo {
|
|
let byte_size_block = formats.iter().fold(1, |max, next| cmp::max(max, next.formatter_item_info.byte_size));
|
|
let print_width_block = formats
|
|
.iter()
|
|
.fold(1, |max, next| {
|
|
cmp::max(max, next.formatter_item_info.print_width * (byte_size_block / next.formatter_item_info.byte_size))
|
|
});
|
|
let print_width_line = print_width_block * (line_bytes / byte_size_block);
|
|
|
|
let spaced_formatters = OutputInfo::create_spaced_formatter_info(&formats, byte_size_block, print_width_block);
|
|
|
|
OutputInfo {
|
|
byte_size_line: line_bytes,
|
|
print_width_line: print_width_line,
|
|
byte_size_block: byte_size_block,
|
|
print_width_block: print_width_block,
|
|
spaced_formatters: spaced_formatters,
|
|
output_duplicates: output_duplicates,
|
|
}
|
|
}
|
|
|
|
fn create_spaced_formatter_info(formats: &[ParsedFormatterItemInfo],
|
|
byte_size_block: usize, print_width_block: usize) -> Vec<SpacedFormatterItemInfo> {
|
|
formats
|
|
.iter()
|
|
.map(|f| SpacedFormatterItemInfo {
|
|
formatter_item_info: f.formatter_item_info,
|
|
add_ascii_dump: f.add_ascii_dump,
|
|
spacing: OutputInfo::calculate_alignment(f, byte_size_block, print_width_block)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// calculates proper alignment for a single line of output
|
|
///
|
|
/// Multiple representations of the same data, will be right-aligned for easy reading.
|
|
/// For example a 64 bit octal and a 32-bit decimal with a 16-bit hexadecimal looks like this:
|
|
/// ```
|
|
/// 1777777777777777777777 1777777777777777777777
|
|
/// 4294967295 4294967295 4294967295 4294967295
|
|
/// ffff ffff ffff ffff ffff ffff ffff ffff
|
|
/// ```
|
|
/// In this example is additional spacing before the first and third decimal number,
|
|
/// and there is additional spacing before the 1st, 3rd, 5th and 7th hexadecimal number.
|
|
/// This way both the octal and decimal, as well as the decimal and hexadecimal numbers
|
|
/// left align. Note that the alignment below both octal numbers is identical.
|
|
///
|
|
/// This function calculates the required spacing for a single line, given the size
|
|
/// of a block, and the width of a block. The size of a block is the largest type
|
|
/// and the width is width of the the type which needs the most space to print that
|
|
/// number of bytes. So both numbers might refer to different types. All widths
|
|
/// include a space at the front. For example the width of a 8-bit hexadecimal,
|
|
/// is 3 characters, for example " FF".
|
|
///
|
|
/// This algorithm first calculates how many spaces needs to be added, based the
|
|
/// block size and the size of the type, and the widths of the block and the type.
|
|
/// The required spaces are spread across the available positions.
|
|
/// If the blocksize is 8, and the size of the type is 8 too, there will be just
|
|
/// one value in a block, so all spacing will be assigned to position 0.
|
|
/// If the blocksize is 8, and the size of the type is 2, the spacing will be
|
|
/// spread across position 0, 2, 4, 6. All 4 positions will get an additional
|
|
/// space as long as there are more then 4 spaces available. If there are 2
|
|
/// spaces available, they will be assigned to position 0 and 4. If there is
|
|
/// 1 space available, it will be assigned to position 0. This will be combined,
|
|
/// For example 7 spaces will be assigned to position 0, 2, 4, 6 like: 3, 1, 2, 1.
|
|
/// And 7 spaces with 2 positions will be assigned to position 0 and 4 like 4, 3.
|
|
///
|
|
/// Here is another example showing the alignment of 64-bit unsigned decimal numbers,
|
|
/// 32-bit hexadecimal number, 16-bit octal numbers and 8-bit hexadecimal numbers:
|
|
/// ```
|
|
/// 18446744073709551615 18446744073709551615
|
|
/// ffffffff ffffffff ffffffff ffffffff
|
|
/// 177777 177777 177777 177777 177777 177777 177777 177777
|
|
/// ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
/// ```
|
|
///
|
|
/// This algorithm assumes the size of all types is a power of 2 (1, 2, 4, 8, 16, ...)
|
|
/// Increase MAX_BYTES_PER_UNIT to allow larger types.
|
|
fn calculate_alignment(sf: &TypeSizeInfo, byte_size_block: usize,
|
|
print_width_block: usize) -> [usize; MAX_BYTES_PER_UNIT] {
|
|
if byte_size_block > MAX_BYTES_PER_UNIT {
|
|
panic!("{}-bits types are unsupported. Current max={}-bits.",
|
|
8 * byte_size_block,
|
|
8 * MAX_BYTES_PER_UNIT);
|
|
}
|
|
let mut spacing = [0; MAX_BYTES_PER_UNIT];
|
|
|
|
let mut byte_size = sf.byte_size();
|
|
let mut items_in_block = byte_size_block / byte_size;
|
|
let thisblock_width = sf.print_width() * items_in_block;
|
|
let mut missing_spacing = print_width_block - thisblock_width;
|
|
|
|
while items_in_block > 0 {
|
|
let avg_spacing: usize = missing_spacing / items_in_block;
|
|
for i in 0..items_in_block {
|
|
spacing[i * byte_size] += avg_spacing;
|
|
missing_spacing -= avg_spacing;
|
|
}
|
|
|
|
items_in_block /= 2;
|
|
byte_size *= 2;
|
|
}
|
|
|
|
spacing
|
|
}
|
|
}
|
|
|
|
trait TypeSizeInfo {
|
|
fn byte_size(&self) -> usize;
|
|
fn print_width(&self) -> usize;
|
|
}
|
|
|
|
impl TypeSizeInfo for ParsedFormatterItemInfo {
|
|
fn byte_size(&self) -> usize { self.formatter_item_info.byte_size }
|
|
fn print_width(&self) -> usize { self.formatter_item_info.print_width }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
struct TypeInfo {
|
|
byte_size: usize,
|
|
print_width: usize,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
impl TypeSizeInfo for TypeInfo {
|
|
fn byte_size(&self) -> usize { self.byte_size }
|
|
fn print_width(&self) -> usize { self.print_width }
|
|
}
|
|
|
|
#[test]
|
|
fn test_calculate_alignment() {
|
|
// For this example `byte_size_block` is 8 and 'print_width_block' is 23:
|
|
// 1777777777777777777777 1777777777777777777777
|
|
// 4294967295 4294967295 4294967295 4294967295
|
|
// ffff ffff ffff ffff ffff ffff ffff ffff
|
|
|
|
// the first line has no additional spacing:
|
|
assert_eq!([0, 0, 0, 0, 0, 0, 0, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:23}, 8, 23));
|
|
// the second line a single space at the start of the block:
|
|
assert_eq!([1, 0, 0, 0, 0, 0, 0, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:11}, 8, 23));
|
|
// the third line two spaces at pos 0, and 1 space at pos 4:
|
|
assert_eq!([2, 0, 0, 0, 1, 0, 0, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:5}, 8, 23));
|
|
|
|
// For this example `byte_size_block` is 8 and 'print_width_block' is 28:
|
|
// 18446744073709551615 18446744073709551615
|
|
// ffffffff ffffffff ffffffff ffffffff
|
|
// 177777 177777 177777 177777 177777 177777 177777 177777
|
|
// ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
|
|
assert_eq!([7, 0, 0, 0, 0, 0, 0, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:21}, 8, 28));
|
|
assert_eq!([5, 0, 0, 0, 5, 0, 0, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:9}, 8, 28));
|
|
assert_eq!([0, 0, 0, 0, 0, 0, 0, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:7}, 8, 28));
|
|
assert_eq!([1, 0, 1, 0, 1, 0, 1, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:3}, 8, 28));
|
|
|
|
// 9 tests where 8 .. 16 spaces are spread across 8 positions
|
|
assert_eq!([1, 1, 1, 1, 1, 1, 1, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 8));
|
|
assert_eq!([2, 1, 1, 1, 1, 1, 1, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 9));
|
|
assert_eq!([2, 1, 1, 1, 2, 1, 1, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 10));
|
|
assert_eq!([3, 1, 1, 1, 2, 1, 1, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 11));
|
|
assert_eq!([2, 1, 2, 1, 2, 1, 2, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 12));
|
|
assert_eq!([3, 1, 2, 1, 2, 1, 2, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 13));
|
|
assert_eq!([3, 1, 2, 1, 3, 1, 2, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 14));
|
|
assert_eq!([4, 1, 2, 1, 3, 1, 2, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 15));
|
|
assert_eq!([2, 2, 2, 2, 2, 2, 2, 2],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 16));
|
|
|
|
// 4 tests where 15 spaces are spread across 8, 4, 2 or 1 position(s)
|
|
assert_eq!([4, 1, 2, 1, 3, 1, 2, 1],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 15));
|
|
assert_eq!([5, 0, 3, 0, 4, 0, 3, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:4}, 8, 16 + 15));
|
|
assert_eq!([8, 0, 0, 0, 7, 0, 0, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:8}, 8, 16 + 15));
|
|
assert_eq!([15, 0, 0, 0, 0, 0, 0, 0],
|
|
OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:16}, 8, 16 + 15));
|
|
}
|