module Network.HTTP.Lucu.MIMEType.Guess
( ExtMap(..)
, extMap
, guessTypeByFileName
)
where
import Control.Applicative
import Data.Attoparsec.Char8
import qualified Data.Attoparsec.Lazy as LP
import qualified Data.ByteString.Lazy.Char8 as Lazy
import Data.Convertible.Base
import Data.Convertible.Instances.Text ()
import Data.Default
import qualified Data.Map as M
import Data.Map (Map)
import Data.Typeable
import Data.List
import Data.Monoid
import Data.Monoid.Unicode
import Data.Text (Text)
import Data.Text.Encoding
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Quote
import Network.HTTP.Lucu.MIMEType
import Network.HTTP.Lucu.OrphanInstances ()
import Network.HTTP.Lucu.Parser
import Prelude.Unicode
import System.FilePath
newtype ExtMap
= ExtMap (Map Text MIMEType)
deriving (Eq, Show, Read, Monoid, Typeable)
instance Lift ExtMap where
lift (ExtMap m) = [| ExtMap $(lift m) |]
extMap ∷ QuasiQuoter
extMap = QuasiQuoter {
quoteExp = lift ∘ parseExtMap ∘ Lazy.pack
, quotePat = const unsupported
, quoteType = const unsupported
, quoteDec = const unsupported
}
where
parseExtMap ∷ Lazy.ByteString → ExtMap
parseExtMap = convertUnsafe
unsupported ∷ Monad m ⇒ m α
unsupported = fail "Unsupported usage of extMap quasi-quoter."
instance ConvertAttempt Lazy.ByteString ExtMap where
convertAttempt src
= case LP.parse pairs src of
LP.Fail _ eCtx e
→ fail $ "Unparsable extension map: "
⊕ intercalate ", " eCtx
⊕ ": "
⊕ e
LP.Done _ xs
→ case compile xs of
Right m → return $ ExtMap m
Left e → fail $ "Duplicate extension: " ⊕ show e
where
pairs ∷ Parser [(MIMEType, [Text])]
pairs = do skipMany linebreak
xs ← sepBy pair (skipMany1 linebreak)
skipMany linebreak
endOfInput
return xs
<?>
"pairs"
pair ∷ Parser (MIMEType, [Text])
pair = do skipSpace
mime ← def
skipSpace1
exts ← sepBy1 ext $ skipWhile1 (≡ '\x20')
return (mime, exts)
<?>
"pair"
ext ∷ Parser Text
ext = (decodeUtf8 <$> takeWhile1 isAlphaNum)
<?>
"ext"
linebreak ∷ Parser ()
linebreak
= ( endOfLine
<|>
try (skipSpace *> char '#' *> skipManyTill anyChar endOfLine)
)
<?>
"linebreak"
compile ∷ Ord k ⇒ [(v, [k])] → Either (k, v, v) (Map k v)
compile = go (∅) ∘ concat ∘ (tr <$>)
where
tr ∷ (v, [k]) → [(k, v)]
tr (v, ks) = [(k, v) | k ← ks]
go ∷ Ord k ⇒ Map k v → [(k, v)] → Either (k, v, v) (Map k v)
go m [] = Right m
go m ((k, v):xs)
= case M.insertLookupWithKey' f k v m of
(Nothing, m') → go m' xs
(Just v0, _ ) → Left (k, v0, v)
f ∷ k → v → v → v
f _ _ = id
guessTypeByFileName ∷ ExtMap → FilePath → Maybe MIMEType
guessTypeByFileName (ExtMap m) fpath
= case takeExtension fpath of
[] → Nothing
(_:ext) → M.lookup (cs ext) m