用Template Haskell嵌入文件

April 30, 2013

今天,我想把之前贝叶斯演示里的CSS和JS文件编译进程序,部署的时候就只用一个二进制程序就可以了。

最后希望这样可以使用:

result_css :: Text
result_css = $(loadFile "static/css/result.css")

我的第一次尝试:

import qualified Data.Text.IO as TIO

loadFile :: FilePath -> Q Exp
loadFile filepath = do
    file <- runIO $ TIO.readFile filepath
    [| file |]

因为只有Lift类的值才能直接插进 [| ... |], 但是Text不是Lift类,我试着自己实现,无果。

然后我去看shakespeare的代码,更复杂了。最后我发现了file-embed,它不是直接插入ByteString, 而是返回了一个AppE表达式(类型是Exp), 在这个表达式里转换。

我简化一点,足够自己用:

loadFile :: FilePath -> Q Exp
loadFile filepath = do
    str <- runIO $ readFile filepath
    convert <- [| T.pack |]
    return $ AppE convert $ LitE $ StringL str

在Scotty里,我用一个HashMap来放文件:

cssFiles :: H.HashMap Text Text
cssFiles = H.fromList
    [ ("login.css",  $(loadFile "static/css/login.css"))
    , ("result.css", $(loadFile "static/css/result.css"))
    ]

然后在Handler里lookup:

get "/static/css/:file" $ do
        file <- param "file"
        case H.lookup file cssFiles of
          Nothing -> status status404
          Just f  -> text f >> header "Content-type" "text/css"

这比原来用wai-middleware-static麻烦多了。