///|
struct Posix {
root : BytesView
anchor : BytesView
parts : ArrayView[BytesView]
} derive(Eq, Hash)
///|
pub impl ToJson for Posix with to_json(self : Posix) -> Json {
self.to_string().to_json()
}
///|
fn Posix::inspect(self : Posix) -> Json raise @utf8.Malformed {
{
"root": @utf8.decode(self.root),
"anchor": @utf8.decode(self.anchor),
"parts": self.parts.map(@utf8.decode(_)),
}
}
///|
pub impl @json.FromJson for Posix with from_json(
json : Json,
path : @json.JsonPath,
) -> Posix {
let string : String = @json.FromJson::from_json(json, path)
Posix::parse(@utf8.encode(string))
}
///|
pub fn Posix::parts(self : Posix) -> @builtin.Iter[BytesView] {
self.parts.iter()
}
///|
pub fn Posix::root(self : Posix) -> BytesView {
self.root
}
///|
pub fn Posix::anchor(self : Posix) -> BytesView {
self.anchor
}
///|
pub impl Compare for Posix with compare(self : Posix, other : Posix) -> Int {
if self.anchor != other.anchor {
return self.anchor.compare(other.anchor)
}
return self.parts.compare(other.parts)
}
///|
fn Posix::parse(path : BytesView) -> Posix {
let root = @buffer.new()
fn make_path(path : Array[BytesView]) -> Posix {
let root = root.contents()
return Posix::{ root, anchor: root, parts: path }
}
let parts : Array[BytesView] = []
let part = @buffer.new()
let path = match path {
[.. "///", .. path] => {
root.write_byte('/')
parts.push("/")
skip~: loop path {
['/', .. path] => continue skip~ path
[char, .. path] => {
part.write_byte(char)
path
}
[] => return make_path(parts)
}
}
[.. "//", .. path] => {
root.write_bytes("//")
parts.push("//")
path
}
['/', .. path] => {
root.write_byte('/')
parts.push("/")
path
}
[.. path] => path
}
fn push_part(part : BytesView) {
if part is ("" | ".") {
return
}
parts.push(part)
}
path~: loop path {
['/', .. path] => {
push_part(part.contents())
skip~: loop path {
['/', .. path] => continue skip~ path
[char, .. path] => {
part.reset()
part.write_byte(char)
continue path~ path
}
[] => break path~ make_path(parts)
}
}
[char, .. path] => {
part.write_byte(char)
continue path~ path
}
[] => {
push_part(part.contents())
return make_path(parts)
}
}
}
///|
test "posix_path/root" {
@json.inspect(Posix::parse("/").inspect(), content={
"root": "/",
"anchor": "/",
"parts": ["/"],
})
@json.inspect(Posix::parse("//").inspect(), content={
"root": "//",
"anchor": "//",
"parts": ["//"],
})
@json.inspect(Posix::parse("///").inspect(), content={
"root": "/",
"anchor": "/",
"parts": ["/"],
})
@json.inspect(Posix::parse("//usr//bin//").inspect(), content={
"root": "//",
"anchor": "//",
"parts": ["//", "usr", "bin"],
})
@json.inspect(Posix::parse("////usr").inspect(), content={
"root": "/",
"anchor": "/",
"parts": ["/", "usr"],
})
@json.inspect(Posix::parse("/usr/bin").inspect(), content={
"root": "/",
"anchor": "/",
"parts": ["/", "usr", "bin"],
})
}
///|
test "posix_path" {
@json.inspect(Posix::parse("usr/bin").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "bin"],
})
@json.inspect(Posix::parse("usr/bin/").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "bin"],
})
@json.inspect(Posix::parse("usr//bin").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "bin"],
})
@json.inspect(Posix::parse("C:\\Windows").inspect(), content={
"root": "",
"anchor": "",
"parts": ["C:\\Windows"],
})
}
///|
test "posix_path/empty" {
@json.inspect(Posix::parse("").inspect(), content={
"root": "",
"anchor": "",
"parts": [],
})
}
///|
test "posix_path/collapse_slashes" {
@json.inspect(Posix::parse("usr/////bin").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "bin"],
})
@json.inspect(Posix::parse("usr//bin//").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "bin"],
})
@json.inspect(Posix::parse("usr///bin//").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "bin"],
})
}
///|
test "posix_path/collapse_dots" {
@json.inspect(Posix::parse("usr/./bin").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "bin"],
})
@json.inspect(Posix::parse("usr/../bin").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "..", "bin"],
})
@json.inspect(Posix::parse("usr/.././bin").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "..", "bin"],
})
@json.inspect(Posix::parse("usr/./../bin").inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "..", "bin"],
})
}
///|
pub impl Show for Posix with output(self : Posix, logger : &Logger) -> Unit {
if self.anchor == "" && self.parts is [] {
logger.write_char('.')
return
}
if self.root != "" {
logger.write_string(@utf8.decode_lossy(self.root))
let parts = self.parts[1:]
for i, part in parts {
logger.write_string(@utf8.decode_lossy(part))
if i < parts.length() - 1 {
logger.write_char('/')
}
}
} else {
for i, part in self.parts {
logger.write_string(@utf8.decode_lossy(part))
if i < self.parts.length() - 1 {
logger.write_char('/')
}
}
}
}
///|
pub fn Posix::to_bytes(self : Posix) -> Bytes {
if self.anchor == "" && self.parts is [] {
return "."
}
let buffer = @buffer.new()
if self.root != "" {
buffer.write_bytesview(self.root)
let parts = self.parts[1:]
for i, part in parts {
buffer.write_bytesview(part)
if i < parts.length() - 1 {
buffer.write_byte('/')
}
}
} else {
for i, part in self.parts {
buffer.write_bytesview(part)
if i < self.parts.length() - 1 {
buffer.write_byte('/')
}
}
}
buffer.contents()
}
///|
pub fn Posix::parent(self : Posix) -> Posix? {
if self.parts.length() == 0 {
return None
}
if self.root != "" && self.parts.length() == 1 {
return None
}
Some(Posix::{ ..self, parts: self.parts[:self.parts.length() - 1] })
}
///|
test "BasicPath::parent" {
let path = Posix::{ root: "", anchor: "", parts: ["usr", "bin"] }
@json.inspect(path.parent(), content=["usr"])
let path = Posix::{ root: "/", anchor: "/", parts: ["/", "usr", "bin"] }
@json.inspect(path.parent(), content=["/usr"])
let path = Posix::{ root: "", anchor: "", parts: [] }
@json.inspect(path.parent() is None, content=true)
}
///|
pub impl ToWindows for Posix with to_windows(self : Posix) -> Windows {
Windows::parse(self.to_bytes())
}
///|
pub fn[ToPosix : ToPosix] Posix::relative_to(
self : Posix,
other : ToPosix,
) -> Posix {
let other = other.to_posix()
let self_parts_length = self.parts.length()
let other_parts_length = other.parts.length()
let minimum_parts_length = @cmp.minimum(self_parts_length, other_parts_length)
let start_index = if self.anchor != "" { 1 } else { 0 }
let common_parts_length = for i in start_index.. Posix {
let other = other.to_posix()
if other.root != "" {
return other
}
Posix::{ ..self, parts: [..self.parts, ..other.parts] }
}
///|
test "Path::join" {
let a = Posix::{ root: "", anchor: "", parts: ["usr", "bin"] }
let b = Posix::{ root: "", anchor: "", parts: ["local", "bin"] }
let c = a.join(b)
@json.inspect(c.inspect(), content={
"root": "",
"anchor": "",
"parts": ["usr", "bin", "local", "bin"],
})
let a = Posix::{ root: "/", anchor: "/", parts: ["/", "usr", "bin"] }
let b = Posix::{ root: "", anchor: "", parts: ["local", "bin"] }
let c = a.join(b)
@json.inspect(c.inspect(), content={
"root": "/",
"anchor": "/",
"parts": ["/", "usr", "bin", "local", "bin"],
})
let a = Posix::{ root: "", anchor: "", parts: ["usr", "bin"] }
let b = Posix::{ root: "/", anchor: "/", parts: ["/", "local", "bin"] }
let c = a.join(b)
@json.inspect(c.inspect(), content={
"root": "/",
"anchor": "/",
"parts": ["/", "local", "bin"],
})
}
///|
pub fn Posix::suffix(self : Posix) -> BytesView? {
guard self.name() is Some(name) else { None }
loop name {
[] => None
[.., '.'] as view => Some(view.data()[view.start_offset():])
[.. rest, _] => continue rest
}
}
///|
pub fn Posix::name(self : Posix) -> BytesView? {
let parts_length = self.parts.length()
if self.anchor != "" && self.parts.length() == 1 {
return None
}
Some(self.parts[parts_length - 1])
}
///|
pub trait ToPosix {
to_posix(Self) -> Posix
}
///|
pub impl ToPosix for String with to_posix(self : String) -> Posix {
ToPosix::to_posix(self.view())
}
///|
pub impl ToPosix for @string.View with to_posix(self : @string.View) -> Posix {
Posix::parse(@utf8.encode(self))
}
///|
pub impl ToPosix for Bytes with to_posix(self : Bytes) -> Posix {
Posix::parse(self)
}
///|
pub impl ToPosix for BytesView with to_posix(self : BytesView) -> Posix {
Posix::parse(self)
}
///|
pub impl ToPosix for Posix with to_posix(self : Posix) -> Posix {
self
}
///|
pub impl[T : ToPosix] ToPosix for Array[T] with to_posix(self : Array[T]) -> Posix {
ToPosix::to_posix(self[:])
}
///|
pub impl[T : ToPosix] ToPosix for ArrayView[T] with to_posix(
self : ArrayView[T],
) -> Posix {
let mut path = Posix::{ root: "", anchor: "", parts: [] }
for item in self {
path = path.join(item.to_posix())
}
path
}
///|
pub fn[ToPosix : ToPosix] posix(path : ToPosix) -> Posix {
path.to_posix()
}