///|
#external
priv type NativeNames
///|
priv type NativePath
///|
#borrow(path)
extern "C" fn native_read_dir_all(path : Bytes) -> NativeNames = "readdirp_read_dir_all"
///|
extern "C" fn native_names_is_null(names : NativeNames) -> Int = "readdirp_names_is_null"
///|
fn native_names_to_fixed_array(names : NativeNames) -> FixedArray[Bytes] = "%identity"
///|
#borrow(path)
extern "C" fn native_stat_path(path : Bytes, follow_symlink : Int) -> Int = "readdirp_stat_path"
///|
#borrow(path)
extern "C" fn native_real_path(path : Bytes) -> NativePath = "readdirp_real_path"
///|
#borrow(path)
extern "C" fn native_path_is_null(path : NativePath) -> Int = "readdirp_names_is_null"
///|
fn native_path_to_bytes(path : NativePath) -> Bytes = "%identity"
///|
extern "C" fn native_stat_size() -> Int64 = "readdirp_stat_size"
///|
extern "C" fn native_stat_mode() -> Int = "readdirp_stat_mode"
///|
extern "C" fn native_last_error_message() -> Bytes = "readdirp_last_error_message"
///|
extern "C" fn native_last_errno() -> Int = "readdirp_last_errno_value"
///|
extern "C" fn native_errno_enoent() -> Int = "readdirp_errno_enoent"
///|
extern "C" fn native_errno_eacces() -> Int = "readdirp_errno_eacces"
///|
extern "C" fn native_errno_eperm() -> Int = "readdirp_errno_eperm"
///|
extern "C" fn native_errno_eloop() -> Int = "readdirp_errno_eloop"
///|
fn last_error_message() -> String {
bytes_to_string(native_last_error_message())
}
///|
fn last_errno() -> Int {
native_last_errno()
}
///|
fn errno_enoent() -> Int {
native_errno_enoent()
}
///|
fn errno_eacces() -> Int {
native_errno_eacces()
}
///|
fn errno_eperm() -> Int {
native_errno_eperm()
}
///|
fn errno_eloop() -> Int {
native_errno_eloop()
}
///|
fn warning_kind_from_errno(errno : Int) -> WarningKind {
if errno == errno_enoent() {
Missing
} else if errno == errno_eacces() || errno == errno_eperm() {
PermissionDenied
} else if errno == errno_eloop() {
TooManySymlinks
} else {
Other
}
}
///|
fn warning_for(
path : String,
kind : WarningKind,
message : String,
) -> ReaddirpWarning {
{ path, kind, message }
}
///|
fn path_to_bytes(path : String) -> Bytes {
string_to_utf8_bytes(path)
}
///|
fn bytes_to_string(value : Bytes) -> String {
utf8_decode_lossy(value)
}
///|
fn string_to_utf8_bytes(value : String) -> Bytes {
let byte_length = value
.iter()
.fold(init=0, (length, char) => {
let code = char.to_int()
if code < 0x80 {
length + 1
} else if code < 0x800 {
length + 2
} else if code < 0x10000 {
length + 3
} else {
length + 4
}
})
let bytes = FixedArray::make(byte_length, b'\x00')
ignore(
value
.iter()
.fold(init=0, (offset, char) => offset + bytes.set_utf8_char(offset, char)),
)
fixed_byte_array_to_bytes(bytes)
}
///|
fn fixed_byte_array_to_bytes(bytes : FixedArray[Byte]) -> Bytes = "%identity"
///|
fn utf8_decode_lossy(value : Bytes) -> String {
let chars : Array[Char] = []
let length = value.length()
for i = 0; i < length; {
let b0 = value.unsafe_get(i).to_int()
if b0 <= 0x7F {
chars.push(b0.unsafe_to_char())
continue i + 1
} else if b0 >= 0xC2 &&
b0 <= 0xDF &&
i + 1 < length &&
is_utf8_continuation(value.unsafe_get(i + 1).to_int()) {
let b1 = value.unsafe_get(i + 1).to_int()
let code = ((b0 & 0x1F) << 6) | (b1 & 0x3F)
chars.push(code.unsafe_to_char())
continue i + 2
} else if is_valid_utf8_three_byte(value, i, length) {
let b1 = value.unsafe_get(i + 1).to_int()
let b2 = value.unsafe_get(i + 2).to_int()
let code = ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F)
chars.push(code.unsafe_to_char())
continue i + 3
} else if is_valid_utf8_four_byte(value, i, length) {
let b1 = value.unsafe_get(i + 1).to_int()
let b2 = value.unsafe_get(i + 2).to_int()
let b3 = value.unsafe_get(i + 3).to_int()
let code = ((b0 & 0x07) << 18) |
((b1 & 0x3F) << 12) |
((b2 & 0x3F) << 6) |
(b3 & 0x3F)
chars.push(code.unsafe_to_char())
continue i + 4
} else {
chars.push((0xFFFD).unsafe_to_char())
continue i + 1
}
}
String::from_array(chars)
}
///|
fn is_utf8_continuation(byte : Int) -> Bool {
byte >= 0x80 && byte <= 0xBF
}
///|
fn is_valid_utf8_three_byte(value : Bytes, index : Int, length : Int) -> Bool {
guard index + 2 < length else { false }
let b0 = value.unsafe_get(index).to_int()
let b1 = value.unsafe_get(index + 1).to_int()
let b2 = value.unsafe_get(index + 2).to_int()
is_utf8_continuation(b2) &&
(
(b0 == 0xE0 && b1 >= 0xA0 && b1 <= 0xBF) ||
(b0 >= 0xE1 && b0 <= 0xEC && is_utf8_continuation(b1)) ||
(b0 == 0xED && b1 >= 0x80 && b1 <= 0x9F) ||
(b0 >= 0xEE && b0 <= 0xEF && is_utf8_continuation(b1))
)
}
///|
fn is_valid_utf8_four_byte(value : Bytes, index : Int, length : Int) -> Bool {
guard index + 3 < length else { false }
let b0 = value.unsafe_get(index).to_int()
let b1 = value.unsafe_get(index + 1).to_int()
let b2 = value.unsafe_get(index + 2).to_int()
let b3 = value.unsafe_get(index + 3).to_int()
is_utf8_continuation(b2) &&
is_utf8_continuation(b3) &&
(
(b0 == 0xF0 && b1 >= 0x90 && b1 <= 0xBF) ||
(b0 >= 0xF1 && b0 <= 0xF3 && is_utf8_continuation(b1)) ||
(b0 == 0xF4 && b1 >= 0x80 && b1 <= 0x8F)
)
}
///|
#warnings("-unused_value")
fn read_dir_all(path : String) -> Array[String] raise ReaddirpError {
let names = native_read_dir_all(path_to_bytes(path))
guard native_names_is_null(names) == 0 else {
raise ReaddirpError(last_error_message())
}
Array::from_fixed_array(native_names_to_fixed_array(names)).map(name => {
bytes_to_string(name)
})
}
///|
#warnings("-unused_value")
fn stat_path(
path : String,
follow_symlink~ : Bool,
) -> FileStats raise ReaddirpError {
let stat = native_stat_path(
path_to_bytes(path),
if follow_symlink {
1
} else {
0
},
)
guard stat == 0 else { raise ReaddirpError(last_error_message()) }
{ size: native_stat_size(), mode: native_stat_mode() }
}
///|
#warnings("-unused_value")
fn real_path(path : String) -> String raise ReaddirpError {
let resolved = native_real_path(path_to_bytes(path))
guard native_path_is_null(resolved) == 0 else {
raise ReaddirpError(last_error_message())
}
bytes_to_string(native_path_to_bytes(resolved))
}
///|
#warnings("-unused_value")
fn file_kind_from_mode(mode : Int) -> FileKind {
let type_bits = mode & 0xF000
if type_bits == 0x8000 {
File
} else if type_bits == 0x4000 {
Directory
} else if type_bits == 0xA000 {
Symlink
} else {
Other
}
}
///|
#borrow(path)
extern "C" fn native_test_create_dir(path : Bytes) -> Int = "readdirp_test_create_dir"
///|
#borrow(path, contents)
extern "C" fn native_test_write_file(path : Bytes, contents : Bytes) -> Int = "readdirp_test_write_file"
///|
#borrow(path)
extern "C" fn native_test_remove_tree(path : Bytes) -> Int = "readdirp_test_remove_tree"
///|
#borrow(target, link_path)
extern "C" fn native_test_symlink(target : Bytes, link_path : Bytes) -> Int = "readdirp_test_symlink"
///|
#warnings("-unused_value")
fn test_create_dir(path : String) -> Unit raise ReaddirpError {
guard native_test_create_dir(path_to_bytes(path)) == 0 else {
raise ReaddirpError(last_error_message())
}
}
///|
#warnings("-unused_value")
fn test_write_file(
path : String,
contents : String,
) -> Unit raise ReaddirpError {
guard native_test_write_file(
path_to_bytes(path),
string_to_utf8_bytes(contents),
) ==
0 else {
raise ReaddirpError(last_error_message())
}
}
///|
#warnings("-unused_value")
fn test_remove_tree(path : String) -> Unit raise ReaddirpError {
guard native_test_remove_tree(path_to_bytes(path)) == 0 else {
raise ReaddirpError(last_error_message())
}
}
///|
#warnings("-unused_value")
fn test_symlink(
target : String,
link_path : String,
) -> Unit raise ReaddirpError {
guard native_test_symlink(path_to_bytes(target), path_to_bytes(link_path)) ==
0 else {
raise ReaddirpError(last_error_message())
}
}