///|
struct Windows {
drive : BytesView
root : BytesView
anchor : BytesView
parts : ArrayView[BytesView]
} derive(Eq, Hash)
///|
pub impl ToJson for Windows with to_json(self : Windows) -> Json {
self.to_string().to_json()
}
///|
fn Windows::inspect(self : Windows) -> Json raise @utf8.Malformed {
{
"drive": @utf8.decode(self.drive),
"root": @utf8.decode(self.root),
"anchor": @utf8.decode(self.anchor),
"parts": self.parts.map(@utf8.decode(_)),
}
}
///|
pub impl @json.FromJson for Windows with from_json(
json : Json,
path : @json.JsonPath,
) -> Windows {
let string : String = @json.FromJson::from_json(json, path)
Windows::parse(@utf8.encode(string))
}
///|
pub fn Windows::parts(self : Windows) -> @builtin.Iter[BytesView] {
self.parts.iter()
}
///|
pub fn Windows::drive(self : Windows) -> BytesView {
self.drive
}
///|
pub fn Windows::root(self : Windows) -> BytesView {
self.root
}
///|
pub fn Windows::anchor(self : Windows) -> BytesView {
self.anchor
}
///|
pub impl Compare for Windows with compare(self : Windows, other : Windows) -> Int {
if self.anchor != other.anchor {
return self.anchor.compare(other.anchor)
}
return self.parts.compare(other.parts)
}
///|
fn Windows::parse(path : BytesView) -> Windows {
enum UncState {
Zero
One
}
let drive = @buffer.new()
let root = @buffer.new()
fn make_path(parts : Array[BytesView]) -> Windows {
let drive_string = drive.contents()
let root = root.contents()
drive.write_bytes(root)
let anchor = drive.contents()
return Windows::{ drive: drive_string, root, anchor, parts }
}
let parts : Array[BytesView] = []
let part = @buffer.new()
let path = match path {
[drive_letter, ':', '/' | '\\', .. path] => {
drive.write_byte(drive_letter)
drive.write_byte(':')
root.write_byte('\\')
parts.push([drive_letter, ..b":\\"])
skip~: loop path {
['/' | '\\', .. path] => continue skip~ path
[char, .. path] => {
part.write_byte(char)
path
}
[] => return make_path(parts)
}
}
[drive_letter, ':', char, .. path] => {
drive.write_byte(drive_letter)
drive.write_byte(':')
parts.push([drive_letter, ':'])
part.write_byte(char)
path
}
[drive_letter, ':'] => {
drive.write_byte(drive_letter)
drive.write_byte(':')
parts.push([drive_letter, ':'])
return make_path(parts)
}
['/' | '\\', '/' | '\\', .. path] => {
drive.write_bytes("\\\\")
unc~: loop (Zero, path) {
(Zero, ['/' | '\\', .. path]) => {
drive.write_byte('\\')
continue unc~ (One, path)
}
(Zero, [char, .. path]) => {
drive.write_byte(char)
continue unc~ (Zero, path)
}
(Zero, []) => {
parts.push(drive.contents())
return make_path(parts)
}
(One, ['/' | '\\', .. path]) => {
root.write_byte('\\')
parts.push([..drive.contents(), '\\'])
path
}
(One, [char, .. path]) => {
drive.write_byte(char)
continue unc~ (One, path)
}
(One, []) => {
let drive = drive.contents()
if drive is [.., '\\'] {
parts.push(drive)
return make_path(parts)
} else {
root.write_byte('\\')
parts.push([..drive, '\\'])
return make_path(parts)
}
}
}
}
['/' | '\\', char, .. path] => {
root.write_byte('\\')
parts.push("\\")
part.write_byte(char)
path
}
['/' | '\\'] => {
root.write_byte('\\')
parts.push("\\")
return make_path(parts)
}
[char, .. path] => {
part.write_byte(char)
path
}
[] => return make_path(parts)
}
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())
break path~ make_path(parts)
}
}
}
///|
test "parse_windows_path/empty" {
@json.inspect(Windows::parse("").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": [],
})
}
///|
test "parse_windows_path/drive" {
@json.inspect(Windows::parse("C:").inspect(), content={
"drive": "C:",
"root": "",
"anchor": "C:",
"parts": ["C:"],
})
@json.inspect(Windows::parse("ABC:").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["ABC:"],
})
@json.inspect(Windows::parse("C:\\").inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["C:\\"],
})
@json.inspect(Windows::parse("C:\\\\").inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["C:\\"],
})
@json.inspect(Windows::parse("C:\\Windows").inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["C:\\", "Windows"],
})
@json.inspect(Windows::parse("C:\\\\Windows").inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["C:\\", "Windows"],
})
@json.inspect(Windows::parse("C:/Windows").inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["C:\\", "Windows"],
})
@json.inspect(Windows::parse("C:/Windows/").inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["C:\\", "Windows"],
})
@json.inspect(Windows::parse("C:\\Windows\\").inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["C:\\", "Windows"],
})
@json.inspect(Windows::parse("C:Windows").inspect(), content={
"drive": "C:",
"root": "",
"anchor": "C:",
"parts": ["C:", "Windows"],
})
@json.inspect(Windows::parse("C:Windows/").inspect(), content={
"drive": "C:",
"root": "",
"anchor": "C:",
"parts": ["C:", "Windows"],
})
}
///|
test "parse_windows_path/root" {
@json.inspect(Windows::parse("/").inspect(), content={
"drive": "",
"root": "\\",
"anchor": "\\",
"parts": ["\\"],
})
@json.inspect(Windows::parse("\\").inspect(), content={
"drive": "",
"root": "\\",
"anchor": "\\",
"parts": ["\\"],
})
@json.inspect(Windows::parse("Windows").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["Windows"],
})
@json.inspect(Windows::parse("/Windows").inspect(), content={
"drive": "",
"root": "\\",
"anchor": "\\",
"parts": ["\\", "Windows"],
})
@json.inspect(Windows::parse("\\Windows").inspect(), content={
"drive": "",
"root": "\\",
"anchor": "\\",
"parts": ["\\", "Windows"],
})
}
///|
test "parse_windows_path/unc" {
@json.inspect(Windows::parse("//").inspect(), content={
"drive": "\\\\",
"root": "",
"anchor": "\\\\",
"parts": ["\\\\"],
})
@json.inspect(Windows::parse("\\\\").inspect(), content={
"drive": "\\\\",
"root": "",
"anchor": "\\\\",
"parts": ["\\\\"],
})
@json.inspect(Windows::parse("/\\").inspect(), content={
"drive": "\\\\",
"root": "",
"anchor": "\\\\",
"parts": ["\\\\"],
})
@json.inspect(Windows::parse("///").inspect(), content={
"drive": "\\\\\\",
"root": "",
"anchor": "\\\\\\",
"parts": ["\\\\\\"],
})
@json.inspect(Windows::parse("////").inspect(), content={
"drive": "\\\\\\",
"root": "\\",
"anchor": "\\\\\\\\",
"parts": ["\\\\\\\\"],
})
@json.inspect(Windows::parse("/////").inspect(), content={
"drive": "\\\\\\",
"root": "\\",
"anchor": "\\\\\\\\",
"parts": ["\\\\\\\\"],
})
@json.inspect(Windows::parse("//unc").inspect(), content={
"drive": "\\\\unc",
"root": "",
"anchor": "\\\\unc",
"parts": ["\\\\unc"],
})
@json.inspect(Windows::parse("//unc/").inspect(), content={
"drive": "\\\\unc\\",
"root": "",
"anchor": "\\\\unc\\",
"parts": ["\\\\unc\\"],
})
@json.inspect(Windows::parse("//unc/share").inspect(), content={
"drive": "\\\\unc\\share",
"root": "\\",
"anchor": "\\\\unc\\share\\",
"parts": ["\\\\unc\\share\\"],
})
@json.inspect(Windows::parse("//unc/share/").inspect(), content={
"drive": "\\\\unc\\share",
"root": "\\",
"anchor": "\\\\unc\\share\\",
"parts": ["\\\\unc\\share\\"],
})
@json.inspect(Windows::parse("\\\\unc\\share"), content="\\\\unc\\share\\")
@json.inspect(Windows::parse("\\\\unc\\share\\"), content="\\\\unc\\share\\")
@json.inspect(
Windows::parse("//unc/share/a/b"),
content="\\\\unc\\share\\a\\b",
)
@json.inspect(
Windows::parse("\\\\unc\\share\\a\\b"),
content="\\\\unc\\share\\a\\b",
)
}
///|
test "parse_windows_path/collapse_slashes" {
@json.inspect(Windows::parse("a//b///c////d").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["a", "b", "c", "d"],
})
@json.inspect(Windows::parse("a\\\\b\\\\\\c\\\\\\\\d").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["a", "b", "c", "d"],
})
@json.inspect(Windows::parse("a/b/c/d").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["a", "b", "c", "d"],
})
}
///|
test "parse_windows_path/collapse_dots" {
@json.inspect(Windows::parse("./a/b/c").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["a", "b", "c"],
})
@json.inspect(Windows::parse("a/b/c/.").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["a", "b", "c"],
})
@json.inspect(Windows::parse("a/b/c/./d").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["a", "b", "c", "d"],
})
@json.inspect(Windows::parse("a/b/c/././d").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["a", "b", "c", "d"],
})
@json.inspect(Windows::parse("a/b/c/./././d").inspect(), content={
"drive": "",
"root": "",
"anchor": "",
"parts": ["a", "b", "c", "d"],
})
@json.inspect(Windows::parse("//./").inspect(), content={
"drive": "\\\\.\\",
"root": "",
"anchor": "\\\\.\\",
"parts": ["\\\\.\\"],
})
@json.inspect(Windows::parse("//./.").inspect(), content={
"drive": "\\\\.\\.",
"root": "\\",
"anchor": "\\\\.\\.\\",
"parts": ["\\\\.\\.\\"],
})
@json.inspect(Windows::parse("//./././a/b/c").inspect(), content={
"drive": "\\\\.\\.",
"root": "\\",
"anchor": "\\\\.\\.\\",
"parts": ["\\\\.\\.\\", "a", "b", "c"],
})
}
///|
pub impl Show for Windows with output(self : Windows, logger : &Logger) -> Unit {
if self.anchor == "" && self.parts is [] {
logger.write_char('.')
return
}
if self.drive != "" || self.root != "" {
logger.write_string(@utf8.decode_lossy(self.drive))
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 Windows::to_bytes(self : Windows) -> Bytes {
if self.anchor == "" && self.parts is [] {
return "."
}
let buffer = @buffer.new()
if self.drive != "" || self.root != "" {
buffer.write_bytesview(self.drive)
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 Windows::parent(self : Windows) -> Windows? {
if self.parts.length() == 0 {
return None
}
if (self.drive != "" || self.root != "") && self.parts.length() == 1 {
return None
}
Some(Windows::{ ..self, parts: self.parts[:self.parts.length() - 1] })
}
///|
test "BasicPath::parent" {
let path = Windows::{
drive: "C:",
root: "\\",
anchor: "C:\\",
parts: ["C:\\", "Windows", "System32"],
}
@json.inspect(path.parent(), content=["C:\\Windows"])
let path = Windows::{
drive: "C:",
root: "\\",
anchor: "C:\\",
parts: ["C:\\"],
}
@json.inspect(path.parent() is None, content=true)
let path = Windows::{ drive: "", root: "", anchor: "", parts: [] }
@json.inspect(path.parent() is None, content=true)
}
///|
pub impl ToPosix for Windows with to_posix(self : Windows) -> Posix {
fn convert_to_slash(source : BytesView, target : @buffer.Buffer) -> Unit {
loop source {
['\\', .. chars] => {
target.write_byte('/')
continue chars
}
[char, .. chars] => {
target.write_byte(char)
continue chars
}
[] => ()
}
}
if self.drive != "" || self.root != "" {
let anchor = @buffer.new()
convert_to_slash(self.drive, anchor)
convert_to_slash(self.root, anchor)
let anchor = Posix::parse(anchor.to_bytes())
Posix::{ ..anchor, parts: [..anchor.parts, ..self.parts[1:]] }
} else {
Posix::{ root: self.root, anchor: self.anchor, parts: [..self.parts] }
}
}
///|
pub fn[ToWindows : ToWindows] Windows::relative_to(
self : Windows,
other : ToWindows,
) -> Windows {
let other = other.to_windows()
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.. Windows {
if other.drive != "" {
return other
}
if other.root != "" {
return Windows::{
drive: self.drive,
root: other.root,
anchor: [..self.drive, ..other.root],
parts: other.parts,
}
}
Windows::{ ..self, parts: [..self.parts, ..other.parts] }
}
///|
test "Path::join" {
let a = Windows::{
drive: "C:",
root: "\\",
anchor: "C:\\",
parts: ["C:\\", "Windows"],
}
let b = Windows::{ drive: "", root: "", anchor: "", parts: ["System32"] }
let c = a.join(b)
@json.inspect(c.inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["C:\\", "Windows", "System32"],
})
let a = Windows::{
drive: "C:",
root: "\\",
anchor: "C:\\",
parts: ["C:\\", "Windows"],
}
let b = Windows::{
drive: "D:",
root: "\\",
anchor: "D:\\",
parts: ["D:\\", "System32"],
}
let c = a.join(b)
@json.inspect(c.inspect(), content={
"drive": "D:",
"root": "\\",
"anchor": "D:\\",
"parts": ["D:\\", "System32"],
})
let a = Windows::{
drive: "C:",
root: "\\",
anchor: "C:\\",
parts: ["C:\\", "Windows"],
}
let b = Windows::{
drive: "",
root: "\\",
anchor: "\\",
parts: ["\\", "System32"],
}
let c = a.join(b)
@json.inspect(c.inspect(), content={
"drive": "C:",
"root": "\\",
"anchor": "C:\\",
"parts": ["\\", "System32"],
})
}
///|
pub fn Windows::suffix(self : Windows) -> 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 Windows::name(self : Windows) -> BytesView? {
let parts_length = self.parts.length()
if self.anchor != "" && self.parts.length() == 1 {
return None
}
Some(self.parts[parts_length - 1])
}
///|
pub trait ToWindows {
to_windows(Self) -> Windows
}
///|
pub impl ToWindows for String with to_windows(self : String) -> Windows {
ToWindows::to_windows(self.view())
}
///|
pub impl ToWindows for @string.View with to_windows(self : @string.View) -> Windows {
Windows::parse(@utf8.encode(self))
}
///|
pub impl ToWindows for Bytes with to_windows(self : Bytes) -> Windows {
Windows::parse(self)
}
///|
pub impl ToWindows for BytesView with to_windows(self : BytesView) -> Windows {
Windows::parse(self)
}
///|
pub impl ToWindows for Windows with to_windows(self : Windows) -> Windows {
self
}
///|
pub impl[T : ToWindows] ToWindows for Array[T] with to_windows(self : Array[T]) -> Windows {
ToWindows::to_windows(self[:])
}
///|
pub impl[T : ToWindows] ToWindows for ArrayView[T] with to_windows(
self : ArrayView[T],
) -> Windows {
let mut path = Windows::{ drive: "", root: "", anchor: "", parts: [] }
for item in self {
path = path.join(item.to_windows())
}
path
}
///|
pub fn[ToWindows : ToWindows] windows(path : ToWindows) -> Windows {
path.to_windows()
}