std1.0.0-beta-7 SafeC docs
// SafeC Standard Library — Directories: the platform-independent tree
// operations (see dir.h). Dir, file_stat, dir_create, dir_remove and
// file_remove come from dir_posix.sc / dir_win32.sc.
#pragma once
#include <std/dir.h>
#include <std/path.h>
#include <std/io_file.h>
#include <std/collections/string.h>
#include <std/collections/vec.h>

namespace std {

struct DirEntry DirEntry::new() {
    struct DirEntry e;
    e.name = String::new();
    e.kind = DIR_NONE;
    return e;
}

void DirEntry::free() { self.drop(); }

void DirEntry::drop() {
    self.name.free();
}

int path_kind(const &char path) {
    struct FileStat st;
    file_stat(path, &st);
    return st.kind;
}

int path_exists(const &char path) {
    return path_kind(path) != DIR_NONE ? 1 : 0;
}

int dir_create_all(const &char path) {
    int kind = path_kind(path);
    if (kind == DIR_DIR) { return 1; }
    if (kind != DIR_NONE) { return 0; }   // a file is in the way
    struct String parent = path_dirname(path);
    int ok = 1;
    if (parent.len > 0UL && !parent.eq_cstr(path)) { ok = dir_create_all(parent.as_ptr()); }
    parent.free();
    if (!ok) { return 0; }
    return dir_create(path);
}

int dir_remove_all(const &char path) {
    int kind = path_kind(path);
    if (kind == DIR_NONE) { return 1; }
    if (kind != DIR_DIR) {
        if (file_remove(path)) { return 1; }
        return dir_remove(path);   // a Windows directory link
    }
    int ok = 1;
    ?&Dir d = Dir::open(path);
    match (d) {
        case some(h): {
            struct DirEntry e = DirEntry::new();
            while (h.next(&e)) {
                struct String child = path_join(path, e.name.as_ptr());
                if (!dir_remove_all(child.as_ptr())) { ok = 0; }
                child.free();
            }
            e.free();
            h.close();
        }
        case null: { ok = 0; }
    }
    if (!dir_remove(path)) { ok = 0; }
    return ok;
}

int file_copy(const &char from, const &char to) {
    ?&File src = File::open(from, "rb");
    match (src) {
        case some(in): {
            ?&File dst = File::open(to, "wb");
            int ok = 1;
            match (dst) {
                case some(out): {
                    unsigned char buf[65536];
                    for (;;) {
                        long long n = in.read(buf[0..65536UL]);
                        if (n < 0LL) { ok = 0; break; }
                        if (n == 0LL) { break; }
                        if (out.write(buf[0..(unsigned long)n]) != n) { ok = 0; break; }
                    }
                    if (out.close() != 0) { ok = 0; }
                }
                case null: { ok = 0; }
            }
            in.close();
            return ok;
        }
        case null: { return 0; }
    }
    return 0;
}

static int dir_cmp_string_(const &String a, const &String b) {
    return a.cmp(b);
}

// Appends 'prefix'-relative paths of the files below 'dir' to 'out'.
static int dir_walk_into_(const &char dir, const &char prefix, &Vec<struct String> out) {
    ?&Dir d = Dir::open(dir);
    int ok = 1;
    match (d) {
        case some(h): {
            struct DirEntry e = DirEntry::new();
            while (h.next(&e)) {
                struct String rel = path_join(prefix, e.name.as_ptr());
                if (e.kind == DIR_DIR) {
                    struct String full = path_join(dir, e.name.as_ptr());
                    if (!dir_walk_into_(full.as_ptr(), rel.as_ptr(), out)) { ok = 0; }
                    full.free();
                    rel.free();
                } else if (e.kind == DIR_LINK) {
                    // A link to a file is listed; a link to a directory is not followed.
                    struct String full = path_join(dir, e.name.as_ptr());
                    ?&Dir probe = Dir::open(full.as_ptr());
                    match (probe) {
                        case some(p): { p.close(); rel.free(); }
                        case null: { out.push(rel); }
                    }
                    full.free();
                } else {
                    out.push(rel);
                }
            }
            e.free();
            h.close();
        }
        case null: { ok = 0; }
    }
    return ok;
}

int dir_walk(const &char root, &Vec<struct String> out) {
    unsigned long first = out.len;
    int ok = dir_walk_into_(root, "", out);
    // Sort just the entries this call added.
    struct Vec<struct String> added = Vec::new();
    while (out.len > first) {
        ?struct String s = out.pop();
        match (s) { case some(v): { added.push(v); } case none: { } }
    }
    added.sort(dir_cmp_string_);
    for (unsigned long i = 0UL; i < added.len; i = i + 1UL) { out.push(std::replace(added.at(i), String::new())); }
    added.free();
    return ok;
}

int dir_copy_tree(const &char from, const &char to) {
    if (!dir_create_all(to)) { return 0; }
    int ok = 1;
    ?&Dir d = Dir::open(from);
    match (d) {
        case some(h): {
            struct DirEntry e = DirEntry::new();
            while (h.next(&e)) {
                struct String src = path_join(from, e.name.as_ptr());
                struct String dst = path_join(to, e.name.as_ptr());
                int kind = e.kind;
                if (kind == DIR_LINK || kind == DIR_OTHER) {
                    // Copy what a link points at: a directory tree or a file.
                    ?&Dir probe = Dir::open(src.as_ptr());
                    match (probe) {
                        case some(p): { p.close(); kind = DIR_DIR; }
                        case null: { kind = DIR_FILE; }
                    }
                }
                if (kind == DIR_DIR) {
                    if (!dir_copy_tree(src.as_ptr(), dst.as_ptr())) { ok = 0; }
                } else if (!file_copy(src.as_ptr(), dst.as_ptr())) {
                    ok = 0;
                }
                src.free();
                dst.free();
            }
            e.free();
            h.close();
        }
        case null: { ok = 0; }
    }
    return ok;
}

} // namespace std