Squash static strings into a single one.
This commit is contained in:
parent
1a8767935e
commit
53fd20d239
@ -49,6 +49,7 @@ module Yesod.Static
|
|||||||
import Prelude hiding (FilePath)
|
import Prelude hiding (FilePath)
|
||||||
import qualified Prelude
|
import qualified Prelude
|
||||||
import System.Directory
|
import System.Directory
|
||||||
|
import Control.Arrow (second)
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Data.FileEmbed (embedDir)
|
import Data.FileEmbed (embedDir)
|
||||||
|
|
||||||
@ -66,11 +67,13 @@ import qualified Data.ByteString.Char8 as S8
|
|||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import qualified Data.Serialize
|
import qualified Data.Serialize
|
||||||
import Data.Text (Text, pack)
|
import Data.Text (Text, pack)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Set as S
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Data.IORef (readIORef, newIORef, writeIORef)
|
import Data.IORef (readIORef, newIORef, writeIORef)
|
||||||
import Network.Wai (pathInfo)
|
import Network.Wai (pathInfo)
|
||||||
import Data.Char (isLower, isDigit)
|
import Data.Char (isLower, isDigit)
|
||||||
import Data.List (foldl')
|
import Data.List (foldl', inits, tails)
|
||||||
import qualified Data.ByteString as S
|
import qualified Data.ByteString as S
|
||||||
import System.PosixCompat.Files (getFileStatus, modificationTime)
|
import System.PosixCompat.Files (getFileStatus, modificationTime)
|
||||||
import System.Posix.Types (EpochTime)
|
import System.Posix.Types (EpochTime)
|
||||||
@ -280,14 +283,32 @@ mkStaticFilesList
|
|||||||
-> Bool -- ^ append checksum query parameter
|
-> Bool -- ^ append checksum query parameter
|
||||||
-> Q [Dec]
|
-> Q [Dec]
|
||||||
mkStaticFilesList fp fs routeConName makeHash = do
|
mkStaticFilesList fp fs routeConName makeHash = do
|
||||||
concat `fmap` mapM mkRoute fs
|
let (squashedFinal, squashMap) = squashStrings ("etag" : concat fs)
|
||||||
|
(squashedName, squashedDecl) <- mkSquashedStringsDecl squashedFinal
|
||||||
|
let refName = mkSquashedReference squashedName squashMap
|
||||||
|
routes <- concat `fmap` mapM (mkRoute refName) fs
|
||||||
|
return (squashedDecl ++ routes)
|
||||||
where
|
where
|
||||||
replace' c
|
replace' c
|
||||||
| 'A' <= c && c <= 'Z' = c
|
| 'A' <= c && c <= 'Z' = c
|
||||||
| 'a' <= c && c <= 'z' = c
|
| 'a' <= c && c <= 'z' = c
|
||||||
| '0' <= c && c <= '9' = c
|
| '0' <= c && c <= '9' = c
|
||||||
| otherwise = '_'
|
| otherwise = '_'
|
||||||
mkRoute f = do
|
mkSquashedStringsDecl squashedFinal = do
|
||||||
|
name <- newName "squashedStrings"
|
||||||
|
pack' <- [|pack|]
|
||||||
|
squashedFinal' <- lift squashedFinal
|
||||||
|
let decl = [ SigD name (ConT ''Text)
|
||||||
|
, FunD name
|
||||||
|
[ Clause [] (NormalB $ pack' `AppE` squashedFinal') []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
return (name, decl)
|
||||||
|
mkSquashedReference squashedName squashMap = \str ->
|
||||||
|
case M.lookup str squashMap of
|
||||||
|
Nothing -> [|pack $(lift str)|]
|
||||||
|
Just (pos, len) -> [|T.take len (T.drop pos $(return (VarE squashedName)))|]
|
||||||
|
mkRoute refName f = do
|
||||||
let name' = intercalate "_" $ map (map replace') f
|
let name' = intercalate "_" $ map (map replace') f
|
||||||
routeName = mkName $
|
routeName = mkName $
|
||||||
case () of
|
case () of
|
||||||
@ -296,12 +317,11 @@ mkStaticFilesList fp fs routeConName makeHash = do
|
|||||||
| isDigit (head name') -> '_' : name'
|
| isDigit (head name') -> '_' : name'
|
||||||
| isLower (head name') -> name'
|
| isLower (head name') -> name'
|
||||||
| otherwise -> '_' : name'
|
| otherwise -> '_' : name'
|
||||||
f' <- [|map pack $(lift f)|]
|
f' <- ListE `fmap` mapM refName f
|
||||||
let route = mkName routeConName
|
let route = mkName routeConName
|
||||||
pack' <- [|pack|]
|
|
||||||
qs <- if makeHash
|
qs <- if makeHash
|
||||||
then do hash <- qRunIO $ base64md5File $ pathFromRawPieces fp f
|
then do hash <- qRunIO $ base64md5File $ pathFromRawPieces fp f
|
||||||
[|[(pack "etag", pack $(lift hash))]|]
|
[|[($(refName "etag"), $(refName hash))]|]
|
||||||
else return $ ListE []
|
else return $ ListE []
|
||||||
return
|
return
|
||||||
[ SigD routeName $ ConT route
|
[ SigD routeName $ ConT route
|
||||||
@ -310,6 +330,44 @@ mkStaticFilesList fp fs routeConName makeHash = do
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
-- | Convert a list of 'String's into a single 'String' and a
|
||||||
|
-- 'M.Map' of the original 'String's into an offset and a length on
|
||||||
|
-- the resulting single 'String'.
|
||||||
|
squashStrings :: [String] -> (String, M.Map String (Int, Int))
|
||||||
|
squashStrings = second M.fromAscList . go 0 "" . S.toAscList . S.fromList
|
||||||
|
where
|
||||||
|
-- Length of the string of maximal length of characters from
|
||||||
|
-- the end of the @lastString@ that are the same. Uses a
|
||||||
|
-- naive algorithm.
|
||||||
|
calculateOverlap lastString newString =
|
||||||
|
let -- Make both strings of equal length.
|
||||||
|
len = length lastString `min` length newString
|
||||||
|
lastString' = reverse $ take len $ reverse lastString
|
||||||
|
newString' = take len newString
|
||||||
|
-- Using 'head' should be safe but we use another
|
||||||
|
-- version to avoid unuseful messages while debugging.
|
||||||
|
safeHead (x:_) = x
|
||||||
|
safeHead [] = error "squashStrings/overlap: never here"
|
||||||
|
in safeHead $ do
|
||||||
|
(lastStringSuffix, newStringPrefix) <-
|
||||||
|
tails lastString' `zip` reverse (inits newString')
|
||||||
|
guard (lastStringSuffix == newStringPrefix)
|
||||||
|
return (length lastStringSuffix)
|
||||||
|
|
||||||
|
-- Position the new strings on the resulting string.
|
||||||
|
go lastPos lastString (newString:nss) =
|
||||||
|
let len = length newString
|
||||||
|
overlap = calculateOverlap lastString newString
|
||||||
|
thisPos = lastPos - overlap
|
||||||
|
newLastPos = lastPos + len - overlap
|
||||||
|
(recString, recMap) = go newLastPos newString nss
|
||||||
|
in ( drop overlap newString ++ recString
|
||||||
|
, (newString, (thisPos, len)) : recMap
|
||||||
|
)
|
||||||
|
go _ _ [] = ([], [])
|
||||||
|
|
||||||
|
|
||||||
base64md5File :: Prelude.FilePath -> IO String
|
base64md5File :: Prelude.FilePath -> IO String
|
||||||
base64md5File = fmap (base64 . encode) . hashFile
|
base64md5File = fmap (base64 . encode) . hashFile
|
||||||
where encode d = Data.Serialize.encode (d :: MD5)
|
where encode d = Data.Serialize.encode (d :: MD5)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user