packages feed

path-text-utf8-0.0.1.11: src/Path/Text/UTF8.hs

-- | 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 Data.Text.Encoding.Error (UnicodeException (..))
import qualified Data.Text.Encoding as TextEncoding

-- 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