]> gitweb @ CieloNegro.org - Lucu.git/blobdiff - Network/HTTP/Lucu/MIMEType/Guess.hs
Reimplement MultipartForm
[Lucu.git] / Network / HTTP / Lucu / MIMEType / Guess.hs
index d94711ac624cdfa9878243b55f46b1dbca61b223..d8bca8e785658efa5390862f8baa97c071a93f58 100644 (file)
@@ -1,5 +1,8 @@
--- |MIME Type guessing by a file extension. This is a poor man's way
--- of guessing MIME Types. It is simple and fast.
+{-# LANGUAGE
+    UnicodeSyntax
+  #-}
+-- |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
@@ -10,111 +13,144 @@ module Network.HTTP.Lucu.MIMEType.Guess
     , serializeExtMap
     )
     where
-
+import Control.Applicative
+import Control.Monad
+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           Language.Haskell.Pretty
-import           Language.Haskell.Syntax
-import           Network.HTTP.Lucu.MIMEType
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Parser.Http
-import           Network.HTTP.Lucu.Utils
-import           System.IO
+import Data.Map (Map)
+import Data.Maybe
+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 Network.HTTP.Lucu.MIMEType
+import Prelude.Unicode
+import System.FilePath
 
--- |'Data.Map.Map' from extension to MIME Type.
-type ExtMap = Map String MIMEType
+-- |A 'Map' from file extensions to 'MIMEType's.
+type ExtMap = Map Text MIMEType
 
--- |Guess the MIME Type of file.
-guessTypeByFileName :: ExtMap -> FilePath -> Maybe MIMEType
+-- |Guess the MIME Type of file.
+guessTypeByFileName ∷ ExtMap → FilePath → Maybe MIMEType
 guessTypeByFileName extMap fpath
-    = extMap `seq` fpath `seq`
-      let ext = last $ splitBy (== '.') fpath
-      in
-        M.lookup ext extMap >>= return
+    = case takeExtension fpath of
+        []      → Nothing
+        (_:ext) → M.lookup (T.pack ext) extMap
 
 -- |Read an Apache mime.types and parse it.
-parseExtMapFile :: FilePath -> IO ExtMap
+parseExtMapFile ∷ FilePath → IO ExtMap
 parseExtMapFile fpath
-    = fpath `seq`
-      do file <- B.readFile fpath
-         case parse (allowEOF extMapP) file of
-           (# Success xs, _ #)
-               -> return $ compile xs
-
-           (# _, input' #)
-               -> let near = B.unpack $ B.take 100 input'
-                  in 
-                    fail ("Failed to parse: " ++ fpath ++ " (near: " ++ near ++ ")")
-
+    = do file ← B.readFile fpath
+         case LP.parse extMapP 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)
 
-extMapP :: Parser [ (MIMEType, [String]) ]
-extMapP = do xs <- many (comment <|> validLine <|> emptyLine)
-             eof
+extMapP ∷ Parser [ (MIMEType, [Text]) ]
+extMapP = do xs ← P.many (try comment <|> try validLine <|> emptyLine)
+             endOfInput
              return $ catMaybes xs
     where
-      spc = oneOf " \t"
+      isSpc ∷ Char → Bool
+      isSpc c = c ≡ '\x20' ∨ c ≡ '\x09'
 
-      comment = do many spc
-                   char '#'
-                   many $ satisfy (/= '\n')
+      comment ∷ Parser (Maybe (MIMEType, [Text]))
+      comment = do skipWhile isSpc
+                   void $ char '#'
+                   skipWhile (≢ '\x0A')
                    return Nothing
 
-      validLine = do many spc
-                     mime <- mimeTypeP
-                     many spc
-                     exts <- sepBy token (many spc)
+      validLine ∷ Parser (Maybe (MIMEType, [Text]))
+      validLine = do skipWhile isSpc
+                     mime ← mimeType
+                     skipWhile isSpc
+                     exts ← sepBy extP (skipWhile isSpc)
                      return $ Just (mime, exts)
 
-      emptyLine = oneOf " \t\n" >> return Nothing
+      extP ∷ Parser Text
+      extP = decodeUtf8 <$> takeWhile1 (\c → (¬) (isSpc c ∨ c ≡ '\x0A'))
 
+      emptyLine ∷ Parser (Maybe (MIMEType, [Text]))
+      emptyLine = do skipWhile isSpc
+                     void $ char '\x0A'
+                     return Nothing
 
-compile :: [ (MIMEType, [String]) ] -> Map String MIMEType
-compile = M.fromList . foldr (++) [] . map tr
+compile ∷ Ord k ⇒ [(v, [k])] → Either (k, v, v) (Map k v)
+compile = go (∅) ∘ concat ∘ map tr
     where
-      tr :: (MIMEType, [String]) -> [ (String, MIMEType) ]
-      tr (mime, exts) = [ (ext, mime) | ext <- exts ]
+      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
 
 -- |@'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 a serialization of
---   @extMap@.
+-- * @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 ∷ ExtMap → String → String → String
 serializeExtMap extMap moduleName variableName
-    = let hsModule = HsModule undefined modName (Just exports) imports decls
-          modName  = Module moduleName
-          exports  = [HsEVar (UnQual (HsIdent variableName))]
-          imports  = [ HsImportDecl undefined (Module "Network.HTTP.Lucu.MIMEType") False Nothing (Just (False, []))
-                     , HsImportDecl undefined (Module "Network.HTTP.Lucu.MIMEType.Guess") False Nothing Nothing
-                     , HsImportDecl undefined (Module "Data.Map") True (Just (Module "M")) Nothing
-                     ]
-          decls    = [ HsTypeSig undefined [HsIdent variableName]
-                                     (HsQualType []
-                                      (HsTyCon (UnQual (HsIdent "ExtMap"))))
-                     , HsFunBind [HsMatch undefined (HsIdent variableName)
-                                  [] (HsUnGuardedRhs extMapExp) []]
-                     ]
-          extMapExp = HsApp (HsVar (Qual (Module "M") (HsIdent "fromList"))) (HsList records)
-          comment   =    "{- !!! WARNING !!!\n"
-                      ++ "   This file is automatically generated.\n"
-                      ++ "   DO NOT EDIT BY HAND OR YOU WILL REGRET -}\n\n"
+    = 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"
+        comment ⧺ prettyPrint hsModule ⧺ "\n"
     where
-      records :: [HsExp]
+      records ∷ [Exp]
       records = map record $ M.assocs extMap
 
-      record :: (String, MIMEType) -> HsExp
+      record ∷ (Text, MIMEType) → Exp
       record (ext, mime)
-          = HsTuple [HsLit (HsString ext), mimeToExp mime]
-                    
-      mimeToExp :: MIMEType -> HsExp
-      mimeToExp mt
-          = HsApp (HsVar (UnQual (HsIdent "read"))) (HsLit (HsString $ show mt))
+          = tuple [ strE (T.unpack ext)
+                  , function "parseMIMEType" `app` strE (mimeToString mime)
+                  ]
+
+      mimeToString ∷ MIMEType → String
+      mimeToString = A.toString ∘ A.fromAsciiBuilder ∘ printMIMEType