-generateHaskellSource ∷ [CmdOpt] → FilePath → IO ()
-generateHaskellSource opts srcFile
- = do modName ← getModuleName opts
- symName ← getSymbolName opts modName
- mimeType ← getMIMEType opts srcFile
- lastMod ← getLastModified srcFile
- input ← openInput srcFile
- output ← openOutput opts
- eTag ← getETag opts input
-
- let compParams = defaultCompressParams { compressLevel = bestCompression }
- gzippedData = compressWith compParams input
- originalLen = Lazy.length input
- gzippedLen = Lazy.length gzippedData
- useGZip = originalLen > gzippedLen
- rawB64 = B64.encode <$> Lazy.toChunks input
- gzippedB64 = B64.encode <$> Lazy.toChunks gzippedData
-
- header ← mkHeader srcFile originalLen gzippedLen useGZip mimeType eTag lastMod
-
- let hsModule = mkModule (ModuleName modName) (name symName) imports decls
- imports = mkImports useGZip
- decls = concat [ declResourceDef
- , entityTagDecl eTag
- , lastModifiedDecl lastMod
- , contentTypeDecl mimeType
- , if useGZip then
- dataDecl (name "gzippedData") gzippedB64
- else
- dataDecl (name "rawData") rawB64
- ]
- declResourceDef
- = [ HsTypeSig (⊥) [HsIdent symName]
- (HsQualType []
- (HsTyCon (UnQual (HsIdent "ResourceDef"))))
- , HsFunBind [HsMatch (⊥) (HsIdent symName)
- [] (HsUnGuardedRhs defResourceDef) []]
- ]
-
- defResourceDef ∷ HsExp
- defResourceDef
- = let defResGet = if useGZip
- then defResGetGZipped
- else resGetRaw
- in
- (HsRecConstr (UnQual (HsIdent "ResourceDef"))
- [ HsFieldUpdate (UnQual (HsIdent "resUsesNativeThread"))
- (HsCon (UnQual (HsIdent "False")))
- , HsFieldUpdate (UnQual (HsIdent "resIsGreedy"))
- (HsCon (UnQual (HsIdent "False")))
- , HsFieldUpdate (UnQual (HsIdent "resGet")) defResGet
- , HsFieldUpdate (UnQual (HsIdent "resHead"))
- (HsCon (UnQual (HsIdent "Nothing")))
- , HsFieldUpdate (UnQual (HsIdent "resPost"))
- (HsCon (UnQual (HsIdent "Nothing")))
- , HsFieldUpdate (UnQual (HsIdent "resPut"))
- (HsCon (UnQual (HsIdent "Nothing")))
- , HsFieldUpdate (UnQual (HsIdent "resDelete"))
- (HsCon (UnQual (HsIdent "Nothing")))
- ]
- )
-
- defResGetGZipped ∷ HsExp
- defResGetGZipped
- = let doExp = HsDo [ foundEntityStmt
- , setContentTypeStmt
- , bindMustGunzip
- , doConditionalOutput
- ]
- bindMustGunzip
- = HsGenerator (⊥)
- (HsPVar (HsIdent "mustGunzip"))
- (HsApp (HsApp (HsVar (UnQual (HsIdent "liftM")))
- (HsVar (UnQual (HsIdent "not"))))
- (HsParen
- (HsApp (HsVar (UnQual (HsIdent "isEncodingAcceptable")))
- (HsLit (HsString "gzip")))))
- doConditionalOutput
- = HsQualifier
- (HsIf (HsVar (UnQual (HsIdent "mustGunzip")))
- expOutputGunzipped
- expOutputGZipped)
- expOutputGunzipped
- = (HsApp (HsVar (UnQual (HsIdent "outputLBS")))
- (HsParen
- (HsApp (HsVar (UnQual (HsIdent "decompress")))
- (HsVar (UnQual (HsIdent "gzippedData"))))))
- expOutputGZipped
- = HsDo [ setContentEncodingGZipStmt
- , outputStmt (var $ name "gzippedData")
- ]
- in
- HsApp (HsCon (UnQual (HsIdent "Just")))
- (HsParen doExp)
-
- hPutStrLn output header
- hPutStrLn output (prettyPrint hsModule)
- hClose output
-
-mkModule ∷ ModuleName → Name → [ImportDecl] → [Decl]
-mkModule modName symName imports decls
- = let modPragma = [ LanguagePragma (⊥) [ name (show OverloadedStrings)
- ]
- ]
- exports = [ EVar (UnQual symName)
- ]
- in
- Module (⊥) modName modPragma Nothing (Just exports) imports decls
-
-mkImports ∷ Bool → [ImportDecl]
-mkImports useGZip
- = [ ImportDecl (⊥) (ModuleName "Data.ByteString.Base64")
- True False (Just (ModuleName "B64")) Nothing
- , ImportDecl (⊥) (ModuleName "Data.ByteString.Lazy")
- True False (Just (ModuleName "Lazy")) Nothing
- , ImportDecl (⊥) (ModuleName "Data.Time")
- False False Nothing Nothing
- , ImportDecl (⊥) (ModuleName "Network.HTTP.Lucu")
- False False Nothing Nothing
- ]
- ⧺
- if useGZip then
- [ ImportDecl (⊥) (ModuleName "Codec.Compression.GZip")
- False False Nothing Nothing
- ]
- else
- []
-
-resHead ∷ Exp
-resHead
- = infixApp (var $ name "Just")
- (op $ name "$" )
- (doE [ foundEntityStmt
- , setContentTypeStmt
- ])
-
-resGetRaw ∷ Exp
-resGetRaw
- = infixApp (var $ name "Just")
- (op $ name "$" )
- (doE [ foundEntityStmt
- , setContentTypeStmt
- , outputStmt (var $ name "rawData")
- ])
-
-setContentEncodingGZipStmt ∷ Stmt
-setContentEncodingGZipStmt
- = qualStmt $
- metaFunction "setContentEncoding" $
- [ listE [ strE "gzip" ] ]
-
-foundEntityStmt ∷ Stmt
-foundEntityStmt
- = qualStmt $
- metaFunction "foundEntity" $
- [ var $ name "entityTag"
- , var $ name "lastModified"
- ]
-
-setContentTypeStmt ∷ Stmt
-setContentTypeStmt
- = qualStmt $
- metaFunction "setContentType" $
- [var $ name "contentType"]
-
-outputStmt ∷ Exp → Stmt
-outputStmt e
- = qualStmt $
- metaFunction "output" [e]
-
-entityTagDecl ∷ ETag → [Decl]
-entityTagDecl eTag
- = [ TypeSig (⊥) [varName] (TyCon (UnQual (name "ETag")))
- , nameBind (⊥) varName valExp
- ]
- where
- varName ∷ Name
- varName = name "entityTag"
-
- valExp ∷ Exp
- valExp = metaFunction "parseETag" [strE $ eTagToString eTag]
-
- eTagToString ∷ ETag → String
- eTagToString = A.toString ∘ A.fromAsciiBuilder ∘ printETag
-
-lastModifiedDecl ∷ UTCTime → [Decl]
-lastModifiedDecl lastMod
- = [ TypeSig (⊥) [varName] (TyCon (UnQual (name "UTCTime")))
- , nameBind (⊥) varName valExp
- ]
- where
- varName ∷ Name
- varName = name "lastModified"
-
- valExp ∷ Exp
- valExp = metaFunction "read" [strE $ show lastMod]
-
-contentTypeDecl ∷ MIMEType → [Decl]
-contentTypeDecl mime
- = [ TypeSig (⊥) [varName] (TyCon (UnQual (name "MIMEType")))
- , nameBind (⊥) varName valExp
- ]
- where
- varName ∷ Name
- varName = name "contentType"
-
- valExp ∷ Exp
- valExp = metaFunction "parseMIMEType" [mimeToString mime]
-
- mimeToString ∷ MIMEType → String
- mimeToString = A.toString ∘ A.fromAsciiBuilder ∘ printMIMEType
-
-dataDecl ∷ Name → [Strict.ByteString] → [Decl]
-dataDecl varName chunks
- = [ TypeSig (⊥) [varName] (TyCon (Qual (ModuleName "Lazy") (name "ByteString")))
- , nameBind (⊥) varName valExp
- ]
- where
- valExp ∷ Exp
- valExp = qvar (ModuleName "Lazy") (name "fromChunks")
- `app`
- listE (chunkToExp <$> chunks)
-
- chunkToExp ∷ Strict.ByteString → Exp
- chunkToExp chunk
- = qvar (ModuleName "B64") (name "decodeLenient")
- `app`
- strE (Strict.unpack chunk)
-
-mkHeader ∷ FilePath → Int64 → Int64 → Bool → MIMEType → String → UTCTime → IO String
-mkHeader srcFile originalLen gzippedLen useGZip mimeType eTag lastMod
- = do localLastMod ← utcToLocalZonedTime lastMod
- return ("{- DO NOT EDIT THIS FILE.\n" ++
- " This file is automatically generated by the lucu-implant-file program.\n" ++
- "\n" ++
- " Source: " ++ (if srcFile ≡ "-"
- then "(stdin)"
- else srcFile) ++ "\n" ++
- " Original Length: " ++ show originalLen ++ " bytes\n" ++
- (if useGZip
- then " Compressed Length: " ++ show gzippedLen ++ " bytes\n" ++
- " Compression: gzip\n"
- else " Compression: disabled\n") ++
- " MIME Type: " ++ show mimeType ++ "\n" ++
- " ETag: " ++ eTag ++ "\n" ++
- " Last Modified: " ++ show localLastMod ++ "\n" ++
- " -}")
-
-
-getModuleName ∷ [CmdOpt] → IO String
-getModuleName opts
- = let modNameOpts = filter (\ x → case x of
- OptModName _ → True
- _ → False) opts
- in
- case modNameOpts of
- [] → error "a module name must be given."
- (OptModName modName):[] → return modName
- _ → error "too many --module options."
-
-
-getSymbolName ∷ [CmdOpt] → String → IO String
-getSymbolName opts modName
- = let symNameOpts = filter (\ x → case x of
- OptSymName _ → True
- _ → False) opts
- -- モジュール名をピリオドで分割した時の最後の項目の先頭文字を
- -- 小文字にしたものを使ふ。
- defaultSymName = mkDefault modName
- mkDefault = headToLower ∘ getLastComp
- headToLower str = case str of
- [] → error "module name must not be empty"
- (x:xs) → toLower x : xs
- getLastComp = reverse ∘ fst ∘ break (≡ '.') ∘ reverse
- in
- case symNameOpts of
- [] → return defaultSymName
- (OptSymName symName):[] → return symName
- _ → error "too many --symbol options."
-
-
-getMIMEType ∷ [CmdOpt] → FilePath → IO MIMEType
-getMIMEType opts srcFile