duplo-1.8.0: src/Development/Duplo/Static.hs
module Development.Duplo.Static where
import Control.Applicative ((<$>))
import Control.Lens hiding (Action)
import Control.Monad (filterM, zipWithM_)
import Data.List (nub, transpose)
import Development.Duplo.FileList (Copy, collapseFileLists,
makeFile, makeFiles, toCopies)
import qualified Development.Duplo.FileList as FileList (filePath)
import qualified Development.Duplo.Types.Config as TC
import Development.Duplo.Utilities (createIntermediaryDirectories,
createPathDirectories,
getDirectoryFiles,
headerPrintSetter, logStatus)
import Development.Shake hiding (getDirectoryFiles)
import Development.Shake.FilePath ((</>))
import System.FilePath.Posix (makeRelative, splitDirectories,
splitExtension)
build :: TC.BuildConfig
-> [FilePath]
-> Action ()
build config outs = do
let targetPath = config ^. TC.targetPath
let assetsPath = config ^. TC.assetsPath
let depsPath = config ^. TC.depsPath
let devPath = config ^. TC.devPath
let devAssetPath = devPath </> "assets"
let testAssetPath = config ^. TC.duploPath </> "etc/test"
-- Convert to relative paths for copying
let filesRel = fmap (makeRelative targetPath) outs
-- Look in assets directory
let assetFiles = makeFiles assetsPath filesRel
-- Look in components' asset directories as well
depAssetDirs <- getDirectoryDirs depsPath
-- Assets are relative to their own asset directories
let depAssetPaths = [ base </> dep </> src </> "assets"
| src <- ["app"]
, base <- [depsPath]
, dep <- depAssetDirs
]
-- Make the actual file records with the asset directory as the base
let depAssetFiles = [ makeFile base file
| base <- depAssetPaths
, file <- filesRel
]
-- Look in the dev directory as well
let devFiles = makeFiles devAssetPath filesRel
-- Look in the test directory as well
let testFiles = makeFiles testAssetPath filesRel
-- Combine matching files into lists each pointing to its corresponding
-- output file. Note that this is in order of precedence.
let possibleFiles = transpose [devFiles, testFiles, assetFiles, depAssetFiles]
-- Each file list collapses into a path that exists
cleanedFiles <- collapseFileLists possibleFiles
-- Path to output index
let targetIndex = targetPath </> "index.html"
-- Is it index in the target output directory?
let isTargetIndex file = targetIndex /= file ^. FileList.filePath
-- Ignore index as it's handling by the markup action
let filesLessIndex = filter isTargetIndex cleanedFiles
-- We need to take the files and turn it into from/to pair
let (froms, tos) = unzip $ toCopies targetPath filesLessIndex
-- Log
let repeat' = replicate $ length froms
let messages = transpose [ repeat' "Copying "
, froms
, repeat' " to "
, tos
]
mapM_ (putNormal . concat) messages
-- Make sure the intermediary directories exist
mapM_ createIntermediaryDirectories tos
-- Copy all files
zipWithM_ copyFileChanged froms tos
-- | Build dependency list for static files
deps :: TC.BuildConfig
-> Action ()
deps config = do
liftIO $ logStatus headerPrintSetter "Copying static files"
let assetsPath = config ^. TC.assetsPath
let depsPath = config ^. TC.depsPath
let targetPath = config ^. TC.targetPath
let devPath = config ^. TC.devPath
let devAssetsPath = devPath </> "assets/"
let testAssetsPath = config ^. TC.duploPath </> "etc/test"
let buildMode = config ^. TC.buildMode
-- Make sure all these directories exist
createPathDirectories [assetsPath, depsPath, targetPath, devAssetsPath]
-- We want all asset files
assetFiles <- getDirectoryFiles assetsPath ["//*"]
-- ... including those of dependencies
depAssetFiles <- getDepAssets depsPath
-- Add dev files to the mix, if we're in dev mode
devFiles' <- getDirectoryFiles devAssetsPath ["//*"]
let devFiles = if buildMode == "development" then devFiles' else []
-- Add test files to the mix, if we're in test mode
testFiles' <- getDirectoryFiles testAssetsPath ["vendor//*"]
let testFiles = if buildMode == "test" then testFiles' else []
-- Mix them together
let allFiles = nub $ concat [depAssetFiles, assetFiles, devFiles, testFiles]
-- We do NOT want index
let files = filter ("index.html" /=) allFiles
let getExt = snd . splitExtension
let getFilename = last . splitDirectories
let firstFilenameChar = head . getFilename
-- Only visible files (UNIX-style with leading dot)
let onlyVisible = filter (('.' /=) . firstFilenameChar)
let staticFiles = onlyVisible files
-- Map to output equivalents
let filesOut = fmap (targetPath ++) staticFiles
-- Declare dependencies
need filesOut
qualify :: TC.BuildConfig
-> FilePath
-> Maybe [FilePath]
qualify config path =
if targetPath ?== path
then Just [path]
else Nothing
where
targetPath = config ^. TC.targetPath ++ "/*"
-- | Given where the dependencies are, return a list of assets relative to
-- their own containing components
getDepAssets :: FilePath -> Action [FilePath]
getDepAssets depsPath = do
-- Get all dependencies' root directories.
depNames <- getDirectoryDirs depsPath
-- In this context, everything is relative to the asset directory.
let depAssetDirs = fmap ((depsPath </>) . (</> "app/assets")) depNames
-- See if there are even assets.
applicableDeps <- filterM doesDirectoryExist depAssetDirs
-- Fetch each depednecy's asset files
let assets = [ getDirectoryFiles dep ["//*"]
| dep <- applicableDeps
]
-- Return the flatten version of all asset files
concat <$> sequence assets