lib.bend source
lib.bend on the hub · documented module
# Path — small Unix-style path helpers for Bend.# Publish entry for this package. Depends only on Base.## Paths are plain Base Strings. v1 uses '/' regardless of host platform.# Path.segments/split discard empty slash-delimited components; normalize# therefore collapses repeated slashes and removes a trailing slash (except# for the root path). '.' and '..' are ordinary components in this light# normalizer; this package does not touch the filesystem.## Base already provides String.split, String.join, String.append and String# prefix/suffix queries. Path keeps those primitives and adds path-aware# wrappers with root, basename, dirname and extension semantics.import Base# ---- segment views --------------------------------------------------------# Non-empty Unix path components. Base provides String.split and List.filter;# the predicate only removes boundary/repeated-slash empty components.def Path.segments(path: String) -> List<&2, String>: List.filter(~String, ~(s => Bool.not(String.is_empty(s))), String.split(path, '/'))def Path.split(path: String) -> List<&2, String>: Path.segments(path)def Path.is_absolute(path: String) -> Bool: match path: case SNil{}: False{} case SCon{h, t}: Char.is_eq(h, '/')# ---- normalization and joining -------------------------------------------def Path.normalized_body(path: String) -> String: String.join(Path.segments(path), "/")def Path.normalize.body(body: String, absolute: Bool, empty: Bool) -> String: match absolute empty: case False{} _: body case True{} True{}: "/" case True{} False{}: String.append("/", body)def Path.normalize(+path: String) -> String: +body = Path.normalized_body(path) Path.normalize.body(body, Path.is_absolute(path), String.is_empty(body))def Path.join.empty(+a: String, +b: String, a_empty: Bool, b_empty: Bool) -> String: match a_empty b_empty: case True{} True{}: "" case True{} False{}: Path.normalize(b) case False{} True{}: Path.normalize(a) case False{} False{}: Path.normalize(String.append(a, String.append("/", b)))def Path.join.if(+a: String, +b: String, absolute: Bool) -> String: match absolute: case True{}: Path.normalize(b) case False{}: Path.join.empty(a, b, String.is_empty(a), String.is_empty(b))def Path.join(+a: String, +b: String) -> String: Path.join.if(a, b, Path.is_absolute(b))def Path.join_all.go(xs: List<&2, String>, acc: String) -> String: match xs: case Nil{}: acc case h <> t: Path.join_all.go(t, Path.join(acc, h))def Path.join_all(xs: List<&2, String>) -> String: Path.join_all.go(xs, "")# ---- final components -----------------------------------------------------def Path.basename.go(xs: List<&2, String>, last: String) -> String: match xs: case Nil{}: last case h <> t: Path.basename.go(t, h)def Path.basename(path: String) -> String: Path.basename.go(Path.segments(path), "")def Path.dirname.parts(xs: List<&2, String>) -> List<&2, String>: match xs: case Nil{}: Nil{} case h <> Nil{}: Nil{} case h <> t: h <> Path.dirname.parts(t)def Path.dirname.body(body: String, absolute: Bool, empty: Bool) -> String: match absolute empty: case True{} True{}: "/" case True{} False{}: String.append("/", body) case False{} _: match empty: case True{}: "." case False{}: bodydef Path.dirname(+path: String) -> String: +body = String.join(Path.dirname.parts(Path.segments(path)), "/") Path.dirname.body(body, Path.is_absolute(path), String.is_empty(body))# ---- extension ------------------------------------------------------------def Path.extension.body.if(suffix: String, empty: Bool) -> String: match empty: case True{}: "" case False{}: String.append(".", String.reverse(suffix))def Path.extension.body.parts(parts: List<&2, String>) -> String: match parts: case Nil{}: "" case +suffix <> t: Path.extension.body.if(suffix, String.is_empty(suffix))def Path.extension.body(basename: String) -> String: Path.extension.body.parts(String.split(String.reverse(basename), '.'))# A leading dot alone names a hidden file, not an extension. A later dot in# that name still starts an extension (e.g. .profile.bak -> .bak).def Path.extension.hidden(+basename: String) -> Bool: Bool.and( String.starts_with(basename, "."), Bool.not(String.contains(String.drop(basename, 1n), ".")))def Path.extension.if(+basename: String, hidden: Bool, has_dot: Bool) -> String: match hidden has_dot: case True{} _: "" case False{} False{}: "" case False{} True{}: Path.extension.body(basename)def Path.extension(path: String) -> String: +b = Path.basename(path) Path.extension.if(b, Path.extension.hidden(b), String.contains(b, "."))