{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Distribution.Types.VersionRange.Internal
( VersionRange(..)
, anyVersion, noVersion
, thisVersion, notThisVersion
, laterVersion, earlierVersion
, orLaterVersion, orEarlierVersion
, unionVersionRanges, intersectVersionRanges
, withinVersion
, majorBoundVersion
, VersionRangeF(..)
, projectVersionRange
, embedVersionRange
, cataVersionRange
, anaVersionRange
, hyloVersionRange
, versionRangeParser
, majorUpperBound
) where
import Distribution.Compat.Prelude
import Distribution.Types.Version
import Prelude ()
import Distribution.CabalSpecVersion
import Distribution.Parsec
import Distribution.Pretty
import Text.PrettyPrint ((<+>))
import qualified Distribution.Compat.CharParsing as P
import qualified Distribution.Compat.DList as DList
import qualified Text.PrettyPrint as Disp
data VersionRange
= AnyVersion
| ThisVersion Version
| LaterVersion Version
| OrLaterVersion Version
| EarlierVersion Version
| OrEarlierVersion Version
| WildcardVersion Version
| MajorBoundVersion Version
| UnionVersionRanges VersionRange VersionRange
| IntersectVersionRanges VersionRange VersionRange
| VersionRangeParens VersionRange
deriving ( Data, Eq, Generic, Read, Show, Typeable )
instance Binary VersionRange
instance NFData VersionRange where rnf = genericRnf
anyVersion :: VersionRange
anyVersion = AnyVersion
noVersion :: VersionRange
noVersion = IntersectVersionRanges (LaterVersion v) (EarlierVersion v)
where v = mkVersion [1]
thisVersion :: Version -> VersionRange
thisVersion = ThisVersion
notThisVersion :: Version -> VersionRange
notThisVersion v = UnionVersionRanges (EarlierVersion v) (LaterVersion v)
laterVersion :: Version -> VersionRange
laterVersion = LaterVersion
orLaterVersion :: Version -> VersionRange
orLaterVersion = OrLaterVersion
earlierVersion :: Version -> VersionRange
earlierVersion = EarlierVersion
orEarlierVersion :: Version -> VersionRange
orEarlierVersion = OrEarlierVersion
unionVersionRanges :: VersionRange -> VersionRange -> VersionRange
unionVersionRanges = UnionVersionRanges
intersectVersionRanges :: VersionRange -> VersionRange -> VersionRange
intersectVersionRanges = IntersectVersionRanges
withinVersion :: Version -> VersionRange
withinVersion = WildcardVersion
majorBoundVersion :: Version -> VersionRange
majorBoundVersion = MajorBoundVersion
data VersionRangeF a
= AnyVersionF
| ThisVersionF Version
| LaterVersionF Version
| OrLaterVersionF Version
| EarlierVersionF Version
| OrEarlierVersionF Version
| WildcardVersionF Version
| MajorBoundVersionF Version
| UnionVersionRangesF a a
| IntersectVersionRangesF a a
| VersionRangeParensF a
deriving ( Data, Eq, Generic, Read, Show, Typeable
, Functor, Foldable, Traversable )
projectVersionRange :: VersionRange -> VersionRangeF VersionRange
projectVersionRange AnyVersion = AnyVersionF
projectVersionRange (ThisVersion v) = ThisVersionF v
projectVersionRange (LaterVersion v) = LaterVersionF v
projectVersionRange (OrLaterVersion v) = OrLaterVersionF v
projectVersionRange (EarlierVersion v) = EarlierVersionF v
projectVersionRange (OrEarlierVersion v) = OrEarlierVersionF v
projectVersionRange (WildcardVersion v) = WildcardVersionF v
projectVersionRange (MajorBoundVersion v) = MajorBoundVersionF v
projectVersionRange (UnionVersionRanges a b) = UnionVersionRangesF a b
projectVersionRange (IntersectVersionRanges a b) = IntersectVersionRangesF a b
projectVersionRange (VersionRangeParens a) = VersionRangeParensF a
cataVersionRange :: (VersionRangeF a -> a) -> VersionRange -> a
cataVersionRange f = c where c = f . fmap c . projectVersionRange
embedVersionRange :: VersionRangeF VersionRange -> VersionRange
embedVersionRange AnyVersionF = AnyVersion
embedVersionRange (ThisVersionF v) = ThisVersion v
embedVersionRange (LaterVersionF v) = LaterVersion v
embedVersionRange (OrLaterVersionF v) = OrLaterVersion v
embedVersionRange (EarlierVersionF v) = EarlierVersion v
embedVersionRange (OrEarlierVersionF v) = OrEarlierVersion v
embedVersionRange (WildcardVersionF v) = WildcardVersion v
embedVersionRange (MajorBoundVersionF v) = MajorBoundVersion v
embedVersionRange (UnionVersionRangesF a b) = UnionVersionRanges a b
embedVersionRange (IntersectVersionRangesF a b) = IntersectVersionRanges a b
embedVersionRange (VersionRangeParensF a) = VersionRangeParens a
anaVersionRange :: (a -> VersionRangeF a) -> a -> VersionRange
anaVersionRange g = a where a = embedVersionRange . fmap a . g
hyloVersionRange :: (VersionRangeF VersionRange -> VersionRange)
-> (VersionRange -> VersionRangeF VersionRange)
-> VersionRange -> VersionRange
hyloVersionRange f g = h where h = f . fmap h . g
instance Pretty VersionRange where
pretty = fst . cataVersionRange alg
where
alg AnyVersionF = (Disp.text "-any", 0 :: Int)
alg (ThisVersionF v) = (Disp.text "==" <<>> pretty v, 0)
alg (LaterVersionF v) = (Disp.char '>' <<>> pretty v, 0)
alg (OrLaterVersionF v) = (Disp.text ">=" <<>> pretty v, 0)
alg (EarlierVersionF v) = (Disp.char '<' <<>> pretty v, 0)
alg (OrEarlierVersionF v) = (Disp.text "<=" <<>> pretty v, 0)
alg (WildcardVersionF v) = (Disp.text "==" <<>> dispWild v, 0)
alg (MajorBoundVersionF v) = (Disp.text "^>=" <<>> pretty v, 0)
alg (UnionVersionRangesF (r1, p1) (r2, p2)) =
(punct 1 p1 r1 <+> Disp.text "||" <+> punct 2 p2 r2 , 2)
alg (IntersectVersionRangesF (r1, p1) (r2, p2)) =
(punct 0 p1 r1 <+> Disp.text "&&" <+> punct 1 p2 r2 , 1)
alg (VersionRangeParensF (r, _)) =
(Disp.parens r, 0)
dispWild ver =
Disp.hcat (Disp.punctuate (Disp.char '.')
(map Disp.int $ versionNumbers ver))
<<>> Disp.text ".*"
punct p p' | p < p' = Disp.parens
| otherwise = id
instance Parsec VersionRange where
parsec = versionRangeParser versionDigitParser
versionRangeParser :: forall m. CabalParsing m => m Int -> m VersionRange
versionRangeParser digitParser = expr
where
expr = do P.spaces
t <- term
P.spaces
(do _ <- P.string "||"
P.spaces
e <- expr
return (unionVersionRanges t e)
<|>
return t)
term = do f <- factor
P.spaces
(do _ <- P.string "&&"
P.spaces
t <- term
return (intersectVersionRanges f t)
<|>
return f)
factor = parens expr <|> prim
prim = do
op <- P.munch1 (`elem` "<>=^-") P.<?> "operator"
case op of
"-" -> anyVersion <$ P.string "any" <|> P.string "none" *> noVersion'
"==" -> do
P.spaces
(do (wild, v) <- verOrWild
pure $ (if wild then withinVersion else thisVersion) v
<|>
(verSet' thisVersion =<< verSet))
"^>=" -> do
P.spaces
(do (wild, v) <- verOrWild
when wild $ P.unexpected $
"wild-card version after ^>= operator"
majorBoundVersion' v
<|>
(verSet' majorBoundVersion =<< verSet))
_ -> do
P.spaces
(wild, v) <- verOrWild
when wild $ P.unexpected $
"wild-card version after non-== operator: " ++ show op
case op of
">=" -> pure $ orLaterVersion v
"<" -> pure $ earlierVersion v
"<=" -> pure $ orEarlierVersion v
">" -> pure $ laterVersion v
_ -> fail $ "Unknown version operator " ++ show op
noVersion' = do
csv <- askCabalSpecVersion
if csv >= CabalSpecV1_22
then pure noVersion
else fail $ unwords
[ "-none version range used."
, "To use this syntax the package needs to specify at least 'cabal-version: 1.22'."
, "Alternatively, if broader compatibility is important then use"
, "<0 or other empty range."
]
majorBoundVersion' v = do
csv <- askCabalSpecVersion
if csv >= CabalSpecV2_0
then pure $ majorBoundVersion v
else fail $ unwords
[ "major bounded version syntax (caret, ^>=) used."
, "To use this syntax the package need to specify at least 'cabal-version: 2.0'."
, "Alternatively, if broader compatibility is important then use:"
, prettyShow $ eliminateMajorBoundSyntax $ majorBoundVersion v
]
where
eliminateMajorBoundSyntax = hyloVersionRange embed projectVersionRange
embed (MajorBoundVersionF u) = intersectVersionRanges
(orLaterVersion u) (earlierVersion (majorUpperBound u))
embed vr = embedVersionRange vr
verSet' op vs = do
csv <- askCabalSpecVersion
if csv >= CabalSpecV3_0
then pure $ foldr1 unionVersionRanges (fmap op vs)
else fail $ unwords
[ "version set syntax used."
, "To use this syntax the package needs to specify at least 'cabal-version: 3.0'."
, "Alternatively, if broader compatibility is important then use"
, "a series of single version constraints joined with the || operator:"
, prettyShow (foldr1 unionVersionRanges (fmap op vs))
]
verSet :: CabalParsing m => m (NonEmpty Version)
verSet = do
_ <- P.char '{'
P.spaces
vs <- P.sepByNonEmpty (verPlain <* P.spaces) (P.char ',' *> P.spaces)
_ <- P.char '}'
pure vs
verPlain :: CabalParsing m => m Version
verPlain = mkVersion <$> P.sepBy1 digitParser (P.char '.')
verOrWild :: CabalParsing m => m (Bool, Version)
verOrWild = do
x <- digitParser
verLoop (DList.singleton x)
verLoop :: CabalParsing m => DList.DList Int -> m (Bool, Version)
verLoop acc = verLoop' acc
<|> (tags *> pure (False, mkVersion (DList.toList acc)))
verLoop' :: CabalParsing m => DList.DList Int -> m (Bool, Version)
verLoop' acc = do
_ <- P.char '.'
let digit = digitParser >>= verLoop . DList.snoc acc
let wild = (True, mkVersion (DList.toList acc)) <$ P.char '*'
digit <|> wild
parens p = P.between
((P.char '(' P.<?> "opening paren") >> P.spaces)
(P.char ')' >> P.spaces)
(do a <- p
P.spaces
return (VersionRangeParens a))
tags :: CabalParsing m => m ()
tags = do
ts <- many $ P.char '-' *> some (P.satisfy isAlphaNum)
case ts of
[] -> pure ()
(_ : _) -> parsecWarning PWTVersionTag "version with tags"
majorUpperBound :: Version -> Version
majorUpperBound = alterVersion $ \numbers -> case numbers of
[] -> [0,1]
[m1] -> [m1,1]
(m1:m2:_) -> [m1,m2+1]