packages feed

cabal-clean (empty) → 0.1.20210815

raw patch · 10 files changed

+765/−0 lines, 10 filesdep +basedep +containersdep +directory

Dependencies added: base, containers, directory, filepath, mtl, optparse-applicative, pretty-terminal, process, split, string-qq

Files

+ CHANGELOG.md view
@@ -0,0 +1,4 @@+0.1.20210815+------------++Initial release.
+ LICENSE view
@@ -0,0 +1,11 @@+Copyright (C) 2021 Andreas Abel++Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,120 @@+[![Hackage version](https://img.shields.io/hackage/v/cabal-clean.svg?label=Hackage&color=informational)](http://hackage.haskell.org/package/cabal-clean)+[![cabal-clean on Stackage Nightly](https://stackage.org/package/cabal-clean/badge/nightly)](https://stackage.org/nightly/package/cabal-clean)+[![Stackage LTS version](https://www.stackage.org/package/cabal-clean/badge/lts?label=Stackage)](https://www.stackage.org/package/cabal-clean)+[![Cabal build](https://github.com/andreasabel/cabal-clean/workflows/Haskell-CI/badge.svg)](https://github.com/andreasabel/cabal-clean/actions)++cabal-clean : Remove superseded artefacts of `cabal v2-build`+=============================================================++Removes compilation artefacts in `dist-newstyle/build` from older+versions of the package or superseded minor versions of GHC.++For the impatient+-----------------++### Installation++    cabal install cabal-clean++### Run++    cd $MY_PROJECT+    cabal-clean++This lists the build artifacts under `dist-newstyle/build`.+```diff+---	191M	dist-newstyle/build/x86_64-osx/ghc-7.10.3/$MY_PROJECT-2.9.2+---	 72M	dist-newstyle/build/x86_64-osx/ghc-8.10.4/$MY_PROJECT-2.9.2+---	162M	dist-newstyle/build/x86_64-osx/ghc-9.0.1/$MY_PROJECT-2.9.2++++	135M	dist-newstyle/build/x86_64-osx/ghc-7.10.3/$MY_PROJECT-2.9.3+---	 70M	dist-newstyle/build/x86_64-osx/ghc-8.10.4/$MY_PROJECT-2.9.3++++	145M	dist-newstyle/build/x86_64-osx/ghc-8.10.5/$MY_PROJECT-2.9.3++++	159M	dist-newstyle/build/x86_64-osx/ghc-9.0.1/$MY_PROJECT-2.9.3+```+The superseded ones, printed in red and prefixed by dashes (`---`),+can then be removed by:++    cabal-clean --delete++Rationale+---------++`v2-cabal` (the nix-based `cabal`) maintains a directory structure for+local builds of the form+`dist-newstyle/build/$ARCH/$HC/$PACKAGE-$VERSION` (plus other stuff+that does not take up much disk space).  During active development+with several `$HC` versions and `$VERSION` bumps for the `$PACKAGE`,+lots of out-dated build artefacts accumulate over time.++A simple way to clean up is removing the whole `dist-newstyle` folder,+but one might want to keep the build artefacts of the most recent+package `$VERSION`s of the most recent versions of the Haskell+compiler (`$HC`).++Philosophy+----------++- Go for saving the most disk space with the simplest approach, rather+  than a complete clean-up.  E.g., don't care about outdated contents+  in `dist-newstyle/package-db` as they take little space.++- Keep only the most recent `$VERSION` of the package.++- Keep only the most recent major versions of `$HC`.++- Assume a monopoly of GHC, ignoring other Haskell compilers, so only+  treat `$HC`s of the form `ghc-$GHCVER`.++- Work autonomously, ignoring `cabal` files.+  This saves us parsing various home-grown `cabal` file formats.+  The latter could be easy using the `Cabal` package,+  but this package is not very stable,+  and we shy the maintenance effort of depending on `Cabal`.++- Dry-run is the default, giving the user opportunity to review the clean-up plan.++Functionality+-------------++- Read the contents of `dist-newstyle/build`,+  organizing them into a tree according to the pattern+  `$ARCH/ghc-$GHCVER/$PACKAGE-$VERSION`.++- Display the outdated versions.++  Try to get the disk usage with `du -h` and display it.+  (I could not find a Haskell library that gets the disk usage OS-agnostically.)++- With option `--delete` actually remove the respective folders.++Examples+--------++List build artifacts of current project,+marking superseded ones that can be deleded:++    cabal-clean++Actually delete superseded builds:++    cabal-clean --delete++Delete superseded builds without changing to directory:++    cabal-clean --delete path/to/my/project/dist-newstyle++Delete superseded builds in many projects:++    find . -name "dist-newstyle" -exec cabal-clean --delete {} \;++Get help:++    cabal-clean --help++Related+-------++`cabal v2-clean` (as of 2021-08-16)++  - Removes *all* build artefacts.+  - Does not have a `--dry-run` preview.
+ cabal-clean.cabal view
@@ -0,0 +1,103 @@+cabal-version:       >=1.10++name:                cabal-clean+version:             0.1.20210815+synopsis:            Remove outdated cabal build artefacts from `dist-newstyle`.++description:         Simple command line tool to remove cabal build artefacts+                     from `dist-newstyle/build` that are superseded by newer+                     versions of the built package or the Haskell compiler.++homepage:            https://github.com/andreasabel/cabal-clean+bug-reports:         https://github.com/andreasabel/cabal-clean/issues+license:             BSD3+license-file:        LICENSE++author:              Andreas Abel+maintainer:          Andreas Abel <andreas.abel@cse.gu.se>+copyright:           Andreas Abel, 2021+category:            Development++build-type:          Simple++extra-source-files:  CHANGELOG.md+                     README.md++tested-with:+  GHC == 8.0.2+  GHC == 8.2.2+  GHC == 8.4.4+  GHC == 8.6.5+  GHC == 8.8.4+  GHC == 8.10.4+  GHC == 9.0.1++source-repository head+  type:     git+  location: git://github.com/andreasabel/cabal-clean.git++source-repository this+  type:     git+  location: git://github.com/andreasabel/cabal-clean.git+  tag:      v0.1.20210815++executable cabal-clean+  main-is:             Main.hs++  other-modules:+    License+    Options+    Structure+    Util+    Version+    Paths_cabal_clean++  build-depends:+    base >= 4.9 && < 5+    -- fromDescList requires containers >= 0.5.8+    , containers >= 0.5.8+    , directory+    -- , directory >= 1.2.6.2 && < 1.4+    , filepath+    -- , filepath == 1.4.*+    , mtl+    , optparse-applicative+    -- , optparse-applicative >= 0.13 && < 0.16+    -- pretty-terminal-0.1.0.0 has base >= 4.9+    , pretty-terminal+    , process+    , split+    , string-qq++  hs-source-dirs:      src+  default-language:    Haskell2010++  default-extensions:+    -- BangPatterns+    -- ConstraintKinds+    -- DefaultSignatures+    -- DeriveDataTypeable+    -- DeriveFoldable+    -- DeriveFunctor+    -- DeriveTraversable+    -- ExistentialQuantification+    -- FlexibleContexts+    -- FlexibleInstances+    -- FunctionalDependencies+    -- InstanceSigs+    LambdaCase+    -- MultiParamTypeClasses+    -- MultiWayIf+    -- NamedFieldPuns+    -- OverloadedStrings+    -- PatternSynonyms+    -- RankNTypes+    RecordWildCards+    -- ScopedTypeVariables+    -- StandaloneDeriving+    TupleSections+    -- TypeSynonymInstances++  ghc-options:+    -Wall+    -Wcompat
+ src/License.hs view
@@ -0,0 +1,25 @@+-- This file was autogenerated from LICENSE, do not edit!!++{-# LANGUAGE QuasiQuotes #-}++module License where++import Data.String.QQ++copyright :: String+copyright = head (lines license)++license :: String+license = [s|+Copyright (C) 2021 Andreas Abel++Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+|]
+ src/Main.hs view
@@ -0,0 +1,182 @@+-- | cabal-clean+--+-- Remove build artefacts of outdated versions from `dist-newstyle`.++{-# LANGUAGE RecordWildCards #-}++module Main where++import Options.Applicative+  ( Parser+  , action, execParser, footerDoc, headerDoc, help, helper, hidden+  , info, infoOption, long, metavar, short, strArgument, switch, value+  )+import Options.Applicative.Help.Pretty+  ( vcat, text )++import System.Console.Pretty+  ( supportsPretty )+import System.IO+  ( hPutStr, hPutStrLn, stderr )++import License+  ( copyright, license )+import Options+import Structure+  ( Entry(..), foldMapEntry, getBuildTree, markObsolete, printBuildTree )+import Util+import Version++self :: String+self = "cabal-clean"++homepage :: String+homepage = concat [ "https://github.com/andreasabel/", self ]++defaultRoot :: FilePath+defaultRoot = "dist-newstyle"++main :: IO ()+main = do++  -- Parse options.++  opts@Options{..} <- options+  -- print opts+  let buildDir = optRoot </> "build"++  -- Get build tree.++  chatLn opts $ unwords [ "Reading", buildDir, "..." ]+  unlessM (doesDirectoryExist buildDir) $ do+    die $ unwords [ "No such directory:", buildDir ]++  (tree0, warns) <- getBuildTree buildDir+  forM_ warns $ putStrLn . ("warning: " ++)++  let tree = markObsolete tree0+  printBuildTree opts tree++  -- Remove obsolete directories+  when optDelete $ do+    chatLn opts $ unwords [ "Removing obsolete directories..." ]+    flip foldMapEntry tree $ \ (Entry dir obsolete) -> do+      if obsolete then do+        chatLn opts $ unwords [ "Removing", dir ]+        removeDirectoryRecursive dir+      else chatLn opts $ unwords [ "Keeping", dir ]++  -- Done.++  chatLn opts $ unwords [ self, "terminated successfully." ]++-- * Option parsing and handling++options :: IO Options+options = do+  opts <- execParser $ info parser infoMod+  supportsPretty >>= \case+    True  -> return opts+    False -> return opts{ optNoColors = True }+  where+  parser = programOptions <**>+    (versionOption <*> numericVersionOption <*> licenseOption <*> helper)+  infoMod = headerDoc header <> footerDoc footer++  versionOption =+    infoOption versionLong+      $  long "version"+      <> short 'V'+      <> help "Show version info."+      <> hidden+  versionText = unwords [ self, "version", version ]+  versionLong = intercalate "\n" $+    [ versionText+    , copyright+    , "This is free software under the BSD-3-clause license."+    ]++  numericVersionOption =+    infoOption version+      $  long "numeric-version"+      <> help "Show just version number."+      <> hidden+      -- Newline at the end:+      -- <> helpDoc (Just $ text "Show just version number." <$$> text "")++  licenseOption =+    infoOption license+      $  long "license"+      -- <> long "licence"+      <> help "Show the license text."+      <> hidden++  -- Obs: match the order with Options.Options!+  programOptions = Options+    <$> oDelete+    <*> oNoColors+    <*> oVerbose+    <*> oRoot++  oDelete =+    switch+      $  long "delete"+      <> long "remove"+      -- <> short 'D'+      <> help "Actually clean up on disk."++  oVerbose =+    switch+      $  long "verbose"+      <> short 'v'+      <> help "Comment on what is happening."++  oNoColors =+    switch+      $  long "no-colors"+      <> help "Disable colorized output.  Automatic if terminal does not support colors."++  oRoot :: Parser FilePath+  oRoot = do+    strArgument+      $  metavar "ROOT"+      <> value defaultRoot+      <> action "directory"+      <> help (concat ["The root of the build tree. Default: ", show defaultRoot, "."])++  -- Note: @header@ does not respect line breaks, so we need @headerDoc@.+  header = Just $ vcat $ map text+    [ unwords [ versionText, homepage ]+    , ""+    , concat+      [ "Clean superseded build artefacts from directory ROOT (typically "+      , show defaultRoot+      , ")."+      ]+    , unwords+      [ "A package build is considered superseded if there is a local build"+      , "of either a newer version of the package or with a newer minor version"+      , "of the Haskell compiler."+      ]+    ]+  footer = Just $ vcat $ map (text . unwords)+    [ [ unwords ["Without option --delete,", self, "does not actually clean out anything,"]+      , "just shows prefixed with '---' and in red what would be removed and prefixed with '+++' and in green what is kept."+      ]+    , [ "" ]+    , [ "Warning: there is check whether the to-be-deleted contents are actually garbage."+      , "(E.g., there could be symlinks to executables stored there.)"+      ]+    ]++-- * Verbosity functionality.++chat :: Options -> String -> IO ()+chat = chatGen $ hPutStr stderr++chatLn :: Options -> String -> IO ()+chatLn = chatGen $ hPutStrLn stderr++chatGen :: (String -> IO ()) -> Options -> String -> IO ()+chatGen prt o msg = when (optVerbose o) $+  prt $ styleOpt o Faint $ unwords ["info:", msg]
+ src/Options.hs view
@@ -0,0 +1,28 @@+module Options (module Options, module X) where++import System.Console.Pretty ( color, style )+import System.Console.Pretty  as X+  ( Color(Green, Red, White)+  , Style(ColoredNormal, Faint, Italic)+  )+import System.FilePath (FilePath)++data Options = Options+  { optDelete     :: Bool+  , optNoColors   :: Bool+  , optVerbose    :: Bool+  , optRoot       :: FilePath+  } deriving Show++-- | Apply ANSI style if coloring is enabled in 'Options'.++styleOpt :: Options -> Style -> String -> String+styleOpt opts = applyWhenColors opts style++colorOpt :: Options -> Color -> String -> String+colorOpt opts = applyWhenColors opts color++applyWhenColors :: Options -> (a -> b -> b) -> a -> b -> b+applyWhenColors opts+  | optNoColors opts = \ _ _ -> id+  | otherwise        = id
+ src/Structure.hs view
@@ -0,0 +1,236 @@+-- {-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}++{-# OPTIONS_GHC -Wno-deprecations #-}  -- versionTags++-- | Data structure for the `dist-newstyle/build` directory.+--+-- Example:+--+-- @+--   dist-newstyle/build/+--   └── x86_64-osx+--       ├── ghc-7.10.3+--       │   ├── BNFC-2.9.2+--       │   └── BNFC-2.9.3+--       ├── ghc-8.10.4+--       │   └── BNFC-2.9.2+--       ├── ghc-8.10.5+--       │   └── BNFC-2.9.3+--       └── ghc-9.0.1+--           ├── BNFC-2.9.2+--           └── BNFC-2.9.3+-- @+--+-- We organize the contents of the build directory+-- in the way we see fit:+--+-- * Level 1: package+-- * Level 2: package version+-- * Level 3: architecture+-- * Level 4: major compiler version+-- * Level 5: minor compiler version++module Structure where++import qualified Data.Map as Map+import Util++import Options++-- | The structure of the build directory.++newtype BuildTree = BuildTree+  { theBuildTree ::+      Map Package+       (Map PackageVersion+         (Map Arch+           (Map MajorVersion+              (Map MinorVersion Entry))))+  }+  deriving Show++-- | The build directory is a map from 'Key' to 'Entry'.++data Key = Key+  { pkg    :: Package+  , pkgVer :: PackageVersion+  , arch   :: Arch+  , ghcMaj :: MajorVersion+  , ghcMin :: MinorVersion+  }++data Entry = Entry+  { dir      :: FilePath+  , obsolete :: Bool+  } deriving Show++-- | We treat the architecture identifier as opaque.+type Arch = String++-- | A package is given by its name.+type Package = String++-- | A package version is a list of natural numbers.+type PackageVersion  = NumericVersion++-- | A GHC major version is a list of natural numbers.+type MajorVersion = NumericVersion++-- | A GHC minor version is a list of natural numbers.+type MinorVersion = NumericVersion++type CompilerVersion = (MajorVersion, MinorVersion)++type NumericVersion = [Int]++-- * Loading the build tree from disc.++type Warnings = [String]++-- | Load the build tree from e.g. @dist-newstyle/build@.+--+-- Generate warning for subdirectories we cannot parse,+-- but ignore them otherwise.++getBuildTree :: FilePath -> IO (BuildTree, Warnings)+getBuildTree root = runWriterT $ do++  -- Traverse architectures+  forMM (getSubdirectories root) $ \ arch -> do+    let archroot = root </> arch++    -- Traverse compilers+    forMM (getSubdirectories archroot) $ \ hc -> do+      let hcroot = archroot </> hc+      case parseCompilerString hc of+        Nothing -> warnIgnoring hcroot+        Just (major, minor) -> do++          -- Traverse packages+          forMM (getSubdirectories hcroot) $ \ s -> do+            let pkgdir = hcroot </> s+            case parsePackageString s of+              Nothing -> warnIgnoring pkgdir+              Just (pkg, ver) -> do+                return $ singleton (Key pkg ver arch major minor) pkgdir+  where+  warnIgnoring dir = do+    tell [ unwords ["Ignoring", dir] ]+    return mempty++-- | Precondition: argument must be a directory.++getSubdirectories :: MonadIO io => FilePath -> io [FilePath]+getSubdirectories root = liftIO $ do+  filterM (doesDirectoryExist . (root </>)) =<< listDirectory root++-- * Mark obsolete entries of the build tree.++-- | Mark entries that are superseded by either a new package version+-- or a new compiler minor version.++markObsolete :: BuildTree -> BuildTree+markObsolete (BuildTree t) = BuildTree $+  flip fmap t $ modifyDesc $ \case+    [] -> []+    (ver, m) : vms ->+      (ver, flip (fmap . fmap) m $ modifyDesc $ \case+        [] -> []+        me : mes -> me : map (second markEntryObsolete) mes+      ) : map (second $ fmap $ fmap $ fmap markEntryObsolete) vms++  where+  modifyDesc f = Map.fromDescList . f . Map.toDescList+  -- mapDesc f = Map.fromDescList . map (second f) . Map.toDescList++markEntryObsolete :: Entry -> Entry+markEntryObsolete (Entry dir _) = Entry dir True++-- | Remove directories marked as obsolete.++removeObsolete :: BuildTree -> IO ()+removeObsolete = foldMapEntry $ \ (Entry dir obsolete) -> do+  when obsolete $ do+    removeDirectoryRecursive dir++-- * Printing the build tree to the terminal.++-- | Print the build tree, coloring parts to keep in green, and parts to remove in red.++printBuildTree :: Options -> BuildTree -> IO ()+printBuildTree opts = foldMapEntry $ \ (Entry dir obsolete) -> do+  (exitcode, stdout, _stderr) <- readProcessWithExitCode "du" ["-hs", dir] ""+  let s = if exitcode == ExitSuccess then stdout else dir ++ "\n"+  putStr $ colorize obsolete s+  where+  colorize True  = colorOpt opts Red   . ("---\t" ++)+  colorize False = colorOpt opts Green . ("+++\t" ++)++-- * Mathematics of the build directory structure.++instance Semigroup BuildTree where+  BuildTree t1 <> BuildTree t2 = BuildTree $+    Map.unionWith (Map.unionWith $ Map.unionWith $ Map.unionWith $ Map.union) t1 t2++instance Monoid BuildTree where+  mempty  = BuildTree $ Map.empty+  mappend = (<>)++-- | A build tree with a single entry.++singleton :: Key -> FilePath -> BuildTree+singleton (Key pkg ver arch major minor) dir = BuildTree $+  Map.singleton pkg $+  Map.singleton ver $+  Map.singleton arch $+  Map.singleton major $+  Map.singleton minor $ Entry dir False++-- | Modify all entries of a build tree.++mapEntry :: (Entry -> Entry) -> BuildTree -> BuildTree+mapEntry f (BuildTree t) = BuildTree $ (fmap . fmap . fmap . fmap . fmap) f t++-- | Modify all entries of a build tree, accumulating effects left-to-right.++traverseEntry :: Applicative m => (Entry -> m Entry) -> BuildTree -> m BuildTree+traverseEntry f (BuildTree t) =+  BuildTree <$> (traverse . traverse . traverse . traverse . traverse) f t++-- | Reduce build tree.++foldMapEntry :: Monoid m => (Entry -> m) -> BuildTree -> m+foldMapEntry f (BuildTree t) = (foldMap . foldMap . foldMap . foldMap . foldMap) f t++-- * Parsing directory names++parseKey :: Arch -> CompilerString -> PackageString -> Maybe Key+parseKey arch hc s = do+  (major, minor) <- parseCompilerString hc+  (pkg  , ver  ) <- parsePackageString s+  return $ Key pkg ver arch major minor++type CompilerString = String++parseCompilerString :: CompilerString -> Maybe CompilerVersion+parseCompilerString s = do+  n <- findIndex (== '-') s+  case splitAt n s of+    ("ghc", _:v) -> splitAt 2 <$> parseVersionString v+    _ -> Nothing++type PackageString = String++parsePackageString :: PackageString -> Maybe (Package, PackageVersion)+parsePackageString s = do+  n <- findIndexEnd (== '-') s+  let (p, _:v) = splitAt n s+  (p,) <$> parseVersionString v++type VersionString = String++parseVersionString :: VersionString -> Maybe NumericVersion+parseVersionString = mapM readMaybe . splitWhen (== '.')
+ src/Util.hs view
@@ -0,0 +1,45 @@+-- | General-purpose utilities and standard imports.++module Util (module Util, module X) where++import Control.Applicative    as X ((<**>))+import Control.Monad          as X ((<=<), filterM, forM, forM_, guard, unless, when)+import Control.Monad.IO.Class as X (MonadIO, liftIO)+import Control.Monad.Writer   as X (WriterT, runWriterT, tell)++import Data.Bifunctor         as X+import Data.Char              as X (isSpace)+import Data.Function          as X (on)+import Data.List              as X (findIndex, findIndices, intercalate)+import Data.List.Split        as X (splitWhen)+import Data.Maybe             as X+import Data.Map               as X (Map)+import Data.Semigroup         as X (Semigroup(..))+import Data.Version           as X (Version(..))++import System.Directory       as X (doesDirectoryExist, listDirectory, removeDirectoryRecursive)+import System.Exit            as X (die, ExitCode(..))+import System.FilePath        as X ((</>))+import System.Process         as X (readProcessWithExitCode)++import Text.Read              as X (readMaybe)++(<&>) :: Functor f => f a -> (a -> b) -> f b+(<&>) = flip (<$>)++forMM :: (Monad m, Monoid b) => m [a] -> (a -> m b) -> m b+forMM m k = mconcat <$> do mapM k =<< m++unlessM :: Monad m => m Bool -> m () -> m ()+unlessM cond m = cond >>= (`unless` m)++findIndexEnd :: Eq a => (a -> Bool) -> [a] -> Maybe Int+findIndexEnd p = lastMaybe . findIndices p++lastMaybe :: [a] -> Maybe a+lastMaybe []     = Nothing+lastMaybe (a:as) = Just $ last1 a as++last1 :: a -> [a] -> a+last1 a []     = a+last1 _ (a:as) = last1 a as
+ src/Version.hs view
@@ -0,0 +1,11 @@+module Version ( version ) where++import Data.List ( intercalate )+import Data.Version ( Version(versionBranch) )++import qualified Paths_cabal_clean as Paths++-- | The program version obtained from the cabal file.++version :: String+version = intercalate "." $ map show $ versionBranch Paths.version