{-|
The module provides a Branch type for Fedora and EPEL for active Release's.
-}

-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.

module Distribution.Fedora.Branch
  ( Branch(..)
  , readBranch
  , showBranch
  , eitherBranch
  , readActiveBranch
  , eitherActiveBranch
  , newerBranch
  , getActiveBranches
  , getActiveBranched
  , getLatestFedoraBranch
  , branchDestTag
  , branchDistTag
  , branchRelease
  , partitionBranches
  )
where

import Data.Char (isDigit)
import Data.Either (partitionEithers)
import Data.List.Extra (delete, elemIndex, replace, sortBy, spanEnd)
import Data.Maybe (mapMaybe)
import Data.Ord (comparing, Down(Down))
import Data.Tuple (swap)
import Numeric.Natural (Natural)
import Safe (headDef, readMay)

import Distribution.Fedora.Release

-- | Branch datatype
--
-- Branch can be rawhide, or a fedora or epel branch
data Branch = EPELMinor !Natural !Natural | EPEL !Natural | EPELNext !Natural | Fedora !Natural | Rawhide
  deriving (Branch -> Branch -> Bool
(Branch -> Branch -> Bool)
-> (Branch -> Branch -> Bool) -> Eq Branch
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Branch -> Branch -> Bool
== :: Branch -> Branch -> Bool
$c/= :: Branch -> Branch -> Bool
/= :: Branch -> Branch -> Bool
Eq)

-- | defined such that: EPELNext 9 < EPEL 10 < Fedora 41 < Rawhide
instance Ord Branch where
  compare :: Branch -> Branch -> Ordering
compare Branch
Rawhide Branch
Rawhide = Ordering
EQ
  compare (Fedora Natural
m) (Fedora Natural
n) = Natural -> Natural -> Ordering
forall a. Ord a => a -> a -> Ordering
compare Natural
m Natural
n
  compare (EPELNext Natural
m) (EPELNext Natural
n) = Natural -> Natural -> Ordering
forall a. Ord a => a -> a -> Ordering
compare Natural
m Natural
n
  compare (EPEL Natural
m) (EPEL Natural
n) = Natural -> Natural -> Ordering
forall a. Ord a => a -> a -> Ordering
compare Natural
m Natural
n
  compare (EPELMinor Natural
m1 Natural
n1) (EPELMinor Natural
m2 Natural
n2) =
    case Natural -> Natural -> Ordering
forall a. Ord a => a -> a -> Ordering
compare Natural
m1 Natural
m2 of
      Ordering
LT -> Ordering
LT
      Ordering
GT -> Ordering
GT
      Ordering
EQ -> Natural -> Natural -> Ordering
forall a. Ord a => a -> a -> Ordering
compare Natural
n1 Natural
n2
  compare Branch
Rawhide Branch
_ = Ordering
GT
  compare Branch
_ Branch
Rawhide = Ordering
LT
  compare (Fedora Natural
_) Branch
_ = Ordering
GT
  compare Branch
_ (Fedora Natural
_) = Ordering
LT
  compare (EPEL Natural
m) (EPELNext Natural
n) = if Natural
m Natural -> Natural -> Bool
forall a. Eq a => a -> a -> Bool
== Natural
n then Ordering
LT else Natural -> Natural -> Ordering
forall a. Ord a => a -> a -> Ordering
compare Natural
m Natural
n
  compare (EPELNext Natural
m) (EPEL Natural
n) = if Natural
m Natural -> Natural -> Bool
forall a. Eq a => a -> a -> Bool
== Natural
n then Ordering
GT else Natural -> Natural -> Ordering
forall a. Ord a => a -> a -> Ordering
compare Natural
m Natural
n
  compare (EPEL Natural
m) (EPELMinor Natural
maj Natural
_) = if Natural
m Natural -> Natural -> Bool
forall a. Ord a => a -> a -> Bool
>= Natural
maj then Ordering
GT else Ordering
LT
  compare (EPELMinor Natural
maj Natural
_) (EPEL Natural
n) = if Natural
maj Natural -> Natural -> Bool
forall a. Ord a => a -> a -> Bool
<= Natural
n then Ordering
LT else Ordering
GT
  compare (EPELMinor Natural
_ Natural
_) (EPELNext Natural
_) = Ordering
GT
  compare (EPELNext Natural
_) (EPELMinor Natural
_ Natural
_) = Ordering
LT

-- | Read a Fedora Branch name, otherwise return branch string
eitherBranch :: String -> Either String Branch
eitherBranch :: String -> Either String Branch
eitherBranch String
str =
  case String
str of
    String
"" -> String -> Either String Branch
forall a b. a -> Either a b
Left String
str -- error or NonEmpty?
    String
"rawhide" -> Branch -> Either String Branch
forall a b. b -> Either a b
Right Branch
Rawhide
    String
"epel8-next" -> Branch -> Either String Branch
forall a b. b -> Either a b
Right (Branch -> Either String Branch) -> Branch -> Either String Branch
forall a b. (a -> b) -> a -> b
$ Natural -> Branch
EPELNext Natural
8
    String
"epel9-next" -> Branch -> Either String Branch
forall a b. b -> Either a b
Right (Branch -> Either String Branch) -> Branch -> Either String Branch
forall a b. (a -> b) -> a -> b
$ Natural -> Branch
EPELNext Natural
9
    String
_ ->
      case (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
spanEnd Char -> Bool
isDigit String
str of
        (String
pre,String
ns) ->
          case String -> Maybe Natural
forall a. Read a => String -> Maybe a
readMay String
ns of
            Maybe Natural
Nothing -> String -> Either String Branch
forall a b. a -> Either a b
Left String
str
            Just Natural
num ->
              case String
pre of
                   String
"f" -> Branch -> Either String Branch
forall a b. b -> Either a b
Right (Branch -> Either String Branch) -> Branch -> Either String Branch
forall a b. (a -> b) -> a -> b
$ Natural -> Branch
Fedora Natural
num
                   String
"epel" -> Branch -> Either String Branch
forall a b. b -> Either a b
Right (Branch -> Either String Branch) -> Branch -> Either String Branch
forall a b. (a -> b) -> a -> b
$ Natural -> Branch
EPEL Natural
num
                   String
"epel10." -> Branch -> Either String Branch
forall a b. b -> Either a b
Right (Branch -> Either String Branch) -> Branch -> Either String Branch
forall a b. (a -> b) -> a -> b
$ Natural -> Natural -> Branch
EPELMinor Natural
10 Natural
num
                   String
"el" -> Branch -> Either String Branch
forall a b. b -> Either a b
Right (Branch -> Either String Branch) -> Branch -> Either String Branch
forall a b. (a -> b) -> a -> b
$ Natural -> Branch
EPEL Natural
num
                   String
_ -> String -> Either String Branch
forall a b. a -> Either a b
Left String
str

-- -- | Read a Fedora Branch name, otherwise return an error message
-- eitherBranch' :: String -> Either String Branch
-- eitherBranch' cs = case eitherBranch cs of
--   Right br -> Right br
--   Left xs -> Left $ xs ++ " is not a known Fedora/EPEL branch"

-- | Read a Fedora Branch name
readBranch :: String -> Maybe Branch
readBranch :: String -> Maybe Branch
readBranch String
bs =
  case String -> Either String Branch
eitherBranch String
bs of
    Left String
_ -> Maybe Branch
forall a. Maybe a
Nothing
    Right Branch
br -> Branch -> Maybe Branch
forall a. a -> Maybe a
Just Branch
br

-- | Read a Branch name (one of the list of active branches)
--
-- Provides error strings for inactive or unknown branches.
eitherActiveBranch :: [Branch] -> String -> Either String Branch
eitherActiveBranch :: [Branch] -> String -> Either String Branch
eitherActiveBranch [Branch]
active String
bs =
  case String -> Either String Branch
eitherBranch String
bs of
    Left String
e -> String -> Either String Branch
forall a b. a -> Either a b
Left String
e
    Right Branch
br -> if Branch
br Branch -> [Branch] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Branch]
active
                then Branch -> Either String Branch
forall a b. b -> Either a b
Right Branch
br
                else String -> Either String Branch
forall a b. a -> Either a b
Left String
bs

-- | Read a Branch name (one of the list of active branches)
--
-- Similar to eitherActiveBranch but ignores any error string
readActiveBranch :: [Branch] -> String -> Maybe Branch
readActiveBranch :: [Branch] -> String -> Maybe Branch
readActiveBranch [Branch]
active String
cs =
  case [Branch] -> String -> Either String Branch
eitherActiveBranch [Branch]
active String
cs of
    Left String
_ -> Maybe Branch
forall a. Maybe a
Nothing
    Right Branch
br -> Branch -> Maybe Branch
forall a. a -> Maybe a
Just Branch
br

-- | render Branch to String
showBranch :: Branch -> String
showBranch :: Branch -> String
showBranch Branch
Rawhide = String
"rawhide"
showBranch (Fedora Natural
n) = String
"f" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Natural -> String
forall a. Show a => a -> String
show Natural
n
showBranch (EPEL Natural
n) = (if Natural
n Natural -> Natural -> Bool
forall a. Ord a => a -> a -> Bool
<= Natural
6 then String
"el" else String
"epel") String -> String -> String
forall a. [a] -> [a] -> [a]
++ Natural -> String
forall a. Show a => a -> String
show Natural
n
showBranch (EPELNext Natural
n) = String
"epel" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Natural -> String
forall a. Show a => a -> String
show Natural
n String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-next"
showBranch (EPELMinor Natural
m Natural
n) = String
"epel" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Natural -> String
forall a. Show a => a -> String
show Natural
m String -> String -> String
forall a. [a] -> [a] -> [a]
++ Char
'.' Char -> String -> String
forall a. a -> [a] -> [a]
: Natural -> String
forall a. Show a => a -> String
show Natural
n

-- | Get Release associated with release Branch
--
-- Fails if given an inactive branch
branchRelease :: Branch -> IO Release
branchRelease :: Branch -> IO Release
branchRelease = String -> IO Release
getBranchRelease (String -> IO Release)
-> (Branch -> String) -> Branch -> IO Release
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Branch -> String
showBranch

-- | Map Branch to Koji destination tag (candidate tag)
branchDestTag :: Branch -> IO String
branchDestTag :: Branch -> IO String
branchDestTag Branch
br = Release -> String
releaseCandidateTag (Release -> String) -> IO Release -> IO String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Branch -> IO Release
branchRelease Branch
br

-- | Converts koji dist tag  to rpm %dist tag for branch
--
-- f41 -> .fc41
--
-- epel10.0 -> .el10_0
branchDistTag :: Branch -> IO String
branchDistTag :: Branch -> IO String
branchDistTag Branch
br = do
  String
dist <- Release -> String
releaseDistTag (Release -> String) -> IO Release -> IO String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Branch -> IO Release
branchRelease Branch
br
  -- f41 -> .fc41
  -- epel10.0 -> .el10_0
  String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> IO String) -> String -> IO String
forall a b. (a -> b) -> a -> b
$ Char
'.' Char -> String -> String
forall a. a -> [a] -> [a]
: (Branch -> String -> String
distroFix Branch
br (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String -> String -> String
forall a. Eq a => [a] -> [a] -> [a] -> [a]
replace String
"." String
"_") String
dist
  where
    distroFix :: Branch -> String -> String
distroFix (EPEL Natural
_) = String -> String -> String -> String
forall a. Eq a => [a] -> [a] -> [a] -> [a]
replace String
"epel" String
"el"
    distroFix (EPELNext Natural
_) = String -> String -> String -> String
forall a. Eq a => [a] -> [a] -> [a] -> [a]
replace String
"epel" String
"el"
    distroFix (EPELMinor Natural
_ Natural
_) = String -> String -> String -> String
forall a. Eq a => [a] -> [a] -> [a] -> [a]
replace String
"epel" String
"el"
    distroFix Branch
_ = String -> String -> String -> String
forall a. Eq a => [a] -> [a] -> [a] -> [a]
replace String
"f" String
"fc"

-- | Returns newer branch than given one from supplied active branches.
--
-- Branches should be in descending order, eg from getFedoraBranches
newerBranch :: Branch -> [Branch] -> Maybe Branch
newerBranch :: Branch -> [Branch] -> Maybe Branch
newerBranch Branch
Rawhide [Branch]
_ = Maybe Branch
forall a. Maybe a
Nothing
newerBranch Branch
br [Branch]
branches =
  case Branch -> [Branch] -> Maybe Int
forall a. Eq a => a -> [a] -> Maybe Int
elemIndex Branch
br [Branch]
branches of
    Just Int
i | Int
i Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 -> Branch -> Maybe Branch
forall a. a -> Maybe a
Just (Branch -> Maybe Branch) -> Branch -> Maybe Branch
forall a b. (a -> b) -> a -> b
$ [Branch]
branches [Branch] -> Int -> Branch
forall a. HasCallStack => [a] -> Int -> a
!! (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1)
    Maybe Int
_ -> Maybe Branch
forall a. Maybe a
Nothing


--olderBranch :: Branch -> Branch
--olderBranch Rawhide = latestBranch
--olderBranch (Fedora n) = Fedora (n-1)

-- | Returns descending list of active Fedora branches, including rawhide and EPEL
getActiveBranches :: IO [Branch]
getActiveBranches :: IO [Branch]
getActiveBranches =
  [Branch] -> [Branch]
forall a. Ord a => [a] -> [a]
reverseSort ([Branch] -> [Branch])
-> ([Release] -> [Branch]) -> [Release] -> [Branch]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Release -> Maybe Branch) -> [Release] -> [Branch]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (String -> Maybe Branch
readBranch (String -> Maybe Branch)
-> (Release -> String) -> Release -> Maybe Branch
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Release -> String
releaseBranch) ([Release] -> [Branch]) -> IO [Release] -> IO [Branch]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [Release]
getActiveReleases

-- | Returns list of active Fedora branches, excluding rawhide
getActiveBranched :: IO [Branch]
getActiveBranched :: IO [Branch]
getActiveBranched = Branch -> [Branch] -> [Branch]
forall a. Eq a => a -> [a] -> [a]
delete Branch
Rawhide ([Branch] -> [Branch]) -> IO [Branch] -> IO [Branch]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [Branch]
getActiveBranches

-- from simple-cmd
error' :: String -> a
error' :: forall a. String -> a
error' = String -> a
forall a. String -> a
errorWithoutStackTrace

-- | separate fedora branches from rest of args
partitionBranches :: [String] -> ([Branch],[String])
partitionBranches :: [String] -> ([Branch], [String])
partitionBranches [String]
args =
  ([String], [Branch]) -> ([Branch], [String])
forall a b. (a, b) -> (b, a)
swap (([String], [Branch]) -> ([Branch], [String]))
-> ([Either String Branch] -> ([String], [Branch]))
-> [Either String Branch]
-> ([Branch], [String])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Either String Branch] -> ([String], [Branch])
forall a b. [Either a b] -> ([a], [b])
partitionEithers ([Either String Branch] -> ([Branch], [String]))
-> [Either String Branch] -> ([Branch], [String])
forall a b. (a -> b) -> a -> b
$ (String -> Either String Branch)
-> [String] -> [Either String Branch]
forall a b. (a -> b) -> [a] -> [b]
map String -> Either String Branch
eitherBranch [String]
args

-- | get newest Fedora branched Release
getLatestFedoraBranch :: IO Branch
getLatestFedoraBranch :: IO Branch
getLatestFedoraBranch =
  Branch -> [Branch] -> Branch
forall a. a -> [a] -> a
headDef (String -> Branch
forall a. String -> a
error' String
"no active branched!") ([Branch] -> Branch) -> IO [Branch] -> IO Branch
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [Branch]
getActiveBranched

reverseSort :: Ord a => [a] -> [a]
reverseSort :: forall a. Ord a => [a] -> [a]
reverseSort = (a -> a -> Ordering) -> [a] -> [a]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy ((a -> Down a) -> a -> a -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing a -> Down a
forall a. a -> Down a
Down)