{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
module Drasil.FileHandling.FileLayout
(
FileLayout,
name,
file,
exactFile,
directory,
OverwritePolicy (..),
writeFiles,
)
where
import Data.Foldable qualified as F
import Data.Map.Strict qualified as M
import Data.Maybe (fromMaybe)
import Drasil.FileHandling.FilePath (PathSegment, toPath, (</>))
import Drasil.FileHandling.WriteFiles (Writeable (..), WritePolicy (..))
import System.Directory.OsPath (createDirectoryIfMissing, doesPathExist)
import System.OsPath (OsPath, decodeUtf)
data FileLayout = FileLayout
{
FileLayout -> PathSegment
pathSeg :: PathSegment,
FileLayout -> FileTree
fileTree :: FileTree
}
name :: FileLayout -> PathSegment
name :: FileLayout -> PathSegment
name = FileLayout -> PathSegment
pathSeg
{-# INLINE name #-}
data FileTree where
Directory :: M.Map PathSegment FileTree -> FileTree
File :: (Writeable doc) => doc -> WritePolicy -> FileTree
file :: (Writeable doc) => PathSegment -> doc -> FileLayout
file :: forall doc. Writeable doc => PathSegment -> doc -> FileLayout
file PathSegment
fp doc
doc = PathSegment -> FileTree -> FileLayout
FileLayout PathSegment
fp (FileTree -> FileLayout) -> FileTree -> FileLayout
forall a b. (a -> b) -> a -> b
$ doc -> WritePolicy -> FileTree
forall doc. Writeable doc => doc -> WritePolicy -> FileTree
File doc
doc WritePolicy
AppendNewline
{-# INLINE file #-}
exactFile :: (Writeable doc) => PathSegment -> doc -> FileLayout
exactFile :: forall doc. Writeable doc => PathSegment -> doc -> FileLayout
exactFile PathSegment
fp doc
doc = PathSegment -> FileTree -> FileLayout
FileLayout PathSegment
fp (FileTree -> FileLayout) -> FileTree -> FileLayout
forall a b. (a -> b) -> a -> b
$ doc -> WritePolicy -> FileTree
forall doc. Writeable doc => doc -> WritePolicy -> FileTree
File doc
doc WritePolicy
ExactBytes
{-# INLINE exactFile #-}
directory :: (Foldable f) => PathSegment -> f FileLayout -> FileLayout
directory :: forall (f :: * -> *).
Foldable f =>
PathSegment -> f FileLayout -> FileLayout
directory PathSegment
fp = PathSegment -> FileTree -> FileLayout
FileLayout PathSegment
fp (FileTree -> FileLayout)
-> (f FileLayout -> FileTree) -> f FileLayout -> FileLayout
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map PathSegment FileTree -> FileTree
Directory (Map PathSegment FileTree -> FileTree)
-> (f FileLayout -> Map PathSegment FileTree)
-> f FileLayout
-> FileTree
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (FileLayout
-> Map PathSegment FileTree -> Map PathSegment FileTree)
-> Map PathSegment FileTree
-> f FileLayout
-> Map PathSegment FileTree
forall a b. (a -> b -> b) -> b -> f a -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
F.foldr' FileLayout -> Map PathSegment FileTree -> Map PathSegment FileTree
insert Map PathSegment FileTree
forall a. Monoid a => a
mempty
{-# INLINE directory #-}
insert :: FileLayout -> M.Map PathSegment FileTree -> M.Map PathSegment FileTree
insert :: FileLayout -> Map PathSegment FileTree -> Map PathSegment FileTree
insert FileLayout
v =
(PathSegment -> FileTree -> FileTree -> FileTree)
-> PathSegment
-> FileTree
-> Map PathSegment FileTree
-> Map PathSegment FileTree
forall k a.
Ord k =>
(k -> a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWithKey
(\PathSegment
dup FileTree
_ -> [Char] -> FileTree -> FileTree
forall a. HasCallStack => [Char] -> a
error ([Char] -> FileTree -> FileTree) -> [Char] -> FileTree -> FileTree
forall a b. (a -> b) -> a -> b
$ [Char]
"duplicate path: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char] -> Maybe [Char] -> [Char]
forall a. a -> Maybe a -> a
fromMaybe [Char]
"cannot decode" $([|decodeUtf $ toPath dup :: Maybe String|]))
(FileLayout -> PathSegment
pathSeg FileLayout
v)
(FileLayout -> FileTree
fileTree FileLayout
v)
{-# INLINE insert #-}
data OverwritePolicy = OverwriteAllowed | NeverOverwrite
writeFiles :: OverwritePolicy -> OsPath -> FileLayout -> IO ()
writeFiles :: OverwritePolicy -> OsPath -> FileLayout -> IO ()
writeFiles OverwritePolicy
OverwriteAllowed OsPath
basePath FileLayout
layout = OsPath -> FileLayout -> IO ()
writeFiles0 OsPath
basePath FileLayout
layout
writeFiles OverwritePolicy
NeverOverwrite OsPath
basePath FileLayout
layout = do
Bool
exists <- OsPath -> IO Bool
doesPathExist OsPath
targetPath
if Bool
exists
then [Char] -> IO ()
forall a. HasCallStack => [Char] -> a
error ([Char] -> IO ()) -> [Char] -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char]
"Path already exists: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ OsPath -> [Char]
forall a. Show a => a -> [Char]
show OsPath
targetPath
else OsPath -> FileLayout -> IO ()
writeFiles0 OsPath
basePath FileLayout
layout
where
targetPath :: OsPath
targetPath = OsPath
basePath OsPath -> PathSegment -> OsPath
</> FileLayout -> PathSegment
pathSeg FileLayout
layout
writeFiles0 :: OsPath -> FileLayout -> IO ()
writeFiles0 :: OsPath -> FileLayout -> IO ()
writeFiles0 OsPath
basePath (FileLayout PathSegment
fname (File doc
content WritePolicy
policy)) =
OsPath -> WritePolicy -> doc -> IO ()
forall doc. Writeable doc => OsPath -> WritePolicy -> doc -> IO ()
writeToFile (OsPath
basePath OsPath -> PathSegment -> OsPath
</> PathSegment
fname) WritePolicy
policy doc
content
writeFiles0 OsPath
basePath (FileLayout PathSegment
dname (Directory Map PathSegment FileTree
children)) = do
let nextPath :: OsPath
nextPath = OsPath
basePath OsPath -> PathSegment -> OsPath
</> PathSegment
dname
Bool -> OsPath -> IO ()
createDirectoryIfMissing Bool
False OsPath
nextPath
((PathSegment, FileTree) -> IO ())
-> [(PathSegment, FileTree)] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
F.traverse_ (\(PathSegment
n, FileTree
c) -> OsPath -> FileLayout -> IO ()
writeFiles0 OsPath
nextPath (PathSegment -> FileTree -> FileLayout
FileLayout PathSegment
n FileTree
c)) (Map PathSegment FileTree -> [(PathSegment, FileTree)]
forall k a. Map k a -> [(k, a)]
M.toList Map PathSegment FileTree
children)
{-# INLINE writeFiles0 #-}