duplo-1.6.9: src/Development/Duplo/Static.hs
module Development.Duplo.Static where
import Control.Applicative ((<$>))
import Control.Lens hiding (Action)
import Control.Monad (zipWithM_, filterM)
import Data.List (transpose, nub)
import Development.Duplo.FileList (makeFile, makeFiles, toCopies, collapseFileLists, Copy)
import Development.Duplo.Utilities (logStatus, headerPrintSetter, createIntermediaryDirectories, createPathDirectories, getDirectoryFiles)
import Development.Shake hiding (getDirectoryFiles)
import Development.Shake.FilePath ((</>))
import System.FilePath.Posix (splitExtension, splitDirectories, makeRelative)
import qualified Development.Duplo.FileList as FileList (filePath)
import qualified Development.Duplo.Types.Config as TC
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"
-- 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 TC.isInDev config then devFiles' else []
-- Add test files to the mix, if we're in test mode
testFiles' <- getDirectoryFiles testAssetsPath ["vendor//*"]
let testFiles = if TC.isInTest config 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