mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
import json import path import time import proc -- found in std/ after the program's own files, so a program's own json.fch always wins. path: join, base, dir, ext, stem, parts, clean, is_abs. time: now, millis, sleep, date, local, format, days_in_month (clock_gettime, strftime through C import). proc: run, output (an error with the command's output when it fails), env, pid, cwd. json: parse (errors with line and column), stringify, pretty, new_*, get/at/set/push/has/names and str_or/int_or/float_or/bool_or; \u escapes with surrogate pairs. The C importer now leaves a plain (non-const) char * parameter a ptr[char], a buffer C writes into; only const char * is text going in. str(ptr) and str([]u8) are in the self-hosted compiler. boot.sh: 40 passed, 0 failed.
128 lines
2.8 KiB
Text
128 lines
2.8 KiB
Text
// Paths, as text. Elements are separated by "/".
|
|
//
|
|
// import path
|
|
// path.join("a", "b.txt") a/b.txt
|
|
// path.base("/tmp/x/a.tar.gz") a.tar.gz path.stem(...) a.tar path.ext(...) .gz
|
|
// path.dir("/tmp/x/a.txt") /tmp/x
|
|
// path.clean("a//b/../c/./d") a/c/d
|
|
|
|
// a and b with one separator between; an absolute b replaces a
|
|
fn join(str a, str b) -> str {
|
|
if a.len == 0 {
|
|
return b
|
|
}
|
|
if b.len == 0 {
|
|
return a
|
|
}
|
|
if b[0] == '/' {
|
|
return b
|
|
}
|
|
if a[a.len - 1] == '/' {
|
|
return a + b
|
|
}
|
|
return a + "/" + b
|
|
}
|
|
|
|
fn is_abs(str p) -> bool {
|
|
return p.len > 0 && p[0] == '/'
|
|
}
|
|
|
|
// the last element: "a/b.txt" -> "b.txt", "a/b/" -> "b", "" -> ".", "/" -> "/"
|
|
fn base(str p) -> str {
|
|
if p.len == 0 {
|
|
return "."
|
|
}
|
|
end := p.len
|
|
while end > 1 && p[end - 1] == '/' {
|
|
end -= 1
|
|
}
|
|
if end == 1 && p[0] == '/' {
|
|
return "/"
|
|
}
|
|
start := end
|
|
while start > 0 && p[start - 1] != '/' {
|
|
start -= 1
|
|
}
|
|
return p.sub(start, end)
|
|
}
|
|
|
|
// everything before the last element: "a/b.txt" -> "a", "b.txt" -> ".", "/b" -> "/"
|
|
fn dir(str p) -> str {
|
|
end := p.len
|
|
while end > 1 && p[end - 1] == '/' {
|
|
end -= 1
|
|
}
|
|
i := end
|
|
while i > 0 && p[i - 1] != '/' {
|
|
i -= 1
|
|
}
|
|
if i == 0 {
|
|
return "."
|
|
}
|
|
while i > 1 && p[i - 1] == '/' {
|
|
i -= 1
|
|
}
|
|
return p.sub(0, i)
|
|
}
|
|
|
|
// the part of the last element from its last dot: "a.tar.gz" -> ".gz"; "" when there is none
|
|
fn ext(str p) -> str {
|
|
b := base(p)
|
|
i := b.len
|
|
while i > 0 && b[i - 1] != '.' {
|
|
i -= 1
|
|
}
|
|
if i <= 1 { // no dot, or a name that only starts with one (.bashrc)
|
|
return ""
|
|
}
|
|
return b.sub(i - 1, b.len)
|
|
}
|
|
|
|
// the last element without its extension
|
|
fn stem(str p) -> str {
|
|
b := base(p)
|
|
e := ext(p)
|
|
return b.sub(0, b.len - e.len)
|
|
}
|
|
|
|
// the elements of a path: "/a/b/c" -> ["a", "b", "c"]
|
|
fn parts(str p) -> []str {
|
|
[]str out
|
|
for piece in p.split("/") {
|
|
if piece.len > 0 {
|
|
out.push(piece)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// the shortest path with the same meaning: no "//", no ".", and ".." resolved where it can be
|
|
fn clean(str p) -> str {
|
|
if p.len == 0 {
|
|
return "."
|
|
}
|
|
abs := is_abs(p)
|
|
[]str out
|
|
for piece in parts(p) {
|
|
if piece == "." {
|
|
continue
|
|
}
|
|
if piece == ".." {
|
|
if out.len > 0 && out[out.len - 1] != ".." {
|
|
out.pop()
|
|
} else if !abs {
|
|
out.push("..")
|
|
}
|
|
continue
|
|
}
|
|
out.push(piece)
|
|
}
|
|
joined := out.join("/")
|
|
if abs {
|
|
return "/" + joined
|
|
}
|
|
if joined.len == 0 {
|
|
return "."
|
|
}
|
|
return joined
|
|
}
|