{-# LANGUAGE OverloadedStrings #-} {- | This module contains the "GhcDatabase", which is a Map-like type associating to each ghc version its "GhcMetadata" informations, and utility functions to to manipulate and query it. -} module GhcDatabase ( GhcMetadata(..) , GhcDatabase , defaultGhcDatabaseURL -- * Conversion from/to List , dbFromList , dbToList , ghcVersions -- * Queries , isEmpty , metadataForGhc , baseVersionForGhc , cabalLibRangeForGhc , hasGhcVersion , newest -- * Parse from CSV file , parseGhcDatabase -- * Filter database entries , filterGhcVersions , excludeGhcVersions , filterBaseVersionIn , filterMinCabalVersionIn ) where import Distribution.Version import Distribution.Parsec.Class import Data.Csv import Data.Foldable (toList) import qualified Data.ByteString.Lazy as B import qualified Data.Map.Strict as M import qualified Data.Set as S -- | The URL from which you can download a ready to be used "GhcDatabase" defaultGhcDatabaseURL :: String defaultGhcDatabaseURL = "https://raw.githubusercontent.com/vabal/vabal-ghc-metadata/master/vabal-ghc-metadata.csv" -- | Metadata associated to a GHC version data GhcMetadata = GhcMetadata { baseVersion :: Version -- ^ base version supported by this GHC , minCabalVersion :: Version -- ^ minimum version of Cabal that supports this GHC } newtype MetadataEntry = MetadataEntry { unwrapMetadataEntry :: (Version, GhcMetadata) } instance FromNamedRecord MetadataEntry where parseNamedRecord r = do field1 <- r .: "ghcVersion" ghcVer <- maybe (fail "Expected version") return $ simpleParsec field1 field2 <- r .: "baseVersion" baseVer <- maybe (fail "Expected version") return $ simpleParsec field2 field3 <- r .: "minCabalVersion" minCabalVer <- maybe (fail "Expected version") return $ simpleParsec field3 return $ MetadataEntry (ghcVer, GhcMetadata baseVer minCabalVer) -- | Map associating to each GHC version its "GhcMetadata" newtype GhcDatabase = GhcDatabase { unwrapDb :: M.Map Version GhcMetadata } -- | /O(n*log n)/. Create a "GhcDatabase" from a list key-value pairs. -- -- The key is the ghc version, -- the value is the "GhcMetadata" for that version dbFromList :: [(Version, GhcMetadata)] -> GhcDatabase dbFromList = GhcDatabase . M.fromList -- | /O(n)/. Convert the "GhcDatabase" to a list of key-value pairs dbToList :: GhcDatabase -> [(Version, GhcMetadata)] dbToList = M.toList . unwrapDb -- | /O(n)/. Get all ghc versions contained in the database ghcVersions :: GhcDatabase -> S.Set Version ghcVersions = M.keysSet . unwrapDb -- | /O(1)/. Check whether the "GhcDatabase" is empty isEmpty :: GhcDatabase -> Bool isEmpty = M.null . unwrapDb -- | /O(log n)/. Get newest GHC (and its metadata) found in the database newest :: GhcDatabase -> Maybe (Version, GhcMetadata) newest = M.lookupMax . unwrapDb -- | /O(log n)/. Get "GhcMetadata" for a GHC version, if it's in the database metadataForGhc :: GhcDatabase -> Version -> Maybe GhcMetadata metadataForGhc (GhcDatabase db) v = M.lookup v db -- | /O(log n)/. Get base version for a GHC version, if it's in the database baseVersionForGhc :: GhcDatabase -> Version -> Maybe Version baseVersionForGhc db v = baseVersion <$> metadataForGhc db v -- | /O(log n)/. Get Supported Cabal version range for a GHC version, if it's in the databasr cabalLibRangeForGhc :: GhcDatabase -> Version -> Maybe VersionRange cabalLibRangeForGhc db v = orLaterVersion . minCabalVersion <$> metadataForGhc db v -- | Parse a "GhcDatabase" from a csv string. -- The format of the csv string must be the following: -- -- The first line is the header, it is expected to contain these three columns: ghcVersion, baseVersion, minCabalVersion -- Each line must at least contain values for those three columns. -- -- A simple example: -- -- > ghcVersion,baseVersion,minCabalVersion -- > 8.6.3,4.12.0.0,2.4 parseGhcDatabase :: B.ByteString -> Either String GhcDatabase parseGhcDatabase contents = do (_, entries) <- decodeByName contents return . GhcDatabase . M.fromList . map unwrapMetadataEntry $ toList entries -- | /O(m*log(n/m + 1)), m <= n/. Get a restricted database with only a set of GHC versions filterGhcVersions :: GhcDatabase -> S.Set Version -> GhcDatabase filterGhcVersions (GhcDatabase db) = GhcDatabase . M.restrictKeys db -- | /O(m*log(n/m + 1)), m <= n/. Exclude all ghc versions in the set from the database excludeGhcVersions :: GhcDatabase -> S.Set Version -> GhcDatabase excludeGhcVersions (GhcDatabase db) = GhcDatabase . M.withoutKeys db -- | /O(log n)/. Check whether the database contains a GHC version hasGhcVersion :: GhcDatabase -> Version -> Bool hasGhcVersion (GhcDatabase s) v = v `M.member` s isVersionInRange :: VersionRange -> Version -> Bool isVersionInRange = flip withinRange -- | /O(n)/. Filter database entries with base version in the given "VersionRange" filterBaseVersionIn :: GhcDatabase -> VersionRange -> GhcDatabase filterBaseVersionIn (GhcDatabase db) vr = GhcDatabase $ M.filter (isVersionInRange vr . baseVersion) db -- | /O(n)/. Filter database entries with supported Cabal version range in the given "VersionRange" filterMinCabalVersionIn :: GhcDatabase -> VersionRange -> GhcDatabase filterMinCabalVersionIn (GhcDatabase db) vr = GhcDatabase $ M.filter (not . isNoVersion . intersectVersionRanges vr . orLaterVersion . minCabalVersion) db