cabal-edit (empty) → 0.1.0.0
raw patch · 6 files changed
+985/−0 lines, 6 filesdep +Cabaldep +Globdep +basesetup-changed
Dependencies added: Cabal, Glob, base, bytestring, containers, directory, filepath, hackage-db, optparse-applicative, process, store, time
Files
- CHANGELOG.md +5/−0
- LICENSE +19/−0
- README.md +366/−0
- Setup.hs +2/−0
- cabal-edit.cabal +41/−0
- exe/Main.hs +552/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for cabal-edit++## 0.1.0.0 -- 2020-07-01++* Initial release.
+ LICENSE view
@@ -0,0 +1,19 @@+Copyright (c) 2020, Stephen Diehl++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to+deal in the Software without restriction, including without limitation the+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or+sell copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS+IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,366 @@+cabal-edit+==========+++++This is an extension to Haskell's package manager Cabal to allow you to add,+remove, and upgrade dependencies by modifying your cabal file from the+command line. This behaves similar to `install --save` commands in other+package managers.+++* [`cabal-edit add`](#add)+* [`cabal-edit upgrade`](#upgrade)+* [`cabal-edit upgradeall`](#upgradeall)+* [`cabal-edit remove`](#remove)+* [`cabal-edit list`](#list)+* [`cabal-edit format`](#format)+* [`cabal-edit extensions`](#extensions)+* [`cabal-edit lint`](#lint)+* [`cabal-edit latest`](#latest)+* [`cabal-edit rebuild`](#rebuild)++Usage+-----++### add++For example to setup a new project one often wants to add common dependencies+like `text` and `aeson`. We can use `cabal-edit` to automatically append these+to our dependency list and deduce the latest versions from Hackage.++```bash+$ cabal init --lib --cabal-version=3.0 ++$ cabal-edit add aeson +Adding dependency: aeson ^>=1.5 to sample.cabal++$ cabal-edit add text +Adding dependency: text ^>=1.2 to sample.cabal+```++If we want to depend on a specific version of `aeson`, we can pass this+explicitly as an argument.++```bash+$ cabal-edit add aeson==1.4.0.0+```++Multiple packages can be passed to the add command at once. For example:++```bash+$ cabal-edit add base bytestring aeson text process filepath directory mtl transformers protolude+Adding latest dependency: base ^>=4.14 to sample.cabal+Adding latest dependency: bytestring ^>=0.10 to sample.cabal+Adding latest dependency: aeson ^>=1.5 to sample.cabal+Adding latest dependency: text ^>=1.2 to sample.cabal+Adding latest dependency: process ^>=1.6 to sample.cabal+Adding latest dependency: filepath ^>=1.4 to sample.cabal+Adding latest dependency: directory ^>=1.3 to sample.cabal+Adding latest dependency: mtl ^>=2.2 to sample.cabal+Adding latest dependency: transformers ^>=0.5 to sample.cabal+Adding latest dependency: protolude ^>=0.3 to sample.cabal+```++*Note: Dependency modification will happen over the library stanza of your Cabal+file, and not the executable sections.*++### upgrade++The upgrade command can be used to safely manipulate the version bounds for a+given library. For instance if one has a simple dependency on `text` for `1.0`+version range, like as follows:+++```haskell+library+ exposed-modules: MyLib+ default-language: Haskell2010+ build-depends:+ base >= 4.14 && <=5.0,+ text ^>= 1.0+```++We can bump the bound of this library to upgrade it to allow the latest version+from Hackage. We simply pass the `upgrade` command the name of the package (i.e.+`text`) and it will automatically figure out the appropriate version range for+the upgrade including previously version ranges. The resulting version ranges+will always have an upper bound and will conform to Hackage PVP standards.++```bash+$ cabal-edit upgrade text+Upgrading bounds for text to 1.3+```++This will produce the following modified Cabal file.++```haskell+library+ exposed-modules: MyLib+ default-language: Haskell2010+ build-depends:+ base >=4.14 && <=5.0,+ text >=1.0 && <=1.3+```++### upgradeall++`upgradeall` behaves like `upgrade` but performs the version bound bump for all+available dependencies. This sets the upper bounds for all dependencies to the+latest available version on Hackage.++```bash+$ cabal-edit upgradeall+Upgrading bounds for Cabal to 3.3+Upgrading bounds for aeson to 1.6+Upgrading bounds for base to 4.15+Upgrading bounds for bytestring to 0.11+Upgrading bounds for ghc to 8.11+Upgrading bounds for text to 1.3+```++### remove++Remove will remove a given dependency from the file completely.++```+$ cabal-edit remove microlens+Removing depndency on microlens+```++### list++The Hackage database can be queried from the command line to search for all+available versions to use with the `list` command.++```bash+$ cabal-edit list filepath+1.0+1.1.0.0+1.1.0.1+1.1.0.2+1.1.0.3+1.1.0.4+1.2.0.0+1.2.0.1+1.3.0.0+1.3.0.1+1.3.0.2+1.4.0.0+1.4.1.0+1.4.1.1+1.4.1.2+1.4.2+1.4.2.1+```++### format++The `format` command will canonicalise the Cabal into by parsing it and running+it through the pretty printer again.++```bash+$ cabal-edit format+Formatting: sample.cabal+```++### extensions++The `extensions` command will enumerate all the default extensions enabled for+the given library. This is useful if you wish to add these headers to files+within the project.++```bash+$ cabal-edit extensions++{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+```++### lint++The `lint` command will detect common problems with your version bounds and+recommend package upgrades when available.++```bash+$ cabal-edit lint +aeson : Consider upgrading major bound to latest version 1.6+text : Consider upgrading major bound to latest version 1.3+```++### latest++The `latest` command will return the dependency string for adding the latest+version of a given package.++```bash+$ cabal-edit latest aeson+aeson ^>=1.5+```++### rebuild++cabal-edit uses a cache of Hackage package versions internally. This is normally+built on first run and whenever the package database is older than 30 days. If+you wish to manually rebuild it after running `cabal update` then run:++```+$ cabal-edit rebuild+Done.+```++Installation+------------++Download this repository.++```bash+$ git clone git@github.com:sdiehl/cabal-edit.git+$ cd cabal-edit+```++Then install either with Stack, Cabal or Nix.++```bash+$ stack install +$ cabal install --installdir=/home/$USER/.local/bin+$ nix-build -A cabal-edit+```++*Note: This library links directly against Cabal, so you must be using the same+version of Cabal you intend to compile your package against. It is reccomened to+use Cabal > 3.0 to support modern versioning schemes.*++Shell Completions+------------------++We can tab complete on the Hackage package index we install the local+completions for you shell. Run one of the following commands to generate the+shell completer appropriate to your shell. Then add the output to one of+`~/.zshrc`, `~/.bashrc` or `~/.config/fish/config.fish`.++```bash+$ cabal-edit --zsh-completion-script cabal-edit+$ cabal-edit --bash-completion-script cabal-edit+$ cabal-edit --fish-completion-script cabal-edit+```++This will completion against the Hackage database prefixed by name.++```+$ cabal-edit add gh+zsh: do you wish to see all 103 possibilities (15 lines)?+$ cabal-edit add ghc-pa+ghc-parmake ghc-parser ghc-paths+```++**bash**++```bash+_cabal-edit()+{+ local CMDLINE+ local IFS=$'\n'+ CMDLINE=(--bash-completion-index $COMP_CWORD)++ for arg in ${COMP_WORDS[@]}; do+ CMDLINE=(${CMDLINE[@]} --bash-completion-word $arg)+ done++ COMPREPLY=( $(cabal-edit "${CMDLINE[@]}") )+}++complete -o filenames -F _cabal-edit cabal-edit+```++**zsh**++```bash+#compdef cabal-edit++local request+local completions+local word+local index=$((CURRENT - 1))++request=(--bash-completion-enriched --bash-completion-index $index)+for arg in ${words[@]}; do+ request=(${request[@]} --bash-completion-word $arg)+done++IFS=$'\n' completions=($( cabal-edit "${request[@]}" ))++for word in $completions; do+ local -a parts++ # Split the line at a tab if there is one.+ IFS=$'\t' parts=($( echo $word ))++ if [[ -n $parts[2] ]]; then+ if [[ $word[1] == "-" ]]; then+ local desc=("$parts[1] ($parts[2])")+ compadd -d desc -- $parts[1]+ else+ local desc=($(print -f "%-019s -- %s" $parts[1] $parts[2]))+ compadd -l -d desc -- $parts[1]+ fi+ else+ compadd -f -- $word+ fi+done+```++**fish**++```bash+ function _cabal-edit+ set -l cl (commandline --tokenize --current-process)+ # Hack around fish issue #3934+ set -l cn (commandline --tokenize --cut-at-cursor --current-process)+ set -l cn (count $cn)+ set -l tmpline --bash-completion-enriched --bash-completion-index $cn+ for arg in $cl+ set tmpline $tmpline --bash-completion-word $arg+ end+ for opt in (cabal-edit $tmpline)+ if test -d $opt+ echo -E "$opt/"+ else+ echo -E "$opt"+ end+ end+end++complete --no-files --command cabal-edit --arguments '(_cabal-edit)'+```++Limitations+-----------++Since this library works directly with the `PackageDescription` data structure+it cannot handle Cabal files in their full generality. Instead we directly+manipulate the internal structure used to represent the Cabal file which is not+capable of representing all surface constructs. If your Cabal file currently+uses:++* Common stanzas+* Conditional blocks+* Preprocessor definitions++These constructs will be compiled into the `PackageDescription` and inlined if+you use `cabal-edit`. This makes `cabal-edit` useful for small beginning+projects and ones that don't use advanced Cabal features.++License+-------++MIT License+Copyright (c) 2020, Stephen Diehl
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cabal-edit.cabal view
@@ -0,0 +1,41 @@+cabal-version: 3.0+name: cabal-edit+version: 0.1.0.0+license: MIT+license-file: LICENSE+copyright: 2020 Stephen Diehl+maintainer: stephen.m.diehl@gmail.com+author: sdiehl+bug-reports: https:/www.github.com/sdiehl/cabal-add/issues+synopsis: Cabal utility+description:+ A utility for managing Hackage dependencies from the command line.++category: Development+build-type: Simple+extra-source-files:+ CHANGELOG.md+ README.md++source-repository head+ type: git+ location: git@github.com:sdiehl/cabal-edit.git++executable cabal-edit+ main-is: Main.hs+ hs-source-dirs: exe+ default-language: Haskell2010+ ghc-options: -Wall+ build-depends:+ , base >=4.12 && <5.0+ , bytestring ^>=0.10+ , Cabal >=3.0 && <4.0+ , containers ^>=0.6+ , directory ^>=1.3+ , filepath ^>=1.4+ , Glob ^>=0.10+ , hackage-db ^>=2.1+ , optparse-applicative ^>=0.15+ , process ^>=1.6+ , store ^>=0.7+ , time >=1.8 && <1.11
+ exe/Main.hs view
@@ -0,0 +1,552 @@+{-# LANGUAGE RecordWildCards #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Main+ ( main,+ )+where++import Control.Monad+import qualified Data.ByteString as BS+import Data.List+import Data.Map as Map+import Data.Maybe+import Data.Set as Set+import Data.Store+import Data.Time.Clock+import qualified Distribution.Hackage.DB.Parsed as P+import Distribution.Hackage.DB.Path+import qualified Distribution.Hackage.DB.Unparsed as U+import Distribution.PackageDescription.Parsec+import Distribution.PackageDescription.PrettyPrint+import Distribution.Parsec+import Distribution.Pretty+import Distribution.Types.BuildInfo+import Distribution.Types.CondTree+import Distribution.Types.Dependency+import Distribution.Types.GenericPackageDescription+import Distribution.Types.Library+import Distribution.Types.LibraryName+import Distribution.Types.PackageDescription+import Distribution.Types.PackageName+import Distribution.Types.Version+import Distribution.Types.VersionRange.Internal+import Distribution.Utils.ShortText+import Distribution.Verbosity+import Options.Applicative+import System.Directory+import System.Exit+import System.FilePath.Glob+import System.FilePath.Posix+import System.Process++-------------------------------------------------------------------------------+-- Library Manipulation+-------------------------------------------------------------------------------++buildInfo :: GenericPackageDescription -> Maybe BuildInfo+buildInfo GenericPackageDescription {..} = fmap go condLibrary+ where+ go (CondNode Library {..} _ _) = libBuildInfo++setBuildInfo ::+ BuildInfo ->+ GenericPackageDescription ->+ GenericPackageDescription+setBuildInfo binfo pkg@GenericPackageDescription {..} = pkg {condLibrary = fmap go condLibrary}+ where+ go (CondNode var deps libs) = CondNode (var {libBuildInfo = binfo}) deps libs++addLibDep ::+ Dependency ->+ BuildInfo ->+ BuildInfo+addLibDep dep binfo@BuildInfo {..} = binfo {targetBuildDepends = targetBuildDepends <> [dep]}++setLibDeps ::+ [Dependency] ->+ BuildInfo ->+ BuildInfo+setLibDeps deps binfo@BuildInfo {} = binfo {targetBuildDepends = deps}++hasLib :: GenericPackageDescription -> IO ()+hasLib pkg =+ if hasLibs (packageDescription pkg)+ then pure ()+ else die "Package has no public library. Cannot modify dependencies."++-------------------------------------------------------------------------------+-- Dependency Manipulation+-------------------------------------------------------------------------------++getDeps ::+ GenericPackageDescription ->+ [Dependency]+getDeps pkg = concat (maybeToList depends)+ where+ depends = fmap targetBuildDepends (buildInfo pkg)++setDeps ::+ [Dependency] ->+ GenericPackageDescription ->+ GenericPackageDescription+setDeps deps pkg@GenericPackageDescription {..} = pkg {condLibrary = fmap go condLibrary}+ where+ go (CondNode var@Library {..} libdeps libs) =+ CondNode (var {libBuildInfo = setLibDeps deps libBuildInfo}) libdeps libs++modifyDeps ::+ (PackageName -> Dependency -> Dependency) ->+ GenericPackageDescription ->+ GenericPackageDescription+modifyDeps f pkg = setDeps [f (depPkgName dep) dep | dep <- getDeps pkg] pkg++-------------------------------------------------------------------------------+-- DepMap+-------------------------------------------------------------------------------++udpateDep ::+ GenericPackageDescription ->+ (PackageName -> Dependency -> Maybe Dependency) ->+ PackageName ->+ Map PackageName Dependency+udpateDep pkg f pk = Map.updateWithKey f pk (depMap pkg)++lookupDep :: GenericPackageDescription -> PackageName -> Maybe Dependency+lookupDep pkg pk = Map.lookup pk (depMap pkg)++modifyDep :: GenericPackageDescription -> PackageName -> Dependency -> GenericPackageDescription+modifyDep pkg pk dep = setDepMap (Map.insert pk dep (depMap pkg)) pkg++deleteDep :: GenericPackageDescription -> PackageName -> GenericPackageDescription+deleteDep pkg pk = setDepMap (Map.delete pk (depMap pkg)) pkg++setDepMap :: Map.Map PackageName Dependency -> GenericPackageDescription -> GenericPackageDescription+setDepMap pkm = setDeps (fmap snd (Map.toList pkm))++depMap :: GenericPackageDescription -> Map.Map PackageName Dependency+depMap pkg = Map.fromList [(depPkgName dep, dep) | dep <- getDeps pkg]++-------------------------------------------------------------------------------+-- Dependency Addition+-------------------------------------------------------------------------------++majorBound :: Version -> Version+majorBound = alterVersion $ \numbers -> case numbers of+ [] -> [0, 1]+ [m1] -> [m1]+ (m1 : m2 : _) -> [m1, m2]++add ::+ Dependency ->+ (FilePath, GenericPackageDescription) ->+ IO GenericPackageDescription+add dep (fname, cabalFile) =+ case depVerRange dep of+ AnyVersion -> do+ let pk = depPkgName dep+ verMap <- cacheDeps+ -- Lookup the latest version and use the majorBound of it.+ ver <- case Map.lookup pk verMap of+ Nothing -> die $ "No such package named: " ++ show pk+ Just vers -> pure (maximum vers)+ let dependency = Dependency pk (majorBoundVersion (majorBound ver)) (Set.singleton defaultLibName)+ putStrLn $ "Adding latest dependency: " ++ prettyShow dependency ++ " to " ++ takeFileName fname+ pure $ modifyDep cabalFile pk dependency+ ThisVersion givenVersion -> addVer ThisVersion givenVersion (fname, cabalFile) dep+ LaterVersion givenVersion -> addVer LaterVersion givenVersion (fname, cabalFile) dep+ OrLaterVersion givenVersion -> addVer OrLaterVersion givenVersion (fname, cabalFile) dep+ EarlierVersion givenVersion -> addVer EarlierVersion givenVersion (fname, cabalFile) dep+ WildcardVersion givenVersion -> addVer WildcardVersion givenVersion (fname, cabalFile) dep+ givenVersion -> die $ "Given version is not on available on Hackage." ++ show givenVersion++upgrade ::+ PackageName ->+ Version ->+ (FilePath, GenericPackageDescription) ->+ IO GenericPackageDescription+upgrade pk latest (_, cabalFile) =+ case lookupDep cabalFile pk of+ Nothing -> do+ putStrLn $ "No current dependency on: " ++ prettyShow pk+ die $ "Perhaps you want to run: cabal-edit add " ++ prettyShow pk+ Just dep -> do+ case depVerRange dep of+ LaterVersion prev ->+ if prev < latest+ then do+ let ver' = intersectVersionRanges (orLaterVersion prev) (orEarlierVersion latest)+ let dep' = Dependency (depPkgName dep) ver' (depLibraries dep)+ pure $ modifyDep cabalFile pk dep'+ else do+ putStrLn "Previous version is inconsistent, replacing lower bound."+ replaceVersion dep+ OrLaterVersion prev -> do+ let ver' = intersectVersionRanges (orLaterVersion prev) (orEarlierVersion latest)+ let dep' = Dependency (depPkgName dep) ver' (depLibraries dep)+ pure $ modifyDep cabalFile pk dep'+ AnyVersion -> replaceVersion dep+ WildcardVersion _ -> replaceVersion dep+ ThisVersion _ -> replaceVersion dep+ OrEarlierVersion _ -> replaceVersion dep+ EarlierVersion _ -> replaceVersion dep+ VersionRangeParens _ -> replaceVersion dep+ IntersectVersionRanges lower _ -> do+ if extractLower lower < latest+ then do+ let ver' = intersectVersionRanges lower (orEarlierVersion latest)+ let dep' = Dependency (depPkgName dep) ver' (depLibraries dep)+ pure $ modifyDep cabalFile pk dep'+ else replaceVersion dep+ UnionVersionRanges _ _ -> replaceVersion dep+ MajorBoundVersion prev -> do+ if prev < latest+ then do+ let ver' = intersectVersionRanges (orLaterVersion prev) (orEarlierVersion latest)+ let dep' = Dependency (depPkgName dep) ver' (depLibraries dep)+ pure $ modifyDep cabalFile pk dep'+ else replaceVersion dep+ where+ extractLower :: VersionRange -> Version+ extractLower (LaterVersion ver) = ver+ extractLower (OrLaterVersion ver) = ver+ extractLower _ = version0+ replaceVersion dep = do+ let dep' = Dependency (depPkgName dep) (majorBoundVersion latest) (depLibraries dep)+ pure $ modifyDep cabalFile pk dep'++remove ::+ PackageName ->+ (FilePath, GenericPackageDescription) ->+ IO GenericPackageDescription+remove pk (_, cabalFile) =+ case lookupDep cabalFile pk of+ Nothing -> die $ "No dependency on: " ++ show pk+ Just _ -> pure $ deleteDep cabalFile pk++addVer ::+ (Version -> VersionRange) ->+ Version ->+ (FilePath, GenericPackageDescription) ->+ Dependency ->+ IO GenericPackageDescription+addVer f givenVersion (fname, cabalFile) dep = do+ let pk = depPkgName dep+ verMap <- cacheDeps+ let dependency = Dependency pk (f givenVersion) (Set.singleton defaultLibName)+ case Map.lookup pk verMap of+ Nothing -> die $ "No such named: " ++ show pk+ Just vers ->+ if givenVersion `elem` vers+ then do+ putStrLn $ "Adding explicit dependency: " ++ prettyShow dependency ++ " to " ++ takeFileName fname+ let dep' = Dependency (depPkgName dep) (majorBoundVersion givenVersion) (depLibraries dep)+ pure $ modifyDep cabalFile pk dep'+ else die $ "Given version is not on available on Hackage." ++ show givenVersion++-------------------------------------------------------------------------------+-- Version Cache+-------------------------------------------------------------------------------++cacheFile :: FilePath+cacheFile = ".cabal-cache.db"++cacheDeps :: IO (Map PackageName [Version])+cacheDeps = do+ cache <- cacheDb+ cacheExists <- doesFileExist cache+ if cacheExists+ then do+ dbContents <- BS.readFile cache+ case decode dbContents of+ Left _ -> die "Corrupted cabal cache file. Run 'cabal-edit rebuild'."+ Right db -> pure db+ else do+ putStrLn "No cache file found, building from HackageDB."+ buildCache++buildCache :: IO (Map PackageName [Version])+buildCache = do+ cache <- cacheDb+ hdb <- hackageTarball+ now <- getCurrentTime+ tdb <- U.readTarball (Just now) hdb+ vers <- forM (Map.toList tdb) $ \(pk, pdata) -> do+ let verMap = Map.keys (P.parsePackageData pk pdata)+ pure (pk, verMap)+ let db = Map.fromList vers+ BS.writeFile cache (encode db)+ pure db++cacheDb :: IO FilePath+cacheDb = do+ home <- getHomeDirectory+ cabalExists <- doesDirectoryExist (home </> ".cabal")+ if cabalExists+ then pure (home </> ".cabal" </> cacheFile)+ else die "No ~/.cabal directory found. Is cabal installed?"++-------------------------------------------------------------------------------+-- Version Linting+-------------------------------------------------------------------------------++lint :: Bool -> (PackageName, Dependency, Version) -> IO ()+lint fix (pk, dep, latest) =+ case depVerRange dep of+ AnyVersion -> putStrLn $ prettyShow pk ++ " : " ++ "Wildcard version detected. Instead use explicit version bounds."+ ThisVersion _ -> putStrLn $ prettyShow pk ++ " : " ++ "Explicit version detected. Considering allowing a range."+ LaterVersion _ -> putStrLn $ prettyShow pk ++ " : " ++ "No upper bound detected. Add an upper bound."+ OrLaterVersion _ -> putStrLn $ prettyShow pk ++ " : " ++ "No upper bound detected. Add an upper bound."+ EarlierVersion ver ->+ case versionNumbers ver of+ (1 : _) -> pure ()+ _ -> putStrLn $ prettyShow pk ++ " : " ++ "Upper bound only for package with >1.0 admits all previous versions. Add a lower bound to major version."+ OrEarlierVersion ver ->+ case versionNumbers ver of+ (1 : _) -> pure ()+ _ -> putStrLn $ prettyShow pk ++ " : " ++ "Upper bound only for package with >1.0 admits all previous versions. Add a lower bound to major version."+ WildcardVersion ver ->+ if Data.List.length (versionNumbers ver) > 2+ then putStrLn $ prettyShow pk ++ " : " ++ "Wildcard on minor version, consider rewriting wildcard to major version n.x"+ else pure ()+ MajorBoundVersion ver ->+ if majorUpperBound ver == majorUpperBound latest+ then pure ()+ else putStrLn $ prettyShow pk ++ " : " ++ "Consider ugprading major bound to latest version " ++ prettyShow (majorBound latest)+ UnionVersionRanges _ _ -> pure ()+ IntersectVersionRanges lower upper ->+ case (lower, upper) of+ (LaterVersion lower, EarlierVersion upper) -> lintRange pk lower upper latest+ (OrLaterVersion lower, EarlierVersion upper) -> lintRange pk lower upper latest+ (LaterVersion lower, OrEarlierVersion upper) -> lintRange pk lower upper latest+ (OrLaterVersion lower, OrEarlierVersion upper) -> lintRange pk lower upper latest+ _ -> putStrLn $ prettyShow pk ++ " : " ++ "Upper and lower bounds for range are inconsistent."+ VersionRangeParens _ -> pure ()++lintRange :: PackageName -> Version -> Version -> Version -> IO ()+lintRange pk lower upper latest+ | lower >= upper = putStrLn $ prettyShow pk ++ " : " ++ "Lower bound is greater or equal to than upper bound. Reverse bounds."+ | upper < latest = putStrLn $ prettyShow pk ++ " : " ++ "Consider upgrading upper bound to latest version " ++ prettyShow latest+ | otherwise = pure ()++-------------------------------------------------------------------------------+-- Commands+-------------------------------------------------------------------------------++listCmd :: String -> IO ()+listCmd packName = do+ pk <- case (simpleParsec packName :: Maybe PackageName) of+ Nothing -> die "Invalid package name."+ Just pk -> pure pk+ verMap <- cacheDeps+ vers <- case Map.lookup pk verMap of+ Nothing -> die $ "No such named: " ++ show pk+ Just vers -> pure (sort vers)+ mapM_ (putStrLn . prettyShow) vers++latestCmd :: String -> IO ()+latestCmd packName = do+ pk <- case (simpleParsec packName :: Maybe PackageName) of+ Nothing -> die "Invalid package name."+ Just pk -> pure pk+ verMap <- cacheDeps+ latest <- case Map.lookup pk verMap of+ Nothing -> die $ "No such named: " ++ show pk+ Just vers -> pure (maximum vers)+ let dependency = Dependency pk (majorBoundVersion (majorBound latest)) (Set.singleton defaultLibName)+ putStrLn (prettyShow dependency)++addCmd :: [String] -> IO ()+addCmd = mapM_ addSingle++addSingle :: String -> IO ()+addSingle packName = do+ dep <- case (simpleParsec packName :: Maybe Dependency) of+ Nothing -> die "Invalid dependency version number."+ Just dep -> pure dep+ (fname, cabalFile) <- getCabal+ cabalFile' <- add dep (fname, cabalFile)+ --hasLib cabalFile+ writeGenericPackageDescription fname cabalFile'+ formatCabalFile fname++upgradeCmd :: String -> IO ()+upgradeCmd packName = do+ pk <- case (simpleParsec packName :: Maybe PackageName) of+ Nothing -> die "Invalid package name."+ Just pk -> pure pk+ verMap <- cacheDeps+ latestVer <- case Map.lookup pk verMap of+ Nothing -> die $ "No such named: " ++ show pk+ Just vers -> pure (maximum vers)+ (fname, cabalFile) <- getCabal+ --hasLib cabalFile+ case lookupDep cabalFile pk of+ Nothing -> die $ "No current dependency on: " ++ show pk+ Just _ -> do+ let ver' = majorUpperBound latestVer+ putStrLn $ "Upgrading bounds for " ++ prettyShow pk ++ " to " ++ prettyShow ver'+ --traverse (putStrLn . prettyShow) (depMap cabalFile)+ cabalFile' <- upgrade pk ver' (fname, cabalFile)+ writeGenericPackageDescription fname cabalFile'+ formatCabalFile fname++upgradeAllCmd :: IO ()+upgradeAllCmd = do+ verMap <- cacheDeps+ (fname, cabalFile) <- getCabal+ let pks = fmap depPkgName (getDeps cabalFile)+ forM_ pks $ \pk -> do+ latestVer <- case Map.lookup pk verMap of+ Nothing -> die $ "No such named: " ++ show pk+ Just vers -> pure (maximum vers)+ let ver' = majorUpperBound latestVer+ putStrLn $ "Upgrading bounds for " ++ prettyShow pk ++ " to " ++ prettyShow ver'+ cabalFile <- readGenericPackageDescription normal fname+ cabalFile' <- upgrade pk ver' (fname, cabalFile)+ writeGenericPackageDescription fname cabalFile'+ formatCabalFile fname++lintCmd :: IO ()+lintCmd = do+ verMap <- cacheDeps+ (fname, cabalFile) <- getCabal+ let pks = Map.toList $ depMap cabalFile+ forM_ pks $ \(pk, dep) -> do+ latestVer <- case Map.lookup pk verMap of+ Nothing -> die $ "No such named: " ++ show pk+ Just vers -> pure (maximum vers)+ lint False (pk, dep, latestVer)++removeCmd :: String -> IO ()+removeCmd packName = do+ pk <- case (simpleParsec packName :: Maybe PackageName) of+ Nothing -> die "Invalid package name."+ Just pk -> pure pk+ (fname, cabalFile) <- getCabal+ case lookupDep cabalFile pk of+ Nothing -> die $ "No current dependency on: " ++ show pk+ Just _ -> do+ putStrLn $ "Removing dependency on " ++ prettyShow pk+ cabalFile' <- remove pk (fname, cabalFile)+ writeGenericPackageDescription fname cabalFile'+ formatCabalFile fname++rebuildCmd :: IO ()+rebuildCmd = buildCache >> putStrLn "Done."++extensionsCmd :: IO ()+extensionsCmd = do+ (fname, cabalFile) <- getCabal+ let extensions = fmap defaultExtensions (buildInfo cabalFile)+ case extensions of+ Nothing -> putStrLn $ "No default extensions in " ++ takeFileName fname+ Just exts -> mapM_ (putStrLn . showExt) exts+ where+ showExt ext = "{-# LANGUAGE " ++ prettyShow ext ++ " #-}"++formatCmd :: IO ()+formatCmd = do+ (fname, cabalFile) <- getCabal+ putStrLn $ "Formatting: " ++ takeFileName fname+ writeGenericPackageDescription fname cabalFile+ formatCabalFile fname++getCabal :: IO (FilePath, GenericPackageDescription)+getCabal = do+ cabalFiles <- glob "*.cabal"+ case cabalFiles of+ [] -> die "No cabal file found in current directory."+ [fname] -> do+ pkg <- readGenericPackageDescription normal fname+ pure (fname, pkg)+ _ -> die "Multiple cabal-files found."++formatCabalFile :: FilePath -> IO ()+formatCabalFile fpath = do+ mexe <- findExecutable "cabal-fmt"+ case mexe of+ Just _ -> callCommand $ "cabal-fmt --inplace " ++ fpath+ Nothing -> pure ()++-------------------------------------------------------------------------------+-- Orphan Sinbin+-------------------------------------------------------------------------------++instance Store PackageName++instance Store ShortText++instance Store Version++-------------------------------------------------------------------------------+-- Options Parsing+-------------------------------------------------------------------------------++data Cmd+ = Add [String]+ | List String+ | Upgrade String+ | UpgradeAll+ | Remove String+ | Latest String+ | Format+ | Rebuild+ | Extensions+ | Lint+ deriving (Eq, Show)++completerPacks :: IO [String]+completerPacks = do+ db <- cacheDeps+ pure (unPackageName <$> Map.keys db)++addParse :: [String] -> Parser Cmd+addParse localPackages = Add <$> many (argument str (metavar "PACKAGE" <> completeWith localPackages))++listParse :: [String] -> Parser Cmd+listParse localPackages = List <$> argument str (metavar "PACKAGE" <> completeWith localPackages)++upgradeParse :: [String] -> Parser Cmd+upgradeParse localPackages = Upgrade <$> argument str (metavar "PACKAGE" <> completeWith localPackages)++removeParse :: [String] -> Parser Cmd+removeParse localPackages = Remove <$> argument str (metavar "PACKAGE" <> completeWith localPackages)++latestParse :: [String] -> Parser Cmd+latestParse localPackages = Latest <$> argument str (metavar "PACKAGE" <> completeWith localPackages)++opts :: [String] -> Parser Cmd+opts localPackages =+ subparser $+ mconcat+ [ command "add" (info (addParse localPackages) (progDesc "Add dependency to cabal file.")),+ command "list" (info (listParse localPackages) (progDesc "List available versions from Hackage.")),+ command "upgrade" (info (upgradeParse localPackages) (progDesc "Upgrade bounds for given package.")),+ command "remove" (info (removeParse localPackages) (progDesc "Remove a given package.")),+ command "latest" (info (latestParse localPackages) (progDesc "Get the latest version of a given package.")),+ command "rebuild" (info (pure Rebuild) (progDesc "Rebuild cache.")),+ command "upgradeall" (info (pure UpgradeAll) (progDesc "Upgrade all dependencies.")),+ command "format" (info (pure Format) (progDesc "Format cabal file.")),+ command "extensions" (info (pure Extensions) (progDesc "List all default language extensions pragmas.")),+ command "lint" (info (pure Lint) (progDesc "Lint dependency bounds."))+ ]++main :: IO ()+main = do+ comps <- completerPacks+ let options = info (opts comps <**> helper) idm+ cmd <- customExecParser p options+ case cmd of+ Add deps -> addCmd deps+ List dep -> listCmd dep+ Upgrade dep -> upgradeCmd dep+ Remove dep -> removeCmd dep+ Format -> formatCmd+ Rebuild -> rebuildCmd+ Latest dep -> latestCmd dep+ Extensions -> extensionsCmd+ UpgradeAll -> upgradeAllCmd+ Lint -> lintCmd+ where+ p = prefs showHelpOnEmpty