packages feed

simple-0.4.0: smpl.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
import Prelude hiding (writeFile)
import Control.Monad
import qualified Data.ByteString.Char8 as S8
import Data.List (sort)
import Data.String
import Filesystem hiding (concat)
import Filesystem.Path hiding (concat)
import Filesystem.Path.CurrentOS hiding (concat)
import Language.Haskell.Interpreter hiding (name)
import Network.Wai.Handler.DevelServer (runQuit)
import System.Console.CmdArgs
import System.Environment

import Web.Simple.Migrations
import Web.Simple.Router ()

data Smpl =
    Server
      { port :: Int
      , moduleName :: String
      } |
    New
      { thing :: String
      , newArgs :: [String]} |
    Create { appName :: String } |
    Migrate |
    Rollback
      { steps :: Int }
    deriving (Show, Data, Typeable)

main :: IO ()
main = do
    env <- getEnvironment
    let myport = maybe 3000 read $ lookup "PORT" env
    let develMode = cmdArgsMode $ modes
                  [ Server { port = myport &= typ "PORT"
                           , moduleName = "Main" &= typ "MODULE"
                                        &= explicit &= name "module"
                           } &= auto
                  , New { thing = "migration" &= argPos 0 &= typ "migration|..."
                        , newArgs = [] &= args }
                  , Create { appName = "" &= argPos 0 &= typ "APP_NAME" }
                  , Migrate
                  , Rollback { steps = 1 &= typ "INTEGER" }]
    smpl <- cmdArgsRun develMode
    case smpl of
      Server p m -> do
        runQuit p m "app" (const $ return [])
        putStrLn $ "Starting server on port " ++ (show myport)
      Rollback steps -> do
        fls <- listDirectory "migrate"
        whileI steps (reverse . sort $ fls) $ \f -> do
          let fileName = encodeString $ filename f
          let version = takeWhile (/= '_') fileName
          let mname = drop 1 $ dropWhile (/= '_') $
                        takeWhile (/= '.') fileName
          runRollback (encodeString f) version mname
      Migrate -> do
        fls <- listDirectory "migrate"
        forM_ (sort fls) $ \f -> do
          let fileName = encodeString $ filename f
          let version = takeWhile (/= '_') fileName
          let mname = drop 1 $ dropWhile (/= '_') $
                        takeWhile (/= '.') fileName
          runMigration (encodeString f) version mname
      New "migration" (name:[]) -> do
        putStrLn $ "Creating migration " ++ name
        newMigration name
      Create appName -> createApplication appName
      otherwise -> return ()
  where whileI 0 _ _ = return ()
        whileI _ [] _ = return ()
        whileI i (f:fs) act = do
          b <- act f
          if b then whileI (i - 1) fs act
            else whileI i fs act

runMigration :: String -> String -> String -> IO Bool
runMigration fileName version mname = do
  eup <- runInterpreter $ do
    loadModules [fileName]
    setImports ["Prelude"]
    setTopLevelModules ["Main"]
    interpret "up" (undefined :: Migration)
  case eup of
    Right up -> do
                  res <- up version mname
                  when res $ putStrLn $ "=== " ++
                      "Finished migration " ++ mname ++ " (" ++ version ++ ")"
                  return res
    Left err -> fail $ show err

runRollback :: String -> String -> String -> IO Bool
runRollback fileName version mname = do
  edown <- runInterpreter $ do
    loadModules [fileName]
    setImports ["Prelude"]
    setTopLevelModules ["Main"]
    interpret "down" (undefined :: Migration)
  case edown of
    Right down -> do
                  res <- down version mname
                  when res $ putStrLn $ "=== " ++
                      "Finished rollback " ++ mname ++ " (" ++ version ++ ")"
                  return res
    Left err -> fail $ show err

createApplication :: String -> IO ()
createApplication appName = do
  createTree $ fromString appName </> "migrate"
  writeFile (fromString appName </> "Main.hs") mainTemplate

mainTemplate :: S8.ByteString
mainTemplate = S8.concat
  [ "{-# LANGUAGE OverloadedStrings #-}\n\n"
  , "module Main where\n\n"
  , "import Web.Simple\n\n"
  , "app runner = runner $ mkRouter $ do\n"
  , "               routeAll $ okHtml \"Hello World\"\n\n"]