packages feed

b9 0.5.44 → 0.5.46

raw patch · 18 files changed

+571/−480 lines, 18 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- B9.B9Config: [_profileFile] :: B9Config -> Maybe FilePath
- B9.B9Config: b9ConfigToConfigParser :: B9Config -> Either CPError ConfigParser
- B9.B9Config: modifyConfigParser :: ConfigParser -> Endo B9Config -> Either CPError ConfigParser
- B9.B9Config: profileFile :: Lens' B9Config (Maybe FilePath)
- B9.B9Config: writeB9ConfigParser :: MonadIO m => Maybe SystemPath -> ConfigParser -> m ()
- B9.B9Config.LibVirtLXC: libVirtLXCConfigToConfigParser :: LibVirtLXCConfig -> ConfigParser -> Either CPError ConfigParser
- B9.B9Config.Repository: remoteRepoToConfigParser :: RemoteRepo -> ConfigParser -> Either CPError ConfigParser
- B9.B9Monad: instance GHC.Classes.Eq B9.B9Monad.ProfilingEntry
- B9.B9Monad: instance GHC.Show.Show B9.B9Monad.ProfilingEntry
- B9.DiskImages: siBuildId :: SharedImage -> SharedImageBuildId
- B9.DiskImages: siDate :: SharedImage -> SharedImageDate
- B9.DiskImages: siName :: SharedImage -> SharedImageName
- Data.ConfigFile.B9Extras: InB9UserDir :: FilePath -> SystemPath
- Data.ConfigFile.B9Extras: InHomeDir :: FilePath -> SystemPath
- Data.ConfigFile.B9Extras: InTempDir :: FilePath -> SystemPath
- Data.ConfigFile.B9Extras: IniFileException :: FilePath -> CPError -> IniFileException
- Data.ConfigFile.B9Extras: Path :: FilePath -> SystemPath
- Data.ConfigFile.B9Extras: UUID :: (Word32, Word16, Word16, Word16, Word32, Word16) -> UUID
- Data.ConfigFile.B9Extras: allOn :: (a -> Maybe Bool) -> a -> a -> Maybe Bool
- Data.ConfigFile.B9Extras: consult :: (MonadIO m, Read a) => FilePath -> m a
- Data.ConfigFile.B9Extras: data IniFileException
- Data.ConfigFile.B9Extras: data SystemPath
- Data.ConfigFile.B9Extras: ensureDir :: MonadIO m => FilePath -> m ()
- Data.ConfigFile.B9Extras: getDirectoryFiles :: MonadIO m => FilePath -> m [FilePath]
- Data.ConfigFile.B9Extras: getOption :: (Get_C a, Monoid a) => ConfigParser -> SectionSpec -> OptionSpec -> a
- Data.ConfigFile.B9Extras: getOptionM :: (Read a) => ConfigParser -> SectionSpec -> OptionSpec -> Maybe a
- Data.ConfigFile.B9Extras: getOptionOr :: (Get_C a) => ConfigParser -> SectionSpec -> OptionSpec -> a -> a
- Data.ConfigFile.B9Extras: instance Data.Data.Data Data.ConfigFile.B9Extras.SystemPath
- Data.ConfigFile.B9Extras: instance GHC.Classes.Eq Data.ConfigFile.B9Extras.SystemPath
- Data.ConfigFile.B9Extras: instance GHC.Classes.Eq Data.ConfigFile.B9Extras.UUID
- Data.ConfigFile.B9Extras: instance GHC.Classes.Ord Data.ConfigFile.B9Extras.UUID
- Data.ConfigFile.B9Extras: instance GHC.Exception.Exception Data.ConfigFile.B9Extras.IniFileException
- Data.ConfigFile.B9Extras: instance GHC.Exception.Exception Data.ConfigFile.B9Extras.ReaderException
- Data.ConfigFile.B9Extras: instance GHC.Read.Read Data.ConfigFile.B9Extras.SystemPath
- Data.ConfigFile.B9Extras: instance GHC.Read.Read Data.ConfigFile.B9Extras.UUID
- Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.IniFileException
- Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.ReaderException
- Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.SystemPath
- Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.UUID
- Data.ConfigFile.B9Extras: instance Text.Printf.PrintfArg Data.ConfigFile.B9Extras.UUID
- Data.ConfigFile.B9Extras: lastOn :: (a -> Maybe b) -> a -> a -> Maybe b
- Data.ConfigFile.B9Extras: maybeConsult :: (MonadIO m, Read a) => Maybe FilePath -> a -> m a
- Data.ConfigFile.B9Extras: maybeConsultSystemPath :: (MonadIO m, Read a) => Maybe SystemPath -> a -> m a
- Data.ConfigFile.B9Extras: newtype UUID
- Data.ConfigFile.B9Extras: randomUUID :: MonadIO m => m UUID
- Data.ConfigFile.B9Extras: readIniFile :: MonadIO m => SystemPath -> m ConfigParser
- Data.ConfigFile.B9Extras: resolve :: MonadIO m => SystemPath -> m FilePath
- Data.ConfigFile.B9Extras: tell :: (MonadIO m, Show a) => FilePath -> a -> m ()
+ B9.B9Config: [_maxLocalSharedImageRevisions] :: B9Config -> Maybe Int
+ B9.B9Config: b9ConfigToCPDocument :: B9Config -> Either CPError CPDocument
+ B9.B9Config: maxLocalSharedImageRevisions :: Lens' B9Config (Maybe Int)
+ B9.B9Config: modifyCPDocument :: CPDocument -> Endo B9Config -> Either CPError CPDocument
+ B9.B9Config: writeB9CPDocument :: MonadIO m => Maybe SystemPath -> CPDocument -> m ()
+ B9.B9Config.LibVirtLXC: libVirtLXCConfigToCPDocument :: LibVirtLXCConfig -> CPDocument -> Either CPError CPDocument
+ B9.B9Config.Repository: remoteRepoToCPDocument :: RemoteRepo -> CPDocument -> Either CPError CPDocument
+ B9.DiskImages: sharedImageBuildId :: SharedImage -> SharedImageBuildId
+ B9.DiskImages: sharedImageDate :: SharedImage -> SharedImageDate
+ B9.DiskImages: sharedImageName :: SharedImage -> SharedImageName
+ Data.ConfigFile.B9Extras: CPReadException :: FilePath -> CPError -> CPReadException
+ Data.ConfigFile.B9Extras: addSectionCP :: MonadError CPError m => CPDocument -> CPSectionSpec -> m CPDocument
+ Data.ConfigFile.B9Extras: data CPReadException
+ Data.ConfigFile.B9Extras: emptyCP :: ConfigParser
+ Data.ConfigFile.B9Extras: instance GHC.Exception.Exception Data.ConfigFile.B9Extras.CPReadException
+ Data.ConfigFile.B9Extras: instance GHC.Show.Show Data.ConfigFile.B9Extras.CPReadException
+ Data.ConfigFile.B9Extras: mergeCP :: CPDocument -> CPDocument -> CPDocument
+ Data.ConfigFile.B9Extras: readCP :: (CPGet a, MonadError CPError m) => CPDocument -> CPSectionSpec -> CPOptionSpec -> m a
+ Data.ConfigFile.B9Extras: readCPDocument :: MonadIO m => SystemPath -> m CPDocument
+ Data.ConfigFile.B9Extras: sectionsCP :: CPDocument -> [SectionSpec]
+ Data.ConfigFile.B9Extras: setCP :: (MonadError CPError m) => CPDocument -> CPSectionSpec -> CPOptionSpec -> String -> m CPDocument
+ Data.ConfigFile.B9Extras: setShowCP :: (Show a, MonadError CPError m) => CPDocument -> CPSectionSpec -> CPOptionSpec -> a -> m CPDocument
+ Data.ConfigFile.B9Extras: toStringCP :: CPDocument -> String
+ Data.ConfigFile.B9Extras: type CPDocument = ConfigParser
+ Data.ConfigFile.B9Extras: type CPError = (CPErrorData, String)
+ Data.ConfigFile.B9Extras: type CPGet a = Get_C a
+ Data.ConfigFile.B9Extras: type CPOptionSpec = OptionSpec
+ Data.ConfigFile.B9Extras: type CPSectionSpec = SectionSpec
+ System.IO.B9Extras: ConsultException :: FilePath -> String -> ConsultException
+ System.IO.B9Extras: InB9UserDir :: FilePath -> SystemPath
+ System.IO.B9Extras: InHomeDir :: FilePath -> SystemPath
+ System.IO.B9Extras: InTempDir :: FilePath -> SystemPath
+ System.IO.B9Extras: Path :: FilePath -> SystemPath
+ System.IO.B9Extras: consult :: (MonadIO m, Read a) => FilePath -> m a
+ System.IO.B9Extras: data ConsultException
+ System.IO.B9Extras: data SystemPath
+ System.IO.B9Extras: data UUID
+ System.IO.B9Extras: ensureDir :: MonadIO m => FilePath -> m ()
+ System.IO.B9Extras: getDirectoryFiles :: MonadIO m => FilePath -> m [FilePath]
+ System.IO.B9Extras: instance Data.Data.Data System.IO.B9Extras.SystemPath
+ System.IO.B9Extras: instance GHC.Classes.Eq System.IO.B9Extras.SystemPath
+ System.IO.B9Extras: instance GHC.Classes.Eq System.IO.B9Extras.UUID
+ System.IO.B9Extras: instance GHC.Classes.Ord System.IO.B9Extras.UUID
+ System.IO.B9Extras: instance GHC.Exception.Exception System.IO.B9Extras.ConsultException
+ System.IO.B9Extras: instance GHC.Read.Read System.IO.B9Extras.SystemPath
+ System.IO.B9Extras: instance GHC.Read.Read System.IO.B9Extras.UUID
+ System.IO.B9Extras: instance GHC.Show.Show System.IO.B9Extras.ConsultException
+ System.IO.B9Extras: instance GHC.Show.Show System.IO.B9Extras.SystemPath
+ System.IO.B9Extras: instance GHC.Show.Show System.IO.B9Extras.UUID
+ System.IO.B9Extras: instance Text.Printf.PrintfArg System.IO.B9Extras.UUID
+ System.IO.B9Extras: prettyPrintToFile :: (MonadIO m, Show a) => FilePath -> a -> m ()
+ System.IO.B9Extras: randomUUID :: MonadIO m => m UUID
+ System.IO.B9Extras: resolve :: MonadIO m => SystemPath -> m FilePath
- B9.B9Config: B9Config :: Maybe LogLevel -> Maybe FilePath -> Maybe FilePath -> Bool -> ExecEnvType -> Maybe FilePath -> BuildVariables -> Bool -> Maybe SystemPath -> Maybe String -> Bool -> Maybe LibVirtLXCConfig -> [RemoteRepo] -> B9Config
+ B9.B9Config: B9Config :: Maybe LogLevel -> Maybe FilePath -> Maybe FilePath -> Bool -> ExecEnvType -> BuildVariables -> Bool -> Maybe SystemPath -> Maybe String -> Bool -> Maybe Int -> Maybe LibVirtLXCConfig -> [RemoteRepo] -> B9Config
- B9.B9Config: openOrCreateB9Config :: MonadIO m => Maybe SystemPath -> m ConfigParser
+ B9.B9Config: openOrCreateB9Config :: MonadIO m => Maybe SystemPath -> m CPDocument
- B9.B9Config: parseB9Config :: ConfigParser -> Either CPError B9Config
+ B9.B9Config: parseB9Config :: CPDocument -> Either CPError B9Config
- B9.B9Config: readB9Config :: MonadIO m => Maybe SystemPath -> m ConfigParser
+ B9.B9Config: readB9Config :: MonadIO m => Maybe SystemPath -> m CPDocument
- B9.B9Config.LibVirtLXC: parseLibVirtLXCConfig :: ConfigParser -> Either CPError LibVirtLXCConfig
+ B9.B9Config.LibVirtLXC: parseLibVirtLXCConfig :: CPDocument -> Either CPError LibVirtLXCConfig
- B9.B9Config.Repository: parseRemoteRepos :: ConfigParser -> Either CPError [RemoteRepo]
+ B9.B9Config.Repository: parseRemoteRepos :: CPDocument -> Either CPError [RemoteRepo]
- B9.B9Monad: errorExitL :: String -> B9 ()
+ B9.B9Monad: errorExitL :: String -> B9 a
- B9.DiskImageBuilder: ensureAbsoluteImageDirExists :: Image -> IO Image
+ B9.DiskImageBuilder: ensureAbsoluteImageDirExists :: Image -> B9 Image
- B9.DiskImageBuilder: pullLatestImage :: SharedImageName -> B9 Bool
+ B9.DiskImageBuilder: pullLatestImage :: SharedImageName -> B9 (Maybe SharedImageBuildId)

Files

b9.cabal view
@@ -1,5 +1,5 @@ name:                b9-version:             0.5.44+version:             0.5.46  synopsis:            A tool and library for building virtual machine images. @@ -98,6 +98,7 @@                    , B9.Vm                    , B9.VmBuilder                    , Data.ConfigFile.B9Extras+                   , System.IO.B9Extras   other-modules:   Paths_b9   -- other-extensions:   build-depends:     ConfigFile >= 1.1.4
src/cli/Main.hs view
@@ -7,7 +7,7 @@  main :: IO () main = do-    b9Opts  <- parseCommandLine+    b9Opts <- parseCommandLine     applyB9RunParameters b9Opts  @@ -40,7 +40,7 @@         (helper <*> (B9RunParameters <$> globals <*> cmds <*> buildVars))         (  fullDesc         <> progDesc-               "Build and run VM-Images inside LXC containers. Custom arguments follow after '--' and are accessable in many strings in build files  trough shell like variable references, i.e. '${arg_N}' referes to positional argument $N.\n\nRepository names passed to the command line are looked up in the B9 configuration file, which is per default located in: '~/.b9/b9.conf'"+               "Build and run VM-Images inside LXC containers. Custom arguments follow after '--' and are accessable in many strings in build files  trough shell like variable references, i.e. '${arg_N}' referes to positional argument $N.\n\nRepository names passed to the command line are looked up in the B9 configuration file, which is per default located in: '~/.b9/b9.conf'. The current working directory is used as ${buildDirRoot} if not otherwise specified in the config file, or via the '-b' option."         <> headerDoc (Just b9HelpHeader)         )     )@@ -78,14 +78,8 @@                 )         <*> optional                 ( strOption-                    (  help "Output file for a command/timing profile"-                    <> long "profile-file"-                    <> metavar "FILENAME"-                    )-                )-        <*> optional-                ( strOption-                    (  help "Root directory for build directories"+                    (  help+                          "Root directory for build directories. If not specified '.'. The path will be canonicalized and stored in ${buildDirRoot}."                     <> short 'b'                     <> long "build-root-dir"                     <> metavar "DIRECTORY"@@ -122,13 +116,12 @@         -> Bool         -> Maybe FilePath         -> Maybe FilePath-        -> Maybe FilePath         -> Bool         -> Bool         -> Maybe FilePath         -> Maybe String         -> B9ConfigOverride-    toGlobalOpts cfg verbose quiet logF profF buildRoot keep notUnique mRepoCache repo+    toGlobalOpts cfg verbose quiet logF buildRoot keep notUnique mRepoCache repo         = let minLogLevel = if verbose                   then Just LogTrace                   else if quiet then Just LogError else Nothing@@ -138,8 +131,6 @@                       .~ minLogLevel                       &  logFile                       .~ logF-                      &  profileFile-                      .~ profF                       &  buildDirRoot                       .~ buildRoot                       &  keepTempDirs@@ -214,7 +205,9 @@            )     <> command            "list"-           (info (pure (void runListSharedImages)) (progDesc "List shared images."))+           ( info (pure (void runListSharedImages))+                  (progDesc "List shared images.")+           )     <> command            "add-repo"            ( info (runAddRepo <$> remoteRepoParser)@@ -222,9 +215,8 @@            )     <> command            "reformat"-           ( info-               ( runFormatBuildFiles    <$> buildFileParser              )-               (progDesc "Re-Format all build files.")+           ( info (runFormatBuildFiles <$> buildFileParser)+                  (progDesc "Re-Format all build files.")            )     ) 
src/lib/B9.hs view
@@ -43,7 +43,7 @@ import Text.Printf as X (printf) import Data.Version as X import B9.B9Monad as X-import Data.ConfigFile.B9Extras as X+import System.IO.B9Extras as X import B9.B9Config as X import B9.ExecEnv as X import B9.DiskImages as X@@ -101,26 +101,24 @@ runPush name = localRuntimeConfig (keepTempDirs .~ False) $ run $ do   conf <- getConfig   if isNothing (conf ^. repository)-    then-      errorExitL-        "No repository specified! Use '-r' to specify a repo BEFORE 'push'."-    else-      pushSharedImageLatestVersion name+    then errorExitL+      "No repository specified! Use '-r' to specify a repo BEFORE 'push'."+    else pushSharedImageLatestVersion name  -- | Either pull a list of available 'SharedImageName's from the remote -- repository if 'Nothing' is passed as parameter, or pull the latest version -- of the image from the remote repository. Note: The remote repository is -- specified in the 'B9Config'. runPull :: MonadIO m => Maybe SharedImageName -> B9ConfigAction m ()-runPull mName =-  localRuntimeConfig (keepTempDirs .~ False)-                     (run (pullRemoteRepos >> maybePullImage))-  where maybePullImage =-          mapM_ (\name -> pullLatestImage name >>= failIfFalse name) mName-        failIfFalse _name True =-          return ()-        failIfFalse name False =-          errorExitL (printf "failed to pull: %s" (show name))+runPull mName = localRuntimeConfig (keepTempDirs .~ False)+                                   (run (pullRemoteRepos >> maybePullImage))+ where+  maybePullImage = mapM_+    ( \name ->+      pullLatestImage name >>= maybe (failPull name) (const (return ()))+    )+    mName+  failPull name = errorExitL (printf "failed to pull: %s" (show name))  -- | Execute an interactive root shell in a running container from a -- 'SharedImageName'.@@ -159,15 +157,16 @@         putStrLn "DELETING FILES:"         putStrLn (unlines filesToDelete)         mapM_ removeIfExists filesToDelete-  obsoleteSharedmages :: [SharedImage] -> [SharedImage]-  obsoleteSharedmages =-    concatMap (tail . reverse) . filter ((> 1) . length) . groupBy-      ((==) `on` siName)-  removeIfExists :: FilePath -> IO ()-  removeIfExists fileName = removeFile fileName `catch` handleExists    where-    handleExists e | isDoesNotExistError e = return ()-                   | otherwise             = throwIO e+    obsoleteSharedmages :: [SharedImage] -> [SharedImage]+    obsoleteSharedmages =+      concatMap (tail . reverse) . filter ((> 1) . length) . groupBy+        ((==) `on` sharedImageName)+    removeIfExists :: FilePath -> IO ()+    removeIfExists fileName = removeFile fileName `catch` handleExists+     where+      handleExists e | isDoesNotExistError e = return ()+                     | otherwise             = throwIO e  -- | Clear the shared image cache for a remote. Note: The remote repository is -- specified in the 'B9Config'.@@ -195,11 +194,10 @@       allRepos <- getRemoteRepos       if isNothing remoteRepo         then liftIO $ do-          putStrLn "Showing local shared images only."-          putStrLn-            "\nTo view the contents of a remote repo add \n\-                        \the '-r' switch with one of the remote \n\-                        \repository ids."+          putStrLn "Showing local shared images only.\n"+          putStrLn "To view the contents of a remote repo add"+          putStrLn "the '-r' switch with one of the remote"+          putStrLn "repository ids."         else liftIO $ putStrLn           (  "Showing shared images on: "           ++ remoteRepoRepoId (fromJust remoteRepo)
src/lib/B9/ArtifactGenerator.hs view
@@ -179,7 +179,7 @@ getArtifactSourceFiles (FromContent   f _) = [f] getArtifactSourceFiles (FromFile      f _) = [f] getArtifactSourceFiles (IntoDirectory pd as) =-    (pd</>) <$> (as >>= getArtifactSourceFiles)+    (pd </>) <$> (as >>= getArtifactSourceFiles) getArtifactSourceFiles (FromDirectory _ as) = as >>= getArtifactSourceFiles getArtifactSourceFiles (SetPermissions _ _ _ as) =     as >>= getArtifactSourceFiles@@ -275,11 +275,11 @@ getAssemblyOutput (VmImages ts _) =     AssemblyGeneratesOutputFiles . getImageDestinationOutputFiles <$> ts getAssemblyOutput (CloudInit ts o) = getCloudInitOutputFiles o <$> ts- where-  getCloudInitOutputFiles baseName t = case t of-      CI_ISO  -> AssemblyGeneratesOutputFiles [baseName <.> "iso"]-      CI_VFAT -> AssemblyGeneratesOutputFiles [baseName <.> "vfat"]-      CI_DIR  -> AssemblyCopiesSourcesToDirectory baseName+  where+    getCloudInitOutputFiles baseName t = case t of+        CI_ISO  -> AssemblyGeneratesOutputFiles [baseName <.> "iso"]+        CI_VFAT -> AssemblyGeneratesOutputFiles [baseName <.> "vfat"]+        CI_DIR  -> AssemblyCopiesSourcesToDirectory baseName   -- * QuickCheck instances@@ -297,21 +297,21 @@  arbitraryEachT :: Gen ([ArtifactGenerator] -> ArtifactGenerator) arbitraryEachT =-    sized $-    \n ->-         EachT <$> vectorOf n (halfSize (listOf1 (choose ('a', 'z')))) <*>-         oneof-             [ listOf (vectorOf n (halfSize arbitrary))-             , listOf1 (listOf (halfSize arbitrary))]+    sized+        $ \n ->+              EachT+                  <$> vectorOf n (halfSize (listOf1 (choose ('a', 'z'))))+                  <*> oneof+                          [ listOf (vectorOf n (halfSize arbitrary))+                          , listOf1 (listOf (halfSize arbitrary))+                          ]  arbitraryEach :: Gen ([ArtifactGenerator] -> ArtifactGenerator)-arbitraryEach =-    sized $-    \n ->-         Each <$>-         listOf-             ((,) <$> listOf1 (choose ('a', 'z')) <*>-              vectorOf n (halfSize (listOf1 (choose ('a', 'z')))))+arbitraryEach = sized $ \n -> Each <$> listOf+    ( (,) <$> listOf1 (choose ('a', 'z')) <*> vectorOf+        n+        (halfSize (listOf1 (choose ('a', 'z'))))+    )   instance Arbitrary ArtifactSource where@@ -336,3 +336,4 @@  instance Arbitrary CloudInitType where     arbitrary = elements [CI_ISO, CI_VFAT, CI_DIR]+
src/lib/B9/ArtifactGeneratorImpl.hs view
@@ -9,7 +9,7 @@ import B9.VmBuilder import B9.Vm import B9.DiskImageBuilder-import Data.ConfigFile.B9Extras hiding (tell)+import System.IO.B9Extras (ensureDir, getDirectoryFiles) import B9.Content.StringTemplate import B9.Content.Generator import B9.Content.AST@@ -47,15 +47,13 @@ getArtifactOutputFiles :: ArtifactGenerator -> Either String [FilePath] getArtifactOutputFiles g =   concatMap getOutputs <$> evalArtifactGenerator undefined undefined [] g-  where-    getOutputs  (IG _ sgs a) =-      let-          toOutFile (AssemblyGeneratesOutputFiles fs) = fs-          toOutFile (AssemblyCopiesSourcesToDirectory pd) =-            let sourceFiles = sourceGeneratorOutputFile <$> sgs-            in (pd </>) <$> sourceFiles--      in getAssemblyOutput  a >>= toOutFile+ where+  getOutputs (IG _ sgs a) =+    let toOutFile (AssemblyGeneratesOutputFiles fs) = fs+        toOutFile (AssemblyCopiesSourcesToDirectory pd) =+          let sourceFiles = sourceGeneratorOutputFile <$> sgs+          in  (pd </>) <$> sourceFiles+    in  getAssemblyOutput a >>= toOutFile  -- | Run an artifact generator to produce the artifacts. assemble :: ArtifactGenerator -> B9 [AssembledArtifact]@@ -124,7 +122,7 @@   in  ce { agEnv = newEnv }  withXBindings :: [(String, [String])] -> CGParser () -> CGParser ()-withXBindings bs cp = (`local`cp) `mapM_` (addBindings <$> allXBindings bs)+withXBindings bs cp = (`local` cp) `mapM_` (addBindings <$> allXBindings bs)  where   allXBindings ((k, vs):rest) =     [ (k, v) : c | v <- vs, c <- allXBindings rest ]@@ -135,7 +133,7 @@   -> [String]   -> [[String]]   -> CGParser [[(String, String)]]-eachBindingSetT g vars valueSets = if all ((==length vars) . length) valueSets+eachBindingSetT g vars valueSets = if all ((== length vars) . length) valueSets   then return (zip vars <$> valueSets)   else cgError     ( printf@@ -143,7 +141,7 @@       (ppShow g)       (ppShow vars)       (length vars)-      (ppShow (head (dropWhile ((==length vars) . length) valueSets)))+      (ppShow (head (dropWhile ((== length vars) . length) valueSets)))     )  eachBindingSet@@ -363,7 +361,7 @@           ensureDir t           copyFile f t         )-        (((instanceDir</>) &&& (ciDir</>)) <$> files)+        (((instanceDir </>) &&& (ciDir </>)) <$> files)       )     infoL (printf "CREATED CI_DIR: '%s'" (takeFileName ciDir))     return (CloudInitTarget CI_DIR ciDir)@@ -395,7 +393,7 @@     let vfatFile = outPath <.> "vfat"         tmpFile  = buildDir </> takeFileName vfatFile     ensureDir tmpFile-    files <- map (instanceDir</>) <$> getDirectoryFiles instanceDir+    files <- map (instanceDir </>) <$> getDirectoryFiles instanceDir     dbgL (printf "creating cloud init vfat image '%s'" tmpFile)     traceL (printf "adding '%s'" (show files))     cmd (printf "truncate --size 2M '%s'" tmpFile)@@ -409,6 +407,3 @@     liftIO (copyFile tmpFile vfatFile)     infoL (printf "CREATED CI_VFAT IMAGE: '%s'" (takeFileName vfatFile))     return (CloudInitTarget CI_ISO vfatFile)---
src/lib/B9/B9Config.hs view
@@ -8,7 +8,6 @@                    , buildDirRoot                    , keepTempDirs                    , execEnvType-                   , profileFile                    , envVars                    , uniqueBuildDirs                    , repositoryCache@@ -16,6 +15,7 @@                    , interactive                    , libVirtLXCConfigs                    , remoteRepos+                   , maxLocalSharedImageRevisions                    , B9ConfigOverride(..)                    , noB9ConfigOverride                    , B9ConfigAction()@@ -35,12 +35,12 @@                    , defaultRepositoryCache                    , defaultB9Config                    , openOrCreateB9Config-                   , writeB9ConfigParser+                   , writeB9CPDocument                    , readB9Config                    , parseB9Config                    , appendPositionalArguments-                   , modifyConfigParser-                   , b9ConfigToConfigParser+                   , modifyCPDocument+                   , b9ConfigToCPDocument                    , LogLevel(..)                    , ExecEnvType (..)                    , BuildVariables@@ -56,13 +56,14 @@ import Control.Monad.Reader import Control.Monad.Writer import System.Directory+import System.IO.B9Extras (SystemPath(..), resolve, ensureDir) import qualified Data.Semigroup as Sem import Data.List (partition, sortBy) import Data.ConfigFile.B9Extras-          ( SystemPath(..), ConfigParser, CPError, Get_C-          , OptionSpec, resolve, ensureDir, readIniFile-          , IniFileException(..), merge, to_string-          , add_section, emptyCP, setshow, get)+          ( CPDocument, CPError, CPGet+          , CPOptionSpec, readCPDocument+          , CPReadException(..), mergeCP, toStringCP+          , addSectionCP, emptyCP, setShowCP, readCP ) import B9.B9Config.LibVirtLXC as X import B9.B9Config.Repository as X import Text.Printf (printf)@@ -79,12 +80,12 @@                          , _buildDirRoot :: Maybe FilePath                          , _keepTempDirs :: Bool                          , _execEnvType :: ExecEnvType-                         , _profileFile :: Maybe FilePath                          , _envVars :: BuildVariables                          , _uniqueBuildDirs :: Bool                          , _repositoryCache :: Maybe SystemPath                          , _repository :: Maybe String                          , _interactive :: Bool+                         , _maxLocalSharedImageRevisions :: Maybe Int                          , _libVirtLXCConfigs :: Maybe LibVirtLXCConfig                          , _remoteRepos :: [RemoteRepo]                          } deriving (Show)@@ -96,20 +97,20 @@       , _buildDirRoot = getLast $ on mappend (Last . _buildDirRoot) c c'       , _keepTempDirs = getAny $ on mappend (Any . _keepTempDirs) c c'       , _execEnvType = LibVirtLXC-      , _profileFile = getLast $ on mappend (Last . _profileFile) c c'       , _envVars = on mappend _envVars c c'       , _uniqueBuildDirs = getAll ((mappend `on` (All . _uniqueBuildDirs)) c c')       , _repositoryCache = getLast $ on mappend (Last . _repositoryCache) c c'       , _repository = getLast ((mappend `on` (Last . _repository)) c c')       , _interactive = getAny ((mappend `on` (Any . _interactive)) c c')+      , _maxLocalSharedImageRevisions = getLast ((mappend `on` (Last . _maxLocalSharedImageRevisions)) c c')       , _libVirtLXCConfigs = getLast ((mappend `on` (Last . _libVirtLXCConfigs)) c c')       , _remoteRepos = (mappend `on` _remoteRepos) c c'       }  instance Monoid B9Config where   mappend = (Sem.<>)-  mempty = B9Config Nothing Nothing Nothing False LibVirtLXC Nothing [] True-                    Nothing Nothing False Nothing []+  mempty = B9Config Nothing Nothing Nothing False LibVirtLXC [] True+                    Nothing Nothing False Nothing Nothing []  -- | Override b9 configuration items and/or the path of the b9 configuration file. -- This is useful, i.e. when dealing with command line parameters.@@ -204,7 +205,7 @@       (res, permanentB9ConfigUpdates) <- runWriterT         (runReaderT (runB9ConfigAction act) runtimeCfg) -      let cpExtErr = modifyConfigParser cp <$> permanentB9ConfigUpdate+      let cpExtErr = modifyCPDocument cp <$> permanentB9ConfigUpdate           permanentB9ConfigUpdate = if null permanentB9ConfigUpdates             then Nothing             else Just (mconcat permanentB9ConfigUpdates)@@ -219,7 +220,7 @@           (return . Just)         )         cpExtErr-      mapM_ (writeB9ConfigParser (cfg ^. customB9ConfigPath)) cpExt+      mapM_ (writeB9CPDocument (cfg ^. customB9ConfigPath)) cpExt       return res -- | Run a 'B9ConfigAction' using 'noB9ConfigOverride'. -- See 'execB9ConfigAction' for more details.@@ -229,45 +230,45 @@ -- | Open the configuration file that contains the 'B9Config'. -- If the configuration does not exist, write a default configuration file, -- and create a all missing directories.-openOrCreateB9Config :: MonadIO m => Maybe SystemPath -> m ConfigParser+openOrCreateB9Config :: MonadIO m => Maybe SystemPath -> m CPDocument openOrCreateB9Config cfgPath = do   cfgFile <- resolve (fromMaybe defaultB9ConfigFile cfgPath)   ensureDir cfgFile   liftIO $ do     exists <- doesFileExist cfgFile     if exists-      then readIniFile (Path cfgFile)+      then readCPDocument (Path cfgFile)       else-        let res = b9ConfigToConfigParser defaultB9Config+        let res = b9ConfigToCPDocument defaultB9Config         in  case res of-              Left  e  -> throwIO (IniFileException cfgFile e)-              Right cp -> writeFile cfgFile (to_string cp) >> return cp+              Left  e  -> throwIO (CPReadException cfgFile e)+              Right cp -> writeFile cfgFile (toStringCP cp) >> return cp --- | Write the configuration in the 'ConfigParser' to either the user supplied+-- | Write the configuration in the 'CPDocument' to either the user supplied -- configuration file path or to 'defaultB9ConfigFile'. -- Create all missing (parent) directories.-writeB9ConfigParser :: MonadIO m => Maybe SystemPath -> ConfigParser -> m ()-writeB9ConfigParser cfgFileIn cp = do+writeB9CPDocument :: MonadIO m => Maybe SystemPath -> CPDocument -> m ()+writeB9CPDocument cfgFileIn cp = do   cfgFile <- resolve (fromMaybe defaultB9ConfigFile cfgFileIn)   ensureDir cfgFile-  liftIO (writeFile cfgFile (to_string cp))+  liftIO (writeFile cfgFile (toStringCP cp))   defaultB9Config :: B9Config defaultB9Config = B9Config-  { _verbosity         = Just LogInfo-  , _logFile           = Nothing-  , _buildDirRoot      = Nothing-  , _keepTempDirs      = False-  , _execEnvType       = LibVirtLXC-  , _profileFile       = Nothing-  , _envVars           = []-  , _uniqueBuildDirs   = True-  , _repository        = Nothing-  , _repositoryCache   = Just defaultRepositoryCache-  , _interactive       = False-  , _libVirtLXCConfigs = Just defaultLibVirtLXCConfig-  , _remoteRepos       = []+  { _verbosity                    = Just LogInfo+  , _logFile                      = Nothing+  , _buildDirRoot                 = Nothing+  , _keepTempDirs                 = False+  , _execEnvType                  = LibVirtLXC+  , _envVars                      = []+  , _uniqueBuildDirs              = True+  , _repository                   = Nothing+  , _repositoryCache              = Just defaultRepositoryCache+  , _interactive                  = False+  , _maxLocalSharedImageRevisions = Just 2+  , _libVirtLXCConfigs            = Just defaultLibVirtLXCConfig+  , _remoteRepos                  = []   }  defaultRepositoryCache :: SystemPath@@ -284,73 +285,78 @@ keepTempDirsK = "keep_temp_dirs" execEnvTypeK :: String execEnvTypeK = "exec_env"-profileFileK :: String-profileFileK = "profile_file" envVarsK :: String envVarsK = "environment_vars" uniqueBuildDirsK :: String uniqueBuildDirsK = "unique_build_dirs" repositoryCacheK :: String repositoryCacheK = "repository_cache"+maxLocalSharedImageRevisionsK :: String+maxLocalSharedImageRevisionsK = "max_cached_shared_images" repositoryK :: String repositoryK = "repository" cfgFileSection :: String cfgFileSection = "global" --- | Parse a 'B9Config', modify it, and merge it back to the given 'ConfigParser'.-modifyConfigParser-  :: ConfigParser -> Endo B9Config -> Either CPError ConfigParser-modifyConfigParser cp f = do+-- | Parse a 'B9Config', modify it, and merge it back to the given 'CPDocument'.+modifyCPDocument :: CPDocument -> Endo B9Config -> Either CPError CPDocument+modifyCPDocument cp f = do   cfg <- parseB9Config cp-  cp2 <- b9ConfigToConfigParser (appEndo f cfg)-  return (merge cp cp2)+  cp2 <- b9ConfigToCPDocument (appEndo f cfg)+  return (mergeCP cp cp2) --- | Append a config file section for the 'B9Config' to an empty 'ConfigParser'.-b9ConfigToConfigParser :: B9Config -> Either CPError ConfigParser-b9ConfigToConfigParser c = do-  cp1 <- add_section emptyCP cfgFileSection-  cp2 <- setshow cp1 cfgFileSection verbosityK (_verbosity c)-  cp3 <- setshow cp2 cfgFileSection logFileK (_logFile c)-  cp4 <- setshow cp3 cfgFileSection buildDirRootK (_buildDirRoot c)-  cp5 <- setshow cp4 cfgFileSection keepTempDirsK (_keepTempDirs c)-  cp6 <- setshow cp5 cfgFileSection execEnvTypeK (_execEnvType c)-  cp7 <- setshow cp6 cfgFileSection profileFileK (_profileFile c)-  cp8 <- setshow cp7 cfgFileSection envVarsK (_envVars c)-  cp9 <- setshow cp8 cfgFileSection uniqueBuildDirsK (_uniqueBuildDirs c)-  cpA <- setshow cp9 cfgFileSection repositoryCacheK (_repositoryCache c)+-- | Append a config file section for the 'B9Config' to an empty 'CPDocument'.+b9ConfigToCPDocument :: B9Config -> Either CPError CPDocument+b9ConfigToCPDocument c = do+  cp1 <- addSectionCP emptyCP cfgFileSection+  cp2 <- setShowCP cp1 cfgFileSection verbosityK (_verbosity c)+  cp3 <- setShowCP cp2 cfgFileSection logFileK (_logFile c)+  cp4 <- setShowCP cp3 cfgFileSection buildDirRootK (_buildDirRoot c)+  cp5 <- setShowCP cp4 cfgFileSection keepTempDirsK (_keepTempDirs c)+  cp6 <- setShowCP cp5 cfgFileSection execEnvTypeK (_execEnvType c)+  cp7 <- setShowCP cp6 cfgFileSection envVarsK (_envVars c)+  cp8 <- setShowCP cp7 cfgFileSection uniqueBuildDirsK (_uniqueBuildDirs c)+  cp9 <- setShowCP cp8+                   cfgFileSection+                   maxLocalSharedImageRevisionsK+                   (_maxLocalSharedImageRevisions c)+  cpA <- setShowCP cp9 cfgFileSection repositoryCacheK (_repositoryCache c)   cpB <-     ( foldr (>=>)             return-            (libVirtLXCConfigToConfigParser <$> (_libVirtLXCConfigs c))+            (libVirtLXCConfigToCPDocument <$> (_libVirtLXCConfigs c))       )       cpA-  cpFinal <- (foldr (>=>) return (remoteRepoToConfigParser <$> _remoteRepos c))+  cpFinal <- (foldr (>=>) return (remoteRepoToCPDocument <$> _remoteRepos c))     cpB-  setshow cpFinal cfgFileSection repositoryK (_repository c)+  setShowCP cpFinal cfgFileSection repositoryK (_repository c) -readB9Config :: MonadIO m => Maybe SystemPath -> m ConfigParser-readB9Config cfgFile = readIniFile (fromMaybe defaultB9ConfigFile cfgFile)+readB9Config :: MonadIO m => Maybe SystemPath -> m CPDocument+readB9Config cfgFile = readCPDocument (fromMaybe defaultB9ConfigFile cfgFile) -parseB9Config :: ConfigParser -> Either CPError B9Config+parseB9Config :: CPDocument -> Either CPError B9Config parseB9Config cp =-  let getr :: (Get_C a) => OptionSpec -> Either CPError a-      getr = get cp cfgFileSection-      getB9Config =-        B9Config-          <$> getr verbosityK-          <*> getr logFileK-          <*> getr buildDirRootK-          <*> getr keepTempDirsK-          <*> getr execEnvTypeK-          <*> getr profileFileK-          <*> getr envVarsK-          <*> getr uniqueBuildDirsK-          <*> getr repositoryCacheK-          <*> getr repositoryK-          <*> pure False-          <*> Right (either (const Nothing) Just (parseLibVirtLXCConfig cp))-          <*> parseRemoteRepos cp-  in  getB9Config+  let+    getr :: (CPGet a) => CPOptionSpec -> Either CPError a+    getr = readCP cp cfgFileSection+    getB9Config =+      B9Config+        <$> getr verbosityK+        <*> getr logFileK+        <*> getr buildDirRootK+        <*> getr keepTempDirsK+        <*> getr execEnvTypeK+        <*> getr envVarsK+        <*> getr uniqueBuildDirsK+        <*> getr repositoryCacheK+        <*> getr repositoryK+        <*> pure False+        <*> pure+              (either (const Nothing) id (getr maxLocalSharedImageRevisionsK))+        <*> pure (either (const Nothing) Just (parseLibVirtLXCConfig cp))+        <*> parseRemoteRepos cp+  in+    getB9Config   -- | If environment variables @arg_1 .. arg_n@ are bound
src/lib/B9/B9Config/LibVirtLXC.hs view
@@ -1,5 +1,5 @@ module B9.B9Config.LibVirtLXC (-                        libVirtLXCConfigToConfigParser+                        libVirtLXCConfigToCPDocument                         , defaultLibVirtLXCConfig                         , parseLibVirtLXCConfig                      , LibVirtLXCConfig(..)@@ -98,22 +98,22 @@ guestRamSizeK :: String guestRamSizeK = "guest_ram_size" -libVirtLXCConfigToConfigParser-    :: LibVirtLXCConfig -> ConfigParser -> Either CPError ConfigParser-libVirtLXCConfigToConfigParser c cp = do-    cp1 <- add_section cp cfgFileSection-    cp2 <- setshow cp1 cfgFileSection useSudoK $ useSudo c-    cp3 <- set cp2 cfgFileSection virshPathK $ virshPath c-    cp4 <- set cp3 cfgFileSection emulatorK $ emulator c-    cp5 <- set cp4 cfgFileSection virshURIK $ virshURI c-    cp6 <- setshow cp5 cfgFileSection networkIdK $ networkId c-    cp7 <- setshow cp6 cfgFileSection guestCapabilitiesK $ guestCapabilities c-    setshow cp7 cfgFileSection guestRamSizeK $ guestRamSize c+libVirtLXCConfigToCPDocument+    :: LibVirtLXCConfig -> CPDocument -> Either CPError CPDocument+libVirtLXCConfigToCPDocument c cp = do+    cp1 <- addSectionCP cp cfgFileSection+    cp2 <- setShowCP cp1 cfgFileSection useSudoK $ useSudo c+    cp3 <- setCP cp2 cfgFileSection virshPathK $ virshPath c+    cp4 <- setCP cp3 cfgFileSection emulatorK $ emulator c+    cp5 <- setCP cp4 cfgFileSection virshURIK $ virshURI c+    cp6 <- setShowCP cp5 cfgFileSection networkIdK $ networkId c+    cp7 <- setShowCP cp6 cfgFileSection guestCapabilitiesK $ guestCapabilities c+    setShowCP cp7 cfgFileSection guestRamSizeK $ guestRamSize c -parseLibVirtLXCConfig :: ConfigParser -> Either CPError LibVirtLXCConfig+parseLibVirtLXCConfig :: CPDocument -> Either CPError LibVirtLXCConfig parseLibVirtLXCConfig cp =-    let getr :: (Get_C a) => OptionSpec -> Either CPError a-        getr = get cp cfgFileSection+    let getr :: (CPGet a) => CPOptionSpec -> Either CPError a+        getr = readCP cp cfgFileSection     in  LibVirtLXCConfig         <$> (getr useSudoK)         <*> (getr virshPathK)
src/lib/B9/B9Config/Repository.hs view
@@ -4,13 +4,13 @@                               , SshPrivKey(..)                               , SshRemoteHost(..)                               , SshRemoteUser(..)-                              , remoteRepoToConfigParser+                              , remoteRepoToCPDocument                               , parseRemoteRepos                               ) where  import Data.Data import Data.List (isSuffixOf)-import Data.ConfigFile+import Data.ConfigFile.B9Extras  newtype RepoCache = RepoCache FilePath   deriving (Read, Show, Typeable, Data)@@ -36,45 +36,46 @@   -- | Persist a repo to a configuration file.-remoteRepoToConfigParser :: RemoteRepo-                      -> ConfigParser-                      -> Either CPError ConfigParser-remoteRepoToConfigParser repo cpIn = cpWithRepo-  where section = repoId ++ repoSectionSuffix-        (RemoteRepo repoId-                    remoteRootDir-                    (SshPrivKey keyFile)-                    (SshRemoteHost (host,port))-                    (SshRemoteUser user)) = repo-        cpWithRepo = do cp1 <- add_section cpIn section-                        cp2 <- set cp1 section repoRemotePathK remoteRootDir-                        cp3 <- set cp2 section repoRemoteSshKeyK keyFile-                        cp4 <- set cp3 section repoRemoteSshHostK host-                        cp5 <- setshow cp4 section repoRemoteSshPortK port-                        set cp5 section repoRemoteSshUserK user+remoteRepoToCPDocument :: RemoteRepo -> CPDocument -> Either CPError CPDocument+remoteRepoToCPDocument repo cpIn = cpWithRepo+ where+  section = repoId ++ repoSectionSuffix+  (RemoteRepo repoId remoteRootDir (SshPrivKey keyFile) (SshRemoteHost (host, port)) (SshRemoteUser user))+    = repo+  cpWithRepo = do+    cp1 <- addSectionCP cpIn section+    cp2 <- setCP cp1 section repoRemotePathK remoteRootDir+    cp3 <- setCP cp2 section repoRemoteSshKeyK keyFile+    cp4 <- setCP cp3 section repoRemoteSshHostK host+    cp5 <- setShowCP cp4 section repoRemoteSshPortK port+    setCP cp5 section repoRemoteSshUserK user  -- | Load a repository from a configuration file that has been written by -- 'writeRepositoryToB9Config'.-parseRemoteRepos :: ConfigParser -> Either CPError [RemoteRepo]+parseRemoteRepos :: CPDocument -> Either CPError [RemoteRepo] parseRemoteRepos cp = traverse parseRepoSection repoSections-  where-    repoSections =-          filter (repoSectionSuffix `isSuffixOf`) (sections cp)-    parseRepoSection section = parseResult-      where-        getsec :: Get_C a =>  OptionSpec -> Either CPError a-        getsec = get cp section-        parseResult =-          RemoteRepo repoId-            <$> getsec repoRemotePathK-            <*> (SshPrivKey <$> getsec repoRemoteSshKeyK)-            <*> (SshRemoteHost <$> ((,) <$> getsec repoRemoteSshHostK-                                        <*> getsec repoRemoteSshPortK))-            <*> (SshRemoteUser <$> getsec repoRemoteSshUserK)-          where-            repoId = let prefixLen = length section - suffixLen-                         suffixLen = length repoSectionSuffix-                         in take prefixLen section+ where+  repoSections = filter (repoSectionSuffix `isSuffixOf`) (sectionsCP cp)+  parseRepoSection section = parseResult+   where+    getsec :: CPGet a => CPOptionSpec -> Either CPError a+    getsec = readCP cp section+    parseResult =+      RemoteRepo repoId+        <$> getsec repoRemotePathK+        <*> (SshPrivKey <$> getsec repoRemoteSshKeyK)+        <*> (   SshRemoteHost+            <$> (   (,)+                <$> getsec repoRemoteSshHostK+                <*> getsec repoRemoteSshPortK+                )+            )+        <*> (SshRemoteUser <$> getsec repoRemoteSshUserK)+     where+      repoId =+        let prefixLen = length section - suffixLen+            suffixLen = length repoSectionSuffix+        in  take prefixLen section  repoSectionSuffix :: String repoSectionSuffix = "-repo"
src/lib/B9/B9Monad.hs view
@@ -19,7 +19,7 @@ import Control.Monad import Control.Monad.IO.Class import Control.Monad.State-import Control.Lens((&), (.~))+import Control.Lens((&), (.~), (^.), (%~)) import qualified Data.ByteString.Char8 as B import Data.Functor () import Data.Maybe@@ -45,64 +45,65 @@              ,bsLogFileHandle :: Maybe SysIO.Handle              ,bsSelectedRemoteRepo :: Maybe RemoteRepo              ,bsRepoCache :: RepoCache-             ,bsProf :: [ProfilingEntry]              ,bsStartTime :: UTCTime              ,bsInheritStdIn :: Bool} -data ProfilingEntry-  = IoActionDuration NominalDiffTime-  | LogEvent LogLevel-             String-  deriving (Eq,Show)-- run :: MonadIO m => B9 a -> B9ConfigAction m a run action = do   cfg <- askRuntimeConfig   liftIO $ do-    buildId <- generateBuildId-    now     <- getCurrentTime-    withBuildDir cfg buildId (withLogFile cfg . runImpl cfg buildId now)+    buildId <- liftIO $ generateBuildId+    now     <- liftIO $ getCurrentTime+    liftIO+      $ withBuildDir cfg buildId (withLogFile cfg . runImpl cfg buildId now)  where+  resolveBuildDirRoot cfg = case _buildDirRoot cfg of+    Nothing    -> getCurrentDirectory >>= canonicalizePath+    Just root' -> do+      createDirectoryIfMissing True root'+      canonicalizePath root'   withLogFile cfg f = maybe     (f Nothing)     (\logf -> SysIO.withFile logf SysIO.AppendMode (f . Just))     (_logFile cfg)   withBuildDir cfg buildId =     bracket (createBuildDir cfg buildId) (removeBuildDir cfg)-  runImpl cfg buildId now buildDir logFileHandle =-    bracket getCurrentDirectory setCurrentDirectory $ \_ -> do-      traverse setCurrentDirectory (_buildDirRoot cfg)+  runImpl cfg buildId now buildDir logFileHandle = do       -- Check repositories-      repoCache <- initRepoCache-        (fromMaybe defaultRepositoryCache (_repositoryCache cfg))-      let buildDate = formatTime undefined "%F-%T" now-      remoteRepos' <- mapM (initRemoteRepo repoCache) (_remoteRepos cfg)-      let-        ctx = BuildState buildId-                         buildDate-                         (cfg & remoteRepos .~ remoteRepos')-                         buildDir-                         logFileHandle-                         selectedRemoteRepo-                         repoCache-                         []-                         now-                         (_interactive cfg)-        selectedRemoteRepo = do-          sel <- _repository cfg-          lookupRemoteRepo remoteRepos' sel <|> error-            ( printf-              "selected remote repo '%s' not configured, valid remote repos are: '%s'"-              sel-              (show remoteRepos')-            )-      (r, ctxOut) <- runStateT (runB9 wrappedAction) ctx-      -- Write a profiling report-      when (isJust (_profileFile cfg)) $ writeFile-        (fromJust (_profileFile cfg))-        (unlines $ show <$> reverse (bsProf ctxOut))-      return r+    repoCache <- initRepoCache+      (fromMaybe defaultRepositoryCache (_repositoryCache cfg))+    let buildDate = formatTime undefined "%F-%T" now+    remoteRepos'    <- mapM (initRemoteRepo repoCache) (_remoteRepos cfg)+    buildDirRootAbs <- resolveBuildDirRoot cfg+    let finalCfg =+          (  cfg+          &  remoteRepos+          .~ remoteRepos'+          &  buildDirRoot+          .~ Just buildDirRootAbs+          &  envVars+          %~ mappend [("buildDirRoot", buildDirRootAbs)]+          )+    let+      ctx = BuildState buildId+                       buildDate+                       finalCfg+                       buildDir+                       logFileHandle+                       selectedRemoteRepo+                       repoCache+                       now+                       (_interactive finalCfg)+      selectedRemoteRepo = do+        sel <- _repository cfg+        lookupRemoteRepo remoteRepos' sel <|> error+          ( printf+            "selected remote repo '%s' not configured, valid remote repos are: '%s'"+            sel+            (show remoteRepos')+          )+    (r, ctxOut) <- runStateT (runB9 wrappedAction) ctx+    return r   createBuildDir cfg buildId = if _uniqueBuildDirs cfg     then do       let subDir = "BUILD-" ++ buildId@@ -115,18 +116,18 @@       createDirectoryIfMissing True buildDir       canonicalizePath buildDir    where-    resolveBuildDir f = case _buildDirRoot cfg of-      Nothing    -> return f-      Just root' -> do-        createDirectoryIfMissing True root'-        root <- canonicalizePath root'-        return $ root </> f+    resolveBuildDir f = do+      root <- resolveBuildDirRoot cfg+      return $ root </> f   removeBuildDir cfg buildDir =     when (_uniqueBuildDirs cfg && not (_keepTempDirs cfg))       $ removeDirectoryRecursive buildDir   generateBuildId = printf "%08X" <$> (randomIO :: IO Word32)   -- Run the action build action-  wrappedAction = do+  wrappedAction   = do+    b9cfg <- getConfig+    traverse (traceL . printf "Root Build Directory: %s")+             (b9cfg ^. buildDirRoot)     startTime <- gets bsStartTime     r         <- action     now       <- liftIO getCurrentTime@@ -158,22 +159,6 @@ getRepoCache :: B9 RepoCache getRepoCache = gets bsRepoCache --- getDownloader :: B9 Downloader--- getDownloader = gets bsDownloader------ -- | Configuration for a tool that retreives arbitrary URL and returns them to--- -- @stdout@.--- data Downloader =---   Downloader {downloaderCmd :: FilePath---              ,downloaderArgsBeforeUrl :: [String]---              ,downloaderUrlArgPrintfFormatString :: [String]---              ,downloaderArgsAfterUrl :: [String]}---   deriving (Read,Show,Eq,Ord,Typeable,Generic)------ readContentFromUrl :: String -> B9 B.ByteString--- readContentFromUrl url = do---   return expression- cmd :: String -> B9 () cmd str = do   inheritStdIn <- gets bsInheritStdIn@@ -225,14 +210,13 @@ errorL :: String -> B9 () errorL = b9Log LogError -errorExitL :: String -> B9 ()+errorExitL :: String -> B9 a errorExitL e = b9Log LogError e >> fail e  b9Log :: LogLevel -> String -> B9 () b9Log level msg = do   lv  <- gets $ _verbosity . bsCfg   lfh <- gets bsLogFileHandle-  modify $ \ctx -> ctx { bsProf = LogEvent level msg : bsProf ctx }   B9 $ liftIO $ logImpl lv lfh level msg  logImpl :: Maybe LogLevel -> Maybe SysIO.Handle -> LogLevel -> String -> IO ()@@ -262,10 +246,4 @@   deriving (Functor,Applicative,Monad,MonadState BuildState)  instance MonadIO B9 where-  liftIO m =-    do start <- B9 $ liftIO getCurrentTime-       res <- B9 $ liftIO m-       stop <- B9 $ liftIO getCurrentTime-       let durMS = IoActionDuration (stop `diffUTCTime` start)-       modify $ \ctx -> ctx {bsProf = durMS : bsProf ctx}-       return res+  liftIO m = B9 $ liftIO m
src/lib/B9/Content/StringTemplate.hs view
@@ -25,12 +25,10 @@ import           Data.Text.Encoding as E import           Data.Text.Lazy.Encoding as LE import           Data.Text.Template (render,templateSafe,renderA)+import           System.IO.B9Extras import           Test.QuickCheck import           Text.Printf import           Text.Show.Pretty (ppShow)--import           Data.ConfigFile.B9Extras- import           B9.QCUtil -- | A wrapper around a file path and a flag indicating if template variable -- expansion should be performed when reading the file contents.
src/lib/B9/DiskImageBuilder.hs view
@@ -22,6 +22,10 @@                            ,pullLatestImage                            ,) where +import System.IO.Error (isDoesNotExistError)+import System.Directory (removeFile)+import System.IO.B9Extras (ensureDir, prettyPrintToFile, consult)+import Control.Lens ((^.)) import Control.Exception import Control.Monad import Control.Monad.IO.Class@@ -29,23 +33,28 @@ import System.FilePath import Text.Printf (printf) import Data.Maybe- import Data.Function- import Text.Show.Pretty (ppShow) import Data.List import Data.Data import Data.Generics.Schemes import Data.Generics.Aliases- import B9.B9Monad+import B9.B9Config import B9.Repository import B9.RepositoryIO import B9.DiskImages import qualified B9.PartitionTable as P-import Data.ConfigFile.B9Extras import B9.Content.StringTemplate +-- -- | Convert relative file paths of images, sources and mounted host directories+-- -- to absolute paths relative to '_buildDirRoot'.+-- makeImagePathsAbsoluteToBuildDirRoot :: ImageTarget -> B9 ImageTarget+-- makeImagePathsAbsoluteToBuildDirRoot img =+--   getConfig >>= maybe (return img) (return . go) . _buildDirRoot+--   where+--     go rootDir = everywhere mkAbs img+--       where mkAbs = mkT  -- | Replace $... variables inside an 'ImageTarget' substImageTarget :: [(String,String)] -> ImageTarget -> ImageTarget@@ -73,8 +82,8 @@      sub = subst env --- | Resolve an ImageSource to an 'Image'. Note however that this source will--- may not exist as is the case for 'EmptyImage'.+-- | Resolve an ImageSource to an 'Image'. The ImageSource might+-- not exist, as is the case for 'EmptyImage'. resolveImageSource :: ImageSource -> B9 Image resolveImageSource src =   case src of@@ -82,12 +91,14 @@       let img = Image fsLabel imgType fsType       in return (changeImageFormat imgType img)     (SourceImage srcImg _part _resize) ->-      liftIO (ensureAbsoluteImageDirExists srcImg)+      ensureAbsoluteImageDirExists srcImg     (CopyOnWrite backingImg) ->-      liftIO (ensureAbsoluteImageDirExists backingImg)-    (From name _resize) -> do-      latestImage <- getLatestImageByName (SharedImageName name)-      liftIO (ensureAbsoluteImageDirExists latestImage)+      ensureAbsoluteImageDirExists backingImg+    (From name _resize) ->+      getLatestImageByName (SharedImageName name)+        >>= maybe (errorExitL (printf "Nothing found for %s."+                                      (show (SharedImageName name))))+                  ensureAbsoluteImageDirExists  -- | Return all valid image types sorted by preference. preferredDestImageTypes :: ImageSource -> B9 [ImageType]@@ -103,10 +114,15 @@         (nub [fmt, Raw, QCow2, Vmdk]          `intersect`          allowedImageTypesForResize resize)-    (From name resize) -> do-      sharedImg <- getLatestImageByName (SharedImageName name)-      preferredDestImageTypes (SourceImage sharedImg NoPT resize)+    (From name resize) ->+      getLatestImageByName (SharedImageName name)+        >>= maybe (errorExitL (printf "Nothing found for %s."+                                       (show (SharedImageName name))))+                  (\sharedImg ->+                    preferredDestImageTypes (SourceImage sharedImg NoPT resize)) +-- | Return all supported source 'ImageType's compatible to a 'ImageDestinaion'+-- in the preferred order. preferredSourceImageTypes :: ImageDestination -> [ImageType] preferredSourceImageTypes dest =   case dest of@@ -128,13 +144,24 @@     ShrinkToMinimum -> [Raw]     _ -> [Raw, QCow2, Vmdk] -ensureAbsoluteImageDirExists :: Image -> IO Image+-- | Create the parent directories for the file that contains the 'Image'.+-- If the path to the image file is relative, prepend '_buildDirRoot' from+-- the 'B9Config'.+ensureAbsoluteImageDirExists :: Image -> B9 Image ensureAbsoluteImageDirExists img@(Image path _ _) = do-  let dir = takeDirectory path-  createDirectoryIfMissing True dir-  dirAbs <- canonicalizePath dir-  return $ changeImageDirectory dirAbs img+  b9cfg <- getConfig+  let dir =+              let dirRel = takeDirectory path+              in if isRelative dirRel then+                    let prefix = fromMaybe "." (b9cfg ^. buildDirRoot)+                    in prefix </> dirRel+                  else dirRel +  liftIO $ do+    createDirectoryIfMissing True dir+    dirAbs <- canonicalizePath dir+    return $ changeImageDirectory dirAbs img+ -- | Create an image from an image source. The destination image must have a -- compatible image type and filesystem. The directory of the image MUST be -- present and the image file itself MUST NOT alredy exist.@@ -149,8 +176,12 @@     (CopyOnWrite backingImg) ->       createCOWImage backingImg dest     (From name resize) -> do-      sharedImg <- getLatestImageByName (SharedImageName name)-      materializeImageSource (SourceImage sharedImg NoPT resize) dest+      getLatestImageByName (SharedImageName name)+        >>= maybe (errorExitL (printf "Nothing found for %s."+                                       (show (SharedImageName name))))+                  (\sharedImg ->+                        materializeImageSource+                          (SourceImage sharedImg NoPT resize) dest)  createImageFromImage :: Image -> Partition -> ImageResize -> Image -> B9 () createImageFromImage src part size out = do@@ -181,6 +212,8 @@                  outFile                  (imageFileExtension fmt)) +-- | Convert some 'Image', e.g. a temporary image used during the build phase+-- to the final destination. createDestinationImage :: Image -> ImageDestination -> B9 () createDestinationImage buildImg dest =     case dest of@@ -383,25 +416,29 @@   return (SharedImage name (SharedImageDate date) (SharedImageBuildId buildId) imgType imgFS)  -- | Convert the disk image and serialize the base image data structure.+-- If the 'maxLocalSharedImageRevisions' configuration is set to @Just n@+-- also delete all but the @n - 1@ newest images from the local cache. createSharedImageInCache :: Image -> SharedImageName -> B9 SharedImage createSharedImageInCache img sname@(SharedImageName name) = do   dbgL (printf "CREATING SHARED IMAGE: '%s' '%s'" (ppShow img) name)   sharedImg <- getSharedImageFromImageInfo sname img   dir <- getSharedImagesCacheDir   convertImage img (changeImageDirectory dir (sharedImageImage sharedImg))-  tell (dir </> sharedImageFileName sharedImg) sharedImg-  dbgL (printf "CREATED SHARED IMAGE IN CAHCE '%s'" (ppShow sharedImg))+  prettyPrintToFile (dir </> sharedImageFileName sharedImg) sharedImg+  dbgL (printf "CREATED SHARED IMAGE IN CACHE '%s'" (ppShow sharedImg))+  cleanOldSharedImageRevisionsFromCache sname   return sharedImg - -- | Publish the latest version of a shared image identified by name to the -- selected repository from the cache. pushSharedImageLatestVersion :: SharedImageName -> B9 ()-pushSharedImageLatestVersion name@(SharedImageName imgName) = do-  sharedImage <- getLatestSharedImageByNameFromCache name-  dbgL (printf "PUSHING '%s'" (ppShow sharedImage))-  pushToSelectedRepo sharedImage-  infoL (printf "PUSHED '%s'" imgName)+pushSharedImageLatestVersion name@(SharedImageName imgName) =+  getLatestSharedImageByNameFromCache name+    >>= maybe (errorExitL (printf "Nothing found for %s." (show imgName)))+              (\sharedImage -> do+                    dbgL (printf "PUSHING '%s'" (ppShow sharedImage))+                    pushToSelectedRepo sharedImage+                    infoL (printf "PUSHED '%s'" imgName))  -- | Upload a shared image from the cache to a selected remote repository pushToSelectedRepo :: SharedImage -> B9 ()@@ -428,13 +465,13 @@  -- | Pull the latest version of an image, either from the selected remote -- repo or from the repo that has the latest version.-pullLatestImage :: SharedImageName -> B9 Bool+pullLatestImage :: SharedImageName -> B9 (Maybe SharedImageBuildId) pullLatestImage name@(SharedImageName dbgName) = do   repos <- getSelectedRepos   let repoPredicate Cache = False       repoPredicate (Remote repoId) = repoId `elem` repoIds       repoIds = map remoteRepoRepoId repos-      hasName sharedImage = name == siName sharedImage+      hasName sharedImage = name == sharedImageName sharedImage   candidates <- lookupSharedImages repoPredicate hasName   let (Remote repoId, image) = last candidates   if null candidates@@ -442,7 +479,7 @@       errorL         (printf "No shared image named '%s' on these remote repositories: '%s'" dbgName            (ppShow repoIds))-      return False+      return Nothing     else do       dbgL (printf "PULLING SHARED IMAGE: '%s'" (ppShow image))       cacheDir <- getSharedImagesCacheDir@@ -456,28 +493,33 @@       pullFromRepo repo repoImgFile cachedImgFile       pullFromRepo repo repoInfoFile cachedInfoFile       infoL (printf "PULLED '%s' FROM '%s'" dbgName repoId)-      return True-+      cleanOldSharedImageRevisionsFromCache name+      return (Just (sharedImageBuildId image))  -- | Return the 'Image' of the latest version of a shared image named 'name' -- from the local cache.-getLatestImageByName :: SharedImageName -> B9 Image+getLatestImageByName :: SharedImageName -> B9 (Maybe Image) getLatestImageByName name = do   sharedImage <- getLatestSharedImageByNameFromCache name   cacheDir <- getSharedImagesCacheDir-  let image = changeImageDirectory cacheDir (sharedImageImage sharedImage)-  dbgL (printf "USING SHARED SOURCE IMAGE '%s'" (show image))+  let image = changeImageDirectory cacheDir . sharedImageImage <$> sharedImage+  case image of+    Just i ->+      dbgL (printf "USING SHARED SOURCE IMAGE '%s'" (show i))+    Nothing ->+      errorL (printf "SOURCE IMAGE '%s' NOT FOUND" (show name))   return image  -- | Return the latest version of a shared image named 'name' from the local cache.-getLatestSharedImageByNameFromCache :: SharedImageName -> B9 SharedImage+getLatestSharedImageByNameFromCache :: SharedImageName -> B9 (Maybe SharedImage) getLatestSharedImageByNameFromCache name@(SharedImageName dbgName) = do-  imgs <- lookupSharedImages (== Cache) ((== name) . siName)+  imgs <- lookupSharedImages (== Cache) ((== name) . sharedImageName)   case reverse imgs of     (Cache, sharedImage):_rest ->-      return sharedImage-    _ ->-      error (printf "No image(s) named '%s' found." dbgName)+      return (Just sharedImage)+    _ -> do+      errorL (printf "No image(s) named '%s' found." dbgName)+      return Nothing  -- | Return a list of all existing sharedImages from cached repositories. getSharedImages :: B9 [(Repository, [SharedImage])]@@ -530,3 +572,30 @@ getSharedImagesCacheDir = do   cacheDir <- localRepoDir <$> getRepoCache   return (cacheDir </> sharedImagesRootDirectory)++-- | Depending on the 'maxLocalSharedImageRevisions' 'B9Config' settings either+-- do nothing or delete all but the configured number of most recent shared+-- images with the given name from the local cache.+cleanOldSharedImageRevisionsFromCache :: SharedImageName -> B9 ()+cleanOldSharedImageRevisionsFromCache sn = do+    b9Cfg <- getConfig+    forM_ (b9Cfg ^. maxLocalSharedImageRevisions) $ \maxRevisions -> do+      toDelete <- take maxRevisions <$> newestSharedImages+      imgDir <- getSharedImagesCacheDir+      let filesToDelete = (imgDir </>) <$> (infoFiles ++ imgFiles)+          infoFiles     = sharedImageFileName <$> toDelete+          imgFiles      = imageFileName . sharedImageImage <$> toDelete+      unless (null filesToDelete) $ do+          traceL (printf "DELETING %d OBSOLETE REVISIONS OF: %s"+                        (length filesToDelete) (show sn))+          mapM_ traceL filesToDelete+          mapM_ removeIfExists filesToDelete+   where+    newestSharedImages :: B9 [SharedImage]+    newestSharedImages =+      reverse . map snd <$> lookupSharedImages (== Cache) ((sn ==) . sharedImageName)++    removeIfExists fileName = liftIO $ removeFile fileName `catch` handleExists+     where+      handleExists e | isDoesNotExistError e = return ()+                     | otherwise             = throwIO e
src/lib/B9/DiskImages.hs view
@@ -295,16 +295,16 @@ -- * Constructors and accessors for 'SharedImage's  -- | Return the name of a shared image.-siName :: SharedImage -> SharedImageName-siName (SharedImage n _ _ _ _) = n+sharedImageName :: SharedImage -> SharedImageName+sharedImageName (SharedImage n _ _ _ _) = n --- | Return the date of a shared image.-siDate :: SharedImage -> SharedImageDate-siDate (SharedImage _ n _ _ _) = n+-- | Return the build date of a shared image.+sharedImageDate :: SharedImage -> SharedImageDate+sharedImageDate (SharedImage _ n _ _ _) = n  -- | Return the build id of a shared image.-siBuildId :: SharedImage -> SharedImageBuildId-siBuildId (SharedImage _ _ n _ _) = n+sharedImageBuildId :: SharedImage -> SharedImageBuildId+sharedImageBuildId (SharedImage _ _ n _ _) = n  -- | Print the contents of the shared image in one line prettyPrintSharedImages :: [SharedImage] -> String@@ -314,9 +314,9 @@       where         cols = [nameC, dateC, idC]           where-            nameC = col "Name" ((\(SharedImageName n) -> n) . siName)-            dateC = col "Date" ((\(SharedImageDate n) -> n) . siDate)-            idC = col "ID" ((\(SharedImageBuildId n) -> n) . siBuildId)+            nameC = col "Name" ((\(SharedImageName n) -> n) . sharedImageName)+            dateC = col "Date" ((\(SharedImageDate n) -> n) . sharedImageDate)+            idC = col "ID" ((\(SharedImageBuildId n) -> n) . sharedImageBuildId)             col title accessor =               (Boxes.text title) Boxes.// (Boxes.vcat Boxes.left cells)               where
src/lib/B9/LibVirtLXC.hs view
@@ -11,15 +11,13 @@ import Text.Printf ( printf ) import Data.Char (toLower) import Control.Lens (view)- import B9.ShellScript import B9.B9Monad import B9.B9Config (libVirtLXCConfigs) import B9.DiskImages import B9.ExecEnv import B9.B9Config.LibVirtLXC as X-import Data.ConfigFile.B9Extras-+import System.IO.B9Extras (UUID(), randomUUID)  logLibVirtLXCConfig :: LibVirtLXCConfig -> B9 () logLibVirtLXCConfig c = traceL $ printf "USING LibVirtLXCConfig: %s" (show c)@@ -215,5 +213,3 @@  cpuCountStr :: ExecEnv -> String cpuCountStr = show . cpuCount . envResources--
src/lib/B9/Repository.hs view
@@ -21,7 +21,7 @@ import System.FilePath import System.Directory import B9.B9Config.Repository as X-import Data.ConfigFile.B9Extras+import System.IO.B9Extras  -- | Initialize the local repository cache directory. initRepoCache :: MonadIO m => SystemPath -> m RepoCache
src/lib/B9/RepositoryIO.hs view
@@ -10,9 +10,7 @@  import B9.Repository import B9.B9Monad-import Data.ConfigFile.B9Extras--+import System.IO.B9Extras (ensureDir) import Data.List import Control.Monad.IO.Class import System.Directory
src/lib/B9/VmBuilder.hs view
@@ -58,7 +58,7 @@       srcImg <- resolveImageSource imageSource       let buildImg = changeImageFormat buildImgType                                        (changeImageDirectory buildDir srcImg)-      buildImgAbsolutePath <- liftIO (ensureAbsoluteImageDirExists buildImg)+      buildImgAbsolutePath <- ensureAbsoluteImageDirExists buildImg       materializeImageSource imageSource buildImg       return buildImgAbsolutePath 
src/lib/Data/ConfigFile/B9Extras.hs view
@@ -1,151 +1,91 @@-{-# Language DeriveDataTypeable #-}+{-# Language DeriveDataTypeable, ConstraintKinds #-} {-| Extensions to 'Data.ConfigFile' and utility functions for dealing with     configuration in general and reading/writing files. -}-module Data.ConfigFile.B9Extras ( allOn-                                , lastOn-                                , SystemPath (..)-                                , resolve-                                , ensureDir-                                , readIniFile-                                , getOptionM-                                , getOption-                                , getOptionOr-                                , IniFileException(..)-                                , module Data.ConfigFile-                                , UUID (..)-                                , randomUUID-                                , tell-                                , consult-                                , getDirectoryFiles-                                , maybeConsult-                                , maybeConsultSystemPath+module Data.ConfigFile.B9Extras ( addSectionCP+                                , setShowCP+                                , setCP+                                , readCP+                                , mergeCP+                                , toStringCP+                                , sectionsCP+                                , emptyCP+                                , type CPGet+                                , type CPOptionSpec+                                , type CPSectionSpec+                                , type CPDocument+                                , CPError()+                                , readCPDocument+                                , CPReadException(..)                                 ) where +#if !MIN_VERSION_base(4,10,0) import Data.Monoid-import Data.Function ( on )+#endif import Data.Typeable #if !MIN_VERSION_base(4,8,0) import Control.Applicative #endif-import Control.Exception-import Control.Monad.IO.Class-import System.Directory-import Text.Read ( readEither )-import System.Random ( randomIO )-import Data.Word ( Word16, Word32 )-import System.FilePath-import Text.Printf import Data.ConfigFile-import Data.Data-import Text.Show.Pretty (ppShow)+import Control.Exception+import Control.Monad.Except+import System.IO.B9Extras -allOn :: (a -> Maybe Bool) -> a -> a -> Maybe Bool-allOn getter x y = getAll <$> on mappend (fmap All . getter) x y+-- * Aliases for functions and types from 'ConfigParser' in 'Data.ConfigFile' -lastOn :: (a -> Maybe b) -> a -> a -> Maybe b-lastOn getter x y = getLast $ on mappend (Last . getter) x y+-- | An alias for  'ConfigParser'+type CPDocument = ConfigParser -data SystemPath = Path FilePath-                | InHomeDir FilePath-                | InB9UserDir FilePath-                | InTempDir FilePath-  deriving (Eq, Read, Show, Typeable, Data)+-- | An alias for 'SectionSpec'.+type CPSectionSpec = SectionSpec -resolve :: MonadIO m => SystemPath -> m FilePath-resolve (Path p) = return p-resolve (InHomeDir p) = liftIO $ do-  d <- getHomeDirectory-  return $ d </> p-resolve (InB9UserDir p) = liftIO $ do-  d <- getAppUserDataDirectory "b9"-  return $ d </> p-resolve (InTempDir p) = liftIO $ do-  d <- getTemporaryDirectory-  return $ d </> p+-- | An alias for 'OptionSpec'+type CPOptionSpec = OptionSpec --- | Get all files from 'dir' that is get ONLY files not directories-getDirectoryFiles :: MonadIO m => FilePath -> m [FilePath]-getDirectoryFiles dir = do-  entries <- liftIO (getDirectoryContents dir)-  fileEntries <- mapM (liftIO . doesFileExist . (dir </>)) entries-  return (snd <$> filter fst (fileEntries `zip` entries))+-- | An alias for 'setshow'.+setShowCP :: (Show a, MonadError CPError m) => CPDocument -> CPSectionSpec -> CPOptionSpec -> a -> m CPDocument+setShowCP = setshow -ensureDir :: MonadIO m => FilePath -> m ()-ensureDir p = liftIO (createDirectoryIfMissing True $ takeDirectory p)+-- | An alias for 'set'.+setCP :: (MonadError CPError m) => CPDocument -> CPSectionSpec -> CPOptionSpec -> String -> m CPDocument+setCP = set -data ReaderException = ReaderException FilePath String-  deriving (Show, Typeable)-instance Exception ReaderException+-- | An alias for 'get'.+readCP :: (CPGet a, MonadError CPError m) => CPDocument -> CPSectionSpec -> CPOptionSpec -> m a+readCP = get -tell :: (MonadIO m, Show a) => FilePath -> a -> m ()-tell f x = do-  ensureDir f-  liftIO (writeFile f (ppShow x))+-- | An alias for 'Get_C'+type CPGet a = Get_C a -consult :: (MonadIO m, Read a) => FilePath -> m a-consult f = liftIO $ do-  c <- readFile f-  case readEither c of-    Left e ->-      throwIO $ ReaderException f e-    Right a ->-      return a+-- | An alias for 'add_section'.+addSectionCP :: MonadError CPError m => CPDocument -> CPSectionSpec -> m CPDocument+addSectionCP = add_section -maybeConsult :: (MonadIO m, Read a) => Maybe FilePath -> a -> m a-maybeConsult Nothing defaultArg = return defaultArg-maybeConsult (Just f) defaultArg = liftIO $ do-  exists <- doesFileExist f-  if exists-    then consult f-    else return defaultArg+-- | An alias for 'merge'.+mergeCP :: CPDocument -> CPDocument -> CPDocument+mergeCP = merge -maybeConsultSystemPath :: (MonadIO m, Read a) => Maybe SystemPath -> a -> m a-maybeConsultSystemPath Nothing defaultArg = return defaultArg-maybeConsultSystemPath (Just f) defaultArg = liftIO $ do-  f' <- resolve f-  exists <- doesFileExist f'-  if exists-    then consult f'-    else return defaultArg+-- | An alias for 'to_string'+toStringCP :: CPDocument -> String+toStringCP = to_string -data IniFileException = IniFileException FilePath CPError-                      deriving (Show, Typeable)-instance Exception IniFileException+-- | An alias for 'sections'.+sectionsCP :: CPDocument -> [SectionSpec]+sectionsCP = sections -readIniFile :: MonadIO m => SystemPath -> m ConfigParser-readIniFile cfgFile' = do+-- * Reading a 'CPDocument' from a 'SystemPath'++-- | Read a file and try to parse the contents as a 'CPDocument', if something+-- goes wrong throw a 'CPReadException'+readCPDocument :: MonadIO m => SystemPath -> m CPDocument+readCPDocument cfgFile' = do   cfgFilePath <- resolve cfgFile'   liftIO $ do     res <- readfile emptyCP cfgFilePath     case res of-      Left e -> throwIO (IniFileException cfgFilePath e)+      Left e -> throwIO (CPReadException cfgFilePath e)       Right cp -> return cp -getOption :: (Get_C a, Monoid a) => ConfigParser -> SectionSpec -> OptionSpec -> a-getOption cp sec key = either (const mempty) id $ get cp sec key--getOptionM :: (Read a) => ConfigParser -> SectionSpec -> OptionSpec -> Maybe a-getOptionM cp sec key = either (const Nothing) id $ get cp sec key--getOptionOr :: (Get_C a) => ConfigParser -> SectionSpec -> OptionSpec -> a -> a-getOptionOr cp sec key dv = either (const dv) id $ get cp sec key--newtype UUID = UUID (Word32, Word16, Word16, Word16, Word32, Word16)-             deriving (Read, Show, Eq, Ord)--instance PrintfArg UUID where-  formatArg (UUID (a, b, c, d, e, f)) fmt-    | fmtChar (vFmt 'U' fmt) == 'U' =-        let str = (printf "%08x-%04x-%04x-%04x-%08x%04x" a b c d e f :: String)-        in formatString str (fmt { fmtChar = 's', fmtPrecision = Nothing })-    | otherwise = errorBadFormat $ fmtChar fmt---randomUUID :: MonadIO m => m UUID-randomUUID = liftIO-               (UUID <$> ((,,,,,) <$> randomIO-                                  <*> randomIO-                                  <*> randomIO-                                  <*> randomIO-                                  <*> randomIO-                                  <*> randomIO))+-- | An exception thrown by 'readCPDocument'.+data CPReadException = CPReadException FilePath CPError+                      deriving (Show, Typeable)+instance Exception CPReadException
+ src/lib/System/IO/B9Extras.hs view
@@ -0,0 +1,118 @@+-- | Some utilities to deal with IO in B9.+module System.IO.B9Extras (SystemPath (..)+                                , resolve+                                , ensureDir+                                , getDirectoryFiles+                                , prettyPrintToFile+                                , consult+                                , ConsultException(..)+                                , randomUUID+                                 , UUID()+                                ) where++import Data.Typeable+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Exception+import Control.Monad.Except+import System.Directory+import Text.Read ( readEither )+import System.Random ( randomIO )+import Data.Word ( Word16, Word32 )+import System.FilePath+import Text.Printf+import Data.Data+import Text.Show.Pretty (ppShow)++-- * Relative Paths++-- | A data type encapsulating different kinds of relative or absolute paths.+data SystemPath = Path FilePath  -- ^ A path that will just be passed through+                | InHomeDir FilePath -- ^ A OS specific path relative to+                                      -- the home directory of a user.+                | InB9UserDir FilePath -- ^ A path relative to the @b9@ sub of+                                       -- the users application configuration+                                       -- directory 'getAppUserDataDirectory'+                | InTempDir FilePath -- ^ A path relative to the systems+                                     -- temporary directory.+  deriving (Eq, Read, Show, Typeable, Data)++-- | Convert a 'SystemPath' to a 'FilePath'.+resolve :: MonadIO m => SystemPath -> m FilePath+resolve (Path p) = return p+resolve (InHomeDir p) = liftIO $ do+  d <- getHomeDirectory+  return $ d </> p+resolve (InB9UserDir p) = liftIO $ do+  d <- getAppUserDataDirectory "b9"+  return $ d </> p+resolve (InTempDir p) = liftIO $ do+  d <- getTemporaryDirectory+  return $ d </> p++-- * File System Directory Utilities++-- | Get all files from 'dir' that is get ONLY files not directories+getDirectoryFiles :: MonadIO m => FilePath -> m [FilePath]+getDirectoryFiles dir = do+  entries <- liftIO (getDirectoryContents dir)+  fileEntries <- mapM (liftIO . doesFileExist . (dir </>)) entries+  return (snd <$> filter fst (fileEntries `zip` entries))++-- | Create all missing parent directories of a file path.+-- Note that the file path is assumed to be of a regular file, and+-- 'takeDirectory' is applied before creating the directory.+ensureDir :: MonadIO m => FilePath -> m ()+ensureDir p = liftIO (createDirectoryIfMissing True $ takeDirectory p)++-- * Reading and Writing from/to Files++-- | Write a value of a type that is an instance of 'Show' to file.+-- This function uses 'ppShow' instead of the given 'Show' instance.+prettyPrintToFile :: (MonadIO m, Show a) => FilePath -> a -> m ()+prettyPrintToFile f x = do+  ensureDir f+  liftIO (writeFile f (ppShow x))++-- | Read a value of a type that is an instance of 'Read' from a file.+-- This function throws a 'ConsultException' when the read the file failed.+consult :: (MonadIO m, Read a) => FilePath -> m a+consult f = liftIO $ do+  c <- readFile f+  case readEither c of+    Left e ->+      throwIO $ ConsultException f e+    Right a ->+      return a++-- | An 'Exception' thrown by 'consult' to indicate the file does not+-- contain a 'read'able String+data ConsultException = ConsultException FilePath String+  deriving (Show, Typeable)++instance Exception ConsultException++-- * Unique Random IDs++-- | A bunch of numbers, enough to make globally unique IDs. Create one of these+-- using 'randomUUID'.+newtype UUID = UUID (Word32, Word16, Word16, Word16, Word32, Word16)+             deriving (Read, Show, Eq, Ord)++instance PrintfArg UUID where+  formatArg (UUID (a, b, c, d, e, f)) fmt+    | fmtChar (vFmt 'U' fmt) == 'U' =+        let str = (printf "%08x-%04x-%04x-%04x-%08x%04x" a b c d e f :: String)+        in formatString str (fmt { fmtChar = 's', fmtPrecision = Nothing })+    | otherwise = errorBadFormat $ fmtChar fmt++-- | Generate a random 'UUID'.+randomUUID :: MonadIO m => m UUID+randomUUID = liftIO+               (UUID <$> ((,,,,,) <$> randomIO+                                  <*> randomIO+                                  <*> randomIO+                                  <*> randomIO+                                  <*> randomIO+                                  <*> randomIO))