module Prelude.File import Builtins import Prelude.List import Prelude.Maybe import Prelude.Functor import Prelude.Monad import Prelude.Chars import Prelude.Strings import Prelude.Nat import Prelude.Cast import Prelude.Bool import Prelude.Basics import Prelude.Interfaces import Prelude.Either import Prelude.Show import IO %access public export ||| A file handle data File : Type where FHandle : (p : Ptr) -> File ||| A directory handle data Directory : Type where DHandle : (p : Ptr) -> Directory -- Usage hints for erasure analysis %used FHandle p %used DHandle p ||| An error from a file operation -- This is built in idris_mkFileError() in rts/idris_stdfgn.c. Make sure -- the values correspond! data FileError = GenericFileError Int -- errno | FileReadError | FileWriteError | FileNotFound | PermissionDenied private strError : Int -> String strError err = unsafePerformIO -- yeah, yeah... (foreign FFI_C "idris_showerror" (Int -> IO String) err) getFileError : IO FileError getFileError = do vm <- getMyVM MkRaw err <- foreign FFI_C "idris_mkFileError" (Ptr -> IO (Raw FileError)) vm pure err Show FileError where show FileReadError = "File Read Error" show FileWriteError = "File Write Error" show FileNotFound = "File Not Found" show PermissionDenied = "Permission Denied" show (GenericFileError errno) = strError errno ||| Standard input export stdin : File stdin = FHandle prim__stdin ||| Standard output export stdout : File stdout = FHandle prim__stdout ||| Standard output export stderr : File stderr = FHandle prim__stderr private do_ferror : Ptr -> IO Int do_ferror h = foreign FFI_C "fileError" (Ptr -> IO Int) h export ferror : File -> IO Bool ferror (FHandle h) = do err <- do_ferror h pure (err /= 0) ||| Call the RTS's file opening function private do_fopen : String -> String -> IO Ptr do_fopen f m = foreign FFI_C "fileOpen" (String -> String -> IO Ptr) f m ||| Open a file ||| @ f the filename ||| @ m the mode as a String (`"r"`, `"w"`, or `"r+"`) export fopen : (f : String) -> (m : String) -> IO (Either FileError File) fopen f m = do h <- do_fopen f m if !(nullPtr h) then Left <$> getFileError else pure (Right (FHandle h)) ||| Check whether a file handle is actually a null pointer export validFile : File -> IO Bool validFile (FHandle h) = not <$> nullPtr h ||| Modes for opening files data Mode = Read | WriteTruncate | Append | ReadWrite | ReadWriteTruncate | ReadAppend modeStr : Mode -> String modeStr Read = "rb" modeStr WriteTruncate = "wb" modeStr Append = "ab" modeStr ReadWrite = "rb+" modeStr ReadWriteTruncate = "wb+" modeStr ReadAppend = "ab+" ||| Open a file ||| @ f the filename ||| @ m the mode; either Read, WriteTruncate, Append, ReadWrite, ReadWriteTruncate, or ReadAppend export openFile : (f : String) -> (m : Mode) -> IO (Either FileError File) openFile f m = fopen f (modeStr m) ||| Open a file using C11 extended modes. ||| @ f the filename ||| @ m the mode; either Read, WriteTruncate, Append, ReadWrite, ReadWriteTruncate, or ReadAppend export openFileX : (f : String) -> (m : Mode) -> IO (Either FileError File) openFileX f m = fopen f $ modeStr m ++ "x" private do_fclose : Ptr -> IO () do_fclose h = foreign FFI_C "fileClose" (Ptr -> IO ()) h export closeFile : File -> IO () closeFile (FHandle h) = do_fclose h private do_getFileSize : Ptr -> IO Int do_getFileSize h = foreign FFI_C "fileSize" (Ptr -> IO Int) h private do_getFileAccessTime : Ptr -> IO Integer do_getFileAccessTime h = do MkRaw i <- foreign FFI_C "fileAccessTime" (Ptr -> IO (Raw Integer)) h pure i private do_getFileModifiedTime : Ptr -> IO Integer do_getFileModifiedTime h = do vm <- getMyVM MkRaw i <- foreign FFI_C "fileModifiedTime" (Ptr -> Ptr -> IO (Raw Integer)) vm h pure i private do_getFileStatusTime : Ptr -> IO Integer do_getFileStatusTime h = do MkRaw i <- foreign FFI_C "fileStatusTime" (Ptr -> IO (Raw Integer)) h pure i ||| Return the size of a File ||| Returns an error if the File is not an ordinary file (e.g. a directory) ||| Also note that this currently returns an Int, which may overflow if the ||| file is very big export fileSize : File -> IO (Either FileError Int) fileSize (FHandle h) = do s <- do_getFileSize h if (s < 0) then Left <$> getFileError else pure (Right s) export fileModifiedTime : File -> IO (Either FileError Integer) fileModifiedTime (FHandle h) = do s <- do_getFileModifiedTime h if (s == -1) then Left <$> getFileError else pure (Right s) export fileAccessTime : File -> IO (Either FileError Integer) fileAccessTime (FHandle h) = do s <- do_getFileAccessTime h if (s < 0) then Left <$> getFileError else pure (Right s) export fileStatusTime : File -> IO (Either FileError Integer) fileStatusTime (FHandle h) = do s <- do_getFileStatusTime h if (s < 0) then Left <$> getFileError else pure (Right s) private do_fread : Ptr -> IO' l String do_fread h = prim_fread h private do_freadChars : Ptr -> Int -> IO' l String do_freadChars h len = prim_freadChars len h export fgetc : File -> IO (Either FileError Char) fgetc (FHandle h) = do let c = cast !(foreign FFI_C "fgetc" (Ptr -> IO Int) h) if !(ferror (FHandle h)) then pure (Left FileReadError) else pure (Right c) export fflush : File -> IO () fflush (FHandle h) = foreign FFI_C "fflush" (Ptr -> IO ()) h private do_popen : String -> String -> IO Ptr do_popen f m = foreign FFI_C "do_popen" (String -> String -> IO Ptr) f m export popen : String -> Mode -> IO (Either FileError File) popen f m = do ptr <- do_popen f (modeStr m) if !(nullPtr ptr) then do err <- getFileError pure (Left err) else pure (Right (FHandle ptr)) export pclose : File -> IO () pclose (FHandle h) = foreign FFI_C "pclose" (Ptr -> IO ()) h ||| Read a line from a file ||| @h a file handle which must be open for reading export fGetLine : (h : File) -> IO (Either FileError String) fGetLine (FHandle h) = do str <- do_fread h if !(ferror (FHandle h)) then pure (Left FileReadError) else pure (Right str) ||| Read up to a number of characters from a file ||| @h a file handle which must be open for reading export fGetChars : (h : File) -> (len : Int) -> IO (Either FileError String) fGetChars (FHandle h) len = do str <- do_freadChars h len if !(ferror (FHandle h)) then pure (Left FileReadError) else pure (Right str) private do_fwrite : Ptr -> String -> IO (Either FileError ()) do_fwrite h s = do res <- prim_fwrite h s if (res /= 0) then do errno <- getErrno if errno == 0 then pure (Left FileWriteError) else Left <$> getFileError else pure (Right ()) ||| Write a line to a file ||| @h a file handle which must be open for writing ||| @str the line to write to the file export fPutStr : (h : File) -> (str : String) -> IO (Either FileError ()) fPutStr (FHandle h) s = do_fwrite h s export fPutStrLn : File -> String -> IO (Either FileError ()) fPutStrLn (FHandle h) s = do_fwrite h (s ++ "\n") private do_feof : Ptr -> IO Int do_feof h = foreign FFI_C "fileEOF" (Ptr -> IO Int) h ||| Check if a file handle has reached the end export fEOF : File -> IO Bool fEOF (FHandle h) = do eof <- do_feof h pure (eof /= 0) private do_fremove : String -> IO Int do_fremove s = foreign FFI_C "fileRemove" (String -> IO Int) s ||| Remove a file ||| @s the file name export fRemove : (s : String) -> IO Bool fRemove s = do res <- do_fremove s pure (res == 0) export fpoll : File -> IO Bool fpoll (FHandle h) = do p <- foreign FFI_C "fpoll" (Ptr -> IO Int) h pure (p > 0) ||| Read the contents of a text file into a string ||| This checks the size of the file before beginning to read, and only ||| reads that many bytes, to ensure that it remains a total function if ||| the file is appended to while being read. ||| This only works reliably with text files, since it relies on null-terminated ||| strings internally. ||| Returns an error if filepath is not a normal file. export readFile : (filepath : String) -> IO (Either FileError String) readFile fn = do Right h <- openFile fn Read | Left err => pure (Left err) Right max <- fileSize h | Left err => do closeFile h pure (Left err) sb <- newStringBuffer (max + 1) c <- readFile' h max sb closeFile h pure c where readFile' : File -> Int -> StringBuffer -> IO (Either FileError String) readFile' h max contents = do x <- fEOF h if not x && max > 0 then do Right l <- fGetChars h 1024000 | Left err => pure (Left err) addToStringBuffer contents l assert_total $ readFile' h (max - 1024000) contents else do str <- getStringFromBuffer contents pure (Right str) ||| Write a string to a file export writeFile : (filepath : String) -> (contents : String) -> IO (Either FileError ()) writeFile fn contents = do Right h <- openFile fn WriteTruncate | Left err => pure (Left err) Right () <- fPutStr h contents | Left err => do closeFile h pure (Left err) closeFile h pure (Right ()) export dirOpen : (d : String) -> IO (Either FileError Directory) dirOpen d = do dptr <- foreign FFI_C "idris_dirOpen" (String -> IO Ptr) d if !(nullPtr dptr) then Left <$> getFileError else pure (Right (DHandle dptr)) export dirClose : Directory -> IO () dirClose (DHandle d) = foreign FFI_C "idris_dirClose" (Ptr -> IO ()) d export dirError : Directory -> IO Bool dirError (DHandle d) = do err <- foreign FFI_C "idris_dirError" (Ptr -> IO Int) d pure (err /= 0) export dirEntry : Directory -> IO (Either FileError String) dirEntry (DHandle d) = do fn <- foreign FFI_C "idris_nextDirEntry" (Ptr -> IO String) d if !(dirError (DHandle d)) then pure (Left FileReadError) else pure (Right fn) export createDir : String -> IO (Either FileError ()) createDir d = do ok <- foreign FFI_C "idris_mkdir" (String -> IO Int) d if (ok == 0) then pure (Right ()) else Left <$> getFileError export changeDir : String -> IO Bool changeDir dir = do ok <- foreign FFI_C "idris_chdir" (String -> IO Int) dir pure (ok == 0) export currentDir : IO String currentDir = do MkRaw s <- foreign FFI_C "idris_currentDir" (IO (Raw String)) pure s