{-# LANGUAGE BangPatterns #-} {-# LANGUAGE NoImplicitPrelude #-} -- | Read and write UTF-8 text files. module Path.Text.UTF8 ( -- * Reading readFile , tryReadFile , ReadError (..) -- * Writing , writeFile , tryWriteFile , WriteError -- * Re-exports , IOError , UnicodeException (DecodeError) , parseAbsFile , parseRelFile ) where -- base import Data.Either (Either (..)) import Data.Functor ((<$>)) import System.IO (IO) import System.IO.Error (IOError) -- safe-exceptions import qualified Control.Exception.Safe as Exception -- bytestring import qualified Data.ByteString as BS -- text import Data.Text (Text) import qualified Data.Text.Encoding as TextEncoding import Data.Text.Encoding.Error (UnicodeException (..)) -- path import Path (Path, parseAbsFile, parseRelFile) import qualified Path data ReadError = ReadErrorIO IOError | ReadErrorDecode UnicodeException type WriteError = IOError -- | Read the contents of a UTF-8 encoded text file. -- -- May throw 'IOError' or 'UnicodeException'. To handle these errors in 'Either' -- instead, use 'tryReadFile'. readFile :: Path base Path.File -> IO Text readFile path = f <$> BS.readFile (Path.toFilePath path) where f bs = let !text = TextEncoding.decodeUtf8 bs in text -- | Read the contents of a UTF-8 encoded text file. -- -- Any 'IOError' or 'UnicodeException' that occurs is caught and returned as a -- 'ReadError' on the 'Left' side of the 'Either'. To throw these exceptions -- instead, use 'readFile'. tryReadFile :: Path base Path.File -> IO (Either ReadError Text) tryReadFile path = f <$> Exception.tryIO (BS.readFile (Path.toFilePath path)) where f (Left e) = Left (ReadErrorIO e) f (Right bs) = first ReadErrorDecode (TextEncoding.decodeUtf8' bs) -- | Write text to a file in a UTF-8 encoding. -- -- May throw 'IOError'. To handle this error in 'Either' instead, use -- 'tryWriteFile'. writeFile :: Path base Path.File -> Text -> IO () writeFile path text = BS.writeFile (Path.toFilePath path) (TextEncoding.encodeUtf8 text) -- | Write text to a file in a UTF-8 encoding. -- -- Any 'IOError' that occurs is caught and returned on the 'Left' side of the -- 'Either'. To throw the exception instead, use 'writeFile'. tryWriteFile :: Path base Path.File -> Text -> IO (Either WriteError ()) tryWriteFile path text = Exception.tryIO (writeFile path text) first :: (a -> a') -> Either a b -> Either a' b first f (Left x) = Left (f x) first _ (Right x) = Right x