]> gitweb @ CieloNegro.org - Lucu.git/blobdiff - Network/HTTP/Lucu/MIMEType/Guess.hs
DefaultExtensionMap is now generated with TH.
[Lucu.git] / Network / HTTP / Lucu / MIMEType / Guess.hs
index 10c11e41c128cc7446082e0f3e5ed810f4a92cf6..8cddcba19bd60934b934f07dd520c97ef9c7fad9 100644 (file)
@@ -1,68 +1,93 @@
 {-# LANGUAGE
-    UnicodeSyntax
+    DeriveDataTypeable
+  , GeneralizedNewtypeDeriving
+  , TemplateHaskell
+  , UnicodeSyntax
+  , ViewPatterns
   #-}
 -- |Guessing MIME Types by file extensions. It's not always accurate
 -- but simple and fast.
 --
 -- In general you don't have to use this module directly.
 module Network.HTTP.Lucu.MIMEType.Guess
-    ( ExtMap
+    ( ExtMap(..)
+    , extMap
+    , parseExtMap
     , guessTypeByFileName
-
-    , parseExtMapFile
-    , serializeExtMap
     )
     where
 import Control.Applicative
 import Control.Monad
+import Control.Monad.Unicode
+import Data.Ascii (Ascii)
 import qualified Data.Ascii as A
 import Data.Attoparsec.Char8 as P
-import qualified Data.Attoparsec.Lazy as LP
-import qualified Data.ByteString.Lazy.Char8 as B
 import qualified Data.Map as M
 import Data.Map (Map)
 import Data.Maybe
+import Data.Typeable
+import Data.Monoid
 import Data.Monoid.Unicode
 import Data.Text (Text)
 import qualified Data.Text as T
 import Data.Text.Encoding
-import Language.Haskell.Exts.Build
-import Language.Haskell.Exts.Extension
-import Language.Haskell.Exts.Pretty
-import Language.Haskell.Exts.Syntax
+import Language.Haskell.TH.Syntax
+import Language.Haskell.TH.Quote
 import Network.HTTP.Lucu.MIMEType
 import Network.HTTP.Lucu.Parser
+import Network.HTTP.Lucu.Utils
 import Prelude.Unicode
 import System.FilePath
 
 -- |A 'Map' from file extensions to 'MIMEType's.
-type ExtMap = Map Text MIMEType
+newtype ExtMap
+    = ExtMap (Map Text MIMEType)
+    deriving (Eq, Show, Read, Monoid, Typeable)
 
--- |Guess the MIME Type of a file.
-guessTypeByFileName ∷ ExtMap → FilePath → Maybe MIMEType
-guessTypeByFileName em fpath
-    = case takeExtension fpath of
-        []      → Nothing
-        (_:ext) → M.lookup (T.pack ext) em
+instance Lift ExtMap where
+    lift (ExtMap m)
+        = [| ExtMap $(liftMap liftText lift m) |]
 
--- |Read an Apache mime.types and parse it.
-parseExtMapFile ∷ FilePath → IO ExtMap
-parseExtMapFile fpath
-    = do file ← B.readFile fpath
-         case LP.parse (finishOff extMap) file of
-           LP.Done _ xs
-               → case compile xs of
-                    Right m → return m
-                    Left  e → fail (concat [ "Duplicate extension \""
-                                           , show e
-                                           , "\" in: "
-                                           , fpath
-                                           ])
-           LP.Fail _ _ e
-               → fail ("Failed to parse: " ⧺ fpath ⧺ ": " ⧺ e)
+-- |'QuasiQuoter' for 'ExtMap' reading Apache @mime.types@.
+--
+-- @
+--   m :: 'ExtMap'
+--   m = ['extMap'|
+--   # MIME Type            Extensions
+--   application/xhtml+xml  xhtml
+--   image/jpeg             jpeg jpg
+--   image/png              png
+--   image/svg+xml          svg
+--   text/html              html
+--   text/plain             txt
+--   |]
+-- @
+extMap ∷ QuasiQuoter
+extMap = QuasiQuoter {
+             quoteExp  = (lift ∘ parseExtMap =≪) ∘ toAscii
+           , quotePat  = const unsupported
+           , quoteType = const unsupported
+           , quoteDec  = const unsupported
+         }
+    where
+      toAscii ∷ Monad m ⇒ String → m Ascii
+      toAscii (A.fromChars → Just a) = return a
+      toAscii _ = fail "Malformed extension map"
+
+      unsupported ∷ Monad m ⇒ m α
+      unsupported = fail "Unsupported usage of extMap quasi-quoter."
 
-extMap ∷ Parser [ (MIMEType, [Text]) ]
-extMap = catMaybes <$> P.many (try comment <|> try validLine <|> emptyLine)
+-- |Parse Apache @mime.types@.
+parseExtMap ∷ Ascii → ExtMap
+parseExtMap src
+    = case parseOnly (finishOff extMapP) $ A.toByteString src of
+        Right xs → case compile xs of
+                      Right m → ExtMap m
+                      Left  e → error ("Duplicate extension: " ⧺ show e)
+        Left err → error ("Unparsable extension map: " ⧺ err)
+
+extMapP ∷ Parser [(MIMEType, [Text])]
+extMapP = catMaybes <$> P.many (try comment <|> try validLine <|> emptyLine)
     where
       isSpc ∷ Char → Bool
       isSpc c = c ≡ '\x20' ∨ c ≡ '\x09'
@@ -104,52 +129,9 @@ compile = go (∅) ∘ concat ∘ map tr
       f ∷ k → v → v → v
       f _ _ = id
 
--- |@'serializeExtMap' extMap moduleName variableName@ generates a
--- Haskell source code which contains the following things:
---
--- * A definition of module named @moduleName@.
---
--- * @variableName :: 'ExtMap'@ whose content is the serialised
--- @extMap@.
---
--- The module "Network.HTTP.Lucu.MIMEType.DefaultExtensionMap" is
--- surely generated using this function.
-serializeExtMap ∷ ExtMap → String → String → String
-serializeExtMap em moduleName variableName
-    = let hsModule  = Module (⊥) (ModuleName moduleName) modPragma
-                      Nothing (Just exports) imports decls
-          modPragma = [ LanguagePragma (⊥) [ name (show OverloadedStrings) ] ]
-          exports   = [ EVar (UnQual (name variableName)) ]
-          imports   = [ ImportDecl (⊥) (ModuleName "Network.HTTP.Lucu.MIMEType")
-                                   False False Nothing Nothing Nothing
-                      , ImportDecl (⊥) (ModuleName "Network.HTTP.Lucu.MIMEType.Guess")
-                                   False False Nothing Nothing Nothing
-                      , ImportDecl (⊥) (ModuleName "Data.Ascii")
-                                   False False Nothing Nothing (Just (False, []))
-                      , ImportDecl (⊥) (ModuleName "Data.Map")
-                                   True False Nothing (Just (ModuleName "M")) Nothing
-                      ]
-          decls     = [ TypeSig (⊥) [name variableName]
-                                    (TyCon (UnQual (name "ExtMap")))
-                      , nameBind (⊥) (name variableName) extMapExp
-                      , InlineSig (⊥) False AlwaysActive (UnQual (name variableName))
-                      ]
-          comment   = concat [ "{- !!! WARNING !!!\n"
-                             , "   This file is automatically generated.\n"
-                             , "   DO NOT EDIT BY HAND OR YOU WILL REGRET -}\n\n"
-                             ]
-          extMapExp = qvar (ModuleName "M") (name "fromList") `app` listE records
-      in
-        comment ⧺ prettyPrint hsModule ⧺ "\n"
-    where
-      records ∷ [Exp]
-      records = map record $ M.assocs em
-
-      record ∷ (Text, MIMEType) → Exp
-      record (ext, mime)
-          = tuple [ strE (T.unpack ext)
-                  , function "parseMIMEType" `app` strE (mimeToString mime)
-                  ]
-
-      mimeToString ∷ MIMEType → String
-      mimeToString = A.toString ∘ A.fromAsciiBuilder ∘ printMIMEType
+-- |Guess the MIME Type of a file.
+guessTypeByFileName ∷ ExtMap → FilePath → Maybe MIMEType
+guessTypeByFileName (ExtMap m) fpath
+    = case takeExtension fpath of
+        []      → Nothing
+        (_:ext) → M.lookup (T.pack ext) m