-- |
-- Module      :  Distribution.Fedora.Branch
-- Copyright   :  (C) 2020-2022,2024  Jens Petersen
--
-- Maintainer  :  Jens Petersen <petersen@fedoraproject.org>
--
-- Explanation: Fedora Branch type and functions

-- 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
  , eitherBranch
  , readActiveBranch
  , eitherActiveBranch
  , newerBranch
  , getFedoraBranches
  , getFedoraBranched
  , getLatestFedoraBranch
  , branchDestTag
  , branchDistTag
  , branchRelease
  , branchTarget
  , partitionBranches
  )
where

import Data.Char (isDigit)
import Data.Either (partitionEithers)
import Data.List (delete)
import Data.Maybe (mapMaybe)
import Data.Tuple (swap)

import Distribution.Fedora.Release

-- | Branch datatype
--
-- Branch can be rawhide, or a fedora or epel branch
data Branch = EPEL !Int | EPELNext !Int | Fedora !Int | 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, Eq Branch
Eq Branch =>
(Branch -> Branch -> Ordering)
-> (Branch -> Branch -> Bool)
-> (Branch -> Branch -> Bool)
-> (Branch -> Branch -> Bool)
-> (Branch -> Branch -> Bool)
-> (Branch -> Branch -> Branch)
-> (Branch -> Branch -> Branch)
-> Ord Branch
Branch -> Branch -> Bool
Branch -> Branch -> Ordering
Branch -> Branch -> Branch
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Branch -> Branch -> Ordering
compare :: Branch -> Branch -> Ordering
$c< :: Branch -> Branch -> Bool
< :: Branch -> Branch -> Bool
$c<= :: Branch -> Branch -> Bool
<= :: Branch -> Branch -> Bool
$c> :: Branch -> Branch -> Bool
> :: Branch -> Branch -> Bool
$c>= :: Branch -> Branch -> Bool
>= :: Branch -> Branch -> Bool
$cmax :: Branch -> Branch -> Branch
max :: Branch -> Branch -> Branch
$cmin :: Branch -> Branch -> Branch
min :: Branch -> Branch -> Branch
Ord)

-- | Read a Fedora Branch name, otherwise return branch string
eitherBranch :: String -> Either String Branch
eitherBranch :: String -> Either String Branch
eitherBranch String
"rawhide" = Branch -> Either String Branch
forall a b. b -> Either a b
Right Branch
Rawhide
eitherBranch (Char
'f':String
ns) | (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
ns = let br :: Branch
br = Int -> Branch
Fedora (String -> Int
forall a. Read a => String -> a
read String
ns) in Branch -> Either String Branch
forall a b. b -> Either a b
Right Branch
br
-- FIXME add proper parsing:
eitherBranch 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
$ Int -> Branch
EPELNext Int
8
eitherBranch 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
$ Int -> Branch
EPELNext Int
9
eitherBranch (Char
'e':Char
'p':Char
'e':Char
'l':String
n) | (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
n = let br :: Branch
br = Int -> Branch
EPEL (String -> Int
forall a. Read a => String -> a
read String
n) in Branch -> Either String Branch
forall a b. b -> Either a b
Right Branch
br
eitherBranch (Char
'e':Char
'l':String
n) | (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
n = let br :: Branch
br = Int -> Branch
EPEL (String -> Int
forall a. Read a => String -> a
read String
n) in Branch -> Either String Branch
forall a b. b -> Either a b
Right Branch
br
eitherBranch String
cs = String -> Either String Branch
forall a b. a -> Either a b
Left String
cs

-- -- | 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

-- Unsafely read a Fedora Branch name: errors for unknown branches
readBranch' :: String -> Branch
readBranch' :: String -> Branch
readBranch' String
bs =
  case String -> Either String Branch
eitherBranch String
bs of
    Left String
e -> String -> Branch
forall a. String -> a
error' (String -> Branch) -> String -> Branch
forall a b. (a -> b) -> a -> b
$! String
"unknown Fedora branch: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
e
    Right Branch
br -> 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

instance Show Branch where
  show :: Branch -> String
show Branch
Rawhide = String
"rawhide"
  show (Fedora Int
n) = String
"f" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
n
  show (EPEL Int
n) = (if Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
6 then String
"el" else String
"epel") String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
n
  show (EPELNext Int
n) = String
"epel" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
n String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-next"

-- | Get Release associated with release Branch
branchRelease :: Branch -> IO Release
branchRelease :: Branch -> IO Release
branchRelease Branch
br = do
  [Release]
rels <- IO [Release]
getReleases
  case (Release -> String) -> (String -> Bool) -> [Release] -> [Release]
forall a. (Release -> a) -> (a -> Bool) -> [Release] -> [Release]
releaseFilter Release -> String
releaseBranch (String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== Branch -> String
forall a. Show a => a -> String
show Branch
br) [Release]
rels of
    [] -> String -> IO Release
forall a. String -> a
error' (String -> IO Release) -> String -> IO Release
forall a b. (a -> b) -> a -> b
$ String
"release not found for branch " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Branch -> String
forall a. Show a => a -> String
show Branch
br
    [Release
rel] -> Release -> IO Release
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Release
rel
    [Release]
rs -> String -> IO Release
forall a. String -> a
error' (String -> IO Release) -> String -> IO Release
forall a b. (a -> b) -> a -> b
$ String
"impossible happened: multiple releases for " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Branch -> String
forall a. Show a => a -> String
show Branch
br String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
":\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ [String] -> String
unwords ((Release -> String) -> [Release] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Release -> String
releaseBranch [Release]
rs)

-- | Map Branch to Koji destination tag
branchDestTag :: Branch -> String
branchDestTag :: Branch -> String
branchDestTag Branch
Rawhide = String
"rawhide"
branchDestTag (Fedora Int
n) = Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
Fedora Int
n) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-updates-candidate"
branchDestTag (EPEL Int
n) = Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
EPEL Int
n) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-testing-candidate"
branchDestTag (EPELNext Int
n) = Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
EPELNext Int
n) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-testing-candidate"

-- | Get %dist tag for branch
branchDistTag :: Branch -> IO String
branchDistTag :: Branch -> IO String
branchDistTag Branch
Rawhide = do
  String
n <- Release -> String
releaseVersion (Release -> String) -> IO Release -> IO String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Branch -> IO Release
branchRelease Branch
Rawhide
  Branch -> IO String
branchDistTag (Int -> Branch
Fedora (String -> Int
forall a. Read a => String -> a
read String
n))
branchDistTag (Fedora Int
n) = 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
$ String
".fc" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
n
branchDistTag (EPEL Int
n) = 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
$ String
".el" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
n
branchDistTag (EPELNext Int
n) = 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
$ String
".el" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
n String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".next"

-- | Default build target associated with a branch
branchTarget :: Branch -> String
branchTarget :: Branch -> String
branchTarget (Fedora Int
n) = Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
Fedora Int
n)
branchTarget (EPEL Int
n) = Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
EPEL Int
n)
branchTarget (EPELNext Int
n) = Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
EPELNext Int
n)
branchTarget Branch
Rawhide = String
"rawhide"

-- | Returns newer branch than given one from supplied active branches.
--
-- Branches should be in descending order, eg from getFedoraBranches
newerBranch :: Branch -> [Branch] -> Branch
newerBranch :: Branch -> [Branch] -> Branch
newerBranch Branch
Rawhide [Branch]
_ = Branch
Rawhide
newerBranch (Fedora Int
n) [Branch]
branches =
  if Int -> Branch
Fedora Int
n Branch -> [Branch] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Branch]
branches
  then if Int -> Branch
Fedora (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) Branch -> [Branch] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Branch]
branches
       then Int -> Branch
Fedora (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)
       else Branch
Rawhide
  else String -> Branch
forall a. String -> a
error' (String -> Branch) -> String -> Branch
forall a b. (a -> b) -> a -> b
$ String
"Unsupported branch: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
Fedora Int
n)
newerBranch (EPEL Int
n) [Branch]
branches =
  if Int -> Branch
EPEL Int
n Branch -> [Branch] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Branch]
branches
  then if Int -> Branch
EPEL (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) Branch -> [Branch] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Branch]
branches
       then Int -> Branch
EPEL (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)
       else Int -> Branch
EPEL Int
n
  else String -> Branch
forall a. String -> a
error' (String -> Branch) -> String -> Branch
forall a b. (a -> b) -> a -> b
$ String
"Unsupported branch: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
EPEL Int
n)
newerBranch (EPELNext Int
n) [Branch]
branches =
  if Int -> Branch
EPELNext Int
n Branch -> [Branch] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Branch]
branches
  then if Int -> Branch
EPELNext (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) Branch -> [Branch] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Branch]
branches
       then Int -> Branch
EPELNext (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)
       else Int -> Branch
EPELNext Int
n
  else String -> Branch
forall a. String -> a
error' (String -> Branch) -> String -> Branch
forall a b. (a -> b) -> a -> b
$ String
"Unsupported branch: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Branch -> String
forall a. Show a => a -> String
show (Int -> Branch
EPELNext Int
n)

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

-- | Returns list of active Fedora branches, including rawhide and EPEL
getFedoraBranches :: IO [Branch]
getFedoraBranches :: IO [Branch]
getFedoraBranches =
  (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]
getReleases

-- | Maps Release to Branch
releaseToBranch :: Release -> Branch
releaseToBranch :: Release -> Branch
releaseToBranch = String -> Branch
readBranch' (String -> Branch) -> (Release -> String) -> Release -> Branch
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Release -> String
releaseBranch

-- | Returns list of active Fedora branches, excluding rawhide
getFedoraBranched :: IO [Branch]
getFedoraBranched :: IO [Branch]
getFedoraBranched = 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]
getFedoraBranches

-- 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 =
  Release -> Branch
releaseToBranch (Release -> Branch)
-> ([Release] -> Release) -> [Release] -> Branch
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Release] -> Release
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Release] -> Release)
-> ([Release] -> [Release]) -> [Release] -> Release
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Release -> String) -> (String -> Bool) -> [Release] -> [Release]
forall a. (Release -> a) -> (a -> Bool) -> [Release] -> [Release]
releaseFilter Release -> String
releaseBranch (String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"rawhide")
  ([Release] -> Branch) -> IO [Release] -> IO Branch
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [Release]
getFedoraReleases

releaseFilter :: (Release -> a) -> (a -> Bool) -> [Release] -> [Release]
releaseFilter :: forall a. (Release -> a) -> (a -> Bool) -> [Release] -> [Release]
releaseFilter Release -> a
f a -> Bool
p = (Release -> Bool) -> [Release] -> [Release]
forall a. (a -> Bool) -> [a] -> [a]
filter (a -> Bool
p (a -> Bool) -> (Release -> a) -> Release -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Release -> a
f)