keramics-core 0.0.1

Keramics core
Documentation
/* Copyright 2024-2026 Joachim Metz <joachim.metz@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may
 * obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

/// Formats an array for debugging purposes.
pub fn debug_format_array(array: &[String]) -> String {
    // TODO: pass string for indentation?
    let number_of_values: usize = array.len();
    if number_of_values == 0 {
        return String::from("[]");
    }
    let string_value: &String = &array[0];
    let mut use_short_hand: bool = true;

    for array_value in array.iter() {
        if string_value != array_value {
            use_short_hand = false;
            break;
        }
    }
    if use_short_hand {
        return format!("[{}; {}]", string_value, number_of_values);
    }
    if number_of_values > 16 {
        let mut string_parts: Vec<String> = Vec::new();
        let last_value_index: usize = number_of_values - 16;
        for value_index in (0..last_value_index).step_by(16) {
            string_parts.push(array[value_index..value_index + 16].join(", "));
        }
        string_parts.push(array[last_value_index..].join(", "));

        return format!("[\n        {}\n    ]", string_parts.join(",\n        "));
    }
    format!("[{}]", array.join(", "))
}

/// Formats a bytes as a hexdump.
pub fn format_as_hexdump(data: &[u8], group: bool) -> String {
    let mut ascii_values: [char; 16] = ['.'; 16];
    let mut character_index: usize = 0;
    let mut data_offset: usize = 0;
    let data_size: usize = data.len();
    let groups_data_size: usize = (data_size / 16) * 16;
    let mut group_data_offset: usize = 0;
    let mut string_parts: Vec<String> = Vec::new();

    while data_offset < groups_data_size {
        if character_index == 0 {
            if group
                && group_data_offset != data_offset
                && data[group_data_offset..group_data_offset + 16]
                    == data[data_offset..data_offset + 16]
                && data_offset + 16 < groups_data_size
            {
                if group_data_offset + 16 == data_offset {
                    string_parts.push(String::from("...\n"))
                }
                data_offset += 16;
                continue;
            }
            group_data_offset = data_offset;

            string_parts.push(format!("{:08x}  ", data_offset));
        }
        let byte_value: u8 = data[data_offset];

        ascii_values[character_index] = match byte_value {
            0x20..0x7f => byte_value as char,
            _ => '.',
        };
        string_parts.push(format!("{:02x} ", byte_value));

        if character_index == 7 {
            string_parts.push(String::from(" "));
        }
        if character_index == 15 {
            character_index += 1;

            string_parts.push(String::from(" "));

            for ascii_value in ascii_values.iter().take(character_index) {
                string_parts.push(format!("{}", ascii_value));
            }
            string_parts.push(String::from("\n"));

            character_index = 0;
        } else {
            character_index += 1;
        }
        data_offset += 1;
    }
    while data_offset < data_size {
        character_index = data_offset % 16;
        if character_index == 0 {
            string_parts.push(format!("{:08x}  ", data_offset));
        }
        let byte_value: u8 = data[data_offset];

        ascii_values[character_index] = match byte_value {
            0x20..0x7f => byte_value as char,
            _ => '.',
        };
        string_parts.push(format!("{:02x} ", data[data_offset]));

        if character_index == 7 {
            string_parts.push(String::from(" "));
        }
        data_offset += 1;
    }
    if data_size > 0 && character_index > 0 {
        character_index += 1;

        for index in character_index..16 {
            if index == 7 {
                string_parts.push(String::from(" "));
            }
            string_parts.push(String::from("   "));
        }
        string_parts.push(String::from(" "));

        for ascii_value in ascii_values.iter().take(character_index) {
            string_parts.push(format!("{}", ascii_value));
        }
        string_parts.push(String::from("\n"));
    }
    string_parts.push(String::from("\n"));

    string_parts.join("")
}

/// Formats a bytes as a string.
pub fn format_as_string(data: &[u8]) -> String {
    data.iter()
        .map(|byte_value| format!("{:02x}", byte_value))
        .collect::<Vec<String>>()
        .join("")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_debug_format_array() {
        let test_data: Vec<String> = ["0"; 16]
            .iter()
            .map(|v| v.to_string())
            .collect::<Vec<String>>();

        let string: String = debug_format_array(&test_data);
        assert_eq!(string, "[0; 16]");

        let test_data: Vec<String> = [
            "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15",
        ]
        .iter()
        .map(|v| v.to_string())
        .collect::<Vec<String>>();

        let string: String = debug_format_array(&test_data);
        assert_eq!(
            string,
            "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]"
        );
    }

    #[test]
    fn test_debug_format_array_with_0bytes() {
        let test_data: Vec<String> = Vec::new();

        let string: String = debug_format_array(&test_data);
        assert_eq!(string, "[]");
    }

    #[test]
    fn test_debug_format_array_with_3bytes() {
        let test_data: Vec<String> = ["1"; 3].iter().map(|value| value.to_string()).collect();

        let string: String = debug_format_array(&test_data);
        assert_eq!(string, "[1; 3]");
    }

    #[test]
    fn test_debug_format_array_with_32bytes() {
        let test_data: Vec<String> = (0..32).map(|value| value.to_string()).collect();

        let first_row: String = (0..16)
            .map(|value| value.to_string())
            .collect::<Vec<String>>()
            .join(", ");

        let last_row: String = (16..32)
            .map(|value| value.to_string())
            .collect::<Vec<String>>()
            .join(", ");

        let string: String = debug_format_array(&test_data);
        assert_eq!(
            string,
            format!("[\n        {},\n        {}\n    ]", first_row, last_row)
        );
    }

    #[test]
    fn test_format_as_hexdump_0bytes() {
        let test_data: [u8; 0] = [];
        let expected_string = ["", ""].join("\n");

        let string: String = format_as_hexdump(&test_data, false);
        assert_eq!(string, expected_string);
    }

    #[test]
    fn test_format_as_hexdump_3bytes() {
        let test_data: [u8; 3] = [0x61, 0x62, 0x63];
        let expected_string = [
            "00000000  61 62 63                                          abc",
            "",
            "",
        ]
        .join("\n");

        let string: String = format_as_hexdump(&test_data, false);
        assert_eq!(string, expected_string);
    }

    #[test]
    fn test_format_as_hexdump_9bytes() {
        let test_data: [u8; 9] = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
        let expected_string = [
            "00000000  00 01 02 03 04 05 06 07  08                       .........",
            "",
            "",
        ]
        .join("\n");

        let string: String = format_as_hexdump(&test_data, false);
        assert_eq!(string, expected_string);
    }

    #[test]
    fn test_format_as_hexdump_16bytes() {
        let test_data: [u8; 16] = [
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
            0x0e, 0x0f,
        ];
        let expected_string = [
            "00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  ................",
            "",
            "",
        ]
        .join("\n");

        let string: String = format_as_hexdump(&test_data, false);
        assert_eq!(string, expected_string);
    }

    #[test]
    fn test_format_as_hexdump_with_group() {
        let test_data: [u8; 428] = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];
        let expected_string = [
            "00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................",
            "...",
            "00000190  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................",
            "000001a0  00 00 00 00 00 00 00 00  00 00 00 00              ............",
            "",
            "",
        ]
        .join("\n");

        let string: String = format_as_hexdump(&test_data, true);
        assert_eq!(string, expected_string);
    }

    #[test]
    fn test_format_as_hexdump_with_group_16byte_aligned() {
        let test_data: [u8; 512] = [
            0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x28, 0x8c, 0x38, 0x27,
            0x71, 0x65, 0x6d, 0x75, 0x00, 0x05, 0x00, 0x03, 0x57, 0x69, 0x32, 0x6b, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x40, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x48, 0x00,
            0x00, 0x79, 0x04, 0x11, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xef, 0xc4, 0xb6, 0x1f,
            0x53, 0xca, 0xa7, 0x86, 0x45, 0x28, 0x90, 0xe2, 0x55, 0xba, 0x79, 0x1a, 0x1c, 0x4c,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];
        // typos:disable
        let expected_string = [
            "00000000  63 6f 6e 65 63 74 69 78  00 00 00 02 00 01 00 00  conectix........",
            "00000010  00 00 00 00 00 00 02 00  28 8c 38 27 71 65 6d 75  ........(.8'qemu",
            "00000020  00 05 00 03 57 69 32 6b  00 00 00 00 00 40 48 00  ....Wi2k.....@H.",
            "00000030  00 00 00 00 00 40 48 00  00 79 04 11 00 00 00 03  .....@H..y......",
            "00000040  ff ff ef c4 b6 1f 53 ca  a7 86 45 28 90 e2 55 ba  ......S...E(..U.",
            "00000050  79 1a 1c 4c 00 00 00 00  00 00 00 00 00 00 00 00  y..L............",
            "00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................",
            "...",
            "000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................",
            "",
            "",
        ]
        .join("\n");
        // typos:enable

        let string: String = format_as_hexdump(&test_data, true);
        assert_eq!(string, expected_string);
    }

    #[test]
    fn test_format_as_string() {
        let test_data: [u8; 16] = [
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
            0x0e, 0x0f,
        ];
        let string: String = format_as_string(&test_data);
        assert_eq!(string, "000102030405060708090a0b0c0d0e0f");
    }
}