laborantin-hs 0.1.2.0 → 0.1.3.0
raw patch · 11 files changed
+426/−178 lines, 11 filesdep +old-locale
Dependencies added: old-locale
Files
- Laborantin.hs +75/−22
- Laborantin/CLI.hs +47/−21
- Laborantin/DSL.hs +41/−5
- Laborantin/Implementation.hs +51/−24
- Laborantin/Query.hs +43/−51
- Laborantin/Query/Interpret.hs +33/−23
- Laborantin/Query/Parse.hs +27/−6
- Laborantin/Types.hs +88/−2
- README.md +4/−12
- examples/main.hs +14/−9
- laborantin-hs.cabal +3/−3
Laborantin.hs view
@@ -1,22 +1,80 @@ -module Laborantin where+module Laborantin (+ prepare+ , load+ , remove+ , runAnalyze+ , missingParameterSets+ , expandParameterSets+) where import Laborantin.Types-import Laborantin.DSL+import Laborantin.Query import Laborantin.Implementation import Control.Monad.IO.Class-import Control.Monad.Reader+import Control.Monad.Reader +import Control.Monad.State import Control.Monad.Error import Control.Applicative import qualified Data.Set as S+import qualified Data.Map as M -execute :: (MonadIO m) => Backend m -> ScenarioDescription m -> ParameterSet -> m ()+-- | Prepare a list of execution actions for a given+-- (scenario, parameter-query, existing) ancestors.+--+-- This function returns one action per ParameterSet which is required by the+-- parameter-query and not yet present in the existing list of executions.+--+-- For instance, if the scenario has one parameter 'foo'; the query wants 'foo'+-- in [1,2,3,4]; and there is one execution with 'foo' == 2; then this function+-- returns 3 actions (for parameters 1, 3, and 4).+--+-- A user can then run these actions in sequence (or concurrently if it makes+-- sense).+prepare :: (MonadIO m) => Backend m+ -> TExpr Bool+ -> [Execution m]+ -> ScenarioDescription m+ -> [m (Execution m)]+prepare b expr execs sc = map toAction neededParamSets+ where toAction = execute b sc+ neededParamSets = missingParameterSets sc expr existing+ where existing = map eParamSet execs++-- | Like matchingParameterSets but also remove existing ParameterSet given as+-- third parameter.+missingParameterSets :: ScenarioDescription m -> TExpr Bool -> [ParameterSet] -> [ParameterSet]+missingParameterSets sc expr sets = listDiff target sets+ where target = matchingParameterSets sc expr++-- | Like expandParameterSets but also filter ParameterSet to only those that+-- actually match the TExpr query.+matchingParameterSets :: ScenarioDescription m -> TExpr Bool -> [ParameterSet]+matchingParameterSets sc expr = filter matching allSets+ where matching = matchTExpr' expr sc+ allSets = expandParameterSets sc expr ++-- | Expands parameters given a TExpr and a ScenarioDescription into a list of+-- parameter spaces (sort of cartesian product of all possible params)+expandParameterSets :: ScenarioDescription m -> TExpr Bool -> [ParameterSet]+expandParameterSets sc expr = paramSets $ expandParamSpace (sParams sc) expr++-- | Shortcut to remove from l1 the items in l2.+-- Implementation transposes objects to sets thus needs the Ord capability.+--+-- e.g. listDiff "abcd" "bcde" = "a"+listDiff :: Ord a => [a] -> [a] -> [a]+listDiff l1 l2 = S.toList (S.fromList l1 `S.difference` S.fromList l2)++-- | Executes a given scenario for a given set of parameters using a given backend.+execute :: (MonadIO m) => Backend m -> ScenarioDescription m -> ParameterSet -> m (Execution m) execute b sc prm = execution where execution = do (exec,final) <- bPrepareExecution b sc prm - status <- runReaderT (runErrorT (go exec `catchError` recover exec)) (b, exec)+ status <- liftM fst $ runReaderT (runStateT (runErrorT (go exec `catchError` recover exec)) emptyEnv) (b, exec) let exec' = either (\_ -> exec {eStatus = Failure}) (\_ -> exec {eStatus = Success}) status bFinalizeExecution b exec' final+ return exec' where go exec = do bSetup b exec bRun b exec@@ -24,26 +82,21 @@ bAnalyze b exec recover exec err = bRecover b err exec >> throwError err -executeAnalysis :: (MonadIO m, Functor m) => Backend m -> Execution m -> m (Either AnalysisError ())-executeAnalysis b exec = do- either rebrandError Right <$> runReaderT (runErrorT (go exec)) (b, exec)- where go exec = bAnalyze b exec- rebrandError (ExecutionError str) = Left $ AnalysisError str --executeExhaustive :: (MonadIO m) => Backend m -> ScenarioDescription m -> [m ()]-executeExhaustive b sc = map f $ paramSets $ sParams sc- where f = execute b sc --executeMissing :: (MonadIO m) => Backend m -> ScenarioDescription m -> [Execution m] -> [m ()]-executeMissing b sc execs = map f $ S.toList (exhaustive `S.difference` existing)- where successful = filter ((== Success) . eStatus) execs- exhaustive = S.fromList $ paramSets (sParams sc)- existing = S.fromList $ map eParamSet successful- f = execute b sc-+-- | Loads executions of given ScenarioDescription and matching a given query+-- using a specific backend. load :: (MonadIO m) => Backend m -> [ScenarioDescription m] -> TExpr Bool -> m [Execution m] load = bLoad +-- | Remove an execution using a specific backend. remove :: (MonadIO m) => Backend m -> Execution m -> m () remove = bRemove++-- | Runs the analysis hooks.+runAnalyze :: (MonadIO m, Functor m) => Backend m -> Execution m -> m (Either AnalysisError ())+runAnalyze b exec = do+ let status = runReaderT (runStateT (runErrorT (go exec)) emptyEnv) (b, exec)+ (either rebrandError Right) . fst <$> status+ where go exec = bAnalyze b exec+ rebrandError (ExecutionError str) = Left $ AnalysisError str+
Laborantin/CLI.hs view
@@ -63,12 +63,42 @@ ] -data Labor = Run { scenarii :: [String] , params :: [String] , continue :: Bool , matcher :: [String]} - | Describe { scenarii :: [String] } - | Find { scenarii :: [String] , params :: [String] , successful :: Bool, today :: Bool, matcher :: [String]} - | Analyze { scenarii :: [String] , params :: [String] , successful :: Bool , today :: Bool, matcher :: [String]} - | Rm { scenarii :: [String] , params :: [String] , successful :: Bool , today :: Bool, matcher :: [String]} - | Query { scenarii :: [String] , params :: [String] , successful :: Bool , today :: Bool, matcher :: [String]} +data Labor = Run { scenarii :: [String]+ , params :: [String]+ , matcher :: [String]+ }+ | Continue { scenarii :: [String]+ , params :: [String]+ , matcher :: [String]+ , successful :: Bool+ , today :: Bool, matcher :: [String]+ }+ | Describe { scenarii :: [String]+ }+ | Find { scenarii :: [String]+ , params :: [String]+ , successful :: Bool+ , today :: Bool, matcher :: [String]+ }+ | Analyze { scenarii :: [String]+ , params :: [String]+ , successful :: Bool+ , today :: Bool, matcher :: [String]+ }+ | Rm { scenarii :: [String]+ , params :: [String]+ , successful :: Bool+ , today :: Bool, matcher :: [String]+ }+ | Query { scenarii :: [String]+ , params :: [String]+ , successful :: Bool+ , today :: Bool, matcher :: [String]+ }+ | Params { scenarii :: [String]+ , params :: [String]+ , matcher :: [String]+ } deriving (Typeable, Data, Show, Eq) instance Attributes Labor where@@ -83,12 +113,6 @@ , Help "Restrict a parameter, format name=type:val." , ArgHelp "PARAMS" ]- , continue %> [ Short "c"- , Long ["continue"]- , Default False- , Invertible True- , Help "Continue execution (skip known)"- ] , successful %> [ Long ["successful"] , Help "Successful only" , Invertible True@@ -163,15 +187,17 @@ now <- getCurrentTime case labor of (Describe scii) -> forM_ xs' (T.putStrLn . describeScenario)- Find {} -> do (execs,_) <- runEnvIO (loadMatching now)+ Find {} -> do execs <- runEnvIO (loadMatching now) mapM_ (T.putStrLn . describeExecution) execs- (Rm {}) -> runSc (loadAndRemove now)- (Run { continue = False }) -> mapM_ runSc allExecs- (Run { continue = True }) -> do (execs,_) <- runEnvIO (loadMatching now)- mapM_ runSc (remainingExecs execs)+ Rm {} -> runSc (loadAndRemove now)+ Run {} -> mapM_ runSc (targetExecs [])+ Continue {} -> do execs <- runEnvIO (loadMatching now)+ mapM_ runSc (targetExecs execs) Analyze {} -> runSc (loadAndAnalyze now) Query {} -> do let expr = simplifyOneBoolLevel $ query now- putStrLn $ showTExpr expr+ print expr+ Params {} -> do let expr = simplifyOneBoolLevel expander + print expr where xs' = filterDescriptions (ScenarioName $ map T.pack $ scenarii labor) xs matcherUExprs = rights $ map parseUExpr (matcher labor)@@ -182,9 +208,9 @@ dateTExpr tst = todayToTExpr (today labor) (tst {utctDayTime = 0}) query tst = conjunctionQueries (paramsTExpr:scenarioTExpr:statusTExpr:dateTExpr':matcherTExprs) where dateTExpr' = dateTExpr tst+ expander = conjunctionQueries (paramsTExpr:scenarioTExpr:matcherTExprs) runSc = void . runEnvIO loadMatching tst = load defaultBackend xs' (query tst) loadAndRemove tst = loadMatching tst >>= mapM (remove defaultBackend)- loadAndAnalyze tst = loadMatching tst >>= mapM (executeAnalysis defaultBackend)- allExecs = concatMap (executeExhaustive defaultBackend) xs'- remainingExecs execs = concatMap (\sc -> executeMissing defaultBackend sc execs) xs'+ loadAndAnalyze tst = loadMatching tst >>= mapM (runAnalyze defaultBackend)+ targetExecs existing = concatMap (prepare defaultBackend expander existing) xs'
Laborantin/DSL.hs view
@@ -5,6 +5,8 @@ scenario , describe , parameter+ , require+ , requireTExpr , dependency , check , resolve@@ -30,13 +32,19 @@ ) where import qualified Data.Map as M+import Data.List (partition, nubBy)+import Laborantin import Laborantin.Types+import Laborantin.Query+import Laborantin.Query.Parse+import Laborantin.Query.Interpret import Control.Monad.State import Control.Monad.Reader import Control.Monad.Error import Control.Applicative import Data.Dynamic-import Data.Text (Text, unpack)+import Data.Text (Text, pack, unpack)+import qualified Data.Text as T class Describable a where changeDescription :: Text -> a -> a@@ -53,7 +61,7 @@ -- | DSL entry point to build a 'ScenarioDescription'. scenario :: Text -> State (ScenarioDescription m) () -> ScenarioDescription m scenario name f = execState f sc0- where sc0 = SDesc name "" M.empty M.empty Nothing []+ where sc0 = SDesc name "" M.empty M.empty Nothing [] (B False) -- | Attach a description to the 'Parameter' / 'Scnario' describe :: Describable a => Text -> State a ()@@ -69,9 +77,11 @@ -- | DSL entry point to build a 'Dependency a' within a scenario. dependency :: (Monad m) => Text -> State (Dependency m) () -> State (ScenarioDescription m) () dependency name f = modify (addDep dep)- where addDep v sc0 = sc0 { sDeps = v:(sDeps sc0)}+ where addDep v sc0 = sc0 { sDeps = v:(sDeps sc0) } dep = execState f dep0- where dep0 = Dep name "" (const (return True)) (const (return ()))+ where dep0 = Dep name "" checkF solveF+ checkF = const (return True)+ solveF = return . fst -- | Set verification action for the dependency check :: (Execution m -> m Bool) -> State (Dependency m) ()@@ -80,7 +90,7 @@ put $ dep0 { dCheck = f } -- | Set resolution action for the dependency-resolve :: (Execution m -> m ()) -> State (Dependency m) ()+resolve :: ((Execution m, Backend m) -> m (Execution m)) -> State (Dependency m) () resolve f = do dep0 <- get put $ dep0 { dSolve = f }@@ -132,6 +142,32 @@ appendHook :: Text -> Step m () -> State (ScenarioDescription m) () appendHook name f = modify (addHook name $ Action f) where addHook k v sc0 = sc0 { sHooks = M.insert k v (sHooks sc0) }++-- | Defines the TExpr Bool to load ancestor+requireTExpr :: (MonadIO m, Monad m) => ScenarioDescription m -> TExpr Bool -> State (ScenarioDescription m) ()+requireTExpr sc query = do+ let depName = T.concat [(sName sc), " ~> ", (pack $ show query)]+ modify (\sc0 -> sc0 {sQuery = query})+ dependency depName $ do+ describe "auto-generated dependency for `require` statement"+ check $ \exec -> do+ let ancestors = filter ((sName sc ==) . sName . eScenario) (eAncestors exec)+ let existing = map eParamSet ancestors+ let missing = missingParameterSets sc query existing+ return (null $ missing)+ resolve $ \(exec,backend) -> do+ storedAncestors <- load backend [sc] query+ let (execAncestors, otherAncestors) = partition ((sName sc ==) . sName . eScenario) (eAncestors exec)+ let allAncestors = nubBy (samePath) (storedAncestors ++ execAncestors)+ newAncestors <- sequence $ prepare backend query allAncestors sc+ return (exec { eAncestors = newAncestors ++ allAncestors }) + where samePath e1 e2 = ePath e1 == ePath e2++-- | Defines the TExpr Bool to load ancestor+require :: (MonadIO m, Monad m) => ScenarioDescription m -> Text -> State (ScenarioDescription m) ()+require sc txt = requireTExpr sc query+ where query = either (const deflt) (toTExpr deflt) (parseUExpr (unpack txt))+ deflt = (B True) -- | Returns a 'Result' object for the given name. --
Laborantin/Implementation.hs view
@@ -5,8 +5,10 @@ , defaultBackend , defaultResult , defaultLog+ , liftIO ) where +import Control.Monad.IO.Class (liftIO) import qualified Data.Map as M import Data.Text (Text) import qualified Data.Text as T@@ -26,6 +28,7 @@ import Data.UUID import System.Directory import System.Random+import System.IO.Error import System.Log.Logger import System.Log.Handler (close) import System.Log.Handler.Simple@@ -34,11 +37,11 @@ -- | Default monad for 'defaultBackend'. -- EnvIO carries a 'DynEnv' in a state and allows you to perform IO actions.-type EnvIO = (StateT DynEnv IO)+type EnvIO = IO -- | Execute an EnvIO action in IO.-runEnvIO :: EnvIO a -> IO (a,DynEnv)-runEnvIO m = runStateT m M.empty+runEnvIO :: IO a -> IO a+runEnvIO = id instance ToJSON ParameterValue where toJSON (StringParam str) = object ["type" .= ("string"::Text), "val" .= str]@@ -54,7 +57,7 @@ , "params" .= params , "path" .= path , "status" .= status- , "ancestors" .= (map toJSON es)+ , "ancestors" .= ancestors , "timestamps" .= tsts ] where ancestors = map f es@@ -103,13 +106,13 @@ finalizer exec now <- liftIO $ getCurrentTime let exec' = updateCompletionTime exec now- liftIO . putStrLn $ "execution finished\n"+ bPrintT $ "execution finished\n" liftIO $ BSL.writeFile (rundir ++ "/execution.json") (encode exec') where rundir = ePath exec setup = callHooks "setup" . eScenario run = callHooks "run" . eScenario teardown = callHooks "teardown" . eScenario- analyze exec = liftIO (T.putStrLn $ advertise exec) >> callHooks "analyze" (eScenario exec)+ analyze exec = callHooks "analyze" (eScenario exec) recover err exec = unAction (doRecover err) where doRecover = fromMaybe (\_ -> Action $ return ()) (sRecoveryAction $ eScenario exec) result exec = return . defaultResult exec@@ -126,20 +129,28 @@ advertise :: Execution m -> Text advertise exec = T.pack $ unlines [ "scenario: " ++ (show . sName . eScenario) exec- , "rundir: " ++ ePath exec- , "json-params: " ++ (C.unpack . encode . eParamSet) exec+ , " rundir: " ++ ePath exec+ , " json-params: " ++ (C.unpack . encode . eParamSet) exec ] +bPrint :: (MonadIO m, Show a) => a -> m ()+bPrint = liftIO . putStrLn . ("backend> " ++) . show++bPrintT :: (MonadIO m) => Text -> m ()+bPrintT = liftIO . T.putStrLn . (T.append "backend> ")+ prepareNewScenario :: ScenarioDescription EnvIO -> ParameterSet -> EnvIO (Execution EnvIO,Finalizer EnvIO) prepareNewScenario sc params = do+ bPrint $ T.append "preparing " (sName sc) (now,uuid) <- liftIO $ do now <- getCurrentTime id <- randomIO :: IO UUID return (now,id) let rundir = intercalate "/" [T.unpack (sName sc), show uuid]- let exec = Exec sc params rundir Running [] (now,now)- liftIO $ print "resolving dependencies"- resolveDependencies exec+ let newExec = Exec sc params rundir Running [] (now,now)+ bPrint "resolving dependencies"+ exec <- resolveDependencies newExec+ bPrintT $ advertise exec handles <- liftIO $ do createDirectoryIfMissing True rundir BSL.writeFile (rundir ++ "/execution.json") (encode exec)@@ -147,36 +158,52 @@ h1 <- fileHandler (rundir ++ "/execution-log.txt") DEBUG h2 <- log4jFileHandler (rundir ++ "/execution-log.xml") DEBUG forM_ [h1,h2] (updateGlobalLogger (loggerName exec) . addHandler)- T.putStrLn $ advertise exec return [h1,h2] return (exec, \_ -> liftIO $ forM_ handles close) -resolveDependencies :: Execution EnvIO -> EnvIO ()+resolveDependencies :: Execution EnvIO -> EnvIO (Execution EnvIO) resolveDependencies exec = do- pending <- getPendingDeps exec (sDeps $ eScenario exec)+ pending <- getPendingDeps exec deps resolveDependencies' exec [] pending+ where deps = sDeps $ eScenario exec -resolveDependencies' :: Execution EnvIO -> [Dependency EnvIO] -> [Dependency EnvIO] -> EnvIO ()-resolveDependencies' exec _ [] = return ()-resolveDependencies' exec attempted trying - | all (\d -> elem d attempted) trying = error "cannot solve dependencies"- | otherwise = do- mapM (flip dSolve exec) trying- pending <- getPendingDeps exec trying- resolveDependencies' exec (trying ++ attempted) pending+resolveDependencies' :: Execution EnvIO -> [Dependency EnvIO] -> [Dependency EnvIO] -> EnvIO (Execution EnvIO)+resolveDependencies' exec [] [] = return exec+resolveDependencies' exec failed [] = error "cannot solve dependencies"+resolveDependencies' exec failed (dep:pending) = do+ bPrint $ "trying to solve " ++ (T.unpack $ dName dep)+ exec2 <- dSolve dep (exec, defaultBackend)+ success <- dCheck dep exec2 + case success of+ True -> do+ bPrint $ "successfully solved " ++ (T.unpack $ dName dep)+ resolveDependencies' exec2 [] (pending ++ failed)+ False -> do+ bPrint $ "failed to solve " ++ (T.unpack $ dName dep)+ resolveDependencies' exec2 failed pending -getPendingDeps exec deps = map fst . filter (not . snd). zip deps <$> mapM (flip dCheck exec) deps+-- | Evaluates and returns, for an execution, the list of failing dependencies +--+getPendingDeps :: (Functor m, Monad m, MonadIO m) => Execution m -> [Dependency m] -> m [Dependency m]+getPendingDeps exec deps = keepFailedChecks <$> mapM checkDep deps+ where keepFailedChecks = map fst . filter (not . snd). zip deps + checkDep dep = do+ bPrintT $ T.append "checking " (dName dep)+ dCheck dep exec loadExisting :: [ScenarioDescription EnvIO] -> TExpr Bool -> EnvIO [Execution EnvIO] loadExisting scs qexpr = do concat <$> mapM f scs where f :: ScenarioDescription EnvIO -> EnvIO [Execution EnvIO] f sc = do- paths <- map ((name ++ "/") ++) . filter notDot <$> liftIO (getDirectoryContents name)+ paths <- map ((name ++ "/") ++) . filter notDot <$> liftIO (getDirectoryContents' name) allExecs <- mapM (loadOne sc scs) paths return $ filter (matchTExpr qexpr) allExecs where notDot dirname = take 1 dirname /= "." name = T.unpack $ sName sc++ getDirectoryContents' dir = catchIOError (getDirectoryContents dir)+ (\e -> if isDoesNotExistError e then return [] else ioError e) loadOne :: ScenarioDescription EnvIO -> [ScenarioDescription EnvIO] -> FilePath -> EnvIO (Execution EnvIO) loadOne sc scs path = do
Laborantin/Query.hs view
@@ -1,12 +1,13 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} -module Laborantin.Query (matchTExpr, simplifyOneBoolLevel, showTExpr) where+module Laborantin.Query (matchTExpr, matchTExpr', simplifyOneBoolLevel, expandParamSpace) where import Laborantin.Types import qualified Data.Map as M import Control.Applicative ((<$>),(<*>)) import Data.Text (Text)+import qualified Data.Map as M import qualified Data.Text as T type Param = Maybe ParameterValue@@ -23,6 +24,10 @@ simplifyOneBoolLevel (Or a b) = (Or (simplifyOneBoolLevel a) (simplifyOneBoolLevel b)) simplifyOneBoolLevel e = e +matchTExpr' :: TExpr Bool -> ScenarioDescription m -> ParameterSet -> Bool+matchTExpr' expr sc params = matchTExpr expr (Exec sc params "" Success [] (epoch, epoch)) + where epoch = error "should not evaluated time"+ matchTExpr :: TExpr Bool -> Execution m -> Bool matchTExpr e q = match' (evalExpr q e) where match' (Right True) = True@@ -73,55 +78,42 @@ coerceNumberParam name (Just (NumberParam r)) = Right r coerceNumberParam name _ = Left (EvalError $ "could not coerce "++ T.unpack name ++" to number") -showTExpr :: TExpr a -> String-showTExpr (N x) = show x-showTExpr (B x) = show x-showTExpr (S x) = show x-showTExpr (L x) = show x-showTExpr (T x) = show x-showTExpr (Not x) = "! " ++ "(" ++ showTExpr x ++ ")"-showTExpr (And e1 e2) = "(" ++ showTExpr e1 ++ " && " ++ showTExpr e2 ++ ")"-showTExpr (Or e1 e2) = "(" ++ showTExpr e1 ++ " || " ++ showTExpr e2 ++ ")"-showTExpr (Contains e1 e2) = "(" ++ showTExpr e1 ++ " in " ++ showTExpr e2 ++ ")"-showTExpr (Gt e1 e2) = "(" ++ showTExpr e1 ++ " > " ++ showTExpr e2 ++ ")"-showTExpr (Eq e1 e2) = "(" ++ showTExpr e1 ++ " == " ++ showTExpr e2 ++ ")"-showTExpr (Plus e1 e2) = "(" ++ showTExpr e1 ++ " + " ++ showTExpr e2 ++ ")"-showTExpr (Times e1 e2) = "(" ++ showTExpr e1 ++ " * " ++ showTExpr e2 ++ ")"-showTExpr ScName = "@sc.name"-showTExpr ScStatus = "@sc.status"-showTExpr ScTimestamp = "@sc.timestamp"-showTExpr (ScParam key) = "@sc.param:" ++ show key-showTExpr (SCoerce x) = showTExpr x-showTExpr (NCoerce x) = showTExpr x-showTExpr (SilentSCoerce x) = showTExpr x-showTExpr (SilentNCoerce x) = showTExpr x-showTExpr (TBind str f x) = "(" ++ str ++ " -> (" ++ showTExpr x ++ "))"--instance (Show (TExpr a)) where- show = showTExpr+-- | Expands a ParameterSpace to all values that could match a TExpr Bool.+--+-- Currently only supports countably finite expansions of parameters. +-- That is TExpr Bool such as (@sc.param "param" > 32) are ignored.+-- Instead (@sc.param "param" in ["foo", "bar"]) gets expanded to ("param", [StringParam "foo", StringParam "bar"])+-- Supported expensions are And / Or / Eq / Contains.+--+-- The idea is that you can generate a list of Execution to run by first+-- expanding all possible points in the ParameterSpace modified by the TExpr,+-- and then filter these possible points using a same TExpr.+--+expandParamSpace :: ParameterSpace -> TExpr Bool -> ParameterSpace+expandParamSpace params query = case query of+ (Or expr1 expr2) -> mergeParamSpaces ps1 ps2+ where ps1 = expand expr1+ ps2 = expand expr2 + (And expr1 expr2) -> mergeParamSpaces ps1 ps2+ where ps1 = expand expr1+ ps2 = expand expr2 + (Eq (SCoerce (ScParam key)) expr) -> update key (toParamValues expr)+ (Eq (NCoerce (ScParam key)) expr) -> update key (toParamValues expr)+ (Eq (SilentSCoerce (ScParam key)) expr) -> update key (toParamValues expr)+ (Eq (SilentNCoerce (ScParam key)) expr) -> update key (toParamValues expr)+ (Contains (SCoerce (ScParam key)) expr) -> update key (toParamValues expr)+ (Contains (NCoerce (ScParam key)) expr) -> update key (toParamValues expr)+ (Contains (SilentSCoerce (ScParam key)) expr) -> update key (toParamValues expr)+ (Contains (SilentNCoerce (ScParam key)) expr) -> update key (toParamValues expr)+ _ -> params -showUExpr :: UExpr -> String-showUExpr (UN x) = show x-showUExpr (UB x) = show x-showUExpr (US x) = show x-showUExpr (UL x) = show x-showUExpr (UT x) = show x-showUExpr (UNot x) = "! " ++ "(" ++ showUExpr x ++ ")"-showUExpr (UAnd e1 e2) = "(" ++ showUExpr e1 ++ " and " ++ showUExpr e2 ++ ")"-showUExpr (UOr e1 e2) = "(" ++ showUExpr e1 ++ " or " ++ showUExpr e2 ++ ")"-showUExpr (UContains e1 e2) = "(" ++ showUExpr e1 ++ " in " ++ showUExpr e2 ++ ")"-showUExpr (UGt e1 e2) = "(" ++ showUExpr e1 ++ " > " ++ showUExpr e2 ++ ")"-showUExpr (UGte e1 e2) = "(" ++ showUExpr e1 ++ " >= " ++ showUExpr e2 ++ ")"-showUExpr (ULt e1 e2) = "(" ++ showUExpr e1 ++ " < " ++ showUExpr e2 ++ ")"-showUExpr (ULte e1 e2) = "(" ++ showUExpr e1 ++ " <= " ++ showUExpr e2 ++ ")"-showUExpr (UEq e1 e2) = "(" ++ showUExpr e1 ++ " == " ++ showUExpr e2 ++ ")"-showUExpr (UPlus e1 e2) = "(" ++ showUExpr e1 ++ " + " ++ showUExpr e2 ++ ")"-showUExpr (UMinus e1 e2) = "(" ++ showUExpr e1 ++ " - " ++ showUExpr e2 ++ ")"-showUExpr (UTimes e1 e2) = "(" ++ showUExpr e1 ++ " * " ++ showUExpr e2 ++ ")"-showUExpr (UDiv e1 e2) = "(" ++ showUExpr e1 ++ " / " ++ showUExpr e2 ++ ")"-showUExpr UScName = "@sc.name"-showUExpr UScStatus = "@sc.status"-showUExpr (UScParam key) = "@sc.param:" ++ show key+ where update = updateParam params+ expand = expandParamSpace params -instance (Show UExpr) where- show = showUExpr+-- | Interprets a `TExpr a` into a list of ParameterValue when it makes sense (i.e.,+-- on TExpr String / TExpr Rational / TExpr [String] / TExpr [Rational] )+toParamValues :: TExpr a -> [ParameterValue]+toParamValues (N x) = [NumberParam x]+toParamValues (S x) = [StringParam x]+toParamValues (L x) = concatMap toParamValues x+toParamValues _ = []
Laborantin/Query/Interpret.hs view
@@ -26,7 +26,7 @@ data ATExpr = forall a . TExpr a ::: TTyp a instance Show ATExpr where- show (expr ::: ty) = showTExpr expr+ show (expr ::: ty) = show expr toTExpr :: TExpr Bool -> UExpr -> (TExpr Bool) toTExpr expr = fromMaybe expr . toTExpr'@@ -40,6 +40,7 @@ interpret :: UExpr -> Either IError ATExpr interpret UScName = Right (ScName ::: TTString) interpret UScStatus = Right (ScStatus ::: TTString)+interpret UScTimestamp = Right (ScTimestamp ::: TTUTCTime) interpret (UScParam a) = Right (ScParam a ::: TTParam) interpret (UB a) = Right (B a ::: TTBool) interpret (UT a) = Right (T a ::: TTUTCTime)@@ -49,52 +50,54 @@ vals <- ((,) <$> interpret a <*> interpret b) case vals of (x ::: TTBool, y ::: TTBool) -> Right (And x y ::: TTBool)- _ -> error "typing"+ _ -> error "expecting 'boolean' for And clause" interpret (UOr a b) = do vals <- ((,) <$> interpret a <*> interpret b) case vals of (x ::: TTBool, y ::: TTBool) -> Right (Or x y ::: TTBool)- _ -> error "typing"+ _ -> error "expecting 'boolean' for Or clause" interpret (UPlus a b) = do vals <- ((,) <$> interpret a <*> interpret b) case vals of (x ::: TTNum, y ::: TTNum) -> Right (Plus x y ::: TTNum)- _ -> error "typing"+ _ -> error "expecting 'number' for Plus operator" interpret (UMinus a b) = do vals <- ((,) <$> interpret a <*> interpret b) case vals of (x ::: TTNum, y ::: TTNum) -> Right (Times x (TBind "0 - x" (\n -> N (0 - n)) y) ::: TTNum)- _ -> error "typing"+ _ -> error "expecting 'number' for Minus operator" interpret (UTimes a b) = do vals <- ((,) <$> interpret a <*> interpret b) case vals of (x ::: TTNum, y ::: TTNum) -> Right (Times x y ::: TTNum)- _ -> error "typing"+ _ -> error "expecting 'number' for Times operator" interpret (UDiv a b) = do vals <- ((,) <$> interpret a <*> interpret b) case vals of (x ::: TTNum, y ::: TTNum) -> Right (Times x (TBind "1/x" (\n -> N (1/n)) y) ::: TTNum)- _ -> error "typing"+ _ -> error "expecting 'number' for Div operator" interpret (UEq a b) = do vals <- ((,) <$> interpret a <*> interpret b) case vals of- (x ::: TTNum, y ::: TTNum) -> Right (Eq x y ::: TTBool)- (x ::: TTString, y ::: TTString) -> Right (Eq x y ::: TTBool)- (x ::: TTBool, y ::: TTBool) -> Right (Eq x y ::: TTBool)- (x ::: TTParam, y ::: TTString) -> Right (Eq (SCoerce x) y ::: TTBool)- (x ::: TTString, y ::: TTParam) -> Right (Eq x (SCoerce y) ::: TTBool)- (x ::: TTParam, y ::: TTNum) -> Right (Eq (NCoerce x) y ::: TTBool)- (x ::: TTNum, y ::: TTParam) -> Right (Eq x (NCoerce y) ::: TTBool)- (x ::: TTParam, y ::: TTParam) -> Right (Eq x y ::: TTBool)- _ -> error "typing"+ (x ::: TTNum, y ::: TTNum) -> Right (Eq x y ::: TTBool)+ (x ::: TTString, y ::: TTString) -> Right (Eq x y ::: TTBool)+ (x ::: TTBool, y ::: TTBool) -> Right (Eq x y ::: TTBool)+ (x ::: TTUTCTime, y ::: TTUTCTime) -> Right (Eq x y ::: TTBool)+ (x ::: TTParam, y ::: TTString) -> Right (Eq (SCoerce x) y ::: TTBool)+ (x ::: TTString, y ::: TTParam) -> Right (Eq x (SCoerce y) ::: TTBool)+ (x ::: TTParam, y ::: TTNum) -> Right (Eq (NCoerce x) y ::: TTBool)+ (x ::: TTNum, y ::: TTParam) -> Right (Eq x (NCoerce y) ::: TTBool)+ (x ::: TTParam, y ::: TTParam) -> Right (Eq x y ::: TTBool)+ _ -> error "type mismatch for equality check" interpret (UGt a b) = do vals <- ((,) <$> interpret a <*> interpret b) case vals of- (x ::: TTNum, y ::: TTNum) -> Right (Gt x y ::: TTBool)- (x ::: TTParam, y ::: TTNum) -> Right (Gt (NCoerce x) y ::: TTBool)- (x ::: TTNum, y ::: TTParam) -> Right (Gt x (NCoerce y) ::: TTBool)- (x ::: TTParam, y ::: TTParam) -> Right (Gt (NCoerce x) (NCoerce y) ::: TTBool)- _ -> error "typing error"+ (x ::: TTNum, y ::: TTNum) -> Right (Gt x y ::: TTBool)+ (x ::: TTUTCTime, y ::: TTUTCTime) -> Right (Gt x y ::: TTBool)+ (x ::: TTParam, y ::: TTNum) -> Right (Gt (NCoerce x) y ::: TTBool)+ (x ::: TTNum, y ::: TTParam) -> Right (Gt x (NCoerce y) ::: TTBool)+ (x ::: TTParam, y ::: TTParam) -> Right (Gt (NCoerce x) (NCoerce y) ::: TTBool)+ _ -> error "type mismatch for comparison" interpret (UGte a b) = interpret (UOr (UGt a b) (UEq a b)) interpret (ULt a b) = interpret (UNot (UGte a b)) interpret (ULte a b) = interpret (UNot (UGt a b))@@ -102,11 +105,12 @@ val <- interpret a case val of (x ::: TTNum) -> Right (Contains x (L $ ttnums b) ::: TTBool)+ (x ::: TTUTCTime) -> Right (Contains x (L $ ttutcs b) ::: TTBool) (x ::: TTString) -> Right (Contains x (L $ ttstrs b) ::: TTBool) (x ::: TTParam) -> Right ((Or (Contains (SilentSCoerce x) (L $ ttstrs b)) (Contains (SilentNCoerce x) (L $ ttnums b))) ::: TTBool)- _ -> error "interpretation unsupported"+ _ -> error "interpretation unsupported for 'in'" interpret (UL as) = do -- TODO evaluate non-heterogeneous lists error "cannot safely evaluate list which may be heterogeneous"@@ -114,7 +118,7 @@ val <- interpret x case val of (x ::: TTBool) -> Right (Not x ::: TTBool)- _ -> error "typing error"+ _ -> error "expecting 'boolean' for Not clause" ttnums :: UExpr -> [TExpr Rational] ttnums (UL xs) = map (\(UN x) -> N x) (filter match xs)@@ -127,3 +131,9 @@ where match (US _) = True match _ = False ttstrs _ = []++ttutcs :: UExpr -> [TExpr UTCTime]+ttutcs (UL xs) = map (\(UT x) -> T x) (filter match xs)+ where match (UT _) = True+ match _ = False+ttutcs _ = []
Laborantin/Query/Parse.hs view
@@ -3,12 +3,16 @@ module Laborantin.Query.Parse (expr, parseUExpr) where import Laborantin.Types (UExpr (..))+import Laborantin.Query import Control.Applicative ((<$>),(<*>),(*>),(<*)) import qualified Data.Text as T import Text.Parsec import Text.Parsec.Char import Text.Parsec.Combinator-import Data.Maybe (fromJust)+import Data.Maybe+import System.Locale+import Data.Time+import Data.Time.Format parseUExpr :: String -> Either ParseError UExpr parseUExpr = parse expr ""@@ -21,7 +25,6 @@ negated :: Parsec String u UExpr negated = UNot <$> (spaces *> char '!' *> spaces *> expr) - ary :: Parsec String u UExpr ary = UL <$> (char '[' *> (expr `sepBy` (spaces >> char ',' >> spaces)) <* char ']') @@ -38,20 +41,38 @@ inclOp = binOp [("in",UContains), ("~>",UContains)] literal :: Parsec String u UExpr-literal = bool <|> (UN <$> number) <|> (US . T.pack <$> quotedString)+literal = try bool <|> date <|> (UN <$> number) <|> (US . T.pack <$> quotedString) +date :: Parsec String u UExpr+date = do+ string "t:"+ str <- quotedString+ let parsed = parseTimeFormats' defaultTimeLocale fmts str+ case parsed of+ [x] -> return $ UT x+ _ -> fail "invalid time format"+ where fmts = [ iso8601DateFormat Nothing+ , rfc822DateFormat+ , iso8601DateFormat (Just "%T")+ ]++parseTimeFormats' locale fmts str = take 1 . catMaybes $ parseResults+ where parseResults = map (\fmt -> parseTime locale fmt str) fmts+ special :: Parsec String u UExpr-special = try scname <|> try scstatus <|> scparam+special = try scname <|> try scstatus <|> try sctimestamp <|> scparam -scname,scstatus,scparam :: Parsec String u UExpr+scname,scstatus,scparam,sctimestamp :: Parsec String u UExpr scname = string "@sc.name" *> return UScName scstatus = string "@sc.status" *> return UScStatus scparam = UScParam . T.pack <$> (syntax1 <|> syntax2) where syntax1 = string "@sc.param" *> spaces *> quotedString syntax2 = char ':' *> plainString+sctimestamp = string "@sc.timestamp" *> return UScTimestamp quotedString :: Parsec String u String-quotedString = char '"' *> many (noneOf "\"") <* char '"'+quotedString = try (char '"' *> many (noneOf "\"") <* char '"')+ <|> (char '\'' *> many (noneOf "'") <* char '\'') plainString :: Parsec String u String plainString = many (noneOf " ")
Laborantin/Types.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE FlexibleInstances #-}@@ -10,7 +11,11 @@ , ParameterValue (..) , ParameterSpace , ParameterSet+ , emptyScenario+ , emptyParameter , paramSets+ , mergeParamSpaces+ , updateParam , expandValue , Result (..) , Backend (..)@@ -24,6 +29,7 @@ , Step , Action (..) , DynEnv (..)+ , emptyEnv , TExpr (..) , UExpr (..) , Dependency (..)@@ -32,12 +38,17 @@ import qualified Data.Map as M import Data.Time (UTCTime) import Control.Monad.Reader+import Control.Monad.State import Control.Monad.Error import Data.Dynamic import Data.Text (Text) import qualified Data.Text as T+import Data.List (nub) type DynEnv = M.Map Text Dynamic+emptyEnv :: DynEnv+emptyEnv = M.empty+ type ParameterSpace = M.Map Text ParameterDescription data ExecutionError = ExecutionError String deriving (Show)@@ -49,7 +60,7 @@ instance Error AnalysisError where noMsg = AnalysisError "A String Error!" strMsg = AnalysisError-type Step m a = ErrorT ExecutionError (ReaderT (Backend m,Execution m) m) a+type Step m a = (ErrorT ExecutionError (StateT DynEnv (ReaderT (Backend m,Execution m) m)) a) newtype Action m = Action { unAction :: Step m () } @@ -66,14 +77,21 @@ , sHooks :: M.Map Text (Action m) , sRecoveryAction :: Maybe (ExecutionError -> Action m) , sDeps :: [Dependency m]+ , sQuery :: TExpr Bool } deriving (Show) +emptyScenario :: ScenarioDescription m+emptyScenario = SDesc "" "" M.empty M.empty Nothing [] noQuery+ data ParameterDescription = PDesc { pName :: Text , pDesc :: Text , pValues :: [ParameterValue] } deriving (Show,Eq,Ord) +emptyParameter :: ParameterDescription+emptyParameter = PDesc "" "" []+ data ParameterValue = StringParam Text | NumberParam Rational | Array [ParameterValue]@@ -106,7 +124,7 @@ dName :: Text , dDesc :: Text , dCheck :: Execution m -> m Bool- , dSolve :: Execution m -> m ()+ , dSolve :: (Execution m, Backend m) -> m (Execution m) } instance Eq (Dependency m) where@@ -129,6 +147,16 @@ f (k,desc) = concatMap (map (pName desc,) . expandValue) $ pValues desc type Finalizer m = Execution m -> m () +mergeParamSpaces :: ParameterSpace -> ParameterSpace -> ParameterSpace+mergeParamSpaces ps1 ps2 = M.mergeWithKey f id id ps1 ps2+ where f k v1 v2 = Just (v1 { pValues = values })+ where values = nub $ (pValues v1) ++ (pValues v2)++-- | +updateParam :: ParameterSpace -> Text -> [ParameterValue] -> ParameterSpace+updateParam ps key values = M.updateWithKey f key ps+ where f k param = Just (param {pValues = values})+ data Backend m = Backend { bName :: Text , bPrepareExecution :: ScenarioDescription m -> ParameterSet -> m (Execution m,Finalizer m)@@ -177,6 +205,36 @@ SilentNCoerce :: TExpr (Text, Maybe ParameterValue) -> TExpr Rational TBind :: String -> (a -> TExpr b) -> TExpr a -> TExpr b +noQuery :: TExpr Bool+noQuery = B False++showTExpr :: TExpr a -> String+showTExpr (N x) = show x+showTExpr (B x) = show x+showTExpr (S x) = show x+showTExpr (L x) = show x+showTExpr (T x) = "t:" ++ show x+showTExpr (Not x) = "! " ++ "(" ++ showTExpr x ++ ")"+showTExpr (And e1 e2) = "(" ++ showTExpr e1 ++ " && " ++ showTExpr e2 ++ ")"+showTExpr (Or e1 e2) = "(" ++ showTExpr e1 ++ " || " ++ showTExpr e2 ++ ")"+showTExpr (Contains e1 e2) = "(" ++ showTExpr e1 ++ " in " ++ showTExpr e2 ++ ")"+showTExpr (Gt e1 e2) = "(" ++ showTExpr e1 ++ " > " ++ showTExpr e2 ++ ")"+showTExpr (Eq e1 e2) = "(" ++ showTExpr e1 ++ " == " ++ showTExpr e2 ++ ")"+showTExpr (Plus e1 e2) = "(" ++ showTExpr e1 ++ " + " ++ showTExpr e2 ++ ")"+showTExpr (Times e1 e2) = "(" ++ showTExpr e1 ++ " * " ++ showTExpr e2 ++ ")"+showTExpr ScName = "@sc.name"+showTExpr ScStatus = "@sc.status"+showTExpr ScTimestamp = "@sc.timestamp"+showTExpr (ScParam key) = "@sc.param:" ++ show key+showTExpr (SCoerce x) = "str!{"++(showTExpr x)++"}"+showTExpr (NCoerce x) = "num!{"++(showTExpr )x++"}"+showTExpr (SilentSCoerce x) = "str{"++(showTExpr x)++"}"+showTExpr (SilentNCoerce x) = "num{"++(showTExpr x)++"}"+showTExpr (TBind str f x) = "(" ++ str ++ " -> (" ++ showTExpr x ++ "))"++instance (Show (TExpr a)) where+ show = showTExpr+ data UExpr = UN Rational | UB Bool | US Text@@ -197,4 +255,32 @@ | UNot UExpr | UScName | UScStatus+ | UScTimestamp | UScParam Text++showUExpr :: UExpr -> String+showUExpr (UN x) = show x+showUExpr (UB x) = show x+showUExpr (US x) = show x+showUExpr (UL x) = show x+showUExpr (UT x) = "t:" ++ show x+showUExpr (UNot x) = "! " ++ "(" ++ showUExpr x ++ ")"+showUExpr (UAnd e1 e2) = "(" ++ showUExpr e1 ++ " and " ++ showUExpr e2 ++ ")"+showUExpr (UOr e1 e2) = "(" ++ showUExpr e1 ++ " or " ++ showUExpr e2 ++ ")"+showUExpr (UContains e1 e2) = "(" ++ showUExpr e1 ++ " in " ++ showUExpr e2 ++ ")"+showUExpr (UGt e1 e2) = "(" ++ showUExpr e1 ++ " > " ++ showUExpr e2 ++ ")"+showUExpr (UGte e1 e2) = "(" ++ showUExpr e1 ++ " >= " ++ showUExpr e2 ++ ")"+showUExpr (ULt e1 e2) = "(" ++ showUExpr e1 ++ " < " ++ showUExpr e2 ++ ")"+showUExpr (ULte e1 e2) = "(" ++ showUExpr e1 ++ " <= " ++ showUExpr e2 ++ ")"+showUExpr (UEq e1 e2) = "(" ++ showUExpr e1 ++ " == " ++ showUExpr e2 ++ ")"+showUExpr (UPlus e1 e2) = "(" ++ showUExpr e1 ++ " + " ++ showUExpr e2 ++ ")"+showUExpr (UMinus e1 e2) = "(" ++ showUExpr e1 ++ " - " ++ showUExpr e2 ++ ")"+showUExpr (UTimes e1 e2) = "(" ++ showUExpr e1 ++ " * " ++ showUExpr e2 ++ ")"+showUExpr (UDiv e1 e2) = "(" ++ showUExpr e1 ++ " / " ++ showUExpr e2 ++ ")"+showUExpr UScName = "@sc.name"+showUExpr UScStatus = "@sc.status"+showUExpr UScTimestamp = "@sc.timestamp"+showUExpr (UScParam key) = "@sc.param:" ++ show key++instance (Show UExpr) where+ show = showUExpr
README.md view
@@ -112,16 +112,8 @@ # Roadmap -For version 1.2--* Syntax to parse clocktime and with --date=today/this-week etc. types of flags-* Print better debug errors on parsing messages-* Function to expand an UExpr (or a TExpr ([(ParameterName,ParameterValue)]))into a ParameterSpace- - then need a function to extend a ParameterSpace with default values- ScenarioDescription -> ParameterSpace -> ParameterSpace-* Add a scSelector :: TExpr Bool field to a scenario to load ancestors at prepare-time- - "require" helper that sets a dep + selection-* exports to propose exported files using "show-exports" command+For version 0.1.4.x -# Annoyances/Bugs-* the "continue" mode doesn't do what it should anymore, don't use it for now+* [improvement] Use './results' as root dir in defaultBackend+* [improvement] Use system locale rather than default locale for time parsing+* [feature] exports to propose exported files using "show-exports" command
examples/main.hs view
@@ -3,22 +3,31 @@ module Main where -import Laborantin-import Laborantin.Types import Laborantin.DSL+import Laborantin.Types import Laborantin.Implementation import Laborantin.CLI-import Laborantin.Query-import Control.Monad.IO.Class import qualified Data.Text as T {- - Example -} +pong :: ScenarioDescription EnvIO+pong = scenario "pong" $ do+ parameter "foo" $ do+ describe "foo"+ values [str "foo"]+ setup (return ())+ run (return ())+ teardown (return ())+ recover (const (return ()))+ analyze (return ())+ ping :: ScenarioDescription EnvIO ping = scenario "ping" $ do describe "ping to a remote server"+ require pong "@sc.param 'foo' == 'foo'" parameter "destination" $ do describe "a destination server (host or ip)" values [str "example.com", str "probecraft.net", str "nonexistent"]@@ -28,10 +37,6 @@ parameter "burst-length" $ do describe "number of back-to-back packets to send" values [range 1 100 10] - dependency "dummy" $ do- describe "always true"- check (\exec -> return True)- resolve (\exec -> liftIO (print "resolving")) setup $ do setVar "hello" ("world"::String) dbg "setting up scenario"@@ -47,4 +52,4 @@ recover $ \err -> dbg $ T.append "here we could recover from error: " (T.pack $ show err) analyze $ liftIO . print $ "analyze action" -main = defaultMain [ping]+main = defaultMain [ping, pong]
laborantin-hs.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: laborantin-hs-version: 0.1.2.0+version: 0.1.3.0 synopsis: an experiment management framework description: Laborantin is a framework and DSL to run and manage results from scientific@@ -10,7 +10,7 @@ run /offline/ such as benchmark and batch analytics. . Writing experiments with Laborantin has at least two advantages over- rolling your own scripts. First, Laborantain standardizes the workflow of your+ rolling your own scripts. First, Laborantin standardizes the workflow of your experimentations. There is one-way to describe what a project can do, what experiments where already run, how to delete files corresponding to a specific experiment etc. Second, Laborantin builds on years of experience running@@ -62,7 +62,7 @@ exposed-modules: Laborantin, Laborantin.DSL, Laborantin.Implementation, Laborantin.Types, Laborantin.CLI, Laborantin.Query other-modules: Laborantin.Query.Parse, Laborantin.Query.Interpret other-extensions: FlexibleContexts, OverloadedStrings, TupleSections- build-depends: base >=4.6 && <4.7, transformers >=0.3 && <0.4, mtl >=2.1 && <2.2, containers >=0.5 && <0.6, text >=0.11 && <0.12, bytestring >=0.10 && <0.11, aeson >=0.6 && <0.7, uuid >=1.2 && <1.3, directory >=1.2 && <1.3, random >=1.0 && <1.1, hslogger >=1.2 && <1.3, cmdlib >= 0.3.5, split >= 0.2.2, time >= 1.4.0.1, parsec >= 3.1.0+ build-depends: base >=4.6 && <4.7, transformers >=0.3 && <0.4, mtl >=2.1 && <2.2, containers >=0.5 && <0.6, text >=0.11 && <0.12, bytestring >=0.10 && <0.11, aeson >=0.6 && <0.7, uuid >=1.2 && <1.3, directory >=1.2 && <1.3, random >=1.0 && <1.1, hslogger >=1.2 && <1.3, cmdlib >= 0.3.5, split >= 0.2.2, time >= 1.4.0.1, parsec >= 3.1.0, old-locale >=1.0.0.5 -- hs-source-dirs: default-language: Haskell2010