module Distribution.Version (
Version(..),
showVersion,
readVersion,
parseVersion,
VersionRange(..),
orLaterVersion, orEarlierVersion,
betweenVersionsInclusive,
withinRange,
showVersionRange,
parseVersionRange,
isAnyVersion,
Dependency(..),
#ifdef DEBUG
hunitTests
#endif
) where
#if __HUGS__ || __GLASGOW_HASKELL__ >= 603
import Data.Version ( Version(..), showVersion, parseVersion )
#endif
import Control.Monad ( liftM )
import Data.Char ( isSpace )
import Data.Maybe ( listToMaybe )
import Distribution.Compat.ReadP
#ifdef DEBUG
import Test.HUnit
#endif
#if ( __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 603 ) || __NHC__
#if __GLASGOW_HASKELL__ <= 602 || __NHC__
import Distribution.Compat.ReadP
#else
import Text.ParserCombinators.ReadP
#endif
#if __GLASGOW_HASKELL__ < 602
import Data.Dynamic ( Typeable(..), TyCon, mkTyCon, mkAppTy )
#else
import Data.Typeable ( Typeable )
#endif
import Data.List ( intersperse, sort )
import Data.Char ( isDigit, isAlphaNum )
data Version =
Version { versionBranch :: [Int],
versionTags :: [String]
}
deriving (Read,Show
#if __GLASGOW_HASKELL__ >= 602
,Typeable
#endif
)
#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 602
versionTc :: TyCon
versionTc = mkTyCon "Version"
instance Typeable Version where
typeOf _ = mkAppTy versionTc []
#endif
instance Eq Version where
v1 == v2 = versionBranch v1 == versionBranch v2
&& sort (versionTags v1) == sort (versionTags v2)
instance Ord Version where
v1 `compare` v2 = versionBranch v1 `compare` versionBranch v2
showVersion :: Version -> String
showVersion (Version branch tags)
= concat (intersperse "." (map show branch)) ++
concatMap ('-':) tags
#if __GLASGOW_HASKELL__ <= 602
parseVersion :: ReadP r Version
#else
parseVersion :: ReadP Version
#endif
parseVersion = do branch <- sepBy1 (liftM read $ munch1 isDigit) (char '.')
tags <- many (char '-' >> munch1 isAlphaNum)
return Version{versionBranch=branch, versionTags=tags}
#endif
readVersion :: String -> Maybe Version
readVersion str =
listToMaybe [ r | (r,s) <- readP_to_S parseVersion str, all isSpace s ]
data VersionRange
= AnyVersion
| ThisVersion Version
| LaterVersion Version
| EarlierVersion Version
| UnionVersionRanges VersionRange VersionRange
| IntersectVersionRanges VersionRange VersionRange
deriving (Show,Read,Eq)
isAnyVersion :: VersionRange -> Bool
isAnyVersion AnyVersion = True
isAnyVersion _ = False
orLaterVersion :: Version -> VersionRange
orLaterVersion v = UnionVersionRanges (ThisVersion v) (LaterVersion v)
orEarlierVersion :: Version -> VersionRange
orEarlierVersion v = UnionVersionRanges (ThisVersion v) (EarlierVersion v)
betweenVersionsInclusive :: Version -> Version -> VersionRange
betweenVersionsInclusive v1 v2 =
IntersectVersionRanges (orLaterVersion v1) (orEarlierVersion v2)
laterVersion :: Version -> Version -> Bool
v1 `laterVersion` v2 = versionBranch v1 > versionBranch v2
earlierVersion :: Version -> Version -> Bool
v1 `earlierVersion` v2 = versionBranch v1 < versionBranch v2
withinRange :: Version -> VersionRange -> Bool
withinRange _ AnyVersion = True
withinRange v1 (ThisVersion v2) = v1 == v2
withinRange v1 (LaterVersion v2) = v1 `laterVersion` v2
withinRange v1 (EarlierVersion v2) = v1 `earlierVersion` v2
withinRange v1 (UnionVersionRanges v2 v3)
= v1 `withinRange` v2 || v1 `withinRange` v3
withinRange v1 (IntersectVersionRanges v2 v3)
= v1 `withinRange` v2 && v1 `withinRange` v3
showVersionRange :: VersionRange -> String
showVersionRange AnyVersion = "-any"
showVersionRange (ThisVersion v) = '=' : '=' : showVersion v
showVersionRange (LaterVersion v) = '>' : showVersion v
showVersionRange (EarlierVersion v) = '<' : showVersion v
showVersionRange (UnionVersionRanges (ThisVersion v1) (LaterVersion v2))
| v1 == v2 = '>' : '=' : showVersion v1
showVersionRange (UnionVersionRanges (LaterVersion v2) (ThisVersion v1))
| v1 == v2 = '>' : '=' : showVersion v1
showVersionRange (UnionVersionRanges (ThisVersion v1) (EarlierVersion v2))
| v1 == v2 = '<' : '=' : showVersion v1
showVersionRange (UnionVersionRanges (EarlierVersion v2) (ThisVersion v1))
| v1 == v2 = '<' : '=' : showVersion v1
showVersionRange (UnionVersionRanges r1 r2)
= showVersionRange r1 ++ "||" ++ showVersionRange r2
showVersionRange (IntersectVersionRanges r1 r2)
= showVersionRange r1 ++ "&&" ++ showVersionRange r2
data Dependency = Dependency String VersionRange
deriving (Read, Show, Eq)
parseVersionRange :: ReadP r VersionRange
parseVersionRange = do
f1 <- factor
skipSpaces
(do
string "||"
skipSpaces
f2 <- factor
return (UnionVersionRanges f1 f2)
+++
do
string "&&"
skipSpaces
f2 <- factor
return (IntersectVersionRanges f1 f2)
+++
return f1)
where
factor = choice ((string "-any" >> return AnyVersion) :
map parseRangeOp rangeOps)
parseRangeOp (s,f) = string s >> skipSpaces >> liftM f parseVersion
rangeOps = [ ("<", EarlierVersion),
("<=", orEarlierVersion),
(">", LaterVersion),
(">=", orLaterVersion),
("==", ThisVersion) ]
#ifdef DEBUG
doVersionParse :: String -> Either String Version
doVersionParse input = case results of
[y] -> Right y
[] -> Left "No parse"
_ -> Left "Ambigous parse"
where results = [ x | (x,"") <- readP_to_S parseVersion input ]
branch1 :: [Int]
branch1 = [1]
branch2 :: [Int]
branch2 = [1,2]
branch3 :: [Int]
branch3 = [1,2,3]
release1 :: Version
release1 = Version{versionBranch=branch1, versionTags=[]}
release2 :: Version
release2 = Version{versionBranch=branch2, versionTags=[]}
release3 :: Version
release3 = Version{versionBranch=branch3, versionTags=[]}
hunitTests :: [Test]
hunitTests
= [
"released version 1" ~: "failed"
~: (Right $ release1) ~=? doVersionParse "1",
"released version 3" ~: "failed"
~: (Right $ release3) ~=? doVersionParse "1.2.3",
"range comparison LaterVersion 1" ~: "failed"
~: True
~=? release3 `withinRange` (LaterVersion release2),
"range comparison LaterVersion 2" ~: "failed"
~: False
~=? release2 `withinRange` (LaterVersion release3),
"range comparison EarlierVersion 1" ~: "failed"
~: True
~=? release3 `withinRange` (LaterVersion release2),
"range comparison EarlierVersion 2" ~: "failed"
~: False
~=? release2 `withinRange` (LaterVersion release3),
"range comparison orLaterVersion 1" ~: "failed"
~: True
~=? release3 `withinRange` (orLaterVersion release3),
"range comparison orLaterVersion 2" ~: "failed"
~: True
~=? release3 `withinRange` (orLaterVersion release2),
"range comparison orLaterVersion 3" ~: "failed"
~: False
~=? release2 `withinRange` (orLaterVersion release3),
"range comparison orEarlierVersion 1" ~: "failed"
~: True
~=? release2 `withinRange` (orEarlierVersion release2),
"range comparison orEarlierVersion 2" ~: "failed"
~: True
~=? release2 `withinRange` (orEarlierVersion release3),
"range comparison orEarlierVersion 3" ~: "failed"
~: False
~=? release3 `withinRange` (orEarlierVersion release2)
]
#endif