module Database.HaskellDB.DBSpec.DBSpecToDBDirect
(specToHDB, dbInfoToModuleFiles)
where
import Database.HaskellDB.BoundedString
import Database.HaskellDB.FieldType
import Database.HaskellDB
import Database.HaskellDB.PrimQuery
import Database.HaskellDB.DBSpec.DBInfo
import Database.HaskellDB.DBSpec.PPHelpers
import Control.Monad (unless)
import Data.List (isPrefixOf)
import System.Directory (createDirectory, doesDirectoryExist)
import Text.PrettyPrint.HughesPJ
header :: Doc
header = ppComment ["Generated by DB/Direct"]
contextStackPragma :: TInfo -> Doc
contextStackPragma ti
= text "-- NOTE: use GHC flag" <+> text flag <+> text "with this module"
where flag = "-fcontext-stack" ++ (show (40 + length (cols ti)))
imports :: Doc
imports = text "import Database.HaskellDB.DBLayout"
dbInfoToModuleFiles :: FilePath
-> String
-> DBInfo -> IO ()
dbInfoToModuleFiles d name =
createModules d name . specToHDB name . finalizeSpec
createModules :: FilePath
-> String
-> [(String,Doc)]
-> IO ()
createModules basedir dbname files
= do
let dir = withPrefix basedir (replace '.' '/' dbname)
createPath dir
mapM_ (\ (name,doc) -> writeFile (moduleNameToFile basedir name)
(render doc)) files
moduleNameToFile :: FilePath -> String -> FilePath
moduleNameToFile base mod = withPrefix base f
where f = replace '.' '/' mod ++ ".hs"
withPrefix :: FilePath -> String -> FilePath
withPrefix base f | null base = f
| otherwise = base ++ "/" ++ f
replace :: Eq a => a -> a -> [a] -> [a]
replace x y zs = [if z == x then y else z | z <- zs]
createPath :: FilePath -> IO ()
createPath p | "/" `isPrefixOf` p = createPath' "/" (dropWhile (=='/') p)
| otherwise = createPath' "" p
where
createPath' _ "" = return ()
createPath' b p = do
let (d,r) = break (=='/') p
n = withPrefix b d
createDirIfNotExists n
createPath' n (dropWhile (=='/') r)
createDirIfNotExists :: FilePath -> IO ()
createDirIfNotExists p = do
exists <- doesDirectoryExist p
unless exists (createDirectory p)
specToHDB :: String
-> DBInfo -> [(String,Doc)]
specToHDB name dbinfo = genDocs name (constructNonClashingDBInfo dbinfo)
genDocs :: String
-> DBInfo
-> [(String,Doc)]
genDocs name dbinfo
= (name,
header
$$ text "module" <+> text name <+> text "where"
<> newline
$$ imports
<> newline
$$ vcat (map (text . ("import qualified " ++)) tbnames)
<> newline
$$ dbInfoToDoc dbinfo)
: rest
where
rest = [tInfoToModule name t | t <- tbls dbinfo, hasName t]
hasName TInfo{tname=name} = name /= ""
tbnames = map fst rest
tInfoToModule :: String
-> TInfo
-> (String,Doc)
tInfoToModule dbname tinfo@TInfo{tname=name,cols=col}
= (modname,
contextStackPragma tinfo $$
header
$$ text "module" <+> text modname <+> text "where"
<> newline
$$ imports
<> newline
$$ ppComment ["Table"]
$$ ppTable tinfo
$$ ppComment ["Fields"]
$$ if col == [] then empty
else vcat (map ppField (columnNamesTypes tinfo)))
where modname = dbname ++ "." ++ moduleName name
ppTable :: TInfo -> Doc
ppTable (TInfo tiName tiColumns) =
hang (text (identifier tiName) <+> text "::" <+> text "Table") 4
(parens (ppColumns tiColumns)
<> newline)
$$
text (identifier tiName) <+> text "=" <+>
hang (text "baseTable" <+>
doubleQuotes (text (checkChars tiName)) <+>
text "$") 0
(vcat $ punctuate (text " #") (map ppColumnValue tiColumns))
<> newline
ppColumns :: [CInfo] -> Doc
ppColumns [] = text ""
ppColumns [c] = parens (ppColumnType c <+> text "RecNil")
ppColumns (c:cs) = parens (ppColumnType c $$ ppColumns cs)
ppColumnType :: CInfo -> Doc
ppColumnType (CInfo ciName (ciType,ciAllowNull))
= text "RecCons" <+>
((text $ toType ciName) <+> parens (text "Expr"
<+> (if (ciAllowNull)
then parens (text "Maybe" <+> text (toHaskellType ciType))
else text (toHaskellType ciType)
)))
ppColumnValue :: CInfo -> Doc
ppColumnValue (CInfo ciName _)
= text "hdbMakeEntry" <+> text (toType ciName)
ppField :: (String,String) -> Doc
ppField (name,typeof) =
ppComment [toType name ++ " Field"]
<> newline $$
text "data" <+> bname <+> equals <+> bname
<> newline $$
hang (text "instance FieldTag" <+> bname <+> text "where") 4
(text "fieldName _" <+> equals <+> doubleQuotes
(text (checkChars name)))
<> newline $$
iname <+> text "::" <+> text "Attr" <+> bname <+> text typeof
$$
iname <+> equals <+> text "mkAttr" <+> bname
<> newline
where
bname = text (toType name)
iname = text (identifier name)
columnNames :: TInfo -> [String]
columnNames table = map cname (cols table)
columnTypes :: TInfo -> [String]
columnTypes table =
[if b then ("(Maybe " ++ t ++ ")") else t | (t,b) <- zippedlist]
where
zippedlist = zip typelist null_list
typelist = map (toHaskellType . fst . descr) (cols table)
null_list = map (snd . descr) (cols table)
columnNamesTypes :: TInfo -> [(String,String)]
columnNamesTypes table@(TInfo tname fields)
= zip (columnNames table) (columnTypes table)