Skip to content

Instantly share code, notes, and snippets.

@tfausak
Created April 17, 2016 15:54
Show Gist options
  • Save tfausak/48a10d6a5724ddb0df3908e27e3a6d21 to your computer and use it in GitHub Desktop.
Save tfausak/48a10d6a5724ddb0df3908e27e3a6d21 to your computer and use it in GitHub Desktop.
Statistics about major version numbers of Haskell packages on Hackage.
#!/usr/bin/env stack
{- stack --install-ghc --resolver lts-5 runghc
--package containers
--package HTTP
--package network-uri
--package tar
--package text
--package zlib
-}
{-# LANGUAGE OverloadedStrings #-}
import qualified Codec.Archive.Tar as Tar
import qualified Codec.Compression.GZip as GZip
import qualified Control.Exception as Exception
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.Text as Text
import qualified Data.Version as Version
import qualified Network.HTTP as HTTP
import qualified Network.URI as URI
import qualified Text.ParserCombinators.ReadP as ReadP
main :: IO ()
main = do
-- Build a request to get the package index.
let method = HTTP.GET
let index = "http://hackage.haskell.org/packages/index.tar.gz"
let Just uri = URI.parseURI index
let request = HTTP.mkRequest method uri
-- Make the request, decompress it, and unpack it.
result <- HTTP.simpleHTTP request
body <- HTTP.getResponseBody result
let archive = GZip.decompress body
let entries = Tar.read archive
-- Parse a textual version number.
let parseVersion text = case reverse (ReadP.readP_to_S Version.parseVersion (Text.unpack text)) of
(version, "") : _ -> Just (version :: Version.Version)
_ -> Nothing
-- Convert the list of entries into a map of package names to versions.
let step entry x = case Tar.entryContent entry of
Tar.NormalFile _ _ -> let
path = Text.pack (Tar.entryPath entry)
in if Text.isSuffixOf ".cabal" path
then case Text.splitOn "/" path of
[package, v, _] -> case parseVersion v of
Just version -> Map.insertWith (++) package [version] x
_ -> x
_ -> x
else x
_ -> x
let initial = Map.empty
let handle formatError = Exception.throw formatError
let versions = Tar.foldEntries step initial handle entries
-- Print out the total number of packages.
putStrLn "Number of packages:"
print (Map.size versions)
putStrLn ""
-- Print out the major version numbers along with how often they show up.
let majors = Map.foldr
(\ e a -> Map.insertWith (+) (head (Version.versionBranch (last (List.sort e)))) (1 :: Int) a)
Map.empty
versions
putStrLn "Major version numbers:"
mapM_ (\ (k, v) -> putStrLn (show k ++ "\t" ++ show v)) (Map.toAscList majors)
putStrLn ""
-- Print out packages that have changed their major version number.
let changes = Map.foldrWithKey
(\ k e a -> let
x = List.nub (map head (map Version.versionBranch (List.sort e)))
in if length x == 1 then a else Map.insert k x a)
Map.empty
versions
putStrLn "Major version number changes:"
mapM_ (\ (k, v) -> putStrLn (Text.unpack k ++ "\t" ++ unwords (map show v))) (Map.toAscList changes)
putStrLn ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment