ivory-artifact-0.1.0.3: src/Ivory/Artifact.hs
module Ivory.Artifact (
-- * Ivory Artifacts
-- | Artifacts permit the user to generate non-Ivory language files
-- as part of an Ivory program's build process. Artifacts are typically
-- used to encapsulate helper code (such as Ivory.Stdlib's string functions
-- helper .c and .h files) or to generate debug or metadata output. The
-- contents of an `Artifact` are given by some external file, or as a string.
--
-- Artifacts are exposed as an abstract type `Artifact` with a set of
-- constructors, an accessor `artifactFileName`, and two functions,
-- `putArtifact` and `printArtifact`, which are used to write an
-- Artifacts to a file or print it to stdout, respectively.
--
-- Users may specify transformations on the contents of an artifact.
-- Optionally, these transformations can throw an error. This is useful when
-- using artifact files as templates.
--
Artifact()
-- | Gives the file name that will be used when writing the `Artifact` to the
-- filesystem.
, artifactFileName
-- * Artifact constructors
-- | `artifactFile` creates an `Artifact` from an output filename of type
-- `FilePath` and an input filepath of type `IO FilePath`. The output filename
-- should be a simple filename, any directory information will be dropped. The
-- input filepath should be an absolute path. The input filepath is in the IO
-- monad for compatiblility with Cabal's `getDataDir` data files functionality.
, artifactFile
-- | `artifactCabalFile` creates an `Artifact` given an input directory of
-- type `IO FilePath` and an input filepath, relative to the input directory
-- in the preceeding argument, of type `FilePath`.
--
-- This function is designed to be used with a Cabal Paths_(packagename)
-- `getDataDir`, which has type `IO FilePath`, and a path to a file inside the
-- data directory. It is implemented in terms of `artifactFileName`. The
-- output filename is given by dropping directory information from the input
-- filepath.
, artifactCabalFile
-- | `artifactText` creates an `Artifact` from an output filename of type
-- `FilePath` and with contents of type `Text`.
, artifactText
-- | `artifactString` creates an `Artifact` from an output filename of type
-- `FilePath` with contents of type `String`. Implemented in terms of
-- `artifactText`.
, artifactString
-- | `artifactTransform` and `artifactTransformString` specify a
-- transformation on the contents of an `Artifact`.
, artifactTransform, artifactTransformString
-- | `artifactTransformErr` and `artifactTransformErrString` specify a
-- transformation on the contents of an `Artifact` which may give an error.
, artifactTransformErr, artifactTransformErrString
-- | `artifactPath` prepends a `FilePath` to the output file path.
, artifactPath
-- * Artifact actions
-- | Takes a directory of type `FilePath` and an `Artifact`
-- writes each `Artifact` to the file system or gives an error explaining why
-- not. `Maybe String` containins errors encountered when an `Artifact` is
-- transformed, or specified by an input filename which does not exist.
, putArtifact
-- | like `putArtifact` but ignores any errors.
, putArtifact_
-- | Takes an `Artifact` and prints it, or an appropriate error message, to
-- stdout.
, printArtifact
-- | Takes a guess at whether two artifacts might be equal.
, mightBeEqArtifact
, Located(..)
, mightBeEqLocatedArtifact
) where
import Control.Monad (void)
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as T
import System.FilePath
import System.Directory
import Ivory.Artifact.Transformer
import Ivory.Artifact.Location
import System.IO.Unsafe (unsafePerformIO)
data Artifact =
Artifact
{ artifact_outputname :: FilePath
, artifact_contents :: AContents
, artifact_transformer :: Transformer T.Text
}
mightBeEqLocatedArtifact :: Located Artifact -> Located Artifact -> Bool
mightBeEqLocatedArtifact (Root a) (Root b) = mightBeEqArtifact a b
mightBeEqLocatedArtifact (Src a) (Src b) = mightBeEqArtifact a b
mightBeEqLocatedArtifact (Incl a) (Incl b) = mightBeEqArtifact a b
mightBeEqLocatedArtifact _ _ = False
mightBeEqArtifact :: Artifact -> Artifact -> Bool
mightBeEqArtifact a b =
and [ artifact_outputname a == artifact_outputname b
, artifact_contents a `mightBeEqAContents` artifact_contents b]
data AContents = LiteralContents T.Text
| FileContents (IO FilePath)
mightBeEqAContents :: AContents -> AContents -> Bool
mightBeEqAContents (LiteralContents a) (LiteralContents b) = a == b
mightBeEqAContents (FileContents a) (FileContents b) = unsafePerformIO a == unsafePerformIO b
mightBeEqAContents _ _ = False
artifactFileName :: Artifact -> FilePath
artifactFileName = artifact_outputname
artifactFile :: FilePath -> IO FilePath -> Artifact
artifactFile outputname inputpath = Artifact
{ artifact_outputname = takeFileName outputname
, artifact_contents = FileContents inputpath
, artifact_transformer = emptyTransformer
}
artifactCabalFile :: IO FilePath -> FilePath -> Artifact
artifactCabalFile inputdir inputfname =
artifactFile (takeFileName inputfname)
(fmap (\i -> (i </> inputfname)) inputdir)
artifactText :: FilePath -> T.Text -> Artifact
artifactText outputname t = Artifact
{ artifact_outputname = takeFileName outputname
, artifact_contents = LiteralContents t
, artifact_transformer = emptyTransformer
}
artifactPath :: FilePath -> Artifact -> Artifact
artifactPath f a = a { artifact_outputname = f </> artifact_outputname a }
artifactString :: FilePath -> String -> Artifact
artifactString f s = artifactText f (T.pack s)
artifactTransform :: (T.Text -> T.Text) -> Artifact -> Artifact
artifactTransform f a =
a { artifact_transformer = transform f (artifact_transformer a) }
artifactTransformString :: (String -> String) -> Artifact -> Artifact
artifactTransformString f a = artifactTransform f' a
where f' = T.pack . f . T.unpack
artifactTransformErr :: (T.Text -> Either String T.Text) -> Artifact -> Artifact
artifactTransformErr f a =
a { artifact_transformer = transformErr f (artifact_transformer a) }
artifactTransformErrString :: (String -> Either String String) -> Artifact -> Artifact
artifactTransformErrString f a = artifactTransformErr f' a
where f' t = fmap T.pack (f (T.unpack t))
getArtifact :: Artifact -> IO (Either String T.Text)
getArtifact a = g (artifact_contents a)
where
runT t = runTransformer (artifact_transformer a) t
g (LiteralContents t) = return (runT t)
g (FileContents getf) = do
srcpath <- getf
-- Check if srcpath exists. If it does not, give an error
exists <- doesFileExist srcpath
case exists of
True -> do
t <- T.readFile srcpath
return (runT t)
False -> return (Left (notfound srcpath))
notfound srcpath = "Path " ++ srcpath ++ " for Artifact named "
++ artifact_outputname a ++ " could not be found."
withContents :: Artifact -> (T.Text -> IO ()) -> IO (Maybe String)
withContents a f = do
contents <- getArtifact a
case contents of
Left err -> return (Just err)
Right c -> f c >> return Nothing
putArtifact :: FilePath -> Artifact -> IO (Maybe String)
putArtifact fp a = withContents a $ \c -> do
let fname = fp </> artifact_outputname a
createDirectoryIfMissing True (dropFileName fname)
T.writeFile fname c
putArtifact_ :: FilePath -> Artifact -> IO ()
putArtifact_ fp a = void (putArtifact fp a)
printArtifact :: Artifact -> IO ()
printArtifact a = do
res <- withContents a aux
case res of
Nothing -> return ()
Just err -> putStrLn $
"Encountered error when creating artifact " ++ artifact_outputname a
++ ":\n" ++ err
where
aux c = do
putStrLn ("Artifact " ++ artifact_outputname a)
putStrLn "================"
T.putStrLn c
putStrLn "================"