std1.0.0-beta-7 SafeC docs
// SafeC Standard Library — Path strings (see path.h)
#pragma once
#include <std/path.h>
#include <std/collections/string.h>
#include <std/collections/vec.h>

namespace std {

int path_is_sep(char c) {
#ifdef _WIN32
    return c == '/' || c == '\\';
#else
    return c == '/';
#endif
}

int path_is_absolute(const &char p) {
    char c0;
    unsafe { c0 = p[0]; }
    if (path_is_sep(c0)) { return 1; }
#ifdef _WIN32
    unsafe {
        if (((c0 >= 'A' && c0 <= 'Z') || (c0 >= 'a' && c0 <= 'z')) && p[1] == ':' && path_is_sep(p[2])) {
            return 1;
        }
    }
#endif
    return 0;
}

struct String path_join(const &char a, const &char b) {
    if (path_is_absolute(b)) { return String::from(b); }
    struct String s = String::from(a);
    unsafe {
        if (b[0] == '\0') { return s; }
        if (s.len > 0UL && !path_is_sep((char)s.char_at(s.len - 1UL))) { s.push_char('/'); }
        unsigned long i = 0UL;
        while (b[i] != '\0' && path_is_sep(b[i]) && s.len > 0UL) { i = i + 1UL; }
        s.push((const char*)b + i);
    }
    return s;
}

// Length of 'p' without trailing separators (but keeping a lone root "/").
static unsigned long path_trimmed_len_(const &char p) {
    unsigned long n = 0UL;
    unsafe { while (p[n] != '\0') { n = n + 1UL; } }
    while (n > 1UL) {
        char c;
        unsafe { c = p[n - 1UL]; }
        if (!path_is_sep(c)) { break; }
        n = n - 1UL;
    }
    return n;
}

// Index where the last component of p[0..n) starts.
static unsigned long path_last_start_(const &char p, unsigned long n) {
    unsigned long i = n;
    while (i > 0UL) {
        char c;
        unsafe { c = p[i - 1UL]; }
        if (path_is_sep(c)) { break; }
        i = i - 1UL;
    }
    return i;
}

struct String path_dirname(const &char p) {
    unsigned long n = path_trimmed_len_(p);
    unsigned long start = path_last_start_(p, n);
    if (start == 0UL) { return String::new(); }
    // Drop the separator(s) before the last component, keeping a root.
    unsigned long end = start;
    while (end > 1UL) {
        char c;
        unsafe { c = p[end - 1UL]; }
        if (!path_is_sep(c)) { break; }
        end = end - 1UL;
    }
    unsafe { return String::from_n_(p, end); }
}

struct String path_basename(const &char p) {
    unsigned long n = path_trimmed_len_(p);
    unsigned long start = path_last_start_(p, n);
    char c0;
    unsafe { c0 = p[0]; }
    if (n == 1UL && path_is_sep(c0)) { return String::new(); }
    unsafe { return String::from_n_((const char*)p + start, n - start); }
}

struct String path_ext(const &char p) {
    struct String base = path_basename(p);
    long long dot = base.rfind_char('.');
    struct String out;
    if (dot <= 0LL) { out = String::new(); }
    else { out = base.substr((unsigned long)dot + 1UL, base.len); }
    base.free();
    return out;
}

struct String path_stem(const &char p) {
    struct String base = path_basename(p);
    long long dot = base.rfind_char('.');
    if (dot > 0LL) { base.truncate((unsigned long)dot); }
    return base;
}

// Splits 'p' into its non-empty, non-"." components, resolving "..".
// Returns 1 if 'p' is absolute; 'prefix' gets a Windows drive ("C:").
static int path_components_(const &char p, &Vec<struct String> parts, &String prefix) {
    int abs = path_is_absolute(p);
    unsigned long i = 0UL;
#ifdef _WIN32
    unsafe {
        if (p[0] != '\0' && p[1] == ':') {
            prefix.push_n_(p, 2UL);
            i = 2UL;
        }
    }
#endif
    for (;;) {
        char c;
        unsafe { c = p[i]; }
        if (c == '\0') { break; }
        if (path_is_sep(c)) { i = i + 1UL; continue; }
        unsigned long s = i;
        unsafe { while (p[i] != '\0' && !path_is_sep(p[i])) { i = i + 1UL; } }
        struct String comp;
        unsafe { comp = String::from_n_((const char*)p + s, i - s); }
        if (comp.eq_cstr(".")) { comp.free(); continue; }
        if (comp.eq_cstr("..")) {
            int can_pop = 0;
            if (parts.len > 0UL) { can_pop = !parts.at(parts.len - 1UL).eq_cstr(".."); }
            if (can_pop) {
                ?struct String last = parts.pop();
                match (last) { case some(l): { l.free(); } case none: { } }
                comp.free();
                continue;
            }
            if (abs) { comp.free(); continue; }   // "/.." is "/"
        }
        parts.push(comp);
    }
    return abs;
}

struct String path_normalize(const &char p) {
    struct Vec<struct String> parts = Vec::new();
    struct String out = String::new();
    int abs = path_components_(p, &parts, &out);
    if (abs) { out.push_char('/'); }
    for (unsigned long i = 0UL; i < parts.len; i = i + 1UL) {
        if (i > 0UL) { out.push_char('/'); }
        out.push_str(parts.at(i));
    }
    if (out.len == 0UL) { out.push_char('.'); }
    parts.drop();
    return out;
}

struct String path_relative(const &char from, const &char to) {
    struct Vec<struct String> a = Vec::new();
    struct Vec<struct String> b = Vec::new();
    struct String pa = String::new();
    struct String pb = String::new();
    int abs_a = path_components_(from, &a, &pa);
    int abs_b = path_components_(to, &b, &pb);
    struct String out = String::new();
    if (abs_a != abs_b || !pa.eq(&pb)) {
        a.drop(); b.drop(); pa.free(); pb.free();
        out.free();
        return path_normalize(to);
    }
    unsigned long common = 0UL;
    while (common < a.len && common < b.len && a.at(common).eq(b.at(common))) { common = common + 1UL; }
    for (unsigned long i = common; i < a.len; i = i + 1UL) {
        if (out.len > 0UL) { out.push_char('/'); }
        out.push("..");
    }
    for (unsigned long i = common; i < b.len; i = i + 1UL) {
        if (out.len > 0UL) { out.push_char('/'); }
        out.push_str(b.at(i));
    }
    if (out.len == 0UL) { out.push_char('.'); }
    a.drop(); b.drop(); pa.free(); pb.free();
    return out;
}

} // namespace std