{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | This module contains functions for file handling. The names of the -- functions and data types are chosen in a way to suggest a qualified import. -- -- @ -- __import qualified__ Require.File __as__ File -- @ module Require.File where import Relude import qualified Data.Text.IO as TIO -- | Wraps the name of a file as given by the user. Usually this corresponds to -- the file's path. newtype Name = Name Text -- | Associates a file's contents with the path from which it was read. data Input = Input { inputName :: Name, inputContent :: Text } -- | A type-safe wrapper for line numbers. newtype LineNumber = LineNumber Int deriving (Enum) -- | Identifies a specific line in a file. data LineTag = LineTag !Name !LineNumber -- | Returns the 'LineTag' referencing the first line in a given 'FileInput'. -- -- Note that the tag's line number is 1-based, which fits well with how GHC -- understands @{-\# LINE ... #-}@ pragmas. initialLineTag :: Input -> LineTag initialLineTag inp = LineTag (inputName inp) (LineNumber 1) -- | Returns a line tag from the same file but referencing the next line. advanceLineTag :: LineTag -> LineTag advanceLineTag (LineTag fn ln) = LineTag fn (succ ln) -- | @read name@ reads the contents of the file identified by @name@. read :: Name -> IO Input read f = Input f <$> readFileText (nameToPath f) -- | @write name lines@ writes all every line in @lines@ to the file identified -- by @name@ and appends a newline. writeLines :: Name -> [Text] -> IO () writeLines name theLines = withFile (nameToPath name) WriteMode $ \h -> traverse_ (TIO.hPutStrLn h) theLines -- | Splits the input into lines and annotates each with a 'LineTag'. inputLines :: Input -> [(LineTag, Text)] inputLines fi = zip lineTags contents where lineTags = iterate advanceLineTag (initialLineTag fi) contents = lines (inputContent fi) -- | Returns the 'FilePath' corresponding a given 'Name'. nameToPath :: Name -> FilePath nameToPath (Name fp) = toString fp