packages feed

herringbone-0.0.2: Web/Herringbone/LocateAssets.hs

-- | This module deals with locating assets on the disk, and calculating how to
-- create assets which need preprocessing.
module Web.Herringbone.LocateAssets where

import Data.Maybe
import Data.Text (Text)
import Filesystem.Path.CurrentOS (FilePath, (</>))
import qualified Filesystem.Path.CurrentOS as F
import qualified Filesystem as F
import Prelude hiding (FilePath)

import Web.Herringbone.Types

locateAssets :: Herringbone -> LogicalPath -> IO [(FilePath, [PP])]
locateAssets hb logPath = do
    let sourceDirs = hbSourceDirs hb
    let pps        = hbPPs hb
    let pathPieces = fromLogicalPath logPath
    assets <- sequence $ map (getAssetsFrom pps pathPieces) sourceDirs
    return $ concat assets

getAssetsFrom :: PPs
              -> [Text]      -- ^ requested path pieces
              -> FilePath    -- ^ Directory to look in
              -> IO [(FilePath, [PP])]
getAssetsFrom _   []          _    = return []
getAssetsFrom pps pathPieces' dir' = do
    let pathPieces        = map F.fromText pathPieces'
    let dir               = foldl (</>) dir' (init pathPieces)
    let assetName         = last pathPieces

    exists <- F.isDirectory dir
    if exists
        then do contents <- F.listDirectory dir
                let filenames = getAssetsFrom'
                                    pps
                                    assetName
                                    (map F.filename contents)
                return $ map (\(path, xs) -> (dir </> path, xs)) filenames
        else return []

-- Given a list of preprocessors, the path of an asset we want to serve, and
-- a list of potential source files, return a list of all the files which could
-- be used as a source for that file, together with the preprocessors which
-- would need to be applied (in the correct order) to preprocess that file.
--
-- For example, given preprocessors for "sass" and "erb", the asset path
-- "style.css", and the following list of potential files:
--
--  "style.css"
--  "style.css.sass"
--  "style.css.sass.erb"
--  "style.css.unrecognised-ext"
--  "javascript.js"
--
-- we should get back:
--
--  [ ("style.css", [])
--  , ("style.css.sass", [sass])
--  , ("style.css.sass.erb", [erb, sass])
--  ]
getAssetsFrom' :: PPs
               -> FilePath      -- ^ Asset to serve
               -> [FilePath]    -- ^ Potential source files
               -> [(FilePath, [PP])]
getAssetsFrom' pps assetPath = catMaybes . map resolve
    where
    resolve :: FilePath -> Maybe (FilePath, [PP])
    resolve fp = fmap (\xs -> (fp, xs)) $ (resolvePPs pps assetPath fp)

-- Can we apply a sequence of the given preprocessors to the given source file
-- path to get the given asset? If so, return the list of preprocessors which
-- should be applied to it to make this happen.
resolvePPs :: PPs -> FilePath -> FilePath -> Maybe [PP]
resolvePPs pps assetPath source = do
    exts   <- getExtraExtensions assetPath source
    ppList <- sequence $ map (\e -> lookupPP e pps) exts
    return ppList

-- Check if a file path is formed from another file path plus a list of
-- extensions, and if so, return those extensions, in reverse order.
-- Eg:
--  getExtraExtensions "game.js" "game.js.coffee"     == Just ["coffee"]
--  getExtraExtensions "style.css" "game.js.coffee"   == Nothing
--  getExtraExtensions "game.js" "game.js.coffee.erb" == Just ["erb", "coffee"]
getExtraExtensions :: FilePath -> FilePath -> Maybe [Text]
getExtraExtensions fp fpWithExts = do
    stripped <- F.stripPrefix fp fpWithExts
    return $ (reverse . F.extensions) stripped