packages feed

summoner-2.2.0.0: src/Summoner/GhcVer.hs

{- |
Module                  : Summoner.GhcVer
Copyright               : (c) 2017-2026 Kowainik
SPDX-License-Identifier : MPL-2.0
Maintainer              : Kowainik <xrom.xkov@gmail.com>
Stability               : Stable
Portability             : Portable

Contains data type for GHC versions supported by Summoner
and some useful functions for manipulation with them.
-}

module Summoner.GhcVer
    ( GhcVer (..)
    , GhcMeta (..)
    , Pvp (..)
    , showGhcVer
    , parseGhcVer
    , latestLts
    , baseVer
    , cabalBaseVersions
    , ghcTable

    , oldGhcs
    ) where

import qualified Data.Text as T
import qualified Text.Show as Show


-- | Represents some selected set of GHC versions.
data GhcVer
    = Ghc802
    | Ghc822
    | Ghc844
    | Ghc865
    | Ghc884
    | Ghc8107
    | Ghc902
    | Ghc928
    | Ghc948
    | Ghc967
    | Ghc984
    | Ghc9103
    | Ghc9122
    deriving stock (Eq, Ord, Show, Enum, Bounded)

-- | Converts 'GhcVer' into dot-separated string.
showGhcVer :: GhcVer -> Text
showGhcVer = \case
    Ghc802  -> "8.0.2"
    Ghc822  -> "8.2.2"
    Ghc844  -> "8.4.4"
    Ghc865  -> "8.6.5"
    Ghc884  -> "8.8.4"
    Ghc8107 -> "8.10.7"
    Ghc902  -> "9.0.2"
    Ghc928  -> "9.2.8"
    Ghc948  -> "9.4.8"
    Ghc967  -> "9.6.7"
    Ghc984  -> "9.8.4"
    Ghc9103 -> "9.10.3"
    Ghc9122 -> "9.12.2"

{- | These are old GHC versions that are not working with default GHC versions
when using Stack.
-}
oldGhcs :: [GhcVer]
oldGhcs = [minBound .. Ghc844]

parseGhcVer :: Text -> Maybe GhcVer
parseGhcVer = inverseMap showGhcVer

-- | Returns latest known LTS resolver for all GHC versions except default one.
latestLts :: GhcVer -> Text
latestLts = \case
    Ghc802  -> "lts-9.21"
    Ghc822  -> "lts-11.22"
    Ghc844  -> "lts-12.26"
    Ghc865  -> "lts-14.27"
    Ghc884  -> "lts-16.31"
    Ghc8107 -> "lts-18.28"
    Ghc902  -> "lts-19.33"
    Ghc928  -> "lts-20.26"
    Ghc948  -> "lts-21.25"
    Ghc967  -> "lts-22.44"
    Ghc984  -> "lts-23.28"
    Ghc9103 -> "lts-24.26"
    Ghc9122 -> "nightly-2026-01-04"

-- | Represents PVP versioning (4 numbers).
data Pvp = Pvp
    { pvpFirst  :: !Int
    , pvpSecond :: !Int
    , pvpThird  :: !Int
    , pvpFourth :: !Int
    }

-- | Show PVP version in a standard way: @1.2.3.4@
instance Show Pvp where
    show (Pvp a b c d) = intercalate "." $ map Show.show [a, b, c, d]

-- | Returns base version by 'GhcVer' as 'Pvp'.
baseVerPvp :: GhcVer -> Pvp
baseVerPvp = \case
    Ghc802  -> Pvp 4 9 1 0
    Ghc822  -> Pvp 4 10 1 0
    Ghc844  -> Pvp 4 11 1 0
    Ghc865  -> Pvp 4 12 0 0
    Ghc884  -> Pvp 4 13 0 0
    Ghc8107 -> Pvp 4 14 3 0
    Ghc902  -> Pvp 4 15 1 0
    Ghc928  -> Pvp 4 16 4 0
    Ghc948  -> Pvp 4 17 2 1
    Ghc967  -> Pvp 4 18 3 1
    Ghc984  -> Pvp 4 19 2 0
    Ghc9103 -> Pvp 4 20 2 0
    Ghc9122 -> Pvp 4 21 0 0

-- | Returns corresponding @base@ version of the given GHC version.
baseVer :: GhcVer -> Text
baseVer = show . baseVerPvp

{- | Returns the @base@ bounds for the list of the given GHC versions.

>>> cabalBaseVersions [Ghc844]
"^>= 4.11.1.0"

>>> cabalBaseVersions [Ghc802, Ghc822, Ghc844]
">= 4.9.0.0 && < 4.12"

-}
cabalBaseVersions :: [GhcVer] -> Text
cabalBaseVersions ghcVers = case sort ghcVers of
    []          -> ""
    [v]         -> "^>= " <> baseVer v
    minGhc:x:xs -> ">= " <> baseVer minGhc <> " && < " <> upperBound (x :| xs)
  where
    upperBound :: NonEmpty GhcVer -> Text
    upperBound ghcs = let Pvp{..} = baseVerPvp $ last ghcs in
        show pvpFirst <> "." <> show (pvpSecond + 1)

-- | Data type to keep meta information for every 'GhcVer'.
data GhcMeta = GhcMeta
    { gmGhc      :: !Text
    , gmBase     :: !Text
    , gmResolver :: !Text
    }

-- | Create corresponding 'GhcMeta' from the given 'GhcVer'.
toGhcMeta :: GhcVer -> GhcMeta
toGhcMeta ghcVer = GhcMeta
    { gmGhc      = "GHC-" <> showGhcVer ghcVer
    , gmBase     = "base-" <> baseVer ghcVer
    , gmResolver = latestLts ghcVer
    }

ghcTable :: [Text]
ghcTable = map (formatGhcMeta . toGhcMeta) universe

{- Formats 'GhcMeta' in a special way.
It aligns the meta to the left, filling on the right with the spaces.

As the pad number it takes the maximum possible length of the data manually.

Example:

@
GHC-8.6.5     base-4.12.0.0   lts-14.17
@
-}
formatGhcMeta :: GhcMeta -> Text
formatGhcMeta GhcMeta{..} =
       T.justifyLeft 12 ' ' gmGhc
    <> "  "
    <> T.justifyLeft 14 ' ' gmBase
    <> "  "
    <> gmResolver