packages feed

keiretsu-0.4.4: src/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

-- Module      : Main
-- Copyright   : (c) 2013-2014 Brendan Hay <brendan.g.hay@gmail.com>
-- License     : This Source Code Form is subject to the terms of
--               the Mozilla Public License, v. 2.0.
--               A copy of the MPL can be found in the LICENSE file or
--               you can obtain it at http://mozilla.org/MPL/2.0/.
-- Maintainer  : Brendan Hay <brendan.g.hay@gmail.com>
-- Stability   : experimental
-- Portability : non-portable (GHC extensions)

module Main
    ( main
    ) where

import           Control.Applicative
import           Control.Arrow
import           Control.Monad
import qualified Data.ByteString.Char8 as BS
import           Data.List
import           Data.Monoid
import           Data.Text             (Text)
import qualified Data.Text             as Text
import qualified Data.Text.Encoding    as Text
import           Keiretsu.Config
import           Keiretsu.Log
import           Keiretsu.Process
import           Keiretsu.Types
import           Options.Applicative
import           System.Directory
import           System.Environment
import           System.Exit

data Start = Start
    { sDir     :: !FilePath
    , sEnvs    :: [FilePath]
    , sRuns    :: [Text]
    , sDelay   :: !Int
    , sExclude :: [Text]
    , sPorts   :: !Int
    , sDryRun  :: !Bool
    , sDebug   :: !Bool
    }

start :: Parser Start
start = Start
    <$> strOption
        ( long "dir"
       <> short 'd'
       <> metavar "DIR"
       <> value "./"
       <> help "Path to the directory containing the root Intfile. (default: ./)"
        )

    <*> many (strOption
        ( long "env"
       <> short 'e'
       <> metavar "FILE"
       <> help "Additional .env files to merge into the environment. (default: none)"
        ))

    <*> many (textOption
        ( long "run"
       <> short 'r'
       <> metavar "CMD"
       <> help "Additional commands to run in the environment. (default: none)"
        ))

    <*> option auto
        ( long "delay"
       <> short 'n'
       <> metavar "MS"
       <> value 1000
       <> help "Millisecond delay between run commands start. (default: 1000)"
        )

    <*> many (textOption
        ( long "exclude"
       <> short 'x'
       <> metavar "PROC"
       <> help "Prefixed name of a proctype to exclude. (default: none)"
        ))

    <*> option auto
        ( long "ports"
       <> short 'p'
       <> metavar "INT"
       <> value 2
       <> help "Number of ports to allocate to a single proctype. (default: 2)"
        )

    <*> switch
        ( long "dry-run"
       <> help "Execute the orchestration plan without starting any processes. (default: false)"
        )

    <*> switch
        ( long "debug"
       <> help "Show debug output. (default: false)"
        )

main :: IO ()
main = do
    s@Start{..} <- customExecParser
        (prefs $ showHelpOnError <> columns 100)
        (info start idm)

    setLogging True
    check s

    l  <- depLocal
    ds <- (l :) <$> dependencies sDir
    ps <- nub . excludeProcs sExclude . concat <$> mapM (proctypes sPorts) ds
    pe <- environment ps sEnvs
    le <- (pe ++) . map (Text.pack *** Text.pack) <$> getEnvironment

    let cs    = map (setLocalEnv pe) ps
        rs    = zipWith (procLocal l le sDelay) [1..] sRuns
        procs = cs ++ excludeProcs sExclude rs

    when sDebug $
        dump procs

    unless sDryRun $
        run procs

check :: Start -> IO ()
check Start{..} = do
    when (sPorts < 0) $ throwError "--ports must be greater-than 0."
    when (null sDir)  $ throwError "--dir must be specified."

    d <- doesDirectoryExist sDir
    unless d . throwError $
        "Directory " ++ sDir ++ " specified by --dir doesn't exist."

    forM_ sEnvs $ \e -> do
        f <- doesFileExist e
        unless f . throwError $
            "File " ++ e ++ " specified by --env doesn't exist."

throwError :: String -> IO ()
throwError s = logError s >> exitFailure

dump :: [Proc] -> IO ()
dump = zipWithM_ (\c -> mapM_ BS.putStrLn . format c) colours
  where
    format c Proc{..} = map (colourise c procPrefix . Text.encodeUtf8)
        $ "command: " <> procCmd
        : "delay: "   <> Text.pack (show procDelay ++ "ms")
        : maybe [] (\x -> ["check: " <> x]) procCheck
       ++ map f procEnv

    f (k, v) = k <> ": " <> v

textOption :: Mod OptionFields String -> Parser Text
textOption = fmap Text.pack . strOption