Newer
Older
ddhx / src / utils / strings.d
/// Formatting utilities.
/// Copyright: dd86k <dd@dax.moe>
/// License: MIT
/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module utils.strings;

import core.stdc.stdio : sscanf;
import core.stdc.string : memcpy;
import std.string : toStringz;
import std.conv : text;
import utils.math;

private enum : double
{
    // SI (base-10)
    SI = 1000,          /// SI base
    kB = SI,            /// Represents one KiloByte (1000)
    MB = kB * SI,       /// Represents one MegaByte (1000²)
    GB = MB * SI,       /// Represents one GigaByte (1000³)
    TB = GB * SI,       /// Represents one TeraByte (1000⁴)
    PB = TB * SI,       /// Represents one PetaByte (1000⁵)
    EB = PB * SI,       /// Represents one ExaByte  (1000⁶)
    // IEC (base-2)
    IEC = 1024,         /// IEC base
    KiB = IEC,          /// Represents one KibiByte (1024)
    MiB = KiB * IEC,    /// Represents one MebiByte (1024²)
    GiB = MiB * IEC,    /// Represents one GibiByte (1024³)
    TiB = GiB * IEC,    /// Represents one TebiByte (1024⁴)
    PiB = TiB * IEC,    /// Represents one PebiByte (1024⁵)
    EiB = PiB * IEC,    /// Represents one PebiByte (1024⁶)
}

/// Format byte size.
/// Params:
///   size = Binary number.
///   b10  = Use SI suffixes instead of IEC suffixes.
/// Returns: Character slice using sformat
const(char)[] formatBin(long size, bool b10 = false) @safe
{
    import std.format : format;
    
    // NOTE: ulong.max = (2^64)-1 Bytes = 16 EiB - 1 = 16 * 1024⁵
    
    //TODO: Consider table+index
    
    static immutable string[] formatsIEC = [
        "%0.0f B",
        "%0.1f KiB",
        "%0.1f MiB",
        "%0.2f GiB",
        "%0.2f TiB",
        "%0.2f PiB",
        "%0.2f EiB",
    ];
    static immutable string[] formatsSI = [
        "%0.0f B",
        "%0.1f kB",
        "%0.1f MB",
        "%0.2f GB",
        "%0.2f TB",
        "%0.2f PB",
        "%0.2f EB",
    ];
    
    size_t i;
    double base = void;
    
    if (b10) // base 1000
    {
        base = 1000.0;
        if (size >= EB)         i = 6;
        else if (size >= PB)    i = 5;
        else if (size >= TB)    i = 4;
        else if (size >= GB)    i = 3;
        else if (size >= MB)    i = 2;
        else if (size >= kB)    i = 1;
    }
    else // base 1024
    {
        base = 1024.0;
        if (size >= EiB)         i = 6;
        else if (size >= PiB)    i = 5;
        else if (size >= TiB)    i = 4;
        else if (size >= GiB)    i = 3;
        else if (size >= MiB)    i = 2;
        else if (size >= KiB)    i = 1;
    }
    
    return format(b10 ? formatsSI[i] : formatsIEC[i], size / (base ^^ i));
}
@safe unittest
{
    assert(formatBin(0) == "0 B");
    assert(formatBin(1) == "1 B");
    assert(formatBin(1023) == "1023 B");
    assert(formatBin(1024) == "1.0 KiB");
    // Wouldn't this more exactly 7.99 EiB? Precision limitation?
    assert(formatBin(long.max) == "8.00 EiB");
    assert(formatBin(999,  true) == "999 B");
    assert(formatBin(1000, true) == "1.0 kB");
    assert(formatBin(1_000_000, true) == "1.0 MB");
}

long cparse(string arg) @trusted
{
    // NOTE: Since toStringz can allocate, and I use this function a lot,
    //       a regular static buffer is used instead.
    enum BUFSZ = 64;
    char[BUFSZ] buf = void;
    size_t sz = min(arg.length+1, BUFSZ-1); // Account for null terminator
    memcpy(buf.ptr, arg.ptr, sz);
    buf[sz] = 0;
    
    long r = void;
    if (sscanf(arg.toStringz, "%lli", &r) != 1)
        throw new Exception("Could not parse: ".text(arg));
    return r;
}
@safe unittest
{
    import std.conv : octal;
    assert(cparse("0") == 0);
    assert(cparse("1") == 1);
    assert(cparse("10") == 10);
    assert(cparse("20") == 20);
    assert(cparse("0x1") == 0x1);
    assert(cparse("0x10") == 0x10);
    assert(cparse("0x20") == 0x20);
    // NOTE: Signed numbers cannot be over 0x8000_0000_0000_000
    assert(cparse("0x1bcd1234ffffaaaa") == 0x1bcd_1234_ffff_aaaa);
    assert(cparse("0") == 0);
    assert(cparse("01") == 1);
    assert(cparse("010") == octal!"010");
    assert(cparse("020") == octal!"020");
}