canteven-http-0.1.5.1: .src/Canteven/Internal.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Canteven.Internal (
staticSite
) where
import Control.Monad (join)
import Data.List ((\\))
import Data.Maybe (catMaybes)
import Language.Haskell.TH (TExp, Q, runIO)
import Language.Haskell.TH.Syntax (addDependentFile)
import Network.HTTP.Types (ok200)
import Network.Mime (defaultMimeLookup)
import Network.Wai (Middleware, responseLBS, pathInfo)
import System.Directory (getDirectoryContents)
import System.FilePath.Posix (combine, (</>))
import System.Posix.Files (isRegularFile, isDirectory, getFileStatus)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Lazy.Char8 as BSL8
import qualified Data.Text as T
{- |
The Template-Haskell splice @$$(staticSite dir)@ will build a
'Middleware' that serves a set of static files determined at
compile time, or else passes the request to the underlying
'Network.Wai.Application'.
All files under @dir@ will be served relative to the root path of
your web server, so the file @\<dir\>\/foo\/bar.html@ will be served at
@http://your-web-site.com/foo/bar.html@
@since 0.1.3
-}
staticSite :: FilePath -> Q (TExp Middleware)
staticSite baseDir = join . runIO $ do
files <- readStaticFiles
mapM_ (printResource . fst) files
return $ mapM_ (addDependentFile . ((baseDir ++ "/") ++) . fst) files >> [||
let
{- |
Build a middleware that serves a single static file path, or
delegates to the underlying application.
-}
static :: (FilePath, String) -> Middleware
static (filename, content) app req respond =
let
{- | Guess the content type of the static file. -}
contentType :: BS.ByteString
contentType =
defaultMimeLookup
. T.pack
$ filename
in
if pathInfo req == T.split (== '/') (T.pack filename)
then
respond (
responseLBS
ok200
[("content-type", contentType)]
(BSL8.pack content)
)
else app req respond
in
foldr (.) id (fmap static files) :: Middleware
||]
where
printResource :: String -> IO ()
printResource file =
putStrLn ("Generating static resource for: " ++ show file)
{- | Reads the static files that make up the admin user interface. -}
readStaticFiles :: IO [(FilePath, String)]
readStaticFiles =
let
findAll :: FilePath -> IO [FilePath]
findAll dir = do
contents <-
(\\ [".", ".."]) <$> getDirectoryContents (baseDir </> dir)
dirs <- catMaybes <$> mapM justDir contents
files <- catMaybes <$> mapM justFile contents
more <- concat <$> mapM (findAll . combine dir) dirs
return $ (combine dir <$> files) ++ more
where
justFile :: FilePath -> IO (Maybe FilePath)
justFile filename = do
isfile <-
isRegularFile <$>
getFileStatus (baseDir </> dir </> filename)
return $ if isfile then Just filename else Nothing
justDir :: FilePath -> IO (Maybe FilePath)
justDir filename = do
isdir <-
isDirectory <$>
getFileStatus (baseDir </> dir </> filename)
return $ if isdir then Just filename else Nothing
in do
allFiles <- findAll "."
allContent <- mapM (fmap BS8.unpack . BS.readFile . combine baseDir) allFiles
return (zip (drop 2 <$> allFiles) allContent)