semver 0.3.4 → 0.4.0.1
raw patch · 5 files changed
Files
- semver.cabal +4/−3
- src/Data/SemVer.hs +1/−1
- src/Data/SemVer/Constraint.hs +110/−0
- src/Data/SemVer/Delimited.hs +3/−3
- test/Main.hs +33/−2
semver.cabal view
@@ -1,8 +1,8 @@ name: semver-version: 0.3.4+version: 0.4.0.1 synopsis: Representation, manipulation, and de/serialisation of Semantic Versions. homepage: https://github.com/brendanhay/semver-license: OtherLicense+license: MPL-2.0 license-file: LICENSE author: Brendan Hay maintainer: Brendan Hay <brendan.g.hay@gmail.com>@@ -10,7 +10,7 @@ category: Data build-type: Simple extra-source-files: README.md-cabal-version: >= 1.10+cabal-version: >= 1.20 description: Representation, manipulation, and de/serialisation of a Version type@@ -29,6 +29,7 @@ exposed-modules: Data.SemVer+ , Data.SemVer.Constraint , Data.SemVer.Delimited other-modules:
src/Data/SemVer.hs view
@@ -240,7 +240,7 @@ -- | A greedy attoparsec 'Parser' which requires the entire 'Text' -- input to match. parser :: Parser Version-parser = Delim.parser Delim.semantic+parser = Delim.parser Delim.semantic True {-# INLINE parser #-} -- | Safely construct a numeric identifier.
+ src/Data/SemVer/Constraint.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+-- Module : Data.SemVer.Constraint+-- Copyright : (c) 2020 Brendan Hay <brendan.g.hay@gmail.com>, Keagan McClelland <keagan.mcclelland@gmail.com>+-- License : This Source Code Form is subject to the terms of+-- the Mozilla Public License, v. 2.0.+-- A copy of the MPL can be found in the LICENSE file or+-- you can obtain it at http://mozilla.org/MPL/2.0/.+-- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)++-- | An implementation of the Semantic Versioning Constraints.+-- In absence of a standard around constraints, the behavior of node-semver is closely followed.+-- The behavior is outlined here: https://github.com/npm/node-semver#ranges+module Data.SemVer.Constraint+ ( Constraint(..)+ , satisfies+ , fromText+ )+where++import Control.Applicative+import Data.Attoparsec.Text+import Data.Monoid ((<>))+import Data.SemVer.Internal+import qualified Data.SemVer.Delimited as DL+import Data.Text (Text)++data Constraint+ = CAny+ | CLt !Version+ | CLtEq !Version+ | CGt !Version+ | CGtEq !Version+ | CEq !Version+ | CAnd !Constraint !Constraint+ | COr !Constraint !Constraint+ deriving (Eq, Show)++-- | Checks whether the 'Version' satisfies the 'Constraint'+--+-- Note: Semantics of this are strange in the presence of pre-release identifiers. Without a proper standard for how+-- constraint satisfaction should behave, this implementation attempts to follow the behavior of node-semver+-- which can be found here: https://github.com/npm/node-semver#prerelease-tags.+--+-- This choice was made because node-semver is the most widely deployed implementation of semantic versioning with+-- the best documentation around how to treat pre-release identifiers.+--+-- The summary is that you must opt into using pre-release identifiers by specifying them in the constraint+-- and they __must__ match the __exact__ version that attempts to use a pre-release identifier.+satisfies :: Version -> Constraint -> Bool+satisfies version constraint = if containsPrerelease version+ then if not . null . filter ((triple version ==) . triple . snd) $ (prereleaseComparators constraint)+ then go version constraint+ else if constraint == CAny then True else False+ else go version constraint+ where+ triple :: Version -> (Int, Int, Int)+ triple = liftA3 (,,) _versionMajor _versionMinor _versionPatch+ containsPrerelease :: Version -> Bool+ containsPrerelease v = not . null . _versionRelease $ v+ -- this helps us gather the comparators that actually consented to prerelease versions+ prereleaseComparators :: Constraint -> [(Version -> Constraint, Version)]+ prereleaseComparators = \case+ CAny -> []+ CLt v -> if containsPrerelease v then [(CLt, v)] else []+ CLtEq v -> if containsPrerelease v then [(CLtEq, v)] else []+ CGt v -> if containsPrerelease v then [(CGt, v)] else []+ CGtEq v -> if containsPrerelease v then [(CGtEq, v)] else []+ CEq v -> if containsPrerelease v then [(CEq, v)] else []+ CAnd a b -> prereleaseComparators a <> prereleaseComparators b+ COr a b -> prereleaseComparators a <> prereleaseComparators b+ -- naive satisfaction checking+ go :: Version -> Constraint -> Bool+ go v c = case c of+ CAny -> True+ CLt vc -> v < vc+ CLtEq vc -> v <= vc+ CGt vc -> v > vc+ CGtEq vc -> v >= vc+ CEq vc -> v == vc+ CAnd a b -> go v a && go v b+ COr a b -> go v a || go v b++-- | Parsing function to create a 'Constraint' from 'Text' according to the rules specified+-- here: https://github.com/npm/node-semver#ranges+--+-- Advanced syntax is not yet supported.+fromText :: Text -> Either String Constraint+fromText = parseOnly parser++parser :: Parser Constraint+parser = parserD DL.semantic++parserD :: Delimiters -> Parser Constraint+parserD d@Delimiters {..} = choice . fmap (<* endOfInput) $ [primP, andP, orP]+ where+ primP = choice+ [ char '*' *> pure CAny+ , char '<' *> (CLt <$> DL.parser d False)+ , string "<=" *> (CLtEq <$> DL.parser d False)+ , char '>' *> (CGt <$> DL.parser d False)+ , string ">=" *> (CGtEq <$> DL.parser d False)+ , CEq <$> ((option '=' $ char '=') *> DL.parser d False)+ ]+ andP = liftA2 CAnd primP (skipSpace *> (andP <|> primP))+ orP = liftA2 COr (andP <|> primP) (skipSpace *> string "||" *> skipSpace *> (orP <|> andP <|> primP))
src/Data/SemVer/Delimited.hs view
@@ -107,14 +107,14 @@ -- | A greedy attoparsec 'Parser' using the specified 'Delimiters' set -- which requires the entire 'Text' input to match.-parser :: Delimiters -> Parser Version-parser Delimiters{..} = Version+parser :: Delimiters -> Bool -> Parser Version+parser Delimiters{..} requireAtEnd = Version <$> (nonNegative <* char _delimMinor) <*> (nonNegative <* char _delimPatch) <*> nonNegative <*> option [] (try (char _delimRelease) *> identifiers) <*> option [] (try (char _delimMeta) *> identifiers)- <* endOfInput+ <* when requireAtEnd endOfInput where identifiers :: Parser [Identifier] identifiers = many (identifierParser $ void (char _delimIdent))
test/Main.hs view
@@ -15,6 +15,7 @@ import Control.Applicative import Data.List (sort) import Data.SemVer+import Data.SemVer.Constraint import Data.Text (Text, unpack) import Test.Tasty import Test.Tasty.HUnit@@ -59,14 +60,39 @@ , testCase "compare 1.2.3 1.2.3+sha.2ac == EQ" $ (sv123 `compare` sv123sha2ac) @=? EQ ]++ , testGroup "constraint satisfaction"+ [ testGroup "basic satisfaction"+ [ testCase "1.2.3 `satisfies` >=1.2.3" $ true (sv123 `satisfies` sc ">=1.2.3")+ , testCase "not $ 1.2.3 `satisfies` >1.2.3" $ false (sv123 `satisfies` sc ">1.2.3")+ , testCase "1.2.3 `satisfies` <1.2.4" $ true (sv123 `satisfies` sc "<1.2.4")+ , testCase "not $ 1.2.3 `satisfies` =1.2.4" $ false (sv123 `satisfies` sc "=1.2.4")+ ]+ , testGroup "composite satisfaction"+ [ testCase "1.2.3 `satisfies` >1.0.0 <2.0.0" $ true (sv123 `satisfies` sc ">1.0.0 <2.0.0")+ , testCase "1.2.3 `satisfies` <2.0.0 || >3.0.0" $ true (sv123 `satisfies` sc "<2.0.0 || >3.0.0")+ , testCase "1.2.3 `satisfies` >1.0.0 <2.0.0 || 3.0.0" $ true (sv123 `satisfies` sc ">1.0.0 <2.0.0 || 3.0.0")+ , testCase "1.2.3 `satisfies` 3.0.0 || <2.0.0 >1.0.0" $ true (sv123 `satisfies` sc "3.0.0 || <2.0.0 >1.0.0")+ ]+ , testGroup "prerelease handling"+ [ testCase "prerelease versions satisfy if triple is same" $ true (sv100alpha `satisfies` sc ">=1.0.0-alpha")+ , testCase "prerelease versions don't satisfy if spec doesn't contain prerelease tag on same triple" $ false (sv100alpha `satisfies` sc ">=1.0.0")+ , testCase "prerelease versions don't satisfy if spec doesn't contain prerelease tag on same triple" $ false (sv "3.0.0-alpha" `satisfies` sc ">=1.0.0-alpha")+ , testCase "prerelease versions do not require tag to be the same to satisfy" $ true (sv "1.0.0-beta" `satisfies` sc ">=1.0.0-alpha")+ , testCase "regular versions can satisfy prerelease constraints" $ true (sv "3.0.0" `satisfies` sc ">=2.0.0-alpha")+ ]+ ] ] iso :: Text -> TestTree-iso t = testCase (unpack t) (Right t @=? (toText <$> fromText t))+iso t = testCase (unpack t) (Right t @=? (toText <$> Data.SemVer.fromText t)) true :: Bool -> Assertion true = (True @=?) +false :: Bool -> Assertion+false = (False @=?)+ sv000, sv100, sv100alpha, sv100alpha1, sv101 :: Version sv110, sv200, sv123sha2ac, sv123beta1shaexpdc2 :: Version sv000 = initial@@ -81,6 +107,11 @@ sv123beta1shaexpdc2 = sv "1.2.3-beta.1+sha.exp.dc2" sv :: Text -> Version-sv t = case fromText t of+sv t = case Data.SemVer.fromText t of Left e -> error e+ Right x -> x++sc :: Text -> Constraint+sc t = case Data.SemVer.Constraint.fromText t of+ Left e -> error e Right x -> x