cubic 0.18.0

Cubic is a lightweight command line manager for virtual machines. It has a simple, daemon-less and rootless design. All Cubic virtual machines run isolated in the user context. Cubic is built on top of QEMU, KVM and cloud-init. https://cubic-vm.org https://github.com/cubic-vm/cubic Show all supported images: $ cubic images Create a new virtual machine instance: $ cubic create mymachine --image ubuntu:noble List all virtual machine instances: $ cubic instances Start an instance: $ cubic start <instance name> Stop an instance: $ cubic stop <instance name> Open a shell in the instance: $ cubic ssh <machine name> Copy a file from the host to the instance: $ cubic scp <path/to/host/file> <machine>:<path/to/guest/file> Copy a file from the instance to the hots: $ cubic scp <machine>:<path/to/guest/file> <path/to/host/file>
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::str::FromStr;

#[derive(Clone, Debug, Default, PartialEq)]
pub struct DataSize {
    bytes: usize,
}

impl DataSize {
    pub fn new(bytes: usize) -> Self {
        Self { bytes }
    }

    pub fn get_bytes(&self) -> usize {
        self.bytes
    }

    pub fn to_size(&self) -> String {
        match self.bytes.checked_ilog(1024) {
            Some(1) => format!("{:.1} KiB", self.bytes as f64 / 1024_f64.powf(1_f64)),
            Some(2) => format!("{:.1} MiB", self.bytes as f64 / 1024_f64.powf(2_f64)),
            Some(3) => format!("{:.1} GiB", self.bytes as f64 / 1024_f64.powf(3_f64)),
            Some(4) => format!("{:.1} TiB", self.bytes as f64 / 1024_f64.powf(4_f64)),
            _ => format!("{}   B", self.bytes as f64),
        }
    }

    pub fn to_speed(&self) -> String {
        match self.bytes.checked_ilog(1024) {
            Some(1) => format!("{} Kbps", self.bytes * 8 / 1000),
            Some(2) => format!("{} Mbps", self.bytes * 8 / 1000_usize.pow(2)),
            Some(3) => format!("{} Gbps", self.bytes * 8 / 1000_usize.pow(3)),
            Some(4) => format!("{} Tbps", self.bytes * 8 / 1000_usize.pow(4)),
            _ => format!("{}  bps", self.bytes * 8),
        }
    }
}

impl FromStr for DataSize {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let error = format!(
            "Cannot parse data size '{value}'. The input should be a number followed by a letter (B, K, M, G or T) for bytes, kilobytes, etc. Example: 1G for one gigabyte."
        );

        if value.is_empty() {
            return Err(error);
        }

        let suffix: char = value.bytes().last().unwrap() as char;
        let size = &value[..value.len() - 1];
        let power = match suffix {
            'B' => 0,
            'K' => 1,
            'M' => 2,
            'G' => 3,
            'T' => 4,
            _ => return Err(error),
        };

        size.parse()
            .map(|size: usize| Self {
                bytes: size * 1024_usize.pow(power),
            })
            .map_err(|_| error)
    }
}

impl Serialize for DataSize {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_u64(self.bytes as u64)
    }
}

impl<'de> Deserialize<'de> for DataSize {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(Self {
            bytes: usize::deserialize(deserializer)?,
        })
    }
}

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

    #[test]
    fn test_byte_to_size() {
        assert_eq!(&DataSize::new(1).to_size(), "1   B")
    }

    #[test]
    fn test_kilobyte_to_size() {
        assert_eq!(&DataSize::new(1024).to_size(), "1.0 KiB")
    }

    #[test]
    fn test_megabyte_to_size() {
        assert_eq!(&DataSize::new(1024_usize.pow(2)).to_size(), "1.0 MiB")
    }

    #[test]
    fn test_gigabyte_to_size() {
        assert_eq!(&DataSize::new(1024_usize.pow(3)).to_size(), "1.0 GiB")
    }

    #[test]
    fn test_terrabyte_to_size() {
        assert_eq!(&DataSize::new(1024_usize.pow(4)).to_size(), "1.0 TiB")
    }

    #[test]
    fn test_byte_to_speed() {
        assert_eq!(&DataSize::new(1).to_speed(), "8  bps")
    }

    #[test]
    fn test_kilobyte_to_speed() {
        assert_eq!(&DataSize::new(1024).to_speed(), "8 Kbps")
    }

    #[test]
    fn test_megabyte_to_speed() {
        assert_eq!(&DataSize::new(1024_usize.pow(2)).to_speed(), "8 Mbps")
    }

    #[test]
    fn test_gigabyte_to_speed() {
        assert_eq!(&DataSize::new(1024_usize.pow(3)).to_speed(), "8 Gbps")
    }

    #[test]
    fn test_terrabyte_to_speed() {
        assert_eq!(&DataSize::new(1024_usize.pow(4)).to_speed(), "8 Tbps")
    }

    #[test]
    fn test_from_byte() {
        assert_eq!(DataSize::from_str("1B").unwrap().get_bytes(), 1)
    }

    #[test]
    fn test_from_kilobyte() {
        assert_eq!(DataSize::from_str("1K").unwrap().get_bytes(), 1024)
    }

    #[test]
    fn test_from_megabyte() {
        assert_eq!(
            DataSize::from_str("1M").unwrap().get_bytes(),
            1024_usize.pow(2)
        )
    }

    #[test]
    fn test_from_gigabyte() {
        assert_eq!(
            DataSize::from_str("1G").unwrap().get_bytes(),
            1024_usize.pow(3)
        )
    }

    #[test]
    fn test_from_terrabyte() {
        assert_eq!(
            DataSize::from_str("1T").unwrap().get_bytes(),
            1024_usize.pow(4)
        )
    }
}