cabal-file (empty) → 0.1.0
raw patch · 8 files changed
+681/−0 lines, 8 filesdep +Cabaldep +basedep +bytestring
Dependencies added: Cabal, base, bytestring, cabal-file, directory, extra, filepath, hackage-security, optparse-applicative, simple-cabal, simple-cmd, simple-cmd-args, time
Files
- ChangeLog.md +8/−0
- LICENSE +29/−0
- README.md +70/−0
- app/Cmds.hs +86/−0
- app/Depends.hs +139/−0
- app/Main.hs +70/−0
- cabal-file.cabal +83/−0
- src/Hackage/Index.hs +196/−0
+ ChangeLog.md view
@@ -0,0 +1,8 @@+# Revision history for cabal-file++## 0.1.0 -- 2020-05-24++- initial release+- cblfile commands: diff (second), list, latest, files, get, date, show,+ metadata, preferred, depends (oldest code)+- Hackage.Index library module
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License++Copyright (c) 2020, Jens Petersen+All rights reserved.++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,70 @@+# cabal-file++cabal-file is a library on top of the hackage-security library for accessing+cabal files and versions, and also a small tool for getting and comparing+.cabal files.++Since it accesses package and .cabal file data directly from+the local Hackage repo cache index tar archive under ~/.cabal/ it is quite fast.++## Usage+List versions of a package:+```+$ cblfile list pandoc+0.4+:+:+2.9.2.1+```++Diff .cabal files of package versions+```+$ cblfile diff pandoc 2.8 2.9.2.1+--- pandoc-2.8.cabal 2020-05-24 19:47:58.487921450 +0800++++ pandoc-2.9.2.1.cabal 2020-05-24 19:47:58.487921450 +0800+@@ -1,10 +1,10 @@+ name: pandoc+-version: 2.8++version: 2.9.2.1+ cabal-version: 2.0+:+:+```++Latest version of a package:+```+$ cblfile latest extra+1.7.1+```++Timestamp for the latest .cabal file (or for a specific version):+```+$ cblfile date dhall+2020-05-19 02:18:01 UTC+```++Save a .cabal file (latest or specific version)+```+$ cblfile get purescript+$ ls purescript.cabal+purescript.cabal+```++Check package source metadata:+```+$ cblfile metadata tls-1.5.4+Size: 150171+SHA256 Hash "ce42bfa581a50f62776581da4b86408ebb1a51bc6cb2f95ad0a2ac7caa19a437"+```++## Depends output+`cblfile depends` outputs quite verbose package dependency lists.++## Library+For library documentation see the [Hackage.Index](https://hackage.haskell.org/package/cabal-file/docs/Hackage-Index.html) documentation.++For example usage see `app/Cmds.hs`.++## Related+If you want full diffs of Hackage sources you can try out cabal-diff from+[cabal-extras](https://github.com/phadej/cabal-extras).
+ app/Cmds.hs view
@@ -0,0 +1,86 @@+module Cmds (+ diffCmd,+ saveCabal,+ showCabal,+ listPkg,+ latestPkg,+ listFiles,+ preferCmd,+ showMetadata,+ dateCabal+ ) where++import Control.Monad+import Control.Monad.Extra+import qualified Data.ByteString.Lazy.Char8 as BL+import Data.List+import Distribution.Version (Version)+import SimpleCabal+import SimpleCmd+import System.Directory+import System.FilePath+import System.IO.Extra (withTempDir)++import Hackage.Index++-- FIXME structural diff of PackageDescription+-- FIXME revisions?+diffCmd :: String -> Version -> Version -> IO ()+diffCmd pkg v1 v2 =+ withTempDir $ \ tmpdir -> do+ setCurrentDirectory tmpdir+ let pkgid1 = PackageIdentifier (mkPackageName pkg) v1+ pkgid2 = PackageIdentifier (mkPackageName pkg) v2+ saveCabals pkgid1 pkgid2+ void $ cmdBool "diff" ["-u", showPkgId pkgid1 <.> "cabal", showPkgId pkgid2 <.> "cabal"]++-- pkgdesc <- finalPackageDescription [] cabal+ where+ saveCabals :: PackageIdentifier -> PackageIdentifier -> IO ()+ saveCabals pkgid1 pkgid2 = do+ (bs1,bs2) <- getCabals pkgid1 pkgid2+ BL.writeFile (showPkgId pkgid1 <.> "cabal") bs1+ BL.writeFile (showPkgId pkgid2 <.> "cabal") bs2++listPkg :: Maybe PackageName -> IO ()+listPkg (Just pkgname) = do+ versions <- packageVersions pkgname+ mapM_ (putStrLn . showVersion) versions+listPkg Nothing = fmap sort listPackages >>= mapM_ putStrLn++saveCabal :: PackageIdentifier -> IO ()+saveCabal pkgid = do+ pkgver <- packageIdOrLatest pkgid+ getCabal pkgver >>= BL.writeFile (showPkgId pkgid <.> "cabal")++showCabal :: PackageIdentifier -> IO ()+showCabal pkgid = do+ pkgver <- packageIdOrLatest pkgid+ getCabal pkgver >>= BL.putStrLn++showMetadata :: PackageIdentifier -> IO ()+showMetadata pkgid = do+ pkgver <- packageIdOrLatest pkgid+ finfo <- getFileInfo pkgver+ let bytes = show . fileLength . fileInfoLength+ putStrLn $ "Size: " ++ bytes finfo+ whenJust (fileInfoSHA256 finfo) $ \ hash ->+ putStrLn $ "SHA256 " ++ show hash++latestPkg :: PackageName -> IO ()+latestPkg pkgname =+ whenJustM (latestVersion pkgname) (putStrLn . showVersion)++listFiles :: IO ()+listFiles = do+ files <- indexFiles+ mapM_ putStrLn files++preferCmd :: PackageName -> IO ()+preferCmd pkgname =+ whenJustM (preferredVersions pkgname) BL.putStrLn++dateCabal :: PackageIdentifier -> IO ()+dateCabal pkgid = do+ pkgver <- packageIdOrLatest pkgid+ whenJustM (getTimestamp pkgver) print
+ app/Depends.hs view
@@ -0,0 +1,139 @@+module Depends (+ Deps(..),+ Details(..),+ dependsCmd)+where++import Control.Monad (unless, when)+import Data.List (nub, (\\))+import Data.Maybe+import System.Directory+import System.FilePath++import Distribution.PackageDescription (Library(..), exeName, setupDepends)+import Distribution.Pretty+import Distribution.Simple.BuildToolDepends (getAllToolDependencies)+import Distribution.Text (simpleParse)+import Distribution.Types.ExeDependency++import SimpleCabal (allBuildInfo, buildDepends, buildDependencies, buildTools,+ exeDepName, extraLibs, findCabalFile,+ finalPackageDescription,+ mkPackageName, package, PackageDescription(..), PackageName,+ pkgcfgDepName, pkgconfigDepends, pkgName,+ setupBuildInfo, setupDependencies)++import Hackage.Index (withCabalFile)++data Deps = Build | Setup | Tool | Legacy | CLib | PkgConfig | NotBuild+ deriving (Eq, Show)++data Details = Normal | Unique | Detail+ deriving Eq+++dependsCmd :: Bool -> Details -> Maybe Deps -> Maybe FilePath -> IO ()+dependsCmd quiet details opt mfile =+ case mfile of+ Nothing -> findCabalFile >>= displayDepends+ Just path -> do+ dir <- doesDirectoryExist path+ if dir+ then setCurrentDirectory path >> findCabalFile >>= displayDepends+ else+ if "cabal" `isExtensionOf` path+ then do+ isfile <- doesFileExist path+ if isfile then displayDepends path+ else error $ path ++ " does not exist"+ else+ case simpleParse path of+ Nothing -> error $ path ++ " not a package identifier"+ Just pkgid -> withCabalFile pkgid displayDepends+ where+ displayDepends :: FilePath -> IO ()+ displayDepends cabal = do+ pkgdesc <- finalPackageDescription [] cabal+ work (details == Detail) (details == Unique) (pkgName (package pkgdesc)) pkgdesc+ where+ work :: Bool -> Bool -> PackageName -> PackageDescription -> IO ()+ work detail unique self pkgdesc = do+ printDeps Build+ printDeps Setup+ printDeps Tool+ printDeps Legacy+ printDeps CLib+ printDeps PkgConfig+ printLib+ printExe+ where+ printDeps :: Deps -> IO ()+ printDeps kind =+ unless (opt == Just NotBuild && kind == Build) $ do+ let deps = nub $ lens kind+ unless (null deps) $+ when (isNothing opt || opt `elem` [Just kind, Just NotBuild]) $ do+ when (not quiet && (opt /= Just kind || opt == Just NotBuild)) $+ title $ show kind ++ " depends"+ mapM_ putStrLn deps++ lens :: Deps -> [String]+ lens Build =+ if detail+ then map prettyShow $ buildDepends pkgdesc+ else map prettyShow $ buildDependencies pkgdesc \\ [self | unique]+ lens Setup =+ if detail+ then map prettyShow $ maybe [] setupDepends (setupBuildInfo pkgdesc)+ else map prettyShow $ setupDependencies pkgdesc \\ (mkPackageName "Cabal" : if unique then buildDependencies pkgdesc else [])+ lens Tool =+ let bts = concatMap (getAllToolDependencies pkgdesc) $+ allBuildInfo pkgdesc+ in if detail then map prettyShow bts+ else map (prettyShow . exeDepPkgName) bts \\ ([prettyShow self | unique] ++ ["hsc2hs" | unique])+ lens Legacy =+ let legacy = concatMap buildTools $ allBuildInfo pkgdesc+ in (if detail then map prettyShow legacy+ else map exeDepName legacy) \\ if unique then "hsc2hs" : prettyShow self : lens Tool else []+ lens CLib = (concatMap extraLibs . allBuildInfo) pkgdesc+ lens PkgConfig =+ let ds = (concatMap pkgconfigDepends . allBuildInfo) pkgdesc+ in (if detail then map prettyShow+ else map pkgcfgDepName) ds+ lens NotBuild = error "No lens for NotBuild!"++ printLib :: IO ()+ printLib =+ case library pkgdesc of+ Nothing -> return ()+ Just lib -> do+ title "Library"+ when detail $ do+ subtitle "Exposed Modules"+ mapM_ prettyPut $ exposedModules lib+ let export = reexportedModules lib+ unless (null export) $ do+ subtitle "Re-exported Modules"+ mapM_ prettyPut export++ printExe :: IO ()+ printExe = do+ let exes = executables pkgdesc+ unless (null exes) $ do+ title $ "Executable" ++ ['s' | length exes > 1]+ when detail $+ mapM_ (prettyPut . exeName) exes++title :: String -> IO ()+title ts =+ putStrLn $ "# " ++ ts++subtitle :: String -> IO ()+subtitle ts =+ putStrLn $ "- " ++ ts++prettyPut :: Pretty a => a -> IO ()+prettyPut = putStrLn . prettyShow++exeDepPkgName :: ExeDependency -> PackageName+exeDepPkgName (ExeDependency n _ _) = n
+ app/Main.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE CPP #-}++import Data.Version.Extra (readVersion)+import Distribution.Package+import Distribution.Text (simpleParse)+import Distribution.Version+#if MIN_VERSION_simple_cmd_args(0,1,7)+#else+import Options.Applicative+#endif+import SimpleCmdArgs++import Cmds+import Depends++main :: IO ()+main =+ simpleCmdArgs Nothing "Cabal file tool"+ "cabal-file can access and compare .cabal files from the cache" $+ subcommands+ [ Subcommand "diff" "Diff .cabal files of package versions" $+ diffCmd <$> strArg "PKG" <*> versionArg <*> versionArg+ , Subcommand "list" "List package versions" $+ listPkg <$> optional pkgArg+ , Subcommand "latest" "Latest package version" $+ latestPkg <$> pkgArg+ , Subcommand "files" "List all index files" $+ pure listFiles+ , Subcommand "get" "Get .cabal file for package version" $+ saveCabal <$> pkgIdArg+ , Subcommand "date" "Timestamp for package version (revision)" $+ dateCabal <$> pkgIdArg+ , Subcommand "show" "Show .cabal file for package version" $+ showCabal <$> pkgIdArg+ , Subcommand "metadata" "Show metadata for package version" $+ showMetadata <$> pkgIdArg+ , Subcommand "preferred" "Show preferred versions for package" $+ preferCmd <$> pkgArg+ , Subcommand "depends" "Print package dependencies" $+ dependsCmd <$> switchWith 'q' "quiet" "No depends section headers"+ <*> detailOpt+ <*> optional depsOpt+ <*> optional (strArg "DIRorCABAL")+ ]+ where+ versionArg :: Parser Version+ versionArg = argumentWith versionM "VERSION"++ versionM = maybeReader (Just . mkVersion' . readVersion)++ pkgIdArg :: Parser PackageIdentifier+ pkgIdArg = argumentWith (maybeReader simpleParse) "PKG[VER]"++ pkgArg :: Parser PackageName+ pkgArg = argumentWith (maybeReader simpleParse) "PKG"++ detailOpt :: Parser Details+ detailOpt =+ flagWith' Detail 'd' "detail" "Show version ranges" <|>+ flagWith Normal Unique 'u' "unique" "Remove self and build-depends from other depends"++ depsOpt :: Parser Deps+ depsOpt =+ flagWith' Build 'b' "build" "Only build-depends" <|>+ flagWith' Setup 's' "setup" "Only setup-depends" <|>+ flagWith' Tool 't' "tool" "Only tool-build-depends" <|>+ flagWith' Legacy 'l' "legacy" "Only build-tools" <|>+ flagWith' CLib 'c' "clibs" "Only extra-libraries" <|>+ flagWith' PkgConfig 'p' "pkgconfig" "Only extra-libraries" <|>+ flagWith' NotBuild 'B' "not-build" "Hide build-depends"
+ cabal-file.cabal view
@@ -0,0 +1,83 @@+name: cabal-file+version: 0.1.0+synopsis: Cabal file access+description:+ cabal-file is a small library on top of the 'hackage-security' library+ for accessing the local Hackage repo index cache, and a command-line tool+ 'cblfile' which can diff .cabal versions, list package versions,+ save a cabal file, and show metadata from the index. It uses+ simple-cabal to display package dependency lists.+license: BSD3+license-file: LICENSE+author: Jens Petersen+maintainer: juhpetersen@gmail.com+copyright: 2019-2020 Jens Petersen+category: Distribution+build-type: Simple+extra-doc-files: README.md+ , ChangeLog.md+cabal-version: 1.18++source-repository head+ type: git+ location: https://github.com/juhp/cabal-file.git++executable cblfile+ main-is: Main.hs+ other-modules: Cmds+ Depends+ hs-source-dirs: app+ build-depends: base < 5+ , bytestring+ , Cabal > 2.2+ , cabal-file+ , directory+ , extra+ , filepath+ , optparse-applicative+ , simple-cabal > 0.1+ , simple-cmd+ , simple-cmd-args >= 0.1.2+ default-language: Haskell2010+ ghc-options: -Wall+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat+ -Widentities+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ -Wredundant-constraints+ if impl(ghc >= 8.2)+ ghc-options: -fhide-source-paths+ if impl(ghc >= 8.4)+ ghc-options: -Wmissing-export-lists+ -Wpartial-fields++library+ build-depends: base < 5+ , bytestring+ , Cabal > 2.2+ , directory+ , extra+ , filepath+ , hackage-security+ , optparse-applicative+ , simple-cabal > 0.1+ , simple-cmd+ , time+ exposed-modules: Hackage.Index+ hs-source-dirs: src++ ghc-options: -Wall+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat+ -Widentities+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ -Wredundant-constraints+ if impl(ghc >= 8.2)+ ghc-options: -fhide-source-paths+ if impl(ghc >= 8.4)+ ghc-options: -Wmissing-export-lists+ -Wpartial-fields++ default-language: Haskell2010
+ src/Hackage/Index.hs view
@@ -0,0 +1,196 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE GADTs #-}++module Hackage.Index (+ getCabal,+ getCabals,+ withCabalFile,+ listPackages,+ packageVersions,+ latestVersion,+ preferredVersions,+ getTimestamp,+ indexFiles,+ getPackageDescription,+ getPackageDescription',+ packageIdOrLatest,+ getFileInfo,+ -- * Re-exports from hackage-security+ FileInfo(..),+ FileLength(..),+ fileInfoSHA256+ ) where++import qualified Data.ByteString.Lazy.Char8 as BL+import Data.List+import Data.Maybe+import Data.Time.Clock+import Data.Time.Clock.POSIX+import Data.Version.Extra (readVersion)+import System.Directory+import System.FilePath+import System.IO.Extra (withTempDir)++import Distribution.Version+#if MIN_VERSION_Cabal(3,0,0)+#else+ hiding (showVersion)+#endif+import Hackage.Security.Client+import qualified Hackage.Security.Client.Repository.Local as Local+import qualified Hackage.Security.Util.Path as Path+--import Hackage.Security.Util.Some+import qualified Hackage.Security.Client.Repository.Cache as Cache+import Hackage.Security.Util.Pretty++import SimpleCabal++-- | Get the contents of the .cabal file for package version+getCabal :: PackageIdentifier -> IO BL.ByteString+getCabal pkgid =+ withLocalRepo $ \rep -> uncheckClientErrors $+ withIndex rep $ \ IndexCallbacks{..} ->+ trusted <$> indexLookupCabal pkgid++-- | Pass a temporary copy of .cabal file to some action+withCabalFile :: PackageIdentifier -> (FilePath -> IO a) -> IO a+withCabalFile pkgid act =+ withTempDir $ \ tmpdir -> do+ bs <- getCabal pkgid+ let filepath = tmpdir </> showPkgId pkgid <.> "cabal"+ BL.writeFile filepath bs+ act filepath++-- | Get two .cabal files at once!+getCabals :: PackageIdentifier -> PackageIdentifier+ -> IO (BL.ByteString, BL.ByteString)+getCabals pkgid1 pkgid2 =+ withLocalRepo $ \rep -> uncheckClientErrors $+ withIndex rep $ \ IndexCallbacks{..} -> do+ bs1 <- trusted <$> indexLookupCabal pkgid1+ bs2 <- trusted <$> indexLookupCabal pkgid2+ return (bs1,bs2)++-- | Get FileInfo metadata for package version source+getFileInfo :: PackageIdentifier -> IO FileInfo+getFileInfo pkgid =+ withLocalRepo $ \rep -> uncheckClientErrors $+ withIndex rep $ \ IndexCallbacks{..} ->+ trusted <$> indexLookupFileInfo pkgid++-- | Get and try to parse the PackageDescription of a package version+getPackageDescription :: PackageIdentifier -> IO (Maybe PackageDescription)+getPackageDescription pkgid =+#if (defined(MIN_VERSION_simple_cabal) && MIN_VERSION_simple_cabal(0,1,2))+ do+ cabal <- getCabal pkgid+ parseFinalPackageDescription [] $ BL.toStrict cabal+#else+ Just <$> withCabalFile pkgid (finalPackageDescription [])+#endif++-- | Get and parse PackageDescription of package version+--+-- Raises an error on failure.+getPackageDescription' :: PackageIdentifier -> IO PackageDescription+getPackageDescription' pkgid = do+ mfpd <- getPackageDescription pkgid+ maybe (error "Failed to parse cabal file") return mfpd++-- | Easy access to the local Hackage repo+withLocalRepo :: (Repository Local.LocalFile -> IO a) -> IO a+withLocalRepo action = do+ home <- getHomeDirectory+ localrepo <- (Path.makeAbsolute . Path.fromFilePath) (home </> ".cabal")+ localcache <- (Path.makeAbsolute . Path.fromFilePath) (home </> ".cabal/packages/hackage.haskell.org")+ Local.withRepository localrepo (cache localcache) hackageRepoLayout hackageIndexLayout logTUF action+ where+ -- FIXME could also support 00-index+ cache localcache = Cache.Cache {+ Cache.cacheRoot = localcache+ , Cache.cacheLayout = cabalCacheLayout+ { cacheLayoutIndexTar = Path.rootPath $ Path.fragment "01-index.tar"+ , cacheLayoutIndexIdx = Path.rootPath $ Path.fragment "01-index.tar.idx"+ , cacheLayoutIndexTarGz = Path.rootPath $ Path.fragment "01-index.tar.gz"}+ }++ logTUF msg = putStrLn $ "# " ++ pretty msg++-- | Get all versions of a package in the index+packageVersions :: PackageName -> IO [Version]+packageVersions pkgname =+ withLocalRepo $ \rep -> uncheckClientErrors $ do+ dir <- getDirectory rep+ let pkg = unPackageName pkgname+ return $ sort . mapMaybe (extractPkgVersion pkg . second) $ directoryEntries dir+ where+ second (_,b,_) = b++ extractPkgVersion :: String -> IndexPath -> Maybe Version+ extractPkgVersion pkg path =+ if Path.takeExtension path == ".cabal" then+ let namever = (Path.toUnrootedFilePath . Path.unrootPath . Path.takeDirectory) path+ in if takeDirectory namever == pkg+ then Just $ mkVersion' . readVersion $ takeFileName namever+ else Nothing+ else Nothing++-- | Get the preferred-versions metadata for package+preferredVersions :: PackageName -> IO (Maybe BL.ByteString)+preferredVersions pkgname =+ withLocalRepo $ \rep -> uncheckClientErrors $+ withIndex rep $ \ IndexCallbacks{..} ->+ fmap indexEntryContent <$> indexLookupFile (IndexPkgPrefs pkgname)++-- | List all files in the Hackage index+-- (.cabal files, metadata .json files, preferred-versions files)+indexFiles :: IO [String]+indexFiles =+ withLocalRepo $ \rep -> uncheckClientErrors $ do+ dir <- getDirectory rep+ return $ map dirEntryPath (directoryEntries dir)+ where+ second (_,b,_) = b++ dirEntryPath = Path.toUnrootedFilePath . Path.unrootPath . second++-- | Get the latest version of package from the index+--+-- Note: does not take preferred-versions into account+latestVersion :: PackageName -> IO (Maybe Version)+latestVersion pkgname = do+ versions <- packageVersions pkgname+ if null versions then return Nothing+ else return $ Just $ last versions++-- | Get the index timestamp for (the latest revision of) pkgid+getTimestamp :: PackageIdentifier -> IO (Maybe UTCTime)+getTimestamp pkgid =+ withLocalRepo $ \rep -> uncheckClientErrors $+ withIndex rep $ \ IndexCallbacks{..} ->+ fmap (posixSecondsToUTCTime . realToFrac . indexEntryTime) <$>+ indexLookupFile (IndexPkgCabal pkgid)++-- | Convert a PackageID if unversioned to latest package version+packageIdOrLatest :: PackageIdentifier -> IO PackageIdentifier+packageIdOrLatest pkgid = do+ let name = pkgName pkgid+ if pkgVersion pkgid == nullVersion then do+ mlatest <- latestVersion name+ return $ maybe pkgid (PackageIdentifier name) mlatest+ else return pkgid++-- | List all packages in the index (unsorted for performance)+listPackages :: IO [String]+listPackages =+ withLocalRepo $ \rep -> uncheckClientErrors $ do+ dir <- getDirectory rep+ return $ nub $ mapMaybe (extractPkg . second) (directoryEntries dir)+ where+ extractPkg path =+ if Path.takeExtension path == ".cabal" then+ (Just . takeWhile (/= '/') . Path.toUnrootedFilePath . Path.unrootPath) path+ else Nothing++ second (_,b,_) = b