Documentation
/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

KiB

Copyright (C) 2016-2017, 2019-2020, 2022, 2024-2025  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2016-2017".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

//! # KiB
//!
//! ## Project
//!
//! - License: GNU Lesser General Public License, either version 3, or (at your option) any later version.
//! - _This project follows [Semantic Versioning 2.0.0]_
//!
//! ## Features
//!
//! This crate helps format bytes to KiB, MiB...
//!
//! ## Notes
//!
//! Documentation is built with all features. Some of them are optional. If you see components from other crates, you can view source to see
//! what features are required.
//!
//! [Semantic Versioning 2.0.0]: https://semver.org/spec/v2.0.0.html

#![warn(missing_docs)]
#![no_std]
#![deny(unsafe_code)]

// ╔═════════════════╗
// ║   IDENTIFIERS   ║
// ╚═════════════════╝

macro_rules! crate_code_name    { () => { "kib" }}
macro_rules! crate_version      { () => { "7.1.1" }}

/// # Crate name
pub const NAME: &str = "KiB";

/// # Crate code name
pub const CODE_NAME: &str = crate_code_name!();

/// # ID of this crate
pub const ID: &str = concat!(
    "c8f97a99-2ba79d4a-0a355f69-9f387d8b-0c0cefe2-67b252af-f02c443d-76b31b5a-",
    "e1afecef-f1ca8727-5231b155-99654c9f-9e48339a-f4a5896d-9ffef1ef-a7e7c9e9",
);

/// # Crate version
pub const VERSION: &str = crate_version!();

/// # Crate release date (year/month/day)
pub const RELEASE_DATE: (u16, u8, u8) = (2025, 6, 4);

/// Tag, which can be used for logging...
pub const TAG: &str = concat!(crate_code_name!(), "::c8f97a99::", crate_version!());

// ╔════════════════════╗
// ║   IMPLEMENTATION   ║
// ╚════════════════════╝

extern crate alloc;

use {
    core::ops::ControlFlow,
    alloc::string::{String, ToString},
};

mod bytes;
mod int;
mod unit;

pub use self::{
    bytes::*,
    int::*,
    unit::*,
};

pub mod version_info;

#[test]
fn test_crate_version() {
    assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
}

/// One KiB in bytes.
pub const KIB: u64 = 1024;

/// One MiB in bytes.
pub const MIB: u64 = 1024 * KIB;

/// One GiB in bytes.
pub const GIB: u64 = 1024 * MIB;

/// One TiB in bytes.
pub const TIB: u64 = 1024 * GIB;

/// One PiB in bytes.
pub const PIB: u64 = 1024 * TIB;

/// One EiB in bytes.
pub const EIB: u64 = 1024 * PIB;

/// One ZiB in bytes.
pub const ZIB: u128 = 1024 * EIB as u128;

/// One YiB in bytes.
pub const YIB: u128 = 1024 * ZIB;

/// # Formats bytes into string.
///
/// The result is a human-readable string, e.g: `1.9 KiB`, `9.9 TiB`...
pub fn fmt<B>(bytes: B) -> String where B: Into<Bytes> {
    let (bytes, unit) = fmt_as_parts(bytes);
    alloc::format!(concat!("{bytes}", ' ', "{unit}"), bytes=bytes, unit=unit)
}

/// # Formats bytes into size and unit
pub fn fmt_as_parts<B>(bytes: B) -> (String, Unit) where B: Into<Bytes> {
    const F1024: f64 = 1024.0;

    let bytes = bytes.into();

    if let Some(bytes) = bytes.as_u64() {
        if bytes < KIB {
            return (bytes.to_string(), match bytes { 1 => Unit::OneByte, _ => Unit::Bytes });
        }
    }
    match bytes.as_f64_lossy() {
        bytes => if bytes < F1024 {
            return (alloc::format!("{bytes:.2}"), Unit::Bytes);
        },
    };

    let (bytes, unit) = match [
        Unit::KiB, Unit::MiB, Unit::GiB, Unit::TiB, Unit::PiB, Unit::EiB, Unit::ZiB, Unit::YiB,
    ].into_iter().try_fold((bytes.as_f64_lossy(), Unit::KiB), |(mut bytes, _), unit| {
        bytes /= F1024;
        if bytes < F1024 {
            ControlFlow::Break((bytes, unit))
        } else {
            ControlFlow::Continue((bytes, unit))
        }
    }) {
        ControlFlow::Break((bytes, unit)) => (bytes, unit),
        ControlFlow::Continue((bytes, unit)) => (bytes, unit),
    };
    (
        alloc::format!("{:.2}", bytes),
        unit,
    )
}