packdeps (empty) → 0.0.0
raw patch · 5 files changed
+274/−0 lines, 5 filesdep +Cabaldep +basedep +bytestringsetup-changed
Dependencies added: Cabal, base, bytestring, containers, directory, split, tar, text, time
Files
- Distribution/PackDeps.hs +174/−0
- LICENSE +25/−0
- Setup.hs +2/−0
- packdeps-cli.hs +36/−0
- packdeps.cabal +37/−0
+ Distribution/PackDeps.hs view
@@ -0,0 +1,174 @@+module Distribution.PackDeps+ ( -- * Data types+ Newest+ , CheckDepsRes (..)+ , DescInfo+ -- * Read package database+ , loadNewest+ , loadNewestFrom+ , parseNewest+ -- * Check a package+ , checkDeps+ -- * Get a single package+ , getPackage+ , parsePackage+ , loadPackage+ -- * Get multiple packages+ , filterPackages+ ) where++import System.Directory (getAppUserDataDirectory)+import qualified Data.Map as Map+import Data.List (foldl', group, sort, isInfixOf)+import Data.Time (UTCTime (UTCTime), addUTCTime)+import Data.Maybe (mapMaybe)++import Distribution.Package+import Distribution.PackageDescription+import Distribution.PackageDescription.Parse+import Distribution.Version+import Distribution.Text++import Data.Char (toLower)+import qualified Data.Text.Lazy as T+import qualified Data.Text.Lazy.Encoding as T+import qualified Data.Text.Encoding.Error as T+import qualified Data.ByteString.Lazy as L++import Data.List.Split (splitOn)+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Entry as Tar++loadNewest :: IO Newest+loadNewest = do+ c <- getAppUserDataDirectory "cabal"+ let fn = c ++ "/packages/hackage.haskell.org/00-index.tar"+ loadNewestFrom fn++loadNewestFrom :: FilePath -> IO Newest+loadNewestFrom = fmap parseNewest . L.readFile++parseNewest :: L.ByteString -> Newest+parseNewest = foldl' addPackage Map.empty . entriesToList . Tar.read++entriesToList :: Tar.Entries -> [Tar.Entry]+entriesToList Tar.Done = []+entriesToList (Tar.Fail s) = error s+entriesToList (Tar.Next e es) = e : entriesToList es++addPackage :: Newest -> Tar.Entry -> Newest+addPackage m entry =+ case splitOn "/" $ Tar.entryPath entry of+ [package', versionS, _] ->+ case simpleParse versionS of+ Just version ->+ case Map.lookup package' m of+ Nothing -> go package' version+ Just PackInfo { piVersion = oldv } ->+ if version > oldv+ then go package' version+ else m+ Nothing -> m+ _ -> m+ where+ go package' version =+ case Tar.entryContent entry of+ Tar.NormalFile bs _ ->+ Map.insert package' PackInfo+ { piVersion = version+ , piDesc = parsePackage bs+ , piEpoch = Tar.entryTime entry+ } m+ _ -> m++data PackInfo = PackInfo+ { piVersion :: Version+ , piDesc :: Maybe DescInfo+ , piEpoch :: Tar.EpochTime+ }+ deriving (Show, Read)++-- | The newest version of every package.+type Newest = Map.Map String PackInfo++-- | Information on a single package.+data DescInfo = DescInfo+ { diHaystack :: String+ , diDeps :: [Dependency]+ , diPackage :: PackageIdentifier+ }+ deriving (Show, Read)++getDescInfo :: GenericPackageDescription -> DescInfo+getDescInfo gpd = DescInfo+ { diHaystack = map toLower $ author p ++ maintainer p ++ name+ , diDeps = getDeps gpd+ , diPackage = pi'+ }+ where+ p = packageDescription gpd+ pi'@(PackageIdentifier (PackageName name) _) = package p++getDeps :: GenericPackageDescription -> [Dependency]+getDeps x = concat+ $ maybe id ((:) . condTreeConstraints) (condLibrary x)+ $ map (condTreeConstraints . snd) (condExecutables x)++checkDeps :: Newest -> DescInfo+ -> (PackageName, Version, CheckDepsRes)+checkDeps newest desc =+ case mapMaybe (notNewest newest) $ diDeps desc of+ [] -> (name, version, AllNewest)+ x -> let y = map head $ group $ sort $ map fst x+ et = maximum $ map snd x+ in (name, version, WontAccept y $ epochToTime et)+ where+ PackageIdentifier name version = diPackage desc++-- | Whether or not a package can accept all of the newest versions of its+-- dependencies. If not, it returns a list of packages which are not accepted,+-- and a timestamp of the most recently updated package.+data CheckDepsRes = AllNewest+ | WontAccept [(String, String)] UTCTime+ deriving Show++epochToTime :: Tar.EpochTime -> UTCTime+epochToTime e = addUTCTime (fromIntegral e) $ UTCTime (read "1970-01-01") 0++notNewest :: Newest -> Dependency -> Maybe ((String, String), Tar.EpochTime)+notNewest newest (Dependency (PackageName s) range) =+ case Map.lookup s newest of+ Nothing -> Just ((s, " no version found"), 0)+ Just PackInfo { piVersion = version, piEpoch = e } ->+ if withinRange version range+ then Nothing+ else Just ((s, display version), e)++-- | Loads up the newest version of a package from the 'Newest' list, if+-- available.+getPackage :: String -> Newest -> Maybe DescInfo+getPackage s n = Map.lookup s n >>= piDesc++-- | Parse information on a package from the contents of a cabal file.+parsePackage :: L.ByteString -> Maybe DescInfo+parsePackage lbs =+ case parsePackageDescription $ T.unpack+ $ T.decodeUtf8With T.lenientDecode lbs of+ ParseOk _ x -> Just $ getDescInfo x+ _ -> Nothing++-- | Load a single package from a cabal file.+loadPackage :: FilePath -> IO (Maybe DescInfo)+loadPackage = fmap parsePackage . L.readFile++-- | Find all of the packages matching a given search string.+filterPackages :: String -> Newest -> [DescInfo]+filterPackages needle =+ mapMaybe go . Map.elems+ where+ needle' = map toLower needle+ go PackInfo { piDesc = Just desc } =+ if needle' `isInfixOf` diHaystack desc+ then Just desc+ else Nothing+ go _ = Nothing
+ LICENSE view
@@ -0,0 +1,25 @@+The following license covers this documentation, and the source code, except+where otherwise indicated.++Copyright 2010, Michael Snoyman. All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++* 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.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "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 HOLDERS 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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ packdeps-cli.hs view
@@ -0,0 +1,36 @@+import Distribution.PackDeps+import System.Environment (getArgs)+import Distribution.Text (display)+import Distribution.Package (PackageName (PackageName))++main :: IO ()+main = do+ newest <- loadNewest+ getArgs >>= mapM_ (go newest)+ where+ go newest fp = do+ mdi <- loadPackage fp+ di <-+ case mdi of+ Just di -> return di+ Nothing -> error $ "Could not parse cabal file: " ++ fp+ case checkDeps newest di of+ (pn, v, AllNewest) ->+ putStrLn $ concat+ [ unPackageName pn+ , "-"+ , display v+ , ": Can use newest versions of all dependencies"+ ]+ (pn, v, WontAccept p _) -> do+ putStrLn $ concat+ [ unPackageName pn+ , "-"+ , display v+ , ": Cannot accept the following packages"+ ]+ flip mapM_ p $ \(x, y) -> putStrLn $ x ++ " " ++ y+ putStrLn ""++unPackageName :: PackageName -> String+unPackageName (PackageName n) = n
+ packdeps.cabal view
@@ -0,0 +1,37 @@+name: packdeps+version: 0.0.0+license: BSD3+license-file: LICENSE+author: Michael Snoyman <michael@snoyman.com>+maintainer: Michael Snoyman <michael@snoyman.com>+synopsis: Check your cabal packages for lagging dependencies.+description:+ This provides a library and command line tool for checking if the upper bounds in your package's dependency list excludes the newest package available. The code was originally available only as a web interface at <http://packdeps.haskellers.com/>, but is now available for standalone use as well.+ .+ The command line tool has an incredibly simple interface: simply pass it a list of cabal files, and it will tell you what dependencies- if any- are restricted.+category: Distribution+stability: Stable+cabal-version: >= 1.6+build-type: Simple+homepage: http://packdeps.haskellers.com/++library+ build-depends: base >= 4 && < 5+ , tar >= 0.3.1 && < 0.4+ , split >= 0.1.2.3 && < 0.2+ , bytestring >= 0.9 && < 0.10+ , text >= 0.7 && < 0.12+ , Cabal >= 1.8 && < 1.11+ , time >= 1.1.4 && < 1.3+ , containers >= 0.2 && < 0.5+ , directory >= 1.0 && < 1.2+ exposed-modules: Distribution.PackDeps+ ghc-options: -Wall++executable packdeps+ ghc-options: -Wall+ main-is: packdeps-cli.hs++source-repository head+ type: git+ location: git://github.com/snoyberg/packdeps.git