packages feed

laborantin-hs (empty) → 0.1.0.0

raw patch · 8 files changed

+696/−0 lines, 8 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, containers, directory, hslogger, mtl, random, text, transformers, uuid

Files

+ LICENSE view
@@ -0,0 +1,191 @@+Apache License+Version 2.0, January 2004+http://www.apache.org/licenses/++TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION++1. Definitions.++"License" shall mean the terms and conditions for use, reproduction, and+distribution as defined by Sections 1 through 9 of this document.++"Licensor" shall mean the copyright owner or entity authorized by the copyright+owner that is granting the License.++"Legal Entity" shall mean the union of the acting entity and all other entities+that control, are controlled by, or are under common control with that entity.+For the purposes of this definition, "control" means (i) the power, direct or+indirect, to cause the direction or management of such entity, whether by+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the+outstanding shares, or (iii) beneficial ownership of such entity.++"You" (or "Your") shall mean an individual or Legal Entity exercising+permissions granted by this License.++"Source" form shall mean the preferred form for making modifications, including+but not limited to software source code, documentation source, and configuration+files.++"Object" form shall mean any form resulting from mechanical transformation or+translation of a Source form, including but not limited to compiled object code,+generated documentation, and conversions to other media types.++"Work" shall mean the work of authorship, whether in Source or Object form, made+available under the License, as indicated by a copyright notice that is included+in or attached to the work (an example is provided in the Appendix below).++"Derivative Works" shall mean any work, whether in Source or Object form, that+is based on (or derived from) the Work and for which the editorial revisions,+annotations, elaborations, or other modifications represent, as a whole, an+original work of authorship. For the purposes of this License, Derivative Works+shall not include works that remain separable from, or merely link (or bind by+name) to the interfaces of, the Work and Derivative Works thereof.++"Contribution" shall mean any work of authorship, including the original version+of the Work and any modifications or additions to that Work or Derivative Works+thereof, that is intentionally submitted to Licensor for inclusion in the Work+by the copyright owner or by an individual or Legal Entity authorized to submit+on behalf of the copyright owner. For the purposes of this definition,+"submitted" means any form of electronic, verbal, or written communication sent+to the Licensor or its representatives, including but not limited to+communication on electronic mailing lists, source code control systems, and+issue tracking systems that are managed by, or on behalf of, the Licensor for+the purpose of discussing and improving the Work, but excluding communication+that is conspicuously marked or otherwise designated in writing by the copyright+owner as "Not a Contribution."++"Contributor" shall mean Licensor and any individual or Legal Entity on behalf+of whom a Contribution has been received by Licensor and subsequently+incorporated within the Work.++2. Grant of Copyright License.++Subject to the terms and conditions of this License, each Contributor hereby+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,+irrevocable copyright license to reproduce, prepare Derivative Works of,+publicly display, publicly perform, sublicense, and distribute the Work and such+Derivative Works in Source or Object form.++3. Grant of Patent License.++Subject to the terms and conditions of this License, each Contributor hereby+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,+irrevocable (except as stated in this section) patent license to make, have+made, use, offer to sell, sell, import, and otherwise transfer the Work, where+such license applies only to those patent claims licensable by such Contributor+that are necessarily infringed by their Contribution(s) alone or by combination+of their Contribution(s) with the Work to which such Contribution(s) was+submitted. If You institute patent litigation against any entity (including a+cross-claim or counterclaim in a lawsuit) alleging that the Work or a+Contribution incorporated within the Work constitutes direct or contributory+patent infringement, then any patent licenses granted to You under this License+for that Work shall terminate as of the date such litigation is filed.++4. Redistribution.++You may reproduce and distribute copies of the Work or Derivative Works thereof+in any medium, with or without modifications, and in Source or Object form,+provided that You meet the following conditions:++You must give any other recipients of the Work or Derivative Works a copy of+this License; and+You must cause any modified files to carry prominent notices stating that You+changed the files; and+You must retain, in the Source form of any Derivative Works that You distribute,+all copyright, patent, trademark, and attribution notices from the Source form+of the Work, excluding those notices that do not pertain to any part of the+Derivative Works; and+If the Work includes a "NOTICE" text file as part of its distribution, then any+Derivative Works that You distribute must include a readable copy of the+attribution notices contained within such NOTICE file, excluding those notices+that do not pertain to any part of the Derivative Works, in at least one of the+following places: within a NOTICE text file distributed as part of the+Derivative Works; within the Source form or documentation, if provided along+with the Derivative Works; or, within a display generated by the Derivative+Works, if and wherever such third-party notices normally appear. The contents of+the NOTICE file are for informational purposes only and do not modify the+License. You may add Your own attribution notices within Derivative Works that+You distribute, alongside or as an addendum to the NOTICE text from the Work,+provided that such additional attribution notices cannot be construed as+modifying the License.+You may add Your own copyright statement to Your modifications and may provide+additional or different license terms and conditions for use, reproduction, or+distribution of Your modifications, or for any such Derivative Works as a whole,+provided Your use, reproduction, and distribution of the Work otherwise complies+with the conditions stated in this License.++5. Submission of Contributions.++Unless You explicitly state otherwise, any Contribution intentionally submitted+for inclusion in the Work by You to the Licensor shall be under the terms and+conditions of this License, without any additional terms or conditions.+Notwithstanding the above, nothing herein shall supersede or modify the terms of+any separate license agreement you may have executed with Licensor regarding+such Contributions.++6. Trademarks.++This License does not grant permission to use the trade names, trademarks,+service marks, or product names of the Licensor, except as required for+reasonable and customary use in describing the origin of the Work and+reproducing the content of the NOTICE file.++7. Disclaimer of Warranty.++Unless required by applicable law or agreed to in writing, Licensor provides the+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,+including, without limitation, any warranties or conditions of TITLE,+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are+solely responsible for determining the appropriateness of using or+redistributing the Work and assume any risks associated with Your exercise of+permissions under this License.++8. Limitation of Liability.++In no event and under no legal theory, whether in tort (including negligence),+contract, or otherwise, unless required by applicable law (such as deliberate+and grossly negligent acts) or agreed to in writing, shall any Contributor be+liable to You for damages, including any direct, indirect, special, incidental,+or consequential damages of any character arising as a result of this License or+out of the use or inability to use the Work (including but not limited to+damages for loss of goodwill, work stoppage, computer failure or malfunction, or+any and all other commercial damages or losses), even if such Contributor has+been advised of the possibility of such damages.++9. Accepting Warranty or Additional Liability.++While redistributing the Work or Derivative Works thereof, You may choose to+offer, and charge a fee for, acceptance of support, warranty, indemnity, or+other liability obligations and/or rights consistent with this License. However,+in accepting such obligations, You may act only on Your own behalf and on Your+sole responsibility, not on behalf of any other Contributor, and only if You+agree to indemnify, defend, and hold each Contributor harmless for any liability+incurred by, or claims asserted against, such Contributor by reason of your+accepting any such warranty or additional liability.++END OF TERMS AND CONDITIONS++APPENDIX: How to apply the Apache License to your work++To apply the Apache License to your work, attach the following boilerplate+notice, with the fields enclosed by brackets "[]" replaced with your own+identifying information. (Don't include the brackets!) The text should be+enclosed in the appropriate comment syntax for the file format. We also+recommend that a file or class name and description of purpose be included on+the same "printed page" as the copyright notice for easier identification within+third-party archives.++   Copyright [yyyy] [name of copyright owner]++   Licensed under the Apache License, Version 2.0 (the "License");+   you may not use this file except in compliance with the License.+   You may obtain a copy of the License at++     http://www.apache.org/licenses/LICENSE-2.0++   Unless required by applicable law or agreed to in writing, software+   distributed under the License is distributed on an "AS IS" BASIS,+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+   See the License for the specific language governing permissions and+   limitations under the License.
+ Laborantin.hs view
@@ -0,0 +1,40 @@++module Laborantin where++import Laborantin.Types+import Laborantin.DSL+import Laborantin.Implementation+import Control.Monad.IO.Class+import Control.Monad.Reader+import Control.Monad.Error+import qualified Data.Set as S++execute :: (MonadIO m) => Backend m -> ScenarioDescription m -> ParameterSet -> 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)+            let exec' = either (\_ -> exec {eStatus = Failure}) (\_ -> exec {eStatus = Success}) status+            bFinalizeExecution b exec' final+            where go exec = do +                        bSetup b exec+                        bRun b exec+                        bTeardown b exec+                        bAnalyze b exec+                  recover exec err = bRecover b exec >> throwError err++executeExhaustive :: (MonadIO m) => Backend m -> ScenarioDescription m -> m ()+executeExhaustive b sc = mapM_ f $ paramSets $ sParams sc+    where f = execute b sc ++executeMissing :: (MonadIO m) => Backend m -> ScenarioDescription m -> m ()+executeMissing b sc = do+    execs <- load b sc+    let exhaustive = S.fromList $ paramSets (sParams sc)+    let existing = S.fromList $ map eParamSet execs+    mapM_ f $ S.toList (exhaustive `S.difference` existing)+    where f = execute b sc+++load :: (MonadIO m) => Backend m -> ScenarioDescription m -> m [Execution m]+load = bLoad
+ Laborantin/DSL.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE FlexibleContexts #-}++module Laborantin.DSL (+        scenario+    ,   describe+    ,   parameter+    ,   values+    ,   str+    ,   num+    ,   range+    ,   arr+    ,   setup+    ,   teardown+    ,   run+    ,   param+    ,   getVar+    ,   setVar+    ,   recover+    ,   analyze+    ,   result+    ,   writeResult+    ,   appendResult+    ,   logger+    ,   dbg+) where++import qualified Data.Map as M+import Laborantin.Types+import Control.Monad.State+import Control.Monad.Reader+import Control.Monad.Error+import Control.Applicative+import Data.Dynamic++class Describable a where+  changeDescription :: String -> a -> a++instance Describable (ScenarioDescription a) where+  changeDescription d sc = sc { sDesc = d }++instance Describable ParameterDescription where+  changeDescription d pa = pa { pDesc = d }++-- | DSL entry point to build a 'ScenarioDescription'.+scenario :: String -> State (ScenarioDescription m) () -> ScenarioDescription m+scenario name f = execState f sc0+  where sc0 = SDesc name "" M.empty M.empty++-- | Attach a description to the 'Parameter' / 'Scnario'+describe :: Describable a => String -> State a ()+describe desc = modify (changeDescription desc)++-- | DSL entry point to build a 'ParameterDescription' within a scenario.+parameter :: String -> State ParameterDescription () -> State (ScenarioDescription m) ()+parameter name f = modify (addParam name param)+  where addParam k v sc0 = sc0 { sParams = M.insert k v (sParams sc0) }+        param = execState f param0+                where param0 = PDesc name "" []++-- | Set default values for the paramater+values :: [ParameterValue] -> State ParameterDescription ()+values xs = do+  param0 <- get+  put $ param0 { pValues = xs }++-- | Encapsulate a String as a 'ParameterValue'+str :: String -> ParameterValue+str = StringParam++-- | Encapsulate an integer value as a 'ParameterValue'+num :: Integer -> ParameterValue+num = NumberParam . fromInteger++-- | Encapsulate a range as a 'ParameterValue'+range :: Rational -> Rational -> Rational -> ParameterValue+range = Range++-- | Encapsulate an array of 'str' or 'num' values as a 'ParameterValue'+arr :: [ParameterValue] -> ParameterValue+arr = Array++-- | Define the setup hook for this scenario+setup :: Step m () -> State (ScenarioDescription m) ()+setup = appendHook "setup"++-- | Define the main run hook for this scenario+run :: Step m () -> State (ScenarioDescription m) ()+run = appendHook "run"++-- | Define the teardown hook for this scenario+teardown :: Step m () -> State (ScenarioDescription m) ()+teardown  = appendHook "teardown"++-- | Define the recovery hook for this scenario+recover :: Step m () -> State (ScenarioDescription m) ()+recover  = appendHook "recover"++-- | Define the offline analysis hook for this scenario+analyze :: Step m () -> State (ScenarioDescription m) ()+analyze = appendHook "analyze"++appendHook :: String -> 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) }++-- | Returns a 'Result' object for the given name.+--+-- Implementations will return their specific results.+result :: Monad m => String -> Step m (Result m)+result name = do +  (b,r) <- ask+  bResult b r name++-- | Write (overwrite) the result in its entirety.+--+-- Implementations will return their specific results.+writeResult :: Monad m => String  -- ^ result name+                       -> String  -- ^ result content+                       -> Step m ()+writeResult name dat = result name >>= flip pWrite dat++-- | Appends a chunk of data to the result. +--+-- Implementations will return their specific results.+appendResult :: Monad m => String -- ^ result name+                        -> String -- ^ content to add+                        -> Step m ()+appendResult name dat = result name >>= flip pAppend dat++-- | Return a 'LogHandler' object for this scenario.+logger :: Monad m => Step m (LogHandler m)+logger = ask >>= uncurry bLogger++-- | Sends a line of data to the logger (debug mode)+dbg :: Monad m => String -> Step m ()+dbg msg = logger >>= flip lLog msg++-- | Get the parameter with given name.+-- Throw an error if the parameter is missing.+param :: Monad m => String -- ^ the parameter name+                 -> Step m ParameterValue+param key = do+    ret <- liftM (M.lookup key . eParamSet . snd) ask+    maybe (throwError $ ExecutionError $ "missing param: " ++ key) return ret++getVar' :: (Functor m, MonadState DynEnv m) => String -> m (Maybe Dynamic)+getVar' k = M.lookup k <$> get++setVar' :: (MonadState DynEnv m) => String -> Dynamic -> m ()+setVar' k v = modify (M.insert k v)++-- | Set an execution variable.+setVar :: (Typeable v, MonadState DynEnv m) =>+            String -- ^ name of the variable+         -> v      -- ^ value of the variable+         -> m ()+setVar k v = setVar' k (toDyn v)++-- | Get an execution variable and tries to cast it from it's Dynamic+-- representation.+--+-- Returns 'Nothing' if the variable is missing or if it could not+-- be cast to the wanted type.+getVar :: (Typeable v, Functor m, MonadState DynEnv m) => +            String              -- ^ name of the variable+         -> m (Maybe v)      +getVar k = maybe Nothing fromDynamic <$> getVar' k+
+ Laborantin/Implementation.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE OverloadedStrings #-}++module Laborantin.Implementation (+        EnvIO, runEnvIO+    , defaultBackend+    , defaultResult+    , defaultLog+) where++import qualified Data.Map as M+import qualified Data.Text as T+import qualified Data.ByteString.Lazy as BSL+import Laborantin.Types+import Data.Aeson (decode,encode,FromJSON,parseJSON,(.:),ToJSON,toJSON,(.=),object)+import qualified Data.Aeson as A+import qualified Data.Aeson.Types as A+import Control.Monad.State+import Control.Applicative ((<$>),(<*>))+import Data.List+import Data.UUID+import System.Directory+import System.Random+import System.Log.Logger+import System.Log.Handler (close)+import System.Log.Handler.Simple+import System.Log.Handler.Log4jXML++-- | Default monad for 'defaultBackend'.+--   EnvIO carries a 'DynEnv' in a state and allows you to perform IO actions.+type EnvIO = (StateT DynEnv IO)++-- | Execute an EnvIO action in IO.+runEnvIO :: EnvIO () -> IO DynEnv+runEnvIO m = snd <$> runStateT m M.empty++instance ToJSON ParameterValue where+    toJSON (StringParam str) = object ["type" .= ("string"::T.Text), "val" .= T.pack str]+    toJSON (NumberParam n)   = object ["type" .= ("num"::T.Text), "val" .= n]+    toJSON (Array xs)        = toJSON xs+    toJSON (Range _ _ _)     = error "should not have to encode ranges but concrete values instead"++instance ToJSON ExecutionStatus where+    toJSON = toJSON . show++instance ToJSON (Execution a) where+    toJSON (Exec sc params path status) = object    [ "scenario-name" .= sName sc+                                                    , "params" .= params+                                                    , "path" .= path+                                                    , "status" .= status+                                                    ] ++instance FromJSON ParameterValue where+    parseJSON (A.Object v) = (v .: "type") >>= match+        where match :: T.Text -> A.Parser ParameterValue+              match "string" = StringParam <$> v .: "val"+              match "num"    = NumberParam <$> v .: "val"+              match "range"  = error "should not have to read ranges"+              match _        = mzero+    +    parseJSON _ = mzero++instance FromJSON ExecutionStatus where+    parseJSON (A.String txt) = return $ read $ T.unpack txt+    parseJSON _ = mzero++instance FromJSON StoredExecution where+    parseJSON (A.Object v) = Stored <$>+                               v .: "params" <*>+                               v .: "path" <*>+                               v .: "status"+    parseJSON _          = mzero++-- | Default backend for the 'EnvIO' monad.  This backend uses the filesystem+-- as storage and UUIDs for scenario instances (supposes that UUID collision+-- cannot happen).+--+-- Parameters, logfiles, and result data all are stored in a unique directory named+-- ./<scenario-name>/<uuid>+-- +-- Results are individual files in this directory. There is no namespacing+-- hence avoid the following names: 'execution.json', 'execution-log.txt', and+-- 'execution-log.xml'. These three files are the scenario execution metadata+-- and logs.+--+defaultBackend :: Backend EnvIO+defaultBackend = Backend "default EnvIO backend" prepare finalize setup run teardown analyze recover result load log+  where prepare :: ScenarioDescription EnvIO -> ParameterSet -> EnvIO (Execution EnvIO,Finalizer EnvIO)+        prepare sc params = do+                  uuid <- liftIO (randomIO :: IO UUID)+                  let rundir = intercalate "/" [sName sc, show uuid]+                  let exec = Exec sc params rundir Running+                  handles <- liftIO $ do+                    createDirectoryIfMissing True rundir+                    BSL.writeFile (rundir ++ "/execution.json") (encode exec)+                    updateGlobalLogger (loggerName exec) (setLevel DEBUG)+                    h1 <- fileHandler (rundir ++ "/execution-log.txt") DEBUG+                    h2 <- log4jFileHandler (rundir ++ "/execution-log.xml") DEBUG+                    forM_ [h1,h2] (updateGlobalLogger (loggerName exec) . addHandler)+                    putStrLn $ advertise exec+                    return [h1,h2]+                  return (exec, \_ -> liftIO $ forM_ handles close)+                  where advertise exec = unlines $ map ($ exec) [show. sName . eScenario+                                                , show . eParamSet+                                                , ePath]+        finalize  exec finalizer = do+                            finalizer exec+                            liftIO . putStrLn $ "done"+                            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           = callHooks "analyze" . eScenario+        recover           = callHooks "recover" . eScenario+        callHooks key sc  = maybe (error $ "no such hook: " ++ key) unAction (M.lookup key $ sHooks sc)+        result exec       = return . defaultResult exec++        load :: ScenarioDescription EnvIO -> EnvIO [Execution EnvIO]+        load sc           = liftIO $ do+            dirs <- filter notDot <$> getDirectoryContents (sName sc)+            let paths = map ((sName sc ++ "/") ++) dirs+            mapM loadOne paths+            where notDot dirname = take 1 dirname /= "."+                  loadOne path = do+                    exec' <- liftM forStored . decode <$> BSL.readFile (path ++ "/execution.json")+                    maybe (error $ "decoding: " ++ path) return exec'++                    where forStored (Stored params path status) = Exec sc params path status+        log exec          = return $ defaultLog exec++-- | Default result handler for the 'EnvIO' monad (see 'defaultBackend').+defaultResult :: Execution m -> String -> Result EnvIO+defaultResult exec name = Result path read append write+  where read        = liftIO $ readFile path+        append dat  = liftIO $ appendFile path dat+        write dat   = liftIO $ writeFile path dat+        path        = intercalate "/" [ePath exec, name]++-- | Default logger for the 'EnvIO' monad (see 'defaultBackend').+defaultLog :: Execution m -> LogHandler EnvIO+defaultLog exec = LogHandler logF+    where logF msg = liftIO $ debugM (loggerName exec) msg+          path = ePath exec ++ "/execution.log"++loggerName :: Execution m -> String+loggerName exec = "laborantin:" ++ ePath exec
+ Laborantin/Types.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE TupleSections #-}++module Laborantin.Types (+        ScenarioDescription (..)+    ,   ParameterDescription (..)+    ,   ParameterValue (..)+    ,   ParameterSpace+    ,   ParameterSet+    ,   paramSets+    ,   Result (..)+    ,   Backend (..)+    ,   Execution (..)+    ,   StoredExecution (..)+    ,   ExecutionError (..)+    ,   ExecutionStatus (..)+    ,   Finalizer (..)+    ,   LogHandler (..)+    ,   Step+    ,   Action (..)+    ,   DynEnv (..)+) where++import qualified Data.Map as M+import Control.Monad.Reader+import Control.Monad.Error+import Data.Dynamic++type DynEnv = M.Map String Dynamic+type ParameterSpace = M.Map String ParameterDescription+data ExecutionError = ExecutionError String+    deriving (Show)+instance Error ExecutionError where+  noMsg    = ExecutionError "A String Error!"+  strMsg   = ExecutionError+type Step m a = ErrorT ExecutionError (ReaderT (Backend m,Execution m) m) a++newtype Action m = Action { unAction :: Step m () }++instance Show (Action m) where+  show _ = "(Action)"++data ScenarioDescription m = SDesc {+    sName   :: String+  , sDesc   :: String+  , sParams :: ParameterSpace+  , sHooks  :: M.Map String (Action m)+  } deriving (Show)++data ParameterDescription = PDesc {+    pName   :: String+  , pDesc   :: String+  , pValues :: [ParameterValue]+  } deriving (Show,Eq,Ord)++data ParameterValue = StringParam String +  | NumberParam Rational+  | Array [ParameterValue]+  | Range Rational Rational Rational -- [from, to], by increment+  deriving (Show,Eq,Ord)++type ParameterSet = M.Map String ParameterValue++data ExecutionStatus = Running | Success | Failure +  deriving (Show,Read)++data Execution m = Exec {+    eScenario :: ScenarioDescription m+  , eParamSet :: ParameterSet+  , ePath     :: String+  , eStatus   :: ExecutionStatus+} deriving (Show)++data StoredExecution = Stored {+    seParamSet :: ParameterSet+  , sePath     :: String+  , seStatus   :: ExecutionStatus+} deriving (Show)++expandValue :: ParameterValue -> [ParameterValue]+expandValue (Range from to by)  = map NumberParam [from,from+by .. to]+expandValue x                   = [x]++paramSets :: ParameterSpace -> [ParameterSet]+paramSets ps = map M.fromList $ sequence possibleValues+    where possibleValues = map f $ M.toList ps+          f (k,desc) = concatMap (map (pName desc,) . expandValue) $ pValues desc+type Finalizer m = Execution m -> m ()++data Backend m = Backend {+    bName      :: String+  , bPrepareExecution  :: ScenarioDescription m -> ParameterSet -> m (Execution m,Finalizer m)+  , bFinalizeExecution :: Execution m -> Finalizer m -> m ()+  , bSetup     :: Execution m -> Step m ()+  , bRun       :: Execution m -> Step m ()+  , bTeardown  :: Execution m -> Step m ()+  , bAnalyze   :: Execution m -> Step m ()+  , bRecover   :: Execution m -> Step m ()+  , bResult    :: Execution m -> String -> Step m (Result m)+  , bLoad      :: ScenarioDescription m -> m [Execution m]+  , bLogger    :: Execution m -> Step m (LogHandler m)+}++data Result m = Result {+    pPath   :: String+  , pRead   :: Step m String+  , pAppend :: String -> Step m ()+  , pWrite  :: String -> Step m ()+}++newtype LogHandler m = LogHandler { lLog :: String -> Step m () }
+ README.md view
@@ -0,0 +1,14 @@+laborantin-hs+=============++Haskell meets Laborantin+++# TODO++* more doc+* use Text rather than String where appropriate+* defaultMain with run/describe/find/analyze/rm modes+* in-memory (pure?) backend+* dependency annotations+* configuration reader
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ laborantin-hs.cabal view
@@ -0,0 +1,25 @@+-- Initial laborantin-hs.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                laborantin-hs+version:             0.1.0.0+synopsis:            an experiment management framework+-- description:         +homepage:            https://github.com/lucasdicioccio/laborantin-hs+license:             Apache-2.0+license-file:        LICENSE+author:              lucas dicioccio+maintainer:          lucas@dicioccio.fr+-- copyright:           +category:            Experiment+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  exposed-modules:     Laborantin, Laborantin.DSL, Laborantin.Implementation, Laborantin.Types+  -- other-modules:       +  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+  -- hs-source-dirs:      +  default-language:    Haskell2010