semver-range 0.2.2 → 0.2.7
raw patch · 4 files changed
+313/−177 lines, 4 files
Files
- semver-range.cabal +1/−1
- src/Data/SemVer/Parser.hs +43/−34
- src/Data/SemVer/Types.hs +92/−25
- tests/Unit.hs +177/−117
semver-range.cabal view
@@ -1,5 +1,5 @@ name: semver-range-version: 0.2.2+version: 0.2.7 synopsis: An implementation of semver and semantic version ranges. license: MIT license-file: LICENSE
src/Data/SemVer/Parser.hs view
@@ -3,10 +3,10 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLists #-} -module Data.SemVer.Parser where -- (- -- parseSemVer, parseSemVerRange, pSemVerRange, pSemVer, p- -- fromHaskellVersion, matchText- -- ) where+module Data.SemVer.Parser (+ parseSemVer, parseSemVerRange, pSemVerRange, pSemVer,+ fromHaskellVersion, matchText, splitWS+ ) where import qualified Prelude as P import ClassyPrelude hiding (try, many)@@ -20,22 +20,26 @@ type Parser = ParsecT String () Identity +-- | Split a text on whitespace. Why isn't this in the stdlib.+splitWS :: Text -> [Text]+splitWS = filter (/= "") . T.split (flip elem (" \t\n\r" :: String))+ ------------------------------------------------------------------------------- -- Wildcards: intermediate representations of semvers -- -- | A partially specified semantic version. Implicitly defines -- a range of acceptable versions, as seen in @wildcardToRange@. data Wildcard = Any- | One Int- | Two Int Int+ | Maj Int+ | Min Int Int | Full SemVer deriving (Show, Eq) -- | Fills in zeros in a wildcard. wildcardToSemver :: Wildcard -> SemVer wildcardToSemver Any = semver 0 0 0-wildcardToSemver (One n) = semver n 0 0-wildcardToSemver (Two n m) = semver n m 0+wildcardToSemver (Maj n) = semver n 0 0+wildcardToSemver (Min n m) = semver n m 0 wildcardToSemver (Full sv) = sv -- | Translates a wildcard (partially specified version) to a range.@@ -44,8 +48,8 @@ wildcardToRange :: Wildcard -> SemVerRange wildcardToRange = \case Any -> Geq $ semver 0 0 0- One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)- Two n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)+ Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)+ Min n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0) Full sv -> Eq sv -- | Translates a ~wildcard to a range.@@ -55,9 +59,9 @@ -- I'm not sure this is officially supported, but just in case... Any -> tildeToRange (Full $ semver 0 0 0) -- ~1 := >=1.0.0 <(1+1).0.0 := >=1.0.0 <2.0.0 (Same as 1.x)- One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)+ Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0) -- ~1.2 := >=1.2.0 <1.(2+1).0 := >=1.2.0 <1.3.0 (Same as 1.2.x)- Two n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)+ Min n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0) -- ~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0 Full (SemVer n m o [] _) -> Geq (semver n m o) `And` Lt (semver n (m+1) 0) -- ~1.2.3-beta.2 := >=1.2.3-beta.2 <1.3.0@@ -67,8 +71,8 @@ -- Ex: ^1.2.x := >=1.2.0 <2.0.0 caratToRange :: Wildcard -> SemVerRange caratToRange = \case- One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)- Two n m -> Geq (semver n m 0) `And` Lt (semver (n+1) 0 0)+ Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)+ Min n m -> Geq (semver n m 0) `And` Lt (semver (n+1) 0 0) Full (SemVer 0 n m tags _) -> Geq (semver' 0 n m tags) `And` Lt (semver' 0 (n+1) 0 tags) Full (SemVer n m o tags _) -> Geq (semver' n m o tags) `And` Lt (semver' (n+1) 0 0 tags) @@ -79,12 +83,12 @@ hyphenatedRange :: Wildcard -> Wildcard -> SemVerRange hyphenatedRange wc1 wc2 = And sv1 sv2 where sv1 = case wc1 of Any -> anyVersion- One n -> Geq (semver n 0 0)- Two n m -> Geq (semver n m 0)+ Maj n -> Geq (semver n 0 0)+ Min n m -> Geq (semver n m 0) Full sv -> Geq sv sv2 = case wc2 of Any -> anyVersion- One n -> Lt (semver (n+1) 0 0)- Two n m -> Lt (semver n (m+1) 0)+ Maj n -> Lt (semver (n+1) 0 0)+ Min n m -> Lt (semver n (m+1) 0) Full sv -> Lt sv -- | Given a parser and a string, attempts to parse the string.@@ -96,11 +100,11 @@ -- | Consumes any spaces (not other whitespace). spaces :: Parser String-spaces = many $ oneOf [' ', '\t']+spaces = many $ oneOf [' ', '\t', '\n', '\r'] -- | Consumes at least one space (not other whitespace). spaces1 :: Parser String-spaces1 = many1 $ oneOf [' ', '\t']+spaces1 = many1 $ oneOf [' ', '\t', '\n', '\r'] -- | Parses the given string and any trailing spaces. sstring :: String -> Parser String@@ -128,15 +132,17 @@ -- Handle a few special cases "" -> return anyVersion "||" -> return anyVersion- t -> parse pSemVerRange t+ t -> parse (pSemVerRange <* eof) t -- | Parse a string as an explicit version, or return an error. parseSemVer :: Text -> Either ParseError SemVer-parseSemVer = parse pSemVer+parseSemVer = parse pSemVer . T.strip -- | Parses a semantic version. pSemVer :: Parser SemVer-pSemVer = wildcardToSemver <$> pWildCard+pSemVer = do+ optional (char '=')+ wildcardToSemver <$> pWildCard pVersionComp :: Parser SemVerRange pVersionComp = cmp >>= \case@@ -154,8 +160,8 @@ where topOf = \case Any -> semver 0 0 0- One n -> semver (n+1) 0 0- Two n m -> semver n (m+1) 0+ Maj n -> semver (n+1) 0 0+ Min n m -> semver n (m+1) 0 Full sv -> sv -- | Parses a comparison operator.@@ -176,8 +182,10 @@ pJoinedSemVerRange = do first <- pSemVerRangeSingle option first $ do- lookAhead (sstring "||" <|> cmp) >>= \case+ let next = choice [sstring "||", sstring "&&", map singleton anyChar]+ lookAhead next >>= \case "||" -> Or first <$> (sstring "||" *> pJoinedSemVerRange)+ "&&" -> And first <$> (sstring "&&" *> pJoinedSemVerRange) _ -> And first <$> pJoinedSemVerRange -- | Parses a hyphenated range.@@ -197,17 +205,18 @@ optional (char 'v') res <- takeWhile isJust <$> sepBy1 bound (sstring ".") >>= \case [] -> return Any- [Just n] -> return $ One n- [Just n, Just m] -> return $ Two n m+ [Just n] -> return $ Maj n+ [Just n, Just m] -> return $ Min n m [Just n, Just m, Just o] -> option (Full $ semver n m o) $ do- -- Release tags might be separated by a hyphen, or not.- optional (char '-')- tags <- PrereleaseTags <$> (tag `sepBy1` char '.')+ tags <- option [] $ do+ -- Release tags might be separated by a hyphen, or not.+ optional (char '-')+ PrereleaseTags <$> (tag `sepBy1` char '.') -- Grab metadata if there is any- option (Full $ semver'' n m o tags []) $ do+ metadata <- option [] $ do char '+'- metadata <- many1 (letter <|> digit <|> char '-') `sepBy1` char '.'- return $ Full $ semver'' n m o tags (map pack metadata)+ many1 (letter <|> digit <|> char '-') `sepBy1` char '.'+ return $ Full $ semver'' n m o tags (map pack metadata) w -> unexpected ("Invalid version " ++ show w) spaces *> return res
src/Data/SemVer/Types.hs view
@@ -150,6 +150,17 @@ And svr1 svr2 -> versionsOf svr1 <> versionsOf svr2 Or svr1 svr2 -> versionsOf svr1 <> versionsOf svr2 +-- | Strip out all prerelease tags from a given 'SemVerRange'.+stripRangeTags :: SemVerRange -> SemVerRange+stripRangeTags = \case+ Eq sv -> Eq (sv { svTags = [] })+ Geq sv -> Geq (sv { svTags = [] })+ Leq sv -> Leq (sv { svTags = [] })+ Lt sv -> Lt (sv { svTags = [] })+ Gt sv -> Gt (sv { svTags = [] })+ And svr1 svr2 -> And (stripRangeTags svr1) (stripRangeTags svr2)+ Or svr1 svr2 -> Or (stripRangeTags svr1) (stripRangeTags svr2)+ -- | Create a SemVer with no version tags. semver :: Int -> Int -> Int -> SemVer semver major minor patch = semver' major minor patch []@@ -195,22 +206,77 @@ -- | Returns whether a given semantic version matches a range. -- Note that there are special cases when there are prerelease tags. For -- details see https://github.com/npm/node-semver#prerelease-tags.--- matches :: SemVerRange -> SemVer -> Bool--- matches range version = case (sharedTags range, svTags version) of--- -- This is the simple case, where neither the range nor the version has given--- -- prerelease tags. Then we can just do regular predicate calculus.--- (Nothing, PrereleaseTags []) -> matchesSimple range version--- _ -> undefined--- -- (Just rTags, PrereleaseTags vTags)--- -- | rTags == vTags -> matchesSimple range version--- -- | tuplesOf range /= [toTuple version] -> False--- -- | otherwise -> matchesTags range rTags vTags--- -- (_, _) -> False+matches :: SemVerRange -> SemVer -> Bool+matches range version =+ case (sharedTags range, svTags version) of + (Nothing, PrereleaseTags vTags)+ -- Neither the range nor the version have prerelease tags+ | null vTags -> matchesSimple range version++ -- If there is no prerelease tag in the range but there is in+ -- the version reject it+ | otherwise -> False++ -- A range with a prerelease tag can match a version without a+ -- prerelease tag provided it *does* meet the semantic version+ -- tuple's constraint criteria+ (Just _, PrereleaseTags []) ->+ matchesSimple range version++ -- The most important invariant when considering a comparison+ -- between a range with prerelease tags and a version with+ -- prerelease tags is whether the semantic version in both is the+ -- same; if it is not, then we must reject the version.+ --+ -- Note that we could have a conjunction or a disjunction, so we+ -- want to see if our version tuple is in the list of tuples for+ -- the range. However, it would be possible to then match with,+ -- say, the upper-bound version tuple which may be constrained by+ -- a less-than relation. Therefore, if there is an equivalent+ -- range tuple to the version tuple, we want to check if it+ -- satisfies the constraints with the goal of rejecting early.+ --+ -- For example, if we assume a range constraint of "^1.2.3-alpha"+ -- this translates to ">=1.2.3-alpha <2.0.0-alpha". Also assume we+ -- have the version "1.2.3-alpha". In the trivial case, we check+ -- to see if the version's tuple ("1.2.3") is in the set of+ -- version tuples for the range ([ (1.2.3), (2.0.0) ]). We can+ -- clearly see that it is, therefore we proceed with a match check+ -- on the tags.+ --+ -- However, consider matching "2.0.0-alpha" against the range+ -- constraint we've already given. If we only check for membership+ -- of our version tuple ("2.0.0") in the set of range tuples ([+ -- (1.2.3), (2.0.0) ]) then we would get a match, this is not+ -- correct. Thus, if the version tuple is a member of the set of+ -- range tuples we must also check that it satisfies the range+ -- constraints sans prerelease tags.+ (Just rTags, vTags)++ -- Explicit rejection, e.g. "^1.2.3-alpha" must reject+ -- "1.2.4-alpha" and "2.0.0-alpha", anything else is safe to+ -- compare based on tags so we can let it "fall through".+ | versionTuple `notElem` rangeTuple || not (matchesSimple rangeNoTags versionNoTags)+ -> False++ | rTags == vTags+ -> True++ | rTags /= vTags+ -> matchesTags range rTags vTags++ where+ rangeTuple = tuplesOf range+ versionTuple = toTuple version++ rangeNoTags = stripRangeTags range+ versionNoTags = version { svTags = [] }+ -- | Simple predicate calculus matching, doing AND and OR combination with -- numerical comparison.-matches :: SemVerRange -> SemVer -> Bool-matches range ver = case range of+matchesSimple :: SemVerRange -> SemVer -> Bool+matchesSimple range ver = case range of Eq sv -> ver == sv Gt sv -> ver > sv Lt sv -> ver < sv@@ -223,18 +289,19 @@ -- | Given a range and two sets of tags, the first being a bound on the second, -- uses the range to compare the tags and see if they match.-matchesTags :: SemVerRange -> [PrereleaseTag] -> [PrereleaseTag] -> Bool-matchesTags range rangeTags verTags = case range of- Eq _ -> verTags == rangeTags- Gt _ -> verTags > rangeTags- Lt _ -> verTags < rangeTags- Geq _ -> verTags >= rangeTags- Leq _ -> verTags <= rangeTags- -- Note that as we're currently doing things, these cases won't get hit.- And svr1 svr2 -> matchesTags svr1 verTags rangeTags &&- matchesTags svr2 verTags rangeTags- Or svr1 svr2 -> matchesTags svr1 verTags rangeTags ||- matchesTags svr2 verTags rangeTags+matchesTags :: SemVerRange -> PrereleaseTags -> PrereleaseTags -> Bool+matchesTags range rangeTags verTags =+ case range of+ Eq _ -> verTags == rangeTags+ Gt _ -> verTags > rangeTags+ Lt _ -> verTags < rangeTags+ Geq _ -> verTags >= rangeTags+ Leq _ -> verTags <= rangeTags++ And svr1 svr2 ->+ matchesTags svr1 rangeTags verTags+ Or svr1 svr2 ->+ matchesTags svr1 rangeTags verTags || matchesTags svr2 rangeTags verTags -- | Gets the highest-matching semver in a range. bestMatch :: SemVerRange -> [SemVer] -> Either String SemVer
tests/Unit.hs view
@@ -12,23 +12,13 @@ module Main (main) where import ClassyPrelude-import Data.Either+import Data.Either (isRight, isLeft) import Test.Hspec import Test.QuickCheck (property, Arbitrary(..), oneof) import qualified Data.Text as T import Data.SemVer --- | This instance seems to be missing :(-instance (Arbitrary a, Arbitrary b, Arbitrary c,- Arbitrary d, Arbitrary e, Arbitrary f)- => Arbitrary (a, b, c, d, e, f) where- arbitrary = (,,,,,) <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary -- | Arbitrary semver instance Arbitrary SemVer where arbitrary = semver' <$> arb <*> arb <*> arb <*> arbitrary where@@ -73,6 +63,9 @@ infixl 1 `shouldBeR` +shouldBeL :: (Show a, Show b, Eq a) => Either a b -> IO ()+shouldBeL x = shouldSatisfy x isLeft+ main :: IO () main = hspec $ do describe "semver parsing" $ do@@ -138,124 +131,191 @@ `shouldBeR` Geq (semver'' 1 2 3 ["pre"] ["asdf"]) `And` Lt (semver'' 2 4 3 ["pre"] ["asdf"]) - it "poo" $ do- let svNoTags = semver 1 2 3- svTags = semver' 1 2 3 ["pre"]- svTags' = semver' 2 4 3 ["pre"]- svNoTags >= svTags `shouldBe` True- let range = Geq svTags `And` Lt svTags'- range `matches` svNoTags `shouldBe` True+ it "should parse semvers with && instead of spaces" $ do+ let expected = Geq (semver 2 0 0) `And` Leq (semver 2 15 0)+ parseSemVerRange ">= 2 && <= 2.14" `shouldBeR` expected++ it "should fail when it's wrong" $ do+ shouldBeL (parseSemVerRange "xyz")+ rangeTests+ cleanTests +-- | These test cases were adapted from+-- https://github.com/npm/node-semver/blob/master/test/clean.js+cleanTests :: Spec+cleanTests = describe "unclean version strings" $ do+ let examples :: [(Text, Maybe Text)] = [+ ("1.2.3", Just "1.2.3"),+ (" 1.2.3 ", Just "1.2.3"),+ (" 1.2.3-4 ", Just "1.2.3-4"),+ (" 1.2.3-pre ", Just "1.2.3-pre"),+ (" =v1.2.3 ", Just "1.2.3"),+ ("v1.2.3", Just "1.2.3"),+ (" v1.2.3 ", Just "1.2.3"),+ ("\t1.2.3", Just "1.2.3"),+ (">1.2.3", Nothing),+ ("~1.2.3", Nothing),+ ("<=1.2.3", Nothing)+ -- The example below is given in the tests but this doesn't+ -- seem like an error to me, so there.+ -- ("1.2.x", Nothing)+ ]+ forM_ examples $ \(string, result) -> case result of+ Just string' -> do+ it ("should parse " <> show string <> " same as " <> show string') $ do+ parseSemVer string `shouldSatisfy` isRight+ parseSemVer string `shouldBe` parseSemVer string' + Nothing -> do+ it ("should not parse " <> show string) $ do+ parseSemVer string `shouldSatisfy` isLeft+ -- | These test cases were adapted from--- https://github.com/npm/node-semver/blob/--- d21444a0658224b152ce54965d02dbe0856afb84/test/index.js#L134+-- https://github.com/npm/node-semver/blob/master/test/index.js#L134 rangeTests :: Spec rangeTests = describe "range tests" $ do -- In each case, the range described in the first element of the -- tuple should be satisfied by the concrete version described in -- the second element of the tuple.- let testCases :: [(Text, Text)] = [- ("1.0.0 - 2.0.0", "1.2.3"),- ("^1.2.3+build", "1.2.3"),- ("^1.2.3+build", "1.3.0"),- ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),- ("1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),- ("1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),- ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),- ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),- ("1.2.3+asdf - 2.4.3+asdf", "1.2.3"),- ("1.0.0", "1.0.0"),- (">=*", "0.2.4"),- ("", "1.0.0"),- ("*", "1.2.3"),- ("*", "v1.2.3"),- (">=1.0.0", "1.0.0"),- (">=1.0.0", "1.0.1"),- (">=1.0.0", "1.1.0"),- (">1.0.0", "1.0.1"),- (">1.0.0", "1.1.0"),- ("<=2.0.0", "2.0.0"),- ("<=2.0.0", "1.9999.9999"),- ("<=2.0.0", "0.2.9"),- ("<2.0.0", "1.9999.9999"),- ("<2.0.0", "0.2.9"),- (">= 1.0.0", "1.0.0"),- (">= 1.0.0", "1.0.1"),- (">= 1.0.0", "1.1.0"),- ("> 1.0.0", "1.0.1"),- ("> 1.0.0", "1.1.0"),- ("<= 2.0.0", "2.0.0"),- ("<= 2.0.0", "1.9999.9999"),- ("<= 2.0.0", "0.2.9"),- ("< 2.0.0", "1.9999.9999"),- ("<\t2.0.0", "0.2.9"),- (">=0.1.97", "v0.1.97"),- (">=0.1.97", "0.1.97"),- ("0.1.20 || 1.2.4", "1.2.4"),- (">=0.2.3 || <0.0.1", "0.0.0"),- (">=0.2.3 || <0.0.1", "0.2.3"),- (">=0.2.3 || <0.0.1", "0.2.4"),- ("||", "1.3.4"),- ("2.x.x", "2.1.3"),- ("1.2.x", "1.2.3"),- ("1.2.x || 2.x", "2.1.3"),- ("1.2.x || 2.x", "1.2.3"),- ("x", "1.2.3"),- ("2.*.*", "2.1.3"),- ("1.2.*", "1.2.3"),- ("1.2.* || 2.*", "2.1.3"),- ("1.2.* || 2.*", "1.2.3"),- ("*", "1.2.3"),- ("2", "2.1.2"),- ("2.3", "2.3.1"),- ("~2.4", "2.4.0"),- ("~2.4", "2.4.5"),- ("~>3.2.1", "3.2.2"),- ("~1", "1.2.3"),- ("~>1", "1.2.3"),- ("~> 1", "1.2.3"),- ("~1.0", "1.0.2"),- ("~ 1.0", "1.0.2"),- ("~ 1.0.3", "1.0.12"),- (">=1", "1.0.0"),- (">= 1", "1.0.0"),- ("<1.2", "1.1.1"),- ("< 1.2", "1.1.1"),- ("~v0.5.4-pre", "0.5.5"),- ("~v0.5.4-pre", "0.5.4"),- ("=0.7.x", "0.7.2"),- ("<=0.7.x", "0.7.2"),- (">=0.7.x", "0.7.2"),- ("<=0.7.x", "0.6.2"),- ("~1.2.1 >=1.2.3", "1.2.3"),- ("~1.2.1 =1.2.3", "1.2.3"),- ("~1.2.1 1.2.3", "1.2.3"),- ("~1.2.1 >=1.2.3 1.2.3", "1.2.3"),- ("~1.2.1 1.2.3 >=1.2.3", "1.2.3"),- ("~1.2.1 1.2.3", "1.2.3"),- (">=1.2.1 1.2.3", "1.2.3"),- ("1.2.3 >=1.2.1", "1.2.3"),- (">=1.2.3 >=1.2.1", "1.2.3"),- (">=1.2.1 >=1.2.3", "1.2.3"),- (">=1.2", "1.2.8"),- ("^1.2.3", "1.8.1"),- ("^0.1.2", "0.1.2"),- ("^0.1", "0.1.2"),- ("^1.2", "1.4.2"),- ("^1.2 ^1", "1.4.2"),- ("^1.2.3-alpha", "1.2.3-pre"),- ("^1.2.0-alpha", "1.2.0-pre"),- ("^0.0.1-alpha", "0.0.1-beta")+ let testCases :: [(Bool, Text, Text)] = [++ -- Range constraints with pre-release tags require that any+ -- version satisfying the constraint must be equivalent (in+ -- its semantic version tuple) to the minimum of all semantic+ -- versions within the range. In this case the minimum of the+ -- range is "1.2.3" and the version's semantic version tuple+ -- is "1.2.4", therefore it does not satisfy the constraints+ -- of the range given the presence of pre-release tags.+ (False, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.4-pre+asdf"),+ (False, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),+ (False, ">=0.0.1-alpha <0.2.0-alpha", "0.1.1-alpha"),+ (False, "^0.0.1-alpha", "0.0.4-alpha"),++ -- Range constraints without prerelease tags are very strict+ -- about not admitting versions *with* prerelease tags+ (False, "^0.1.2", "0.1.2-beta1"),+ (False, "^0.1.2", "0.1.4-beta1"),++ -- Despite the numeric quantity, these versions have+ -- prerelease tags and are therefore subjected to the same+ -- invariant checking.+ (False, "^1.2.3-1", "1.8.1-1"),+ (False, "^1.2.3-1", "1.8.1-4"),++ -- If we ever have an exact version tuple match at the top of+ -- a given range then it must satisfy the range constraint!+ --+ -- e.g. "^1.2.3-alpha" translates to ">=1.2.3-alpha+ -- <2.0.0-alpha" and the version to check is "2.0.0-alpha". In+ -- this case the version tuples are equivalent, sans+ -- prerelease tags, but it does not satisfy the upper-bound+ -- less-than relation.+ (False, "^1.2.3-alpha", "2.0.0-alpha"),++ (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre+asdf"),+ (True, "", "1.0.0"),+ (True, "*", "1.2.3"),+ (True, "*", "1.2.3"),+ (True, "*", "v1.2.3"),+ (True, "0.1.20 || 1.2.4", "1.2.4"),+ (True, "1.0.0 - 2.0.0", "1.2.3"),+ (True, "1.0.0", "1.0.0"),+ (True, "1.2.* || 2.*", "1.2.3"),+ (True, "1.2.* || 2.*", "2.1.3"),+ (True, "1.2.*", "1.2.3"),+ (True, "1.2.3 - 2.4.3", "1.2.4"),+ (True, "1.2.3 >=1.2.1", "1.2.3"),+ (True, "1.2.3+asdf - 2.4.3+asdf", "1.2.3"),+ (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),+ (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),+ (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pred"),+ (True, "1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),+ (True, "1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),+ (True, "1.2.x || 2.x", "1.2.3"),+ (True, "1.2.x || 2.x", "2.1.3"),+ (True, "1.2.x", "1.2.3"),+ (True, "2", "2.1.2"),+ (True, "2.*.*", "2.1.3"),+ (True, "2.3", "2.3.1"),+ (True, "2.x.x", "2.1.3"),+ (True, "< 2.0.0", "1.9999.9999"),+ (True, "< 1.2", "1.1.1"),+ (True, "<1.2", "1.1.1"),+ (True, "<2.0.0", "0.2.9"),+ (True, "<2.0.0", "1.9999.9999"),+ (True, "<= 2.0.0", "2.0.0"),+ (True, "<= 2.0.0", "0.2.9"),+ (True, "<= 2.0.0", "1.9999.9999"),+ (True, "<=0.7.x", "0.6.2"),+ (True, "<=0.7.x", "0.7.2"),+ (True, "<=2.0.0", "0.2.9"),+ (True, "<=2.0.0", "1.9999.9999"),+ (True, "<=2.0.0", "2.0.0"),+ (True, "<\t2.0.0", "0.2.9"),+ (True, "=0.7.x", "0.7.2"),+ (True, "> 1.0.0", "1.1.0"),+ (True, "> 1.0.0", "1.0.1"),+ (True, ">1.0.0", "1.0.1"),+ (True, ">1.0.0", "1.1.0"),+ (True, ">= 1.0.0", "1.1.0"),+ (True, ">= 1.0.0", "1.0.1"),+ (True, ">= 1", "1.0.0"),+ (True, ">= 1.0.0", "1.0.0"),+ (True, ">= 4.0.0 <4.1.0-0", "4.0.1"),+ (True, ">=*", "0.2.4"),+ (True, ">=0.1.97", "0.1.97"),+ (True, ">=0.1.97", "v0.1.97"),+ (True, ">=0.2.3 || <0.0.1", "0.0.0"),+ (True, ">=0.2.3 || <0.0.1", "0.2.3"),+ (True, ">=0.2.3 || <0.0.1", "0.2.4"),+ (True, ">=0.7.x", "0.7.2"),+ (True, ">=1", "1.0.0"),+ (True, ">=1.0.0", "1.0.0"),+ (True, ">=1.0.0", "1.0.1"),+ (True, ">=1.0.0", "1.1.0"),+ (True, ">=1.2", "1.2.8"),+ (True, ">=1.2.1 1.2.3", "1.2.3"),+ (True, ">=1.2.1 >=1.2.3", "1.2.3"),+ (True, ">=1.2.3 >=1.2.1", "1.2.3"),+ (True, "^0.0.1-alpha", "0.0.1-beta"),+ (True, "^0.0.1-alpha.1", "0.0.1-alpha.t"),+ (True, "^0.0.1-alpha.1", "0.0.1-alpha.tdff.dddddddddd"),+ (True, "^0.1", "0.1.2"),+ (True, "^0.1.2", "0.1.2"),+ (True, "^1.2 ^1", "1.4.2"),+ (True, "^1.2", "1.4.2"),+ (True, "^1.2.0-alpha", "1.2.0-pre"),+ (True, "^1.2.3", "1.8.1"),+ (True, "^1.2.3+build", "1.2.3"),+ (True, "^1.2.3+build", "1.3.0"),+ (True, "^1.2.3-alpha", "1.2.3-pre"),+ (True, "^1.2.3-alpha.1", "1.2.3-alpha.7"),+ (True, "^1.2.3-boop", "1.2.4"),+ (True, "x", "1.2.3"),+ (True, "||", "1.3.4"),+ (True, "~ 1.0", "1.0.2"),+ (True, "~ 1.0.3", "1.0.12"),+ (True, "~1", "1.2.3"),+ (True, "~1.0", "1.0.2"),+ (True, "~1.2.1 1.2.3 >=1.2.3", "1.2.3"),+ (True, "~1.2.1 1.2.3", "1.2.3"),+ (True, "~1.2.1 1.2.3", "1.2.3"),+ (True, "~1.2.1 =1.2.3", "1.2.3"),+ (True, "~1.2.1 >=1.2.3 1.2.3", "1.2.3"),+ (True, "~1.2.1 >=1.2.3", "1.2.3"),+ (True, "~2.4", "2.4.0"),+ (True, "~2.4", "2.4.5"),+ (True, "~> 1", "1.2.3"),+ (True, "~>1", "1.2.3"),+ (True, "~v0.5.4-pre", "0.5.4"),+ (True, "~v0.5.4-pre", "0.5.5"),+ (True, "~>3.2.1", "3.2.2") ]- forM_ testCases $ \(range, version) -> do+ forM_ testCases $ \(expectedMatchBool, range, version) -> do let fail_ = expectationFailure it (show version <> " satisfies range " <> show range) $ do case (parseSemVerRange range, parseSemVer version) of (Left err, _) -> fail $ "Semver range parse failed: " <> show err (_, Left err) -> fail $ "Semver parse failed: " <> show err- (Right range, Right version) -> case matches range version of- True -> True `shouldBe` True -- return ()- False -> fail $ "Version " <> show version <> " didn't match range "- <> show range --`shouldBe` True+ (Right range, Right version) -> matches range version `shouldBe` expectedMatchBool