Skip to content

Commit 0bbd08c

Browse files
authored
file time functions (#350)
* file time functions * musl changes
1 parent e94dc92 commit 0bbd08c

File tree

8 files changed

+214
-4
lines changed

8 files changed

+214
-4
lines changed

ci/all_tests.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ for roc_file in $EXAMPLES_DIR*.roc; do
6060
continue
6161
fi
6262

63+
## Skip file-accessed-modified-created-time.roc when IS_MUSL=1
64+
if [ "$IS_MUSL" == "1" ] && [ "$base_file" == "file-accessed-modified-created-time.roc" ]; then
65+
continue
66+
fi
67+
6368
roc_file_only="$(basename "$roc_file")"
6469
no_ext_name=${roc_file_only%.*}
6570

@@ -100,6 +105,8 @@ for roc_file in $EXAMPLES_DIR*.roc; do
100105
DB_PATH=${EXAMPLES_DIR}todos.db $ROC dev $roc_file $ROC_BUILD_FLAGS
101106
elif [ "$base_file" == "temp-dir.roc" ]; then
102107
$ROC dev $roc_file $ROC_BUILD_FLAGS --linker=legacy
108+
elif [ "$base_file" == "file-accessed-modified-created-time.roc" ] && [ "$IS_MUSL" == "1" ]; then
109+
continue
103110
else
104111

105112
$ROC dev $roc_file $ROC_BUILD_FLAGS
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/expect
2+
3+
# uncomment line below for debugging
4+
# exp_internal 1
5+
6+
set timeout 7
7+
8+
source ./ci/expect_scripts/shared-code.exp
9+
10+
spawn $env(EXAMPLES_DIR)file-accessed-modified-created-time
11+
12+
expect {
13+
-re {LICENSE file time metadata:\r\n Modified: "20.*"\r\n Accessed: "20.*"\r\n Created: "20.*"\r\n} {
14+
expect eof {
15+
check_exit_and_segfault
16+
}
17+
}
18+
timeout {
19+
puts stderr "\nError: timed out waiting for expected output."
20+
exit 1
21+
}
22+
}
23+
24+
puts stderr "\nError: output was different from expected value."
25+
exit 1

crates/roc_file/src/lib.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,84 @@ pub fn file_is_writable(roc_path: &RocList<u8>) -> RocResult<bool, IOErr> {
265265
}
266266
}
267267

268+
pub fn file_time_accessed(roc_path: &RocList<u8>) -> RocResult<roc_std::U128, IOErr> {
269+
let rust_path = path_from_roc_path(roc_path);
270+
let metadata_res = std::fs::metadata(rust_path);
271+
272+
match metadata_res {
273+
Ok(metadata) => {
274+
let accessed = metadata.accessed();
275+
match accessed {
276+
Ok(time) => {
277+
RocResult::ok(
278+
roc_std::U128::from(
279+
time.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
280+
)
281+
)
282+
}
283+
Err(err) => {
284+
RocResult::err(err.into())
285+
}
286+
}
287+
}
288+
Err(err) => {
289+
RocResult::err(err.into())
290+
}
291+
}
292+
}
293+
294+
pub fn file_time_modified(roc_path: &RocList<u8>) -> RocResult<roc_std::U128, IOErr> {
295+
let rust_path = path_from_roc_path(roc_path);
296+
let metadata_res = std::fs::metadata(rust_path);
297+
298+
match metadata_res {
299+
Ok(metadata) => {
300+
let modified = metadata.modified();
301+
match modified {
302+
Ok(time) => {
303+
RocResult::ok(
304+
roc_std::U128::from(
305+
time.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
306+
)
307+
)
308+
}
309+
Err(err) => {
310+
RocResult::err(err.into())
311+
}
312+
}
313+
}
314+
Err(err) => {
315+
RocResult::err(err.into())
316+
}
317+
}
318+
}
319+
320+
pub fn file_time_created(roc_path: &RocList<u8>) -> RocResult<roc_std::U128, IOErr> {
321+
let rust_path = path_from_roc_path(roc_path);
322+
let metadata_res = std::fs::metadata(rust_path);
323+
324+
match metadata_res {
325+
Ok(metadata) => {
326+
let created = metadata.created();
327+
match created {
328+
Ok(time) => {
329+
RocResult::ok(
330+
roc_std::U128::from(
331+
time.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
332+
)
333+
)
334+
}
335+
Err(err) => {
336+
RocResult::err(err.into())
337+
}
338+
}
339+
}
340+
Err(err) => {
341+
RocResult::err(err.into())
342+
}
343+
}
344+
}
345+
268346
pub fn dir_list(roc_path: &RocList<u8>) -> RocResult<RocList<RocList<u8>>, IOErr> {
269347
let path = path_from_roc_path(roc_path);
270348

crates/roc_host/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ pub fn init() {
318318
roc_fx_file_is_executable as _,
319319
roc_fx_file_is_readable as _,
320320
roc_fx_file_is_writable as _,
321+
roc_fx_file_time_accessed as _,
322+
roc_fx_file_time_modified as _,
323+
roc_fx_file_time_created as _,
321324
roc_fx_cwd as _,
322325
roc_fx_posix_time as _,
323326
roc_fx_sleep_millis as _,
@@ -540,6 +543,27 @@ pub extern "C" fn roc_fx_file_is_writable(
540543
roc_file::file_is_writable(roc_path)
541544
}
542545

546+
#[no_mangle]
547+
pub extern "C" fn roc_fx_file_time_accessed(
548+
roc_path: &RocList<u8>,
549+
) -> RocResult<roc_std::U128, roc_io_error::IOErr> {
550+
roc_file::file_time_accessed(roc_path)
551+
}
552+
553+
#[no_mangle]
554+
pub extern "C" fn roc_fx_file_time_modified(
555+
roc_path: &RocList<u8>,
556+
) -> RocResult<roc_std::U128, roc_io_error::IOErr> {
557+
roc_file::file_time_modified(roc_path)
558+
}
559+
560+
#[no_mangle]
561+
pub extern "C" fn roc_fx_file_time_created(
562+
roc_path: &RocList<u8>,
563+
) -> RocResult<roc_std::U128, roc_io_error::IOErr> {
564+
roc_file::file_time_created(roc_path)
565+
}
566+
543567

544568
#[no_mangle]
545569
pub extern "C" fn roc_fx_cwd() -> RocResult<RocList<u8>, ()> {

examples/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ file-read-buffered
1212
file-read-memory-map
1313
file-size
1414
file-permissions
15+
file-accessed-modified-created-time
1516
form
1617
hello-world
1718
http-get
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
app [main!] { pf: platform "../platform/main.roc" }
2+
3+
# To run this example: check the README.md in this folder
4+
5+
import pf.Stdout
6+
import pf.File
7+
import pf.Utc
8+
9+
main! = |_args|
10+
file = "LICENSE"
11+
12+
# NOTE: these functions will not work if basic-cli was built with musl, which is the case for the normal tar.br URL release.
13+
# See https://github.com/roc-lang/basic-cli?tab=readme-ov-file#running-locally to build basic-cli without musl.
14+
15+
time_modified = Utc.to_iso_8601(File.time_modified!(file)?)
16+
17+
time_accessed = Utc.to_iso_8601(File.time_accessed!(file)?)
18+
19+
time_created = Utc.to_iso_8601(File.time_created!(file)?)
20+
21+
22+
Stdout.line!(
23+
"""
24+
${file} file time metadata:
25+
Modified: ${Inspect.to_str(time_modified)}
26+
Accessed: ${Inspect.to_str(time_accessed)}
27+
Created: ${Inspect.to_str(time_created)}
28+
"""
29+
)

platform/File.roc

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ module [
1313
is_executable!,
1414
is_readable!,
1515
is_writable!,
16+
time_accessed!,
17+
time_modified!,
18+
time_created!,
1619
type!,
1720
open_reader!,
1821
open_reader_with_capacity!,
@@ -25,6 +28,7 @@ import Path exposing [Path]
2528
import InternalIOErr
2629
import Host
2730
import InternalPath
31+
import Utc exposing [Utc]
2832

2933
## Tag union of possible errors when reading and writing a file or directory.
3034
##
@@ -213,28 +217,65 @@ is_sym_link! = |path_str|
213217

214218
## Checks if the file has the execute permission for the current process.
215219
##
216-
## This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html)
220+
## This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html).
217221
is_executable! : Str => Result Bool [PathErr IOErr]
218222
is_executable! = |path_str|
219223
Host.file_is_executable!(InternalPath.to_bytes(Path.from_str(path_str)))
220224
|> Result.map_err(|err| PathErr(InternalIOErr.handle_err(err)))
221225

222226
## Checks if the file has the readable permission for the current process.
223227
##
224-
## This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html)
228+
## This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html).
225229
is_readable! : Str => Result Bool [PathErr IOErr]
226230
is_readable! = |path_str|
227231
Host.file_is_readable!(InternalPath.to_bytes(Path.from_str(path_str)))
228232
|> Result.map_err(|err| PathErr(InternalIOErr.handle_err(err)))
229233

230234
## Checks if the file has the writeable permission for the current process.
231235
##
232-
## This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html)
236+
## This uses rust [std::fs::Metadata](https://doc.rust-lang.org/std/fs/struct.Metadata.html).
233237
is_writable! : Str => Result Bool [PathErr IOErr]
234238
is_writable! = |path_str|
235239
Host.file_is_writable!(InternalPath.to_bytes(Path.from_str(path_str)))
236240
|> Result.map_err(|err| PathErr(InternalIOErr.handle_err(err)))
237241

242+
## Returns the time when the file was last accessed.
243+
##
244+
## This uses [rust's std::fs::Metadata::accessed](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.accessed).
245+
## Note that this is [not guaranteed to be correct in all cases](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.accessed).
246+
##
247+
## NOTE: these functions will not work if basic-cli was built with musl, which is the case for the normal tar.br URL release.
248+
## See https://github.com/roc-lang/basic-cli?tab=readme-ov-file#running-locally to build basic-cli without musl.
249+
time_accessed! : Str => Result Utc [PathErr IOErr]
250+
time_accessed! = |path_str|
251+
Host.file_time_accessed!(InternalPath.to_bytes(Path.from_str(path_str)))
252+
|> Result.map_ok(|time_u128| Num.to_i128(time_u128) |> Utc.from_nanos_since_epoch)
253+
|> Result.map_err(|err| PathErr(InternalIOErr.handle_err(err)))
254+
255+
## Returns the time when the file was last modified.
256+
##
257+
## This uses [rust's std::fs::Metadata::modified](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.modified).
258+
##
259+
## NOTE: these functions will not work if basic-cli was built with musl, which is the case for the normal tar.br URL release.
260+
## See https://github.com/roc-lang/basic-cli?tab=readme-ov-file#running-locally to build basic-cli without musl.
261+
time_modified! : Str => Result Utc [PathErr IOErr]
262+
time_modified! = |path_str|
263+
Host.file_time_modified!(InternalPath.to_bytes(Path.from_str(path_str)))
264+
|> Result.map_ok(|time_u128| Num.to_i128(time_u128) |> Utc.from_nanos_since_epoch)
265+
|> Result.map_err(|err| PathErr(InternalIOErr.handle_err(err)))
266+
267+
## Returns the time when the file was created.
268+
##
269+
## This uses [rust's std::fs::Metadata::created](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.created).
270+
##
271+
## NOTE: these functions will not work if basic-cli was built with musl, which is the case for the normal tar.br URL release.
272+
## See https://github.com/roc-lang/basic-cli?tab=readme-ov-file#running-locally to build basic-cli without musl.
273+
time_created! : Str => Result Utc [PathErr IOErr]
274+
time_created! = |path_str|
275+
Host.file_time_created!(InternalPath.to_bytes(Path.from_str(path_str)))
276+
|> Result.map_ok(|time_u128| Num.to_i128(time_u128) |> Utc.from_nanos_since_epoch)
277+
|> Result.map_err(|err| PathErr(InternalIOErr.handle_err(err)))
278+
238279
## Return the type of the path if the path exists on disk.
239280
## This uses [rust's std::path::is_symlink](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_symlink).
240281
##

platform/Host.roc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ hosted [
2323
file_is_executable!,
2424
file_is_readable!,
2525
file_is_writable!,
26+
file_time_accessed!,
27+
file_time_modified!,
28+
file_time_created!,
2629
get_locale!,
2730
get_locales!,
2831
hard_link!,
@@ -61,7 +64,6 @@ import InternalCmd
6164
import InternalPath
6265
import InternalIOErr
6366
import InternalSqlite
64-
6567
# COMMAND
6668
command_status! : InternalCmd.Command => Result I32 InternalIOErr.IOErrFromHost
6769
command_output! : InternalCmd.Command => InternalCmd.OutputFromHost
@@ -75,6 +77,9 @@ file_size_in_bytes! : List U8 => Result U64 InternalIOErr.IOErrFromHost
7577
file_is_executable! : List U8 => Result Bool InternalIOErr.IOErrFromHost
7678
file_is_readable! : List U8 => Result Bool InternalIOErr.IOErrFromHost
7779
file_is_writable! : List U8 => Result Bool InternalIOErr.IOErrFromHost
80+
file_time_accessed! : List U8 => Result U128 InternalIOErr.IOErrFromHost
81+
file_time_modified! : List U8 => Result U128 InternalIOErr.IOErrFromHost
82+
file_time_created! : List U8 => Result U128 InternalIOErr.IOErrFromHost
7883

7984
FileReader := Box {}
8085
file_reader! : List U8, U64 => Result FileReader InternalIOErr.IOErrFromHost

0 commit comments

Comments
 (0)