2021-08-30 17:31:37 +03:00
|
|
|
module System.File.ReadWrite
|
|
|
|
|
|
|
|
import public Data.Fuel
|
|
|
|
|
2021-11-03 18:56:29 +03:00
|
|
|
import Data.SnocList
|
2021-08-30 17:31:37 +03:00
|
|
|
|
|
|
|
import System.File.Handle
|
|
|
|
import public System.File.Error
|
|
|
|
import System.File.Support
|
|
|
|
import public System.File.Types
|
|
|
|
|
|
|
|
%default total
|
|
|
|
|
|
|
|
%foreign support "idris2_seekLine"
|
|
|
|
"node:support:seekLine,support_system_file"
|
|
|
|
prim__seekLine : FilePtr -> PrimIO Int
|
|
|
|
|
|
|
|
%foreign support "idris2_readLine"
|
|
|
|
"node:support:readLine,support_system_file"
|
|
|
|
prim__readLine : FilePtr -> PrimIO (Ptr String)
|
|
|
|
|
|
|
|
%foreign support "idris2_readChars"
|
|
|
|
prim__readChars : Int -> FilePtr -> PrimIO (Ptr String)
|
|
|
|
%foreign "C:fgetc,libc 6"
|
|
|
|
prim__readChar : FilePtr -> PrimIO Int
|
|
|
|
|
|
|
|
%foreign support "idris2_writeLine"
|
|
|
|
"node:lambda:(filePtr, line) => require('fs').writeSync(filePtr.fd, line, undefined, 'utf-8')"
|
|
|
|
prim__writeLine : FilePtr -> String -> PrimIO Int
|
|
|
|
|
|
|
|
%foreign support "idris2_eof"
|
|
|
|
"node:lambda:x=>(x.eof?1:0)"
|
|
|
|
prim__eof : FilePtr -> PrimIO Int
|
|
|
|
|
|
|
|
%foreign support "idris2_removeFile"
|
|
|
|
prim__removeFile : String -> PrimIO Int
|
|
|
|
|
|
|
|
||| Seek through the next newline.
|
|
|
|
||| This is @fGetLine@ without the overhead of copying
|
|
|
|
||| any characters.
|
2021-10-29 19:58:29 +03:00
|
|
|
|||
|
|
|
|
||| @ h the file handle to seek through
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
2022-01-17 15:24:00 +03:00
|
|
|
covering
|
2021-08-30 17:31:37 +03:00
|
|
|
fSeekLine : HasIO io => (h : File) -> io (Either FileError ())
|
|
|
|
fSeekLine (FHandle f)
|
|
|
|
= do res <- primIO (prim__seekLine f)
|
|
|
|
if res /= 0
|
|
|
|
then returnError
|
|
|
|
else ok ()
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Get the next line from the given file handle, returning the empty string if
|
|
|
|
||| nothing was read.
|
|
|
|
|||
|
|
|
|
||| @ h the file handle to get the line from
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
2022-01-17 15:24:00 +03:00
|
|
|
covering
|
2021-08-30 17:31:37 +03:00
|
|
|
fGetLine : HasIO io => (h : File) -> io (Either FileError String)
|
|
|
|
fGetLine (FHandle f)
|
|
|
|
= do res <- primIO (prim__readLine f)
|
|
|
|
if prim__nullPtr res /= 0
|
|
|
|
then returnError
|
|
|
|
else ok (prim__getString res)
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Get a number of characters from the given file handle.
|
|
|
|
|||
|
|
|
|
||| @ h the file handle to read from
|
|
|
|
||| @ max the number of characters to read
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
2021-10-29 19:58:29 +03:00
|
|
|
fGetChars : HasIO io => (h : File) -> (max : Int) -> io (Either FileError String)
|
2021-08-30 17:31:37 +03:00
|
|
|
fGetChars (FHandle f) max
|
|
|
|
= do res <- primIO (prim__readChars max f)
|
|
|
|
if prim__nullPtr res /= 0
|
|
|
|
then returnError
|
|
|
|
else ok (prim__getString res)
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Get the next character from the given file handle.
|
|
|
|
|||
|
|
|
|
||| @ h the file handle to read from
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
|
|
|
fGetChar : HasIO io => (h : File) -> io (Either FileError Char)
|
|
|
|
fGetChar h@(FHandle f)
|
|
|
|
= do c <- primIO (prim__readChar f)
|
|
|
|
ferr <- fileError h
|
|
|
|
if ferr
|
|
|
|
then returnError
|
|
|
|
else ok (cast c)
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Write the given string to the file handle.
|
|
|
|
|||
|
|
|
|
||| @ h the file handle to write to
|
|
|
|
||| @ str the string to write
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
2021-10-29 19:58:29 +03:00
|
|
|
fPutStr : HasIO io => (h : File) -> (str : String) -> io (Either FileError ())
|
2021-08-30 17:31:37 +03:00
|
|
|
fPutStr (FHandle f) str
|
|
|
|
= do res <- primIO (prim__writeLine f str)
|
|
|
|
if res == 0
|
|
|
|
then returnError
|
|
|
|
else ok ()
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Write the given string, followed by a newline, to the file handle.
|
|
|
|
|||
|
|
|
|
||| @ fh the file handle to write to
|
|
|
|
||| @ str the string to write
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
2021-10-29 19:58:29 +03:00
|
|
|
fPutStrLn : HasIO io => (fh : File) -> (str : String) -> io (Either FileError ())
|
|
|
|
fPutStrLn fh str = fPutStr fh (str ++ "\n")
|
2021-08-30 17:31:37 +03:00
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Check whether the end-of-file indicator for the given file handle is set.
|
|
|
|
|||
|
|
|
|
||| @ h the file handle to check
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
|
|
|
fEOF : HasIO io => (h : File) -> io Bool
|
|
|
|
fEOF (FHandle f)
|
|
|
|
= do res <- primIO (prim__eof f)
|
|
|
|
pure (res /= 0)
|
|
|
|
|
2021-11-03 18:56:29 +03:00
|
|
|
||| Read all the remaining contents of a file handle
|
|
|
|
export
|
|
|
|
covering
|
|
|
|
fRead : HasIO io => (h : File) -> io (Either FileError String)
|
|
|
|
fRead h = fRead' h [<]
|
|
|
|
where
|
|
|
|
fRead' : HasIO io' => (h : File) -> (acc : SnocList String) -> io' (Either FileError String)
|
|
|
|
fRead' h acc = do
|
|
|
|
if !(fEOF h)
|
|
|
|
then pure $ Right $ concat acc
|
|
|
|
else do
|
|
|
|
Right line <- fGetLine h
|
|
|
|
| Left err => pure $ Left err
|
|
|
|
fRead' h $ acc :< line
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Delete the file at the given name.
|
|
|
|
|||
|
|
|
|
||| @ fname the file to delete
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
2021-10-29 19:58:29 +03:00
|
|
|
removeFile : HasIO io => (fname : String) -> io (Either FileError ())
|
2021-08-30 17:31:37 +03:00
|
|
|
removeFile fname
|
|
|
|
= do res <- primIO (prim__removeFile fname)
|
|
|
|
if res == 0
|
|
|
|
then ok ()
|
|
|
|
else returnError
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Read a number of lines into an accumulator, optionally starting at an offset
|
|
|
|
||| in the given file handle. Requires `Fuel` to run since the operation is
|
|
|
|
||| total but may run indefinitely; the functions `limit` and `forever` help
|
|
|
|
||| with providing `Fuel`.
|
|
|
|
|||
|
|
|
|
||| On success, returns a tuple of whether the EOF was reached (if it wasn't, we
|
|
|
|
||| ran out of fuel first) and the lines accumulated.
|
|
|
|
|||
|
|
|
|
||| Note: each line will still have a newline character at the end.
|
|
|
|
|||
|
|
|
|
||| @ acc the accumulator to read the lines onto
|
|
|
|
||| @ offset the offset to start reading at
|
|
|
|
||| @ fuel an amount of `Fuel`
|
|
|
|
||| @ h the file handle to read from
|
2021-08-30 17:31:37 +03:00
|
|
|
readLinesOnto : HasIO io => (acc : List String) ->
|
|
|
|
(offset : Nat) ->
|
|
|
|
(fuel : Fuel) ->
|
2021-10-29 19:58:29 +03:00
|
|
|
(h : File) ->
|
2021-08-30 17:31:37 +03:00
|
|
|
io (Either FileError (Bool, List String))
|
|
|
|
readLinesOnto acc _ Dry h = pure (Right (False, reverse acc))
|
|
|
|
readLinesOnto acc offset (More fuel) h
|
|
|
|
= do False <- fEOF h
|
|
|
|
| True => pure $ Right (True, reverse acc)
|
|
|
|
case offset of
|
|
|
|
(S offset') => (fSeekLine h *> readLinesOnto acc offset' (More fuel) h) @{Applicative.Compose}
|
|
|
|
0 => (fGetLine h >>= \str => readLinesOnto (str :: acc) 0 fuel h) @{Monad.Compose}
|
|
|
|
|
|
|
|
||| Read a chunk of a file in a line-delimited fashion.
|
|
|
|
||| You can use this function to read an entire file
|
|
|
|
||| as with @readFile@ by reading until @forever@ or by
|
|
|
|
||| iterating through pages until hitting the end of
|
|
|
|
||| the file.
|
|
|
|
|||
|
|
|
|
||| The @limit@ function can provide you with enough
|
|
|
|
||| fuel to read exactly a given number of lines.
|
|
|
|
|||
|
|
|
|
||| On success, returns a tuple of whether the end of
|
|
|
|
||| the file was reached or not and the lines read in
|
|
|
|
||| from the file.
|
|
|
|
|||
|
|
|
|
||| Note that each line will still have a newline
|
|
|
|
||| character at the end.
|
|
|
|
|||
|
|
|
|
||| Important: because we are chunking by lines, this
|
|
|
|
||| function's totality depends on the assumption that
|
|
|
|
||| no single line in the input file is infinite.
|
2021-10-29 19:58:29 +03:00
|
|
|
|||
|
|
|
|
||| @ offset the offset to start reading at
|
|
|
|
||| @ until the `Fuel` limiting how far we can read
|
|
|
|
||| @ fname the name of the file to read
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
2021-10-29 19:58:29 +03:00
|
|
|
readFilePage : HasIO io => (offset : Nat) -> (until : Fuel) -> (fname : String) ->
|
|
|
|
io (Either FileError (Bool, List String))
|
|
|
|
readFilePage offset fuel fname
|
|
|
|
= withFile fname Read pure $
|
2021-08-30 17:31:37 +03:00
|
|
|
readLinesOnto [] offset fuel
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Read the entire file at the given name. This function is `partial` since
|
|
|
|
||| there is no guarantee that the given file isn't infinite.
|
|
|
|
|||
|
|
|
|
||| @ fname the name of the file to read
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
2022-02-03 20:41:03 +03:00
|
|
|
covering
|
2021-10-29 19:58:29 +03:00
|
|
|
readFile : HasIO io => (fname : String) -> io (Either FileError String)
|
2021-08-30 17:31:37 +03:00
|
|
|
readFile = (map $ map (fastConcat . snd)) . readFilePage 0 forever
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Write the given string to the file at the specified name. Opens the file
|
|
|
|
||| with the `WriteTruncate` mode.
|
|
|
|
||| (If you have a file handle (a `File`), you may be looking for `fPutStr`.)
|
|
|
|
|||
|
|
|
|
||| @ filePath the file to write to
|
|
|
|
||| @ contents the string to write to the file
|
2021-08-30 17:31:37 +03:00
|
|
|
export
|
|
|
|
writeFile : HasIO io =>
|
2021-10-23 09:29:13 +03:00
|
|
|
(filePath : String) -> (contents : String) ->
|
2021-08-30 17:31:37 +03:00
|
|
|
io (Either FileError ())
|
|
|
|
writeFile file contents
|
|
|
|
= withFile file WriteTruncate pure $
|
2021-10-23 09:29:13 +03:00
|
|
|
flip fPutStr contents
|
|
|
|
|
2021-10-29 19:58:29 +03:00
|
|
|
||| Append the given string to the file at the specified name. Opens the file in
|
|
|
|
||| with the `Append` mode.
|
|
|
|
|||
|
|
|
|
||| @ filePath the file to write to
|
|
|
|
||| @ contents the string to write to the file
|
2021-10-23 09:29:13 +03:00
|
|
|
export
|
|
|
|
appendFile : HasIO io =>
|
|
|
|
(filePath : String) -> (contents : String) ->
|
|
|
|
io (Either FileError ())
|
|
|
|
appendFile file contents
|
|
|
|
= withFile file Append pure $
|
|
|
|
flip fPutStr contents
|