module Coadjute.CoData
( CoData, asks, runCoData
, CoadjuteData ( coArgs
, coUserArgs
, coVerbosity
, coForceDB
, coForceNoDB
, coForceNoHash
, coDBExists
, coParallel
, coParallelOpt
)
, coUsingDB
, dbFileName
, Verbosity(..)
, ParallelOpt(..)
, io, MonadIO
) where
import Control.Arrow (first, second)
import Control.Monad (foldM, when)
import Control.Monad.Reader (ReaderT, runReaderT, asks)
import Control.Monad.Trans (MonadIO, liftIO)
import Data.List (nub, partition)
import qualified Data.Set as Set
import System.Console.GetOpt
import System.Directory (doesFileExist)
import System.Environment (getArgs, getProgName)
import System.Exit (exitWith, ExitCode(..))
import System.IO (hPutStrLn, stderr)
import Text.Printf (printf)
import Text.PrettyPrint.HughesPJ (renderStyle, style, Style(..), fsep, text)
import Coadjute.Util.Misc (mread)
dbFileName :: FilePath
dbFileName = ".coadjute.db"
versionString :: String
versionString = "0.0.1"
getHelpString :: IO String
getHelpString = do
pn <- getProgName
return . flip usageInfo (optDesc undefined) . unlines . map prettify $
[ printf "Usage: %s [-dqvDH] [-p[N]] [USERARGS...]" pn
, ""
, "Apply the build rules specified in the Adjutant to construct build\
tasks. Then run those tasks which are necessary. Any USERARGS are\
passed through to the tasks."
, ""
, printf "If a database (%s) exists, it is used by default unless D is\
passed."
dbFileName
]
where
prettify = renderStyle (style {lineLength = 80, ribbonsPerLine = 1})
. fsep . map text . words
type CoData = ReaderT CoadjuteData IO
runCoData :: CoData a -> IO a
runCoData co = do
dbExists <- doesFileExist dbFileName
helpString <- getHelpString
args <- getArgs
let (opts,[],otherOpt,_) = getOpt' (ReturnInOrder (handleNonOpt helpString))
(optDesc helpString)
args
let (helpRequests, userOpts) = partition isHelp otherOpt
let initialMsgs = if null helpRequests then [] else [(helpString,True)]
defaultData =
CoadjuteData
{ coArgs = Set.fromList args
, coUserArgs = userOpts
, coVerbosity = Normal
, coDBExists = dbExists
, coForceDB = False
, coForceNoDB = False
, coForceNoHash = False
, coParallel = False
, coParallelOpt = Uncapped
}
(coData,msgs) = foldl (\cd optFunc -> optFunc cd)
(defaultData,initialMsgs)
opts
when (not.null $ msgs) . io $ do
anyErr <- foldM (\a (s,err) ->
if err
then hPutStrLn stderr s >> return True
else putStrLn s >> return a
) False (nub.reverse $ msgs)
exitWith (if anyErr then ExitFailure 1 else ExitSuccess)
runReaderT co coData
data CoadjuteData = CoadjuteData
{ coArgs :: Set.Set String
, coUserArgs :: [String]
, coVerbosity :: Verbosity
, coDBExists :: Bool
, coForceDB :: Bool
, coForceNoDB :: Bool
, coForceNoHash :: Bool
, coParallel :: Bool
, coParallelOpt :: ParallelOpt
}
type OptionData = (CoadjuteData, [(String,Bool)])
coUsingDB :: CoadjuteData -> Bool
coUsingDB d = coForceDB d || (coDBExists d && not (coForceNoDB d))
data Verbosity = Quiet | Normal | Verbose | VeryVerbose deriving (Eq, Ord)
data ParallelOpt =
Capped Int | Uncapped
#ifdef __GLASGOW_HASKELL__
| Processor
#endif
optDesc :: String -> [OptDescr (OptionData -> OptionData)]
optDesc helpString = map (\(Option s l d msg) -> Option s l d (prettify msg))
[ Option "V" ["version"]
(NoArg$ second ((versionString,True):))
"Output version string and exit."
, Option "h" ["help"]
(NoArg$ second ((helpString,True):))
"Output this help string and exit."
, Option "v" ["verbose"]
(noArg$ \o@(CoadjuteData {coVerbosity = v}) ->
o { coVerbosity =
if v == Verbose || v == VeryVerbose
then VeryVerbose
else Verbose }
)
"Verbose output. Specify twice for more verbosity."
, Option "q" ["quiet"]
(noArg$ \o -> o {coVerbosity = Quiet})
"Quiet output."
, Option "p" ["parallel"]
(flip OptArg "N" $ \ma (o,s) ->
let o' = o { coParallel = True }
err m = "Invalid parallel count, expected integer: '" ++ m ++ "'"
in case ma of
Nothing -> (o' { coParallelOpt = Uncapped },s)
Just a ->
let mn = mread a
in case mn of
Nothing -> (o', (err a,False) : s)
Just 1 -> (o, s)
Just n ->
(o' { coParallelOpt =
case compare n 0 of
#ifdef __GLASGOW_HASKELL__
LT -> Processor
#else
LT -> Capped (abs n)
#endif
GT -> Capped n
EQ -> Uncapped }, s))
("Perform tasks in parallel, up to N at a time. If N is negative, the "++
#ifdef __GLASGOW_HASKELL__
"number of OS threads (i.e. the +RTS -N setting) is used."++
#else
"absolute value is used."++
#endif
" If N is zero or not given, the number of simultaneous tasks will\
not be limited. If N is 1, the argument is ignored.")
, Option "d" ["usedb"]
(noArg$ \o -> o {coForceDB = True})
("Use a database, creating one if it doesn't exist. If one does\
exist, it is used by default.")
, Option "D" ["nousedb"]
(noArg$ \o -> o {coForceNoDB = True})
"Don't use a database even if one exists."
, Option "H" ["no-hashing"]
(noArg$ \o -> o {coForceNoHash = True})
"Use timestamps, instead of hashes in the database. The database will\
be used only to store USERARGS specified for storage in the Adjutant."
]
where
-- like NoArg and OptArg, but works inside OptionData
noArg = NoArg . first
prettify = renderStyle (style {lineLength = 80}) . fsep . map text . words
isHelp :: String -> Bool
isHelp = flip elem [ pre ++ opt | pre <- ["","/","--"]
, opt <- ["?","h","hlp","help"] ]
handleNonOpt :: String -> String -> OptionData -> OptionData
handleNonOpt helpString opt (cd,s) =
if isHelp opt
then (cd, (helpString,True):s)
else (cd { coUserArgs = opt : coUserArgs cd }, s)
io :: (MonadIO m) => IO a -> m a
io = liftIO