packages feed

frame-0.1: src/Frame/Shell.lhs

> module Main where

> import IO
> import Database.HaskellDB hiding (describe, (!))
> import Database.HaskellDB.HDBC.ODBC
> import Database.HaskellDB.DBSpec.DBSpecToDBDirect
> import Database.HaskellDB.BoundedList
> import Database.HaskellDB.Query (tableName, attributeName)
> import Database.HaskellDB.DBLayout hiding (fieldName)
> import Database.HaskellDB.PrimQuery
> import Database.HaskellDB.Database hiding (describe)
> import Control.Monad.Trans

> import Database.HaskellDB.DBSpec.PPHelpers
>   (MakeIdentifiers, moduleName, toType, identifier, checkChars,
>    ppComment, newline, mkIdentCamelCase, )
> import qualified Database.HaskellDB.DBSpec.PPHelpers as PP
> import Data.Char (toLower, )
> import Text.PrettyPrint.HughesPJ
> import System.Directory
> import Data.List (isPrefixOf, (!!))
> import Control.Monad (unless)
> import System.Environment

> lcase :: String -> String
> lcase = map toLower

> imports :: Doc
> imports = text "import Frame"

> header :: Doc
> header = ppComment ["Generated by Frame"]

Main doc

> mainImports :: String -> Doc
> mainImports m = text "import" <+> text (m ++ ".Router") $$
>                 text "import" <+> text (m ++ ".Config") $$
>                 text "import" <+> text (m ++ ".Validation") <> newline $$
>                 text "import Frame" $$
>                 text "import Frame.Router"

> genMain :: String -- ^ Top-level module name
>	-> Doc -- ^ list of module name, module contents pairs
> genMain m
>     = header
>       $$ text "module Main where"
>       <> newline
>       $$ imports $$ mainImports m
>       <> newline
>       $$ text "test :: IO ()"
>       $$ text "test = testServer (withSession $ flash . router) config validations"
>       <> newline
>       $$ text "main :: IO ()"
>       $$ text "main = server (withSession $ flash . router) config validations"

Config docs

> configImports :: String -> Doc
> configImports m = text "import Frame.Config" <> newline $$
>                   text "import" <+> text (m ++ ".DB")

> genConfig :: String -- ^ Top-level module name
>   -> String -- ^ Database URL
>   -> String -- ^ Database name
>   -> String -- ^ Current path
>	-> Doc -- ^ list of module name, module contents pairs
> genConfig m du dn cp
>     = header
>       $$ text "module" <+> text (m ++ ".Config") <+> text "where"
>       <> newline
>       $$ imports $$ configImports m
>       <> newline
>       $$ text "config = Config {database=" <> text dn <>
>          text ", dbURL=" <> doubleQuotes (text du) <>
>          text ", dbName=" <> doubleQuotes (text dn) <>
>          text ", sessionPath=\"" <> text (cp ++ "/Sessions/") <> text "\", sessionId=\"\", css=[]}"
>       <> newline
>       $$ text "production = config"

Validation docs

> validationImports :: String -> [String] -> Doc
> validationImports m [] = text "import Data.Map (fromList)" <> newline
> validationImports m (r:rs) = validationImports m rs $$
>                              text "import" <+> text (m ++ ".DB." ++ r)

> genValidation :: String -- ^ Top-level module name
>   -> [String]
>	-> Doc -- ^ list of module name, module contents pairs
> genValidation m rs
>     = header
>       $$ text "module" <+> text (m ++ ".Validation") <+> text "where"
>       <> newline
>       $$ imports $$ validationImports m rs
>       <> newline
>       $$ text "validations :: Validators"
>       $$ text "validations = fromList []"


Router docs

> routerImports :: String -> [String] -> Doc
> routerImports m [] = text "import Frame.Router" $$
>                      text "import Frame.Model" $$
>                      text "import Frame.View" <> newline
> routerImports m (r:rs) = routerImports m rs $$
>                          text "import qualified" <+> text (m ++ ".Router." ++ r) <+> text "as" <+> text r

> routerRoutePattern :: [String] -> Doc
> routerRoutePattern [] = empty
> routerRoutePattern (p:ps) = doubleQuotes (text $ lcase p) <> text ":" <> routerRoutePattern ps

> routerRoutes :: [String] -> Doc
> routerRoutes [] = text "router _ = return Error404"
> routerRoutes (ps:rs) = text "router " <> 
>                        parens (routerRoutePattern [ps] <> text "ps") <> 
>                        text " = " <> text ps <> text "." <> text "index" $$ 
>                        routerRoutes rs

> genRouter :: String -- ^ Top-level module name
>   -> [String]
>	-> Doc -- ^ list of module name, module contents pairs
> genRouter m rs
>     = header
>       $$ text "module" <+> text (m ++ ".Router") <+> text "where"
>       <> newline
>       $$ imports $$ routerImports m rs
>       <> newline
>       $$ text "router :: FrameRouter m => [String] -> m Data"
>       $$ text "router " <> text "[]" <> 
>            text " = return $ ViewPart [paragraph $ text" <+> doubleQuotes (text "Generated index") <> text "]"
>       $$ routerRoutes rs

Sub-router docs

> subRouterImports :: String -> String -> Doc
> subRouterImports m n = text "import Frame.Router" $$
>                    text "import Frame.View" $$
>                    text "import Frame.Model" <> newline $$
>                    text "import " <> text m <> text ".Model." <> text n $$
>                    text "import " <> text m <> text ".View." <> text n

> genSubRouter :: String -- ^ Top-level module name
>   -> String -- ^ View name
>	-> Doc -- ^ list of module name, module contents pairs
> genSubRouter m n
>     = header
>       $$ text "module" <+> text (m ++ ".Router." ++ n) <+> text "where"
>       <> newline
>       $$ imports $$ subRouterImports m n
>       <> newline
>       $$ text "index :: FrameRouter m => m Data"
>       $$ text "index = return $ ViewPart [paragraph $ text" <+> doubleQuotes (text "Generated index for" <+> text n) <> text "]"

Model docs

> modelImports :: Doc
> modelImports = text "import Frame.Model"

> genModel :: String -- ^ Top-level module name
>   -> String
>	-> Doc -- ^ list of module name, module contents pairs
> genModel m n
>     = header
>       $$ text "module" <+> text (m ++ ".Model." ++ n) <+> text "where"
>       <> newline
>       $$ imports $$ modelImports

View docs

> viewImports :: Doc
> viewImports = text "import Frame.View"

> genView :: String -- ^ Top-level module name
>   -> String
>	-> Doc -- ^ list of module name, module contents pairs
> genView m n
>     = header
>       $$ text "module" <+> text (m ++ ".View." ++ n) <+> text "where"
>       <> newline
>       $$ imports $$ viewImports


> withODBC :: MonadIO m => String -> (Database -> m a) -> m a
> withODBC u = (connect driver) [("DSN", u)]

> -- | Automatically creates the database modules from a db description
> genDB :: String -- ^ The name of the application e.g. ''MyApp''
>          -> String -- ^ The DSN url for the database
>          -> String -- ^ The name of the database to be described
>          -> IO DBInfo
> genDB a u n = do
>     s <- withODBC u $ \db -> dbToDBSpec True mkIdentCamelCase n db
>     dbInfoToModuleFiles "" (a ++ ".DB") s
>     return s

> listTables :: [TInfo] -> IO ()
> listTables [] = return ()
> listTables (TInfo{tname=n}:ts) 
>     = do
>          putStr $ " * " ++ n ++ "\n"
>          listTables ts

> findTable :: [TInfo] -> String -> Bool
> findTable [] _ = False
> findTable (TInfo{tname=tn}:ts) n 
>     = if n == tn then True else False

> createModule :: String -> String -> String -> IO ()
> createModule "router" n t = do
>        createPath (n ++ "/Router/")
>        writeFile (n ++ "/Router/" ++ t ++ ".hs") $ show $ genSubRouter n t
>        return ()
> createModule "model" n t = do
>        createPath (n ++ "/Model/")
>        writeFile (n ++ "/Model/" ++ t ++ ".hs") $ show $ genModel n t
>        return ()
> createModule "view" n t = do
>        createPath (n ++ "/View/")
>        writeFile (n ++ "/View/" ++ t ++ ".hs") $ show $ genView n t
>        return ()
> createModule _ _ _ = return ()

> mainCreateFile :: String -> String -> DBInfo -> IO ()
> mainCreateFile mt n db = do
>          case db of 
>             DBInfo{tbls=[]} -> do
>                putStr "Perhaps you need to generate a DB first\n"
>                return ()
>             DBInfo{tbls=ts} -> do
>                putStr $ "Which table would you like to create a " ++ mt ++ " for?\n"
>                listTables ts
>                putStr "\n b) Back\n\n"
>                t <- getLine
>                putStr "\n"
>                case t == "b" || findTable ts t of
>                   True  -> createModule mt n t
>                   False -> do
>                       putStr "Could not find that table\n\n"
>                       mainCreateFile mt n db

> tName :: TInfo -> String
> tName TInfo{tname=n} = n

> appM :: (String -> IO ()) -> [String] -> IO ()
> appM _ []      = return ()
> appM mf (a:as) = do
>     mf a
>     appM mf as
>     return ()

> gen :: String -> String -> String -> IO ()
> gen n du dn = do 
>      db <- genDB n du dn         
>      case db of 
>         DBInfo{tbls=[]} -> do
>            putStr "No tables were found\n"
>            return ()
>         DBInfo{tbls=ts} -> do
>            cd <- getCurrentDirectory
>            createPath (n ++ "/Sessions/")
>            writeFile (n ++ "/Config.hs") $ show $ genConfig n du dn $ cd ++ "/" ++ n
>            writeFile (n ++ "/Validation.hs") $ show $ genValidation n $ map tName ts
>            appM (createModule "model" n) $ map tName ts
>            appM (createModule "view" n) $ map tName ts
>            appM (createModule "router" n) $ map tName ts
>            writeFile (n ++ "/Router.hs") $ show $ genRouter n $ map tName ts
>            writeFile (n ++ "/Main.hs") $ show $ genMain n
>      return ()

> helpText :: String
> helpText = "Usage: frame-gen APPNAME DBURL DBNAME\n"

> main :: IO ()
> main = do
>   as <- getArgs
>   case length as == 3 of
>      True -> gen (as!!0) (as!!1) (as!!2)
>      False -> do 
>                  putStr helpText
>                  return ()

> -- From DBSpecToDBDirect
> 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)


> withPrefix :: FilePath -> String -> FilePath
> withPrefix base f | null base = f
>                   | otherwise = base ++ "/" ++ f