mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
Merge pull request #4030 from roc-lang/cli-env
This commit is contained in:
commit
15eb36e4ab
11 changed files with 280 additions and 40 deletions
1
examples/interactive/.gitignore
vendored
1
examples/interactive/.gitignore
vendored
|
@ -4,3 +4,4 @@ effects
|
||||||
form
|
form
|
||||||
tui
|
tui
|
||||||
http-get
|
http-get
|
||||||
|
file-io
|
||||||
|
|
25
examples/interactive/cli-platform/Dir.roc
Normal file
25
examples/interactive/cli-platform/Dir.roc
Normal file
|
@ -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]*]*
|
|
@ -6,15 +6,18 @@ hosted Effect
|
||||||
always,
|
always,
|
||||||
forever,
|
forever,
|
||||||
loop,
|
loop,
|
||||||
|
dirList,
|
||||||
|
cwd,
|
||||||
stdoutLine,
|
stdoutLine,
|
||||||
stderrLine,
|
stderrLine,
|
||||||
stdinLine,
|
stdinLine,
|
||||||
sendRequest,
|
sendRequest,
|
||||||
fileReadBytes,
|
fileReadBytes,
|
||||||
|
fileDelete,
|
||||||
fileWriteUtf8,
|
fileWriteUtf8,
|
||||||
fileWriteBytes,
|
fileWriteBytes,
|
||||||
]
|
]
|
||||||
imports [InternalHttp.{ Request, Response }, InternalFile]
|
imports [InternalHttp.{ Request, Response }, InternalFile, InternalDir]
|
||||||
generates Effect with [after, map, always, forever, loop]
|
generates Effect with [after, map, always, forever, loop]
|
||||||
|
|
||||||
stdoutLine : Str -> Effect {}
|
stdoutLine : Str -> Effect {}
|
||||||
|
@ -23,6 +26,10 @@ stdinLine : Effect Str
|
||||||
|
|
||||||
fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr)
|
fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr)
|
||||||
fileWriteUtf8 : List U8, Str -> 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)
|
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
|
sendRequest : Box Request -> Effect Response
|
||||||
|
|
15
examples/interactive/cli-platform/Env.roc
Normal file
15
examples/interactive/cli-platform/Env.roc
Normal file
|
@ -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
|
|
@ -1,11 +1,13 @@
|
||||||
interface File
|
interface File
|
||||||
exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes]
|
exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes, delete]
|
||||||
imports [Effect, Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath]
|
imports [Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath, Effect.{ Effect }]
|
||||||
|
|
||||||
ReadErr : InternalFile.ReadErr
|
ReadErr : InternalFile.ReadErr
|
||||||
|
|
||||||
WriteErr : InternalFile.WriteErr
|
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)
|
## For example, suppose you have a [JSON](https://en.wikipedia.org/wiki/JSON)
|
||||||
## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format
|
## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format
|
||||||
## to write some encodable data to a file as JSON, like so:
|
## 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
|
# TODO handle encoding errors here, once they exist
|
||||||
writeBytes path bytes
|
writeBytes path bytes
|
||||||
|
|
||||||
## Write bytes to a file.
|
## Writes bytes to a file.
|
||||||
##
|
##
|
||||||
## # Writes the bytes 1, 2, 3 to the file `myfile.dat`.
|
## # Writes the bytes 1, 2, 3 to the file `myfile.dat`.
|
||||||
## File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3]
|
## File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3]
|
||||||
|
@ -41,12 +43,9 @@ write = \path, val, fmt ->
|
||||||
## To format data before writing it to a file, you can use [File.write] instead.
|
## 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, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||||
writeBytes = \path, bytes ->
|
writeBytes = \path, bytes ->
|
||||||
InternalPath.toBytes path
|
toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes
|
||||||
|> Effect.fileWriteBytes bytes
|
|
||||||
|> InternalTask.fromEffect
|
|
||||||
|> Task.mapFail \err -> FileWriteErr path err
|
|
||||||
|
|
||||||
## 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`.
|
## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`.
|
||||||
## File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!"
|
## File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!"
|
||||||
|
@ -56,12 +55,29 @@ writeBytes = \path, bytes ->
|
||||||
## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
|
## 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 -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||||
writeUtf8 = \path, str ->
|
writeUtf8 = \path, str ->
|
||||||
InternalPath.toBytes path
|
toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str
|
||||||
|> Effect.fileWriteUtf8 str
|
|
||||||
|> InternalTask.fromEffect
|
|
||||||
|> Task.mapFail \err -> FileWriteErr path err
|
|
||||||
|
|
||||||
## Read all the bytes in a file.
|
## Deletes 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
|
||||||
|
|
||||||
|
## Reads all the bytes in a file.
|
||||||
##
|
##
|
||||||
## # Read all the bytes in `myfile.txt`.
|
## # Read all the bytes in `myfile.txt`.
|
||||||
## File.readBytes (Path.fromStr "myfile.txt")
|
## File.readBytes (Path.fromStr "myfile.txt")
|
||||||
|
@ -71,12 +87,9 @@ writeUtf8 = \path, str ->
|
||||||
## To read and decode data from a file, you can use `File.read` instead.
|
## 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 -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]*
|
||||||
readBytes = \path ->
|
readBytes = \path ->
|
||||||
InternalPath.toBytes path
|
toReadTask path \bytes -> Effect.fileReadBytes bytes
|
||||||
|> Effect.fileReadBytes
|
|
||||||
|> InternalTask.fromEffect
|
|
||||||
|> Task.mapFail \err -> FileReadErr path err
|
|
||||||
|
|
||||||
## 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`.
|
## # Reads UTF-8 encoded text into a `Str` from the file `myfile.txt`.
|
||||||
## File.readUtf8 (Path.fromStr "myfile.txt")
|
## File.readUtf8 (Path.fromStr "myfile.txt")
|
||||||
|
@ -119,3 +132,16 @@ readUtf8 = \path ->
|
||||||
# Err decodingErr -> Err (FileReadDecodeErr decodingErr)
|
# Err decodingErr -> Err (FileReadDecodeErr decodingErr)
|
||||||
# Err readErr -> Err (FileReadErr readErr)
|
# Err readErr -> Err (FileReadErr readErr)
|
||||||
# InternalTask.fromEffect effect
|
# 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
32
examples/interactive/cli-platform/FileMetadata.roc
Normal file
32
examples/interactive/cli-platform/FileMetadata.roc
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
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
|
41
examples/interactive/cli-platform/InternalDir.roc
Normal file
41
examples/interactive/cli-platform/InternalDir.roc
Normal file
|
@ -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,
|
||||||
|
]
|
|
@ -5,6 +5,8 @@ interface InternalPath
|
||||||
wrap,
|
wrap,
|
||||||
unwrap,
|
unwrap,
|
||||||
toBytes,
|
toBytes,
|
||||||
|
fromArbitraryBytes,
|
||||||
|
fromOsBytes,
|
||||||
]
|
]
|
||||||
imports []
|
imports []
|
||||||
|
|
||||||
|
@ -61,3 +63,11 @@ toBytes = \@InternalPath path ->
|
||||||
FromOperatingSystem bytes -> bytes
|
FromOperatingSystem bytes -> bytes
|
||||||
ArbitraryBytes bytes -> bytes
|
ArbitraryBytes bytes -> bytes
|
||||||
FromStr str -> Str.toUtf8 str
|
FromStr str -> Str.toUtf8 str
|
||||||
|
|
||||||
|
fromArbitraryBytes : List U8 -> InternalPath
|
||||||
|
fromArbitraryBytes = \bytes ->
|
||||||
|
@InternalPath (ArbitraryBytes bytes)
|
||||||
|
|
||||||
|
fromOsBytes : List U8 -> InternalPath
|
||||||
|
fromOsBytes = \bytes ->
|
||||||
|
@InternalPath (FromOperatingSystem bytes)
|
||||||
|
|
|
@ -6,6 +6,7 @@ interface Path
|
||||||
WindowsRoot,
|
WindowsRoot,
|
||||||
# toComponents,
|
# toComponents,
|
||||||
# walkComponents,
|
# walkComponents,
|
||||||
|
display,
|
||||||
fromStr,
|
fromStr,
|
||||||
fromBytes,
|
fromBytes,
|
||||||
withExtension,
|
withExtension,
|
||||||
|
@ -82,13 +83,13 @@ fromBytes = \bytes ->
|
||||||
## have been encoded with the same charset as the operating system's curent locale (which
|
## 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
|
## 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
|
## 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
|
## For a conversion to [Str] that is lossy but does not return a [Result], see
|
||||||
## [displayUtf8].
|
## [display].
|
||||||
# toInner : Path -> [Str Str, Bytes (List U8)]
|
# toInner : Path -> [Str Str, Bytes (List U8)]
|
||||||
## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8),
|
## 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,
|
## 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)
|
## 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
|
## 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
|
## 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,
|
## 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)
|
## [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
|
## If you happen to know the `Charset` that was used to encode the path, you can use
|
||||||
## [toStrUsingCharset] instead of [displayUtf8].
|
## `toStrUsingCharset` instead of [display].
|
||||||
# displayUtf8 : Path -> Str
|
display : Path -> Str
|
||||||
# displayUtf8 = \path ->
|
display = \path ->
|
||||||
# when InternalPath.unwrap path is
|
when InternalPath.unwrap path is
|
||||||
# FromStr str -> str
|
FromStr str -> str
|
||||||
# FromOperatingSystem bytes | ArbitraryBytes bytes ->
|
FromOperatingSystem bytes | ArbitraryBytes bytes ->
|
||||||
# Str.displayUtf8 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 _ -> "<22>"
|
||||||
|
|
||||||
# isEq : Path, Path -> Bool
|
# isEq : Path, Path -> Bool
|
||||||
# isEq = \p1, p2 ->
|
# isEq = \p1, p2 ->
|
||||||
# when InternalPath.unwrap p1 is
|
# when InternalPath.unwrap p1 is
|
||||||
|
|
|
@ -210,11 +210,76 @@ pub fn os_str_from_list(bytes: &RocList<u8>) -> &OsStr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn roc_fx_fileReadBytes(path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
|
pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
|
||||||
let path = path_from_roc_path(path);
|
use std::io::Read;
|
||||||
println!("TODO read bytes from {:?}", path);
|
|
||||||
|
|
||||||
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]
|
||||||
|
pub extern "C" fn roc_fx_fileDelete(roc_path: &RocList<u8>) -> 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_cwd() -> RocList<u8> {
|
||||||
|
// 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<u8>,
|
||||||
|
) -> RocResult<RocList<RocList<u8>>, 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::<RocList<RocList<u8>>>(),
|
||||||
|
),
|
||||||
|
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<u8> {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
RocList::from(os_str.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|
|
@ -1,17 +1,30 @@
|
||||||
app "file"
|
app "file-io"
|
||||||
packages { pf: "cli-platform/main.roc" }
|
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
|
provides [main] to pf
|
||||||
|
|
||||||
main : Task.Task {} [] [Write [File, Stdout, Stderr]]
|
main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File], Env]
|
||||||
main =
|
main =
|
||||||
|
path = Path.fromStr "out.txt"
|
||||||
task =
|
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
|
_ <- 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 ->
|
Task.attempt task \result ->
|
||||||
when result is
|
when result is
|
||||||
Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied"
|
Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied"
|
||||||
Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported"
|
Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported"
|
||||||
Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)"
|
Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)"
|
||||||
_ -> Stdout.line "Successfully wrote a string to out.txt"
|
Err (FileReadErr _ _) -> Stderr.line "Error reading file"
|
||||||
|
Err _ -> Stderr.line "Uh oh, there was an error!"
|
||||||
|
Ok _ -> Stdout.line "Successfully wrote a string to out.txt"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue