bsize 0.1.0-rc.3

Semantic wrappers and utilities for byte size representations.
Documentation
// Copyright 2026 FastLabs Developers
//
// 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
//
//     http://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.

use core::fmt;

use crate::BSize;

/// Display wrapper for [`BSize`].
///
/// Supports various styles, see methods. By default, the [`binary`] style is used.
///
/// [`binary`]: Display::binary
///
/// # Examples
///
/// ```
/// # use bsize::BSize;
/// assert_eq!(
///     "1.0 MiB",
///     BSize::<u64>::mib(1).display().binary().to_string(),
/// );
///
/// assert_eq!(
///     "42.0 kB",
///     BSize::<u64>::kb(42).display().decimal().to_string(),
/// );
/// ```
#[derive(Debug, Clone)]
pub struct Display {
    size: u64,
    mode: DisplayMode,
}

#[derive(Debug, Clone)]
enum DisplayMode {
    Binary,
    Decimal,
}

impl Display {
    /// Format using binary units (e.g., `11.8MiB`)
    pub fn binary(mut self) -> Self {
        self.mode = DisplayMode::Binary;
        self
    }

    /// Format using decimal units (e.g., `11.8MB`)
    pub fn decimal(mut self) -> Self {
        self.mode = DisplayMode::Decimal;
        self
    }

    fn new(size: u64) -> Self {
        Self {
            size,
            mode: DisplayMode::Binary,
        }
    }
}

impl fmt::Display for Display {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let bytes = self.size;

        let unit = match self.mode {
            DisplayMode::Binary => 1024,
            DisplayMode::Decimal => 1000,
        };

        let unit_prefixes = match self.mode {
            DisplayMode::Binary => b"KMGTPE",
            DisplayMode::Decimal => b"kMGTPE",
        };
        let unit_suffix = match self.mode {
            DisplayMode::Binary => "iB",
            DisplayMode::Decimal => "B",
        };
        let unit_separator = " ";
        let precision = f.precision().unwrap_or(1);

        if bytes < unit {
            write!(f, "{bytes}{unit_separator}B")?;
        } else {
            let size = bytes as f64;

            let mut ideal_prefix = 0usize;
            let mut ideal_size = size;
            loop {
                ideal_prefix += 1;
                ideal_size /= unit as f64;

                if ideal_size < unit as f64 {
                    break;
                }
            }
            let exp = ideal_prefix;

            let unit_prefix = unit_prefixes[exp - 1] as char;

            write!(
                f,
                "{:.precision$}{unit_separator}{unit_prefix}{unit_suffix}",
                size / unit.pow(exp as u32) as f64,
            )?;
        }

        Ok(())
    }
}

macro_rules! impl_display {
    ($($ty:ty),* $(,)?) => {
        $(
            impl BSize<$ty> {
                /// Returns a display wrapper.
                pub fn display(self) -> Display {
                    Display::new(self.0 as u64)
                }
            }
        )*
    };
}

impl_display!(u8, u16, u32, u64, usize);