module Ecluse.Core.Version.Pep440 (
Pep440Key (..),
parsePep440,
renderPep440,
isPep440Stable,
) where
import Data.Char (isDigit)
import Data.List (dropWhileEnd, unsnoc)
import Data.Text qualified as T
import Ecluse.Core.Version.Token (VToken (VNum, VStr), classifyRun, isAsciiAlphaNum, numOr0, parseNumSeg, withinVersionLength)
data Pep440Key = Pep440Key
{ Pep440Key -> Integer
p440Epoch :: Integer
, Pep440Key -> [Integer]
p440Release :: [Integer]
, Pep440Key -> (Int, Int, Integer)
p440Pre :: (Int, Int, Integer)
, Pep440Key -> (Int, Integer)
p440Post :: (Int, Integer)
, Pep440Key -> (Int, Integer)
p440Dev :: (Int, Integer)
, Pep440Key -> [VToken]
p440Local :: [VToken]
}
deriving stock (Pep440Key -> Pep440Key -> Bool
(Pep440Key -> Pep440Key -> Bool)
-> (Pep440Key -> Pep440Key -> Bool) -> Eq Pep440Key
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Pep440Key -> Pep440Key -> Bool
== :: Pep440Key -> Pep440Key -> Bool
$c/= :: Pep440Key -> Pep440Key -> Bool
/= :: Pep440Key -> Pep440Key -> Bool
Eq, Eq Pep440Key
Eq Pep440Key =>
(Pep440Key -> Pep440Key -> Ordering)
-> (Pep440Key -> Pep440Key -> Bool)
-> (Pep440Key -> Pep440Key -> Bool)
-> (Pep440Key -> Pep440Key -> Bool)
-> (Pep440Key -> Pep440Key -> Bool)
-> (Pep440Key -> Pep440Key -> Pep440Key)
-> (Pep440Key -> Pep440Key -> Pep440Key)
-> Ord Pep440Key
Pep440Key -> Pep440Key -> Bool
Pep440Key -> Pep440Key -> Ordering
Pep440Key -> Pep440Key -> Pep440Key
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 :: Pep440Key -> Pep440Key -> Ordering
compare :: Pep440Key -> Pep440Key -> Ordering
$c< :: Pep440Key -> Pep440Key -> Bool
< :: Pep440Key -> Pep440Key -> Bool
$c<= :: Pep440Key -> Pep440Key -> Bool
<= :: Pep440Key -> Pep440Key -> Bool
$c> :: Pep440Key -> Pep440Key -> Bool
> :: Pep440Key -> Pep440Key -> Bool
$c>= :: Pep440Key -> Pep440Key -> Bool
>= :: Pep440Key -> Pep440Key -> Bool
$cmax :: Pep440Key -> Pep440Key -> Pep440Key
max :: Pep440Key -> Pep440Key -> Pep440Key
$cmin :: Pep440Key -> Pep440Key -> Pep440Key
min :: Pep440Key -> Pep440Key -> Pep440Key
Ord, Int -> Pep440Key -> ShowS
[Pep440Key] -> ShowS
Pep440Key -> String
(Int -> Pep440Key -> ShowS)
-> (Pep440Key -> String)
-> ([Pep440Key] -> ShowS)
-> Show Pep440Key
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Pep440Key -> ShowS
showsPrec :: Int -> Pep440Key -> ShowS
$cshow :: Pep440Key -> String
show :: Pep440Key -> String
$cshowList :: [Pep440Key] -> ShowS
showList :: [Pep440Key] -> ShowS
Show, (forall x. Pep440Key -> Rep Pep440Key x)
-> (forall x. Rep Pep440Key x -> Pep440Key) -> Generic Pep440Key
forall x. Rep Pep440Key x -> Pep440Key
forall x. Pep440Key -> Rep Pep440Key x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Pep440Key -> Rep Pep440Key x
from :: forall x. Pep440Key -> Rep Pep440Key x
$cto :: forall x. Rep Pep440Key x -> Pep440Key
to :: forall x. Rep Pep440Key x -> Pep440Key
Generic)
instance NFData Pep440Key
parsePep440 :: Text -> Maybe Pep440Key
parsePep440 :: Text -> Maybe Pep440Key
parsePep440 Text
raw = do
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Text -> Bool
withinVersionLength Text
raw)
let lowered :: Text
lowered = Text -> Text
T.toLower (Text -> Text
T.strip Text
raw)
noV :: Text
noV = Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
lowered (Text -> Text -> Maybe Text
T.stripPrefix Text
"v" Text
lowered)
(Text
mainPart, Text
localRaw) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"+" Text
noV
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard ((Char -> Bool) -> Text -> Bool
T.all Char -> Bool
isMainChar Text
mainPart)
(epoch, afterEpoch) <- Text -> Maybe (Integer, Text)
parseEpoch Text
mainPart
(release, suffix) <- parseRelease afterEpoch
suffixParts <- parsePep440Suffix suffix
localToks <- parseLocal localRaw
pure (force (assembleKey epoch release suffixParts localToks))
where
isMainChar :: Char -> Bool
isMainChar Char
c = Char -> Bool
isAsciiAlphaNum Char
c Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'!' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'-' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'_'
parseEpoch :: Text -> Maybe (Integer, Text)
parseEpoch :: Text -> Maybe (Integer, Text)
parseEpoch Text
mainPart = do
let (Text
epochText, Text
afterEpoch) = case HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"!" Text
mainPart of
(Text
e, Text
rest)
| Text -> Bool
T.null Text
rest -> (Text
"", Text
mainPart)
| Bool
otherwise -> (Text
e, Int -> Text -> Text
T.drop Int
1 Text
rest)
epoch <- if Text -> Bool
T.null Text
epochText then Integer -> Maybe Integer
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Integer
0 else Text -> Maybe Integer
parseNumSeg Text
epochText
pure (epoch, afterEpoch)
parseRelease :: Text -> Maybe ([Integer], Text)
parseRelease :: Text -> Maybe ([Integer], Text)
parseRelease Text
afterEpoch = do
let (Text
releaseText, Text
suffix) = (Char -> Bool) -> Text -> (Text, Text)
T.span (\Char
c -> Char -> Bool
isDigit Char
c Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') Text
afterEpoch
relSegs :: [Text]
relSegs = [Text] -> [Text]
dropTrailingEmpty (HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
"." Text
releaseText)
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Bool
not ((Text -> Bool) -> [Text] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Text -> Bool
T.null [Text]
relSegs))
release <- (Text -> Maybe Integer) -> [Text] -> Maybe [Integer]
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> [a] -> f [b]
traverse Text -> Maybe Integer
parseNumSeg [Text]
relSegs
guard (not (null release))
pure (release, suffix)
where
dropTrailingEmpty :: [Text] -> [Text]
dropTrailingEmpty [Text]
segs = case [Text] -> Maybe ([Text], Text)
forall a. [a] -> Maybe ([a], a)
unsnoc [Text]
segs of
Just ([Text]
initSegs, Text
lastSeg) | Text -> Bool
T.null Text
lastSeg -> [Text]
initSegs
Maybe ([Text], Text)
_ -> [Text]
segs
parseLocal :: Text -> Maybe [VToken]
parseLocal :: Text -> Maybe [VToken]
parseLocal Text
lr
| Text -> Bool
T.null Text
lr = [VToken] -> Maybe [VToken]
forall a. a -> Maybe a
Just []
| Bool
otherwise =
let segs :: [Text]
segs = (Char -> Bool) -> Text -> [Text]
T.split (Char -> String -> Bool
forall (f :: * -> *) a.
(Foldable f, DisallowElem f, Eq a) =>
a -> f a -> Bool
`elem` [Char
'.', Char
'-', Char
'_']) (Int -> Text -> Text
T.drop Int
1 Text
lr)
in if (Text -> Bool) -> [Text] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (\Text
s -> Bool -> Bool
not (Text -> Bool
T.null Text
s) Bool -> Bool -> Bool
&& (Char -> Bool) -> Text -> Bool
T.all Char -> Bool
isAsciiAlphaNum Text
s) [Text]
segs
then [VToken] -> Maybe [VToken]
forall a. a -> Maybe a
Just ((Text -> VToken) -> [Text] -> [VToken]
forall a b. (a -> b) -> [a] -> [b]
map Text -> VToken
classifyRun [Text]
segs)
else Maybe [VToken]
forall a. Maybe a
Nothing
assembleKey ::
Integer -> [Integer] -> (Maybe (Int, Integer), Maybe Integer, Maybe Integer) -> [VToken] -> Pep440Key
assembleKey :: Integer
-> [Integer]
-> (Maybe (Int, Integer), Maybe Integer, Maybe Integer)
-> [VToken]
-> Pep440Key
assembleKey Integer
epoch [Integer]
release (Maybe (Int, Integer)
mPre, Maybe Integer
mPost, Maybe Integer
mDev) [VToken]
localToks =
Pep440Key
{ p440Epoch :: Integer
p440Epoch = Integer
epoch
, p440Release :: [Integer]
p440Release = [Integer] -> [Integer]
stripTrailingZeros [Integer]
release
, p440Pre :: (Int, Int, Integer)
p440Pre = (Int, Int, Integer)
pre
, p440Post :: (Int, Integer)
p440Post = (Int, Integer)
post
, p440Dev :: (Int, Integer)
p440Dev = (Int, Integer)
dev
, p440Local :: [VToken]
p440Local = [VToken]
localToks
}
where
pre :: (Int, Int, Integer)
pre = case Maybe (Int, Integer)
mPre of
Just (Int
stage, Integer
n) -> (Int
1, Int
stage, Integer
n)
Maybe (Int, Integer)
Nothing
| Maybe Integer -> Bool
forall a. Maybe a -> Bool
isJust Maybe Integer
mDev Bool -> Bool -> Bool
&& Maybe Integer -> Bool
forall a. Maybe a -> Bool
isNothing Maybe Integer
mPost -> (Int
0, Int
0, Integer
0)
| Bool
otherwise -> (Int
2, Int
0, Integer
0)
post :: (Int, Integer)
post = case Maybe Integer
mPost of
Maybe Integer
Nothing -> (Int
0, Integer
0)
Just Integer
n -> (Int
1, Integer
n)
dev :: (Int, Integer)
dev = case Maybe Integer
mDev of
Maybe Integer
Nothing -> (Int
1, Integer
0)
Just Integer
n -> (Int
0, Integer
n)
stripTrailingZeros :: [Integer] -> [Integer]
stripTrailingZeros = (Integer -> Bool) -> [Integer] -> [Integer]
forall a. (a -> Bool) -> [a] -> [a]
dropWhileEnd (Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0)
parsePep440Suffix ::
Text -> Maybe (Maybe (Int, Integer), Maybe Integer, Maybe Integer)
parsePep440Suffix :: Text -> Maybe (Maybe (Int, Integer), Maybe Integer, Maybe Integer)
parsePep440Suffix Text
s0 =
let (Maybe (Int, Integer)
pre, Text
s1) = Text -> (Maybe (Int, Integer), Text)
consumePre Text
s0
(Maybe Integer
post, Text
s2) = Text -> (Maybe Integer, Text)
consumePost Text
s1
(Maybe Integer
dev, Text
s3) = Text -> (Maybe Integer, Text)
consumeDev Text
s2
in if Text -> Bool
T.null Text
s3 then (Maybe (Int, Integer), Maybe Integer, Maybe Integer)
-> Maybe (Maybe (Int, Integer), Maybe Integer, Maybe Integer)
forall a. a -> Maybe a
Just (Maybe (Int, Integer)
pre, Maybe Integer
post, Maybe Integer
dev) else Maybe (Maybe (Int, Integer), Maybe Integer, Maybe Integer)
forall a. Maybe a
Nothing
dropSep :: Text -> Text
dropSep :: Text -> Text
dropSep Text
s = case Text -> Maybe (Char, Text)
T.uncons Text
s of
Just (Char
c, Text
rest) | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'-' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'_' -> Text
rest
Maybe (Char, Text)
_ -> Text
s
consumePre :: Text -> (Maybe (Int, Integer), Text)
consumePre :: Text -> (Maybe (Int, Integer), Text)
consumePre Text
s =
case [Maybe (Int, Text)] -> Maybe (Int, Text)
forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum (((Text, Int) -> Maybe (Int, Text))
-> [(Text, Int)] -> [Maybe (Int, Text)]
forall a b. (a -> b) -> [a] -> [b]
map (\(Text
lbl, Int
rk) -> (,) Int
rk (Text -> (Int, Text)) -> Maybe Text -> Maybe (Int, Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Text -> Maybe Text
T.stripPrefix Text
lbl (Text -> Text
dropSep Text
s)) [(Text, Int)]
preLabels) of
Maybe (Int, Text)
Nothing -> (Maybe (Int, Integer)
forall a. Maybe a
Nothing, Text
s)
Just (Int
rk, Text
afterLabel) ->
let (Text
digits, Text
rest) = (Char -> Bool) -> Text -> (Text, Text)
T.span Char -> Bool
isDigit (Text -> Text
dropSep Text
afterLabel)
in ((Int, Integer) -> Maybe (Int, Integer)
forall a. a -> Maybe a
Just (Int
rk, Text -> Integer
numOr0 Text
digits), Text
rest)
where
preLabels :: [(Text, Int)]
preLabels =
[ (Text
"alpha", Int
0)
, (Text
"beta", Int
1)
, (Text
"preview", Int
2)
, (Text
"pre", Int
2)
, (Text
"rc", Int
2)
, (Text
"a", Int
0)
, (Text
"b", Int
1)
, (Text
"c", Int
2)
]
consumePost :: Text -> (Maybe Integer, Text)
consumePost :: Text -> (Maybe Integer, Text)
consumePost Text
s =
case [Maybe Text] -> Maybe Text
forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum ((Text -> Maybe Text) -> [Text] -> [Maybe Text]
forall a b. (a -> b) -> [a] -> [b]
map (\Text
lbl -> Text -> Text -> Maybe Text
T.stripPrefix Text
lbl (Text -> Text
dropSep Text
s)) [Text
"post", Text
"rev", Text
"r"]) of
Just Text
afterLabel ->
let (Text
digits, Text
rest) = (Char -> Bool) -> Text -> (Text, Text)
T.span Char -> Bool
isDigit (Text -> Text
dropSep Text
afterLabel)
in (Integer -> Maybe Integer
forall a. a -> Maybe a
Just (Text -> Integer
numOr0 Text
digits), Text
rest)
Maybe Text
Nothing -> case Text -> Text -> Maybe Text
T.stripPrefix Text
"-" Text
s of
Just Text
afterDash ->
let (Text
digits, Text
rest) = (Char -> Bool) -> Text -> (Text, Text)
T.span Char -> Bool
isDigit Text
afterDash
in if Text -> Bool
T.null Text
digits then (Maybe Integer
forall a. Maybe a
Nothing, Text
s) else (Integer -> Maybe Integer
forall a. a -> Maybe a
Just (Text -> Integer
numOr0 Text
digits), Text
rest)
Maybe Text
Nothing -> (Maybe Integer
forall a. Maybe a
Nothing, Text
s)
consumeDev :: Text -> (Maybe Integer, Text)
consumeDev :: Text -> (Maybe Integer, Text)
consumeDev Text
s =
case Text -> Text -> Maybe Text
T.stripPrefix Text
"dev" (Text -> Text
dropSep Text
s) of
Just Text
afterLabel ->
let (Text
digits, Text
rest) = (Char -> Bool) -> Text -> (Text, Text)
T.span Char -> Bool
isDigit (Text -> Text
dropSep Text
afterLabel)
in (Integer -> Maybe Integer
forall a. a -> Maybe a
Just (Text -> Integer
numOr0 Text
digits), Text
rest)
Maybe Text
Nothing -> (Maybe Integer
forall a. Maybe a
Nothing, Text
s)
renderPep440 :: Pep440Key -> Text
renderPep440 :: Pep440Key -> Text
renderPep440 Pep440Key
k =
Text
epoch Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
release Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
pre Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
post Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
dev Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
localSegment
where
epoch :: Text
epoch = case Pep440Key -> Integer
p440Epoch Pep440Key
k of
Integer
0 -> Text
""
Integer
n -> Integer -> Text
forall b a. (Show a, IsString b) => a -> b
show Integer
n Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"!"
release :: Text
release = case Pep440Key -> [Integer]
p440Release Pep440Key
k of
[] -> Text
"0"
[Integer]
segs -> Text -> [Text] -> Text
T.intercalate Text
"." ((Integer -> Text) -> [Integer] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> Text
forall b a. (Show a, IsString b) => a -> b
show [Integer]
segs)
pre :: Text
pre = case Pep440Key -> (Int, Int, Integer)
p440Pre Pep440Key
k of
(Int
1, Int
stage, Integer
n) -> Int -> Text
stageLabel Int
stage Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Integer -> Text
forall b a. (Show a, IsString b) => a -> b
show Integer
n
(Int, Int, Integer)
_ -> Text
""
post :: Text
post = case Pep440Key -> (Int, Integer)
p440Post Pep440Key
k of
(Int
1, Integer
n) -> Text
".post" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Integer -> Text
forall b a. (Show a, IsString b) => a -> b
show Integer
n
(Int, Integer)
_ -> Text
""
dev :: Text
dev = case Pep440Key -> (Int, Integer)
p440Dev Pep440Key
k of
(Int
0, Integer
n) -> Text
".dev" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Integer -> Text
forall b a. (Show a, IsString b) => a -> b
show Integer
n
(Int, Integer)
_ -> Text
""
localSegment :: Text
localSegment = case Pep440Key -> [VToken]
p440Local Pep440Key
k of
[] -> Text
""
[VToken]
toks -> Text
"+" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
"." ((VToken -> Text) -> [VToken] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map VToken -> Text
renderToken [VToken]
toks)
stageLabel :: Int -> Text
stageLabel :: Int -> Text
stageLabel = \case
Int
0 -> Text
"a"
Int
1 -> Text
"b"
Int
_ -> Text
"rc"
renderToken :: VToken -> Text
renderToken :: VToken -> Text
renderToken = \case
VNum Integer
n -> Integer -> Text
forall b a. (Show a, IsString b) => a -> b
show Integer
n
VStr Text
s -> Text
s
isPep440Stable :: Pep440Key -> Bool
isPep440Stable :: Pep440Key -> Bool
isPep440Stable Pep440Key
k = Int
preBand Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
1 Bool -> Bool -> Bool
&& Int
devBand Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
0
where
(Int
preBand, Int
_, Integer
_) = Pep440Key -> (Int, Int, Integer)
p440Pre Pep440Key
k
(Int
devBand, Integer
_) = Pep440Key -> (Int, Integer)
p440Dev Pep440Key
k