From d5cc75907fab78c38f7a23e1f5e7f23ff0776f5a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 10:02:23 -0400 Subject: [PATCH 1/6] Update file I/O example --- examples/interactive/.gitignore | 1 + examples/interactive/file.roc | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/interactive/.gitignore b/examples/interactive/.gitignore index cb41e998d8..6eacf36982 100644 --- a/examples/interactive/.gitignore +++ b/examples/interactive/.gitignore @@ -4,3 +4,4 @@ effects form tui http-get +file-io diff --git a/examples/interactive/file.roc b/examples/interactive/file.roc index 4f30de7065..e6f9b3a3fd 100644 --- a/examples/interactive/file.roc +++ b/examples/interactive/file.roc @@ -1,17 +1,21 @@ -app "file" +app "file-io" packages { pf: "cli-platform/main.roc" } imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path] provides [main] to pf -main : Task.Task {} [] [Write [File, Stdout, Stderr]] +main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File]] main = + path = Path.fromStr "out.txt" task = _ <- Stdout.line "Writing a string to out.txt" |> Task.await - File.writeUtf8 (Path.fromStr "out.txt") "a string!\n" + _ <- File.writeUtf8 path "a string!" |> Task.await + contents <- File.readUtf8 path |> Task.await + Stdout.line "I read the file back. Its contents: \"\(contents)\"" Task.attempt task \result -> when result is Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied" Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported" Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)" + Err (FileReadErr _ _) -> Stderr.line "Error reading file" _ -> Stdout.line "Successfully wrote a string to out.txt" From 81d11e4edd1a253d1d1dc9ef4869268c797772b0 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 10:03:35 -0400 Subject: [PATCH 2/6] Add File.delete to CLI platform --- examples/interactive/cli-platform/Effect.roc | 2 + examples/interactive/cli-platform/File.roc | 41 +++++++++++++++----- examples/interactive/cli-platform/src/lib.rs | 10 +++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/examples/interactive/cli-platform/Effect.roc b/examples/interactive/cli-platform/Effect.roc index 634bc7eeeb..92a6fc49ae 100644 --- a/examples/interactive/cli-platform/Effect.roc +++ b/examples/interactive/cli-platform/Effect.roc @@ -11,6 +11,7 @@ hosted Effect stdinLine, sendRequest, fileReadBytes, + fileDelete, fileWriteUtf8, fileWriteBytes, ] @@ -23,6 +24,7 @@ stdinLine : Effect Str fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr) fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr) +fileDelete : List U8 -> Effect (Result {} InternalFile.WriteErr) fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr) sendRequest : Box Request -> Effect Response diff --git a/examples/interactive/cli-platform/File.roc b/examples/interactive/cli-platform/File.roc index f6e84107ab..107892285b 100644 --- a/examples/interactive/cli-platform/File.roc +++ b/examples/interactive/cli-platform/File.roc @@ -1,6 +1,6 @@ interface File - exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes] - imports [Effect, Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath] + exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes, delete] + imports [Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath, Effect.{ Effect }] ReadErr : InternalFile.ReadErr @@ -41,10 +41,7 @@ write = \path, val, fmt -> ## To format data before writing it to a file, you can use [File.write] instead. writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* writeBytes = \path, bytes -> - InternalPath.toBytes path - |> Effect.fileWriteBytes bytes - |> InternalTask.fromEffect - |> Task.mapFail \err -> FileWriteErr path err + toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes ## Write a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## @@ -56,10 +53,27 @@ writeBytes = \path, bytes -> ## To write unformatted bytes to a file, you can use [File.writeBytes] instead. writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* writeUtf8 = \path, str -> - InternalPath.toBytes path - |> Effect.fileWriteUtf8 str - |> InternalTask.fromEffect - |> Task.mapFail \err -> FileWriteErr path err + toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str + +## Delete a file from the filesystem. +## +## # Deletes the file named +## File.delete (Path.fromStr "myfile.dat") [1, 2, 3] +## +## Note that this does not securely erase the file's contents from disk; instead, the operating +## system marks the space it was occupying as safe to write over in the future. Also, the operating +## system may not immediately mark the space as free; for example, on Windows it will wait until +## the last file handle to it is closed, and on UNIX, it will not remove it until the last +## [hard link](https://en.wikipedia.org/wiki/Hard_link) to it has been deleted. +## +## This performs a [`DeleteFile`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletefile) +## on Windows and [`unlink`](https://en.wikipedia.org/wiki/Unlink_(Unix)) on UNIX systems. +## +## On Windows, this will fail when attempting to delete a readonly file; the file's +## readonly permission must be disabled before it can be successfully deleted. +delete : Path -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* +delete = \path -> + toWriteTask path \bytes -> Effect.fileDelete bytes ## Read all the bytes in a file. ## @@ -119,3 +133,10 @@ readUtf8 = \path -> # Err decodingErr -> Err (FileReadDecodeErr decodingErr) # Err readErr -> Err (FileReadErr readErr) # InternalTask.fromEffect effect + +toWriteTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileWriteErr Path err]* [Write [File]*]* +toWriteTask = \path, toEffect -> + InternalPath.toBytes path + |> toEffect + |> InternalTask.fromEffect + |> Task.mapFail \err -> FileWriteErr path err diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/interactive/cli-platform/src/lib.rs index cf7f4337e1..09faea6e8c 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/interactive/cli-platform/src/lib.rs @@ -217,6 +217,16 @@ pub extern "C" fn roc_fx_fileReadBytes(path: &RocList) -> RocResult) -> RocResult<(), ReadErr> { + match std::fs::remove_file(path_from_roc_path(roc_path)) { + Ok(()) => RocResult::ok(()), + Err(_) => { + todo!("Report a file write error"); + } + } +} + #[no_mangle] pub extern "C" fn roc_fx_sendRequest(roc_request: &glue::Request) -> glue::Response { let mut builder = reqwest::blocking::ClientBuilder::new(); From 4ccd7261417e44adba91e5b0fda3781c0a9b69d6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 10:03:51 -0400 Subject: [PATCH 3/6] Fix File.readBytes --- examples/interactive/cli-platform/File.roc | 12 ++++++++---- examples/interactive/cli-platform/src/lib.rs | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/examples/interactive/cli-platform/File.roc b/examples/interactive/cli-platform/File.roc index 107892285b..257218b082 100644 --- a/examples/interactive/cli-platform/File.roc +++ b/examples/interactive/cli-platform/File.roc @@ -85,10 +85,7 @@ delete = \path -> ## To read and decode data from a file, you can use `File.read` instead. readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]* readBytes = \path -> - InternalPath.toBytes path - |> Effect.fileReadBytes - |> InternalTask.fromEffect - |> Task.mapFail \err -> FileReadErr path err + toReadTask path \bytes -> Effect.fileReadBytes bytes ## Read a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ## @@ -140,3 +137,10 @@ toWriteTask = \path, toEffect -> |> toEffect |> InternalTask.fromEffect |> Task.mapFail \err -> FileWriteErr path err + +toReadTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileReadErr Path err]* [Read [File]*]* +toReadTask = \path, toEffect -> + InternalPath.toBytes path + |> toEffect + |> InternalTask.fromEffect + |> Task.mapFail \err -> FileReadErr path err diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/interactive/cli-platform/src/lib.rs index 09faea6e8c..58b7a89cfc 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/interactive/cli-platform/src/lib.rs @@ -210,11 +210,22 @@ pub fn os_str_from_list(bytes: &RocList) -> &OsStr { } #[no_mangle] -pub extern "C" fn roc_fx_fileReadBytes(path: &RocList) -> RocResult, ReadErr> { - let path = path_from_roc_path(path); - println!("TODO read bytes from {:?}", path); +pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList) -> RocResult, ReadErr> { + use std::io::Read; - RocResult::ok(RocList::empty()) + let mut bytes = Vec::new(); + + match File::open(path_from_roc_path(roc_path)) { + Ok(mut file) => match file.read_to_end(&mut bytes) { + Ok(_bytes_read) => RocResult::ok(RocList::from(bytes.as_slice())), + Err(_) => { + todo!("Report a file write error"); + } + }, + Err(_) => { + todo!("Report a file open error"); + } + } } #[no_mangle] From 21b74f6dbbe8fe3dc309dd2cb0c94fd6d5b04a65 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 16:12:36 -0400 Subject: [PATCH 4/6] Add Dir and Env to CLI platform --- examples/interactive/cli-platform/Dir.roc | 25 +++++++++++ examples/interactive/cli-platform/Effect.roc | 7 ++- examples/interactive/cli-platform/Env.roc | 15 +++++++ examples/interactive/cli-platform/File.roc | 12 ++--- .../interactive/cli-platform/FileMetadata.roc | 35 +++++++++++++++ .../interactive/cli-platform/InternalDir.roc | 41 +++++++++++++++++ .../interactive/cli-platform/InternalPath.roc | 10 +++++ examples/interactive/cli-platform/Path.roc | 25 ++++++----- examples/interactive/cli-platform/src/lib.rs | 44 +++++++++++++++++++ examples/interactive/file.roc | 13 ++++-- 10 files changed, 208 insertions(+), 19 deletions(-) create mode 100644 examples/interactive/cli-platform/Dir.roc create mode 100644 examples/interactive/cli-platform/Env.roc create mode 100644 examples/interactive/cli-platform/FileMetadata.roc create mode 100644 examples/interactive/cli-platform/InternalDir.roc diff --git a/examples/interactive/cli-platform/Dir.roc b/examples/interactive/cli-platform/Dir.roc new file mode 100644 index 0000000000..cf5a615815 --- /dev/null +++ b/examples/interactive/cli-platform/Dir.roc @@ -0,0 +1,25 @@ +interface Dir + exposes [ReadErr, DeleteErr, DirEntry, deleteEmptyDir, deleteRecursive, list] + imports [Effect, Task.{ Task }, InternalTask, Path.{ Path }, InternalPath, InternalDir] + +ReadErr : InternalDir.ReadErr + +DeleteErr : InternalDir.DeleteErr + +DirEntry : InternalDir.DirEntry + +## Lists the files and directories inside the directory. +list : Path -> Task (List Path) [DirReadErr Path ReadErr]* [Read [File]*]* +list = \path -> + effect = Effect.map (Effect.dirList (InternalPath.toBytes path)) \result -> + when result is + Ok entries -> Ok (List.map entries InternalPath.fromOsBytes) + Err err -> Err (DirReadErr path err) + + InternalTask.fromEffect effect + +## Deletes a directory if it's empty. +deleteEmptyDir : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* + +## Recursively deletes the directory as well as all files and directories inside it. +deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* \ No newline at end of file diff --git a/examples/interactive/cli-platform/Effect.roc b/examples/interactive/cli-platform/Effect.roc index 92a6fc49ae..26505ddc07 100644 --- a/examples/interactive/cli-platform/Effect.roc +++ b/examples/interactive/cli-platform/Effect.roc @@ -6,6 +6,8 @@ hosted Effect always, forever, loop, + dirList, + cwd, stdoutLine, stderrLine, stdinLine, @@ -15,7 +17,7 @@ hosted Effect fileWriteUtf8, fileWriteBytes, ] - imports [InternalHttp.{ Request, Response }, InternalFile] + imports [InternalHttp.{ Request, Response }, InternalFile, InternalDir] generates Effect with [after, map, always, forever, loop] stdoutLine : Str -> Effect {} @@ -26,5 +28,8 @@ fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr) fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr) fileDelete : List U8 -> Effect (Result {} InternalFile.WriteErr) fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr) +dirList : List U8 -> Effect (Result (List (List U8)) InternalDir.ReadErr) + +cwd : Effect (List U8) sendRequest : Box Request -> Effect Response diff --git a/examples/interactive/cli-platform/Env.roc b/examples/interactive/cli-platform/Env.roc new file mode 100644 index 0000000000..72a10e8d61 --- /dev/null +++ b/examples/interactive/cli-platform/Env.roc @@ -0,0 +1,15 @@ +interface Env + exposes [cwd] + imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask] + +## Reads the [current working directory](https://en.wikipedia.org/wiki/Working_directory) +## from the environment. +cwd : Task Path [CwdUnavailable]* [Env]* +cwd = + effect = Effect.map Effect.cwd \bytes -> + if List.isEmpty bytes then + Err CwdUnavailable + else + Ok (InternalPath.fromArbitraryBytes bytes) + + InternalTask.fromEffect effect diff --git a/examples/interactive/cli-platform/File.roc b/examples/interactive/cli-platform/File.roc index 257218b082..4fd0b03e61 100644 --- a/examples/interactive/cli-platform/File.roc +++ b/examples/interactive/cli-platform/File.roc @@ -6,6 +6,8 @@ ReadErr : InternalFile.ReadErr WriteErr : InternalFile.WriteErr +## Encodes a value using the given `EncodingFormat` and writes it to a file. +## ## For example, suppose you have a [JSON](https://en.wikipedia.org/wiki/JSON) ## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format ## to write some encodable data to a file as JSON, like so: @@ -31,7 +33,7 @@ write = \path, val, fmt -> # TODO handle encoding errors here, once they exist writeBytes path bytes -## Write bytes to a file. +## Writes bytes to a file. ## ## # Writes the bytes 1, 2, 3 to the file `myfile.dat`. ## File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3] @@ -43,7 +45,7 @@ writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File writeBytes = \path, bytes -> toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes -## Write a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). +## Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## ## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`. ## File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!" @@ -55,7 +57,7 @@ writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* writeUtf8 = \path, str -> toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str -## Delete a file from the filesystem. +## Deletes a file from the filesystem. ## ## # Deletes the file named ## File.delete (Path.fromStr "myfile.dat") [1, 2, 3] @@ -75,7 +77,7 @@ delete : Path -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* delete = \path -> toWriteTask path \bytes -> Effect.fileDelete bytes -## Read all the bytes in a file. +## Reads all the bytes in a file. ## ## # Read all the bytes in `myfile.txt`. ## File.readBytes (Path.fromStr "myfile.txt") @@ -87,7 +89,7 @@ readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]* readBytes = \path -> toReadTask path \bytes -> Effect.fileReadBytes bytes -## Read a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. +## Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ## ## # Reads UTF-8 encoded text into a `Str` from the file `myfile.txt`. ## File.readUtf8 (Path.fromStr "myfile.txt") diff --git a/examples/interactive/cli-platform/FileMetadata.roc b/examples/interactive/cli-platform/FileMetadata.roc new file mode 100644 index 0000000000..89386f259f --- /dev/null +++ b/examples/interactive/cli-platform/FileMetadata.roc @@ -0,0 +1,35 @@ +interface FileMetadata + exposes [FileMetadata, bytes, type, isReadonly, mode] + imports [] + +# Design note: this is an opaque type rather than a type alias so that +# we can add new operating system info if new OS releases introduce them, +# as a backwards-compatible change. + +FileMetadata := { + bytes : U64, + type : [File, Dir, Symlink], + isReadonly : Bool, + mode : [Unix U32, NonUnix], +} + +bytes : FileMetadata -> U64 +bytes = \@FileMetadata info -> info.bytes + +isReadonly : FileMetadata -> Bool +isReadonly = \@FileMetadata info -> info.isReadonly + +type : FileMetadata -> [File, Dir, Symlink] +type = \@FileMetadata info -> info.type + +mode : FileMetadata -> [Unix U32, NonUnix] +mode = \@FileMetadata info -> info.mode + +# TODO need to create a Time module and return something like Time.Utc here. +# lastModified : FileMetadata -> Utc + +# TODO need to create a Time module and return something like Time.Utc here. +# lastAccessed : FileMetadata -> Utc + +# TODO need to create a Time module and return something like Time.Utc here. +# created : FileMetadata -> Utc \ No newline at end of file diff --git a/examples/interactive/cli-platform/InternalDir.roc b/examples/interactive/cli-platform/InternalDir.roc new file mode 100644 index 0000000000..6163125e84 --- /dev/null +++ b/examples/interactive/cli-platform/InternalDir.roc @@ -0,0 +1,41 @@ +interface InternalDir + exposes [ReadErr, DeleteErr, DirEntry] + imports [FileMetadata.{ FileMetadata }, Path.{ Path }] + +DirEntry : { + path : Path, + type : [File, Dir, Symlink], + metadata : FileMetadata, +} + +ReadErr : [ + NotFound, + Interrupted, + InvalidFilename, + PermissionDenied, + TooManySymlinks, # aka FilesystemLoop + TooManyHardlinks, + TimedOut, + StaleNetworkFileHandle, + NotADirectory, + OutOfMemory, + Unsupported, + Unrecognized I32 Str, +] + +DeleteErr : [ + NotFound, + Interrupted, + InvalidFilename, + PermissionDenied, + TooManySymlinks, # aka FilesystemLoop + TooManyHardlinks, + TimedOut, + StaleNetworkFileHandle, + NotADirectory, + ReadOnlyFilesystem, + DirectoryNotEmpty, + OutOfMemory, + Unsupported, + Unrecognized I32 Str, +] diff --git a/examples/interactive/cli-platform/InternalPath.roc b/examples/interactive/cli-platform/InternalPath.roc index b27ff8e5e5..6731b1a92a 100644 --- a/examples/interactive/cli-platform/InternalPath.roc +++ b/examples/interactive/cli-platform/InternalPath.roc @@ -5,6 +5,8 @@ interface InternalPath wrap, unwrap, toBytes, + fromArbitraryBytes, + fromOsBytes, ] imports [] @@ -61,3 +63,11 @@ toBytes = \@InternalPath path -> FromOperatingSystem bytes -> bytes ArbitraryBytes bytes -> bytes FromStr str -> Str.toUtf8 str + +fromArbitraryBytes : List U8 -> InternalPath +fromArbitraryBytes = \bytes -> + @InternalPath (ArbitraryBytes bytes) + +fromOsBytes : List U8 -> InternalPath +fromOsBytes = \bytes -> + @InternalPath (FromOperatingSystem bytes) \ No newline at end of file diff --git a/examples/interactive/cli-platform/Path.roc b/examples/interactive/cli-platform/Path.roc index 12de18ee32..dad8f989f6 100644 --- a/examples/interactive/cli-platform/Path.roc +++ b/examples/interactive/cli-platform/Path.roc @@ -6,6 +6,7 @@ interface Path WindowsRoot, # toComponents, # walkComponents, + display, fromStr, fromBytes, withExtension, @@ -85,10 +86,10 @@ fromBytes = \bytes -> ## with the given [Charset]. (Use [Env.charset] to get the current system charset.) ## ## For a conversion to [Str] that is lossy but does not return a [Result], see -## [displayUtf8]. +## [display]. # toInner : Path -> [Str Str, Bytes (List U8)] ## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), -## and converts it to a string using [Str.displayUtf8]. +## and converts it to a string using [Str.display]. ## ## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens, ## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) @@ -103,17 +104,21 @@ fromBytes = \bytes -> ## Converting paths to strings can be an unreliable operation, because operating systems ## don't record the paths' encodings. This means it's possible for the path to have been ## encoded with a different character set than UTF-8 even if UTF-8 is the system default, -## which means when [displayUtf8] converts them to a string, the string may include gibberish. +## which means when [display] converts them to a string, the string may include gibberish. ## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863) ## ## If you happen to know the [Charset] that was used to encode the path, you can use -## [toStrUsingCharset] instead of [displayUtf8]. -# displayUtf8 : Path -> Str -# displayUtf8 = \path -> -# when InternalPath.unwrap path is -# FromStr str -> str -# FromOperatingSystem bytes | ArbitraryBytes bytes -> -# Str.displayUtf8 bytes +## [toStrUsingCharset] instead of [display]. +display : Path -> Str +display = \path -> + when InternalPath.unwrap path is + FromStr str -> str + FromOperatingSystem bytes | ArbitraryBytes bytes -> + when Str.fromUtf8 bytes is + Ok str -> str + # TODO: this should use the builtin Str.display to display invalid UTF-8 chars in just the right spots, but that does not exist yet! + Err _ -> "�" + # isEq : Path, Path -> Bool # isEq = \p1, p2 -> # when InternalPath.unwrap p1 is diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/interactive/cli-platform/src/lib.rs index 58b7a89cfc..d801fcd8da 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/interactive/cli-platform/src/lib.rs @@ -238,6 +238,50 @@ pub extern "C" fn roc_fx_fileDelete(roc_path: &RocList) -> RocResult<(), Rea } } +#[no_mangle] +pub extern "C" fn roc_fx_cwd() -> RocList { + // TODO instead, call getcwd on UNIX and GetCurrentDirectory on Windows + match std::env::current_dir() { + Ok(path_buf) => os_str_to_roc_path(path_buf.into_os_string().as_os_str()), + Err(_) => { + // Default to empty path + RocList::empty() + } + } +} + +#[no_mangle] +pub extern "C" fn roc_fx_dirList( + // TODO: this RocResult should use Dir.WriteErr - but right now it's File.WriteErr + // because glue doesn't have Dir.WriteErr yet. + roc_path: &RocList, +) -> RocResult>, WriteErr> { + println!("Dir.list..."); + match std::fs::read_dir(path_from_roc_path(roc_path)) { + Ok(dir_entries) => RocResult::ok( + dir_entries + .map(|opt_dir_entry| match opt_dir_entry { + Ok(entry) => os_str_to_roc_path(entry.path().into_os_string().as_os_str()), + Err(_) => { + todo!("handle dir_entry path didn't resolve") + } + }) + .collect::>>(), + ), + Err(_) => { + todo!("handle Dir.list error"); + } + } +} + +#[cfg(target_family = "unix")] +/// TODO convert from EncodeWide to RocPath on Windows +fn os_str_to_roc_path(os_str: &OsStr) -> RocList { + use std::os::unix::ffi::OsStrExt; + + RocList::from(os_str.as_bytes()) +} + #[no_mangle] pub extern "C" fn roc_fx_sendRequest(roc_request: &glue::Request) -> glue::Response { let mut builder = reqwest::blocking::ClientBuilder::new(); diff --git a/examples/interactive/file.roc b/examples/interactive/file.roc index e6f9b3a3fd..d491ec9477 100644 --- a/examples/interactive/file.roc +++ b/examples/interactive/file.roc @@ -1,12 +1,18 @@ app "file-io" packages { pf: "cli-platform/main.roc" } - imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path] + imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path, pf.Env, pf.Dir] provides [main] to pf -main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File]] +main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File], Env] main = path = Path.fromStr "out.txt" task = + cwd <- Env.cwd |> Task.await + cwdStr = Path.display cwd + _ <- Stdout.line "cwd: \(cwdStr)" |> Task.await + dirEntries <- Dir.list cwd |> Task.await + contentsStr = Str.joinWith (List.map dirEntries Path.display) "\n " + _ <- Stdout.line "Directory contents:\n \(contentsStr)\n" |> Task.await _ <- Stdout.line "Writing a string to out.txt" |> Task.await _ <- File.writeUtf8 path "a string!" |> Task.await contents <- File.readUtf8 path |> Task.await @@ -18,4 +24,5 @@ main = Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported" Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)" Err (FileReadErr _ _) -> Stderr.line "Error reading file" - _ -> Stdout.line "Successfully wrote a string to out.txt" + Err _ -> Stderr.line "Uh oh, there was an error!" + Ok _ -> Stdout.line "Successfully wrote a string to out.txt" From 33ca6147c9802fd6e8a2457aeb8ea50fad08977f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 20:21:19 -0400 Subject: [PATCH 5/6] Fix CLI platform docs --- examples/interactive/cli-platform/Path.roc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/interactive/cli-platform/Path.roc b/examples/interactive/cli-platform/Path.roc index dad8f989f6..7a9453df5e 100644 --- a/examples/interactive/cli-platform/Path.roc +++ b/examples/interactive/cli-platform/Path.roc @@ -83,13 +83,13 @@ fromBytes = \bytes -> ## have been encoded with the same charset as the operating system's curent locale (which ## typically does not change after it is set during installation of the OS), so ## this should convert a [Path] to a valid string as long as the path was created -## with the given [Charset]. (Use [Env.charset] to get the current system charset.) +## with the given `Charset`. (Use `Env.charset` to get the current system charset.) ## ## For a conversion to [Str] that is lossy but does not return a [Result], see ## [display]. # toInner : Path -> [Str Str, Bytes (List U8)] ## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), -## and converts it to a string using [Str.display]. +## and converts it to a string using `Str.display`. ## ## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens, ## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) @@ -107,8 +107,8 @@ fromBytes = \bytes -> ## which means when [display] converts them to a string, the string may include gibberish. ## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863) ## -## If you happen to know the [Charset] that was used to encode the path, you can use -## [toStrUsingCharset] instead of [display]. +## If you happen to know the `Charset` that was used to encode the path, you can use +## `toStrUsingCharset` instead of [display]. display : Path -> Str display = \path -> when InternalPath.unwrap path is From 3f467459f94034e567a371bd2cb97980a94ab764 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 20:15:46 -0400 Subject: [PATCH 6/6] roc format --- examples/interactive/cli-platform/Dir.roc | 2 +- examples/interactive/cli-platform/File.roc | 1 - examples/interactive/cli-platform/FileMetadata.roc | 5 +---- examples/interactive/cli-platform/InternalPath.roc | 2 +- examples/interactive/file.roc | 2 ++ 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/interactive/cli-platform/Dir.roc b/examples/interactive/cli-platform/Dir.roc index cf5a615815..6b657600b3 100644 --- a/examples/interactive/cli-platform/Dir.roc +++ b/examples/interactive/cli-platform/Dir.roc @@ -22,4 +22,4 @@ list = \path -> deleteEmptyDir : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* ## Recursively deletes the directory as well as all files and directories inside it. -deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* \ No newline at end of file +deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* diff --git a/examples/interactive/cli-platform/File.roc b/examples/interactive/cli-platform/File.roc index 4fd0b03e61..de0181ecce 100644 --- a/examples/interactive/cli-platform/File.roc +++ b/examples/interactive/cli-platform/File.roc @@ -132,7 +132,6 @@ readUtf8 = \path -> # Err decodingErr -> Err (FileReadDecodeErr decodingErr) # Err readErr -> Err (FileReadErr readErr) # InternalTask.fromEffect effect - toWriteTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileWriteErr Path err]* [Write [File]*]* toWriteTask = \path, toEffect -> InternalPath.toBytes path diff --git a/examples/interactive/cli-platform/FileMetadata.roc b/examples/interactive/cli-platform/FileMetadata.roc index 89386f259f..4c071e6fa4 100644 --- a/examples/interactive/cli-platform/FileMetadata.roc +++ b/examples/interactive/cli-platform/FileMetadata.roc @@ -5,7 +5,6 @@ interface FileMetadata # Design note: this is an opaque type rather than a type alias so that # we can add new operating system info if new OS releases introduce them, # as a backwards-compatible change. - FileMetadata := { bytes : U64, type : [File, Dir, Symlink], @@ -27,9 +26,7 @@ mode = \@FileMetadata info -> info.mode # TODO need to create a Time module and return something like Time.Utc here. # lastModified : FileMetadata -> Utc - # TODO need to create a Time module and return something like Time.Utc here. # lastAccessed : FileMetadata -> Utc - # TODO need to create a Time module and return something like Time.Utc here. -# created : FileMetadata -> Utc \ No newline at end of file +# created : FileMetadata -> Utc diff --git a/examples/interactive/cli-platform/InternalPath.roc b/examples/interactive/cli-platform/InternalPath.roc index 6731b1a92a..10ebe7f0e5 100644 --- a/examples/interactive/cli-platform/InternalPath.roc +++ b/examples/interactive/cli-platform/InternalPath.roc @@ -70,4 +70,4 @@ fromArbitraryBytes = \bytes -> fromOsBytes : List U8 -> InternalPath fromOsBytes = \bytes -> - @InternalPath (FromOperatingSystem bytes) \ No newline at end of file + @InternalPath (FromOperatingSystem bytes) diff --git a/examples/interactive/file.roc b/examples/interactive/file.roc index d491ec9477..c022f2e1f9 100644 --- a/examples/interactive/file.roc +++ b/examples/interactive/file.roc @@ -9,9 +9,11 @@ main = task = cwd <- Env.cwd |> Task.await cwdStr = Path.display cwd + _ <- Stdout.line "cwd: \(cwdStr)" |> Task.await dirEntries <- Dir.list cwd |> Task.await contentsStr = Str.joinWith (List.map dirEntries Path.display) "\n " + _ <- Stdout.line "Directory contents:\n \(contentsStr)\n" |> Task.await _ <- Stdout.line "Writing a string to out.txt" |> Task.await _ <- File.writeUtf8 path "a string!" |> Task.await