salve 0.0.6 → 0.0.7
raw patch · 4 files changed
+124/−71 lines, 4 filesdep ~basedep ~doctestdep ~microlensPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: base, doctest, microlens
API changes (from Hackage documentation)
+ Salve.Internal: stringToIntegral :: Integral a => String -> a
+ Salve.Internal: toWord64 :: Integer -> Maybe Word64
- Salve: majorLens :: Functor f => (Word -> f Word) -> Version -> f Version
+ Salve: majorLens :: Functor f => (Word64 -> f Word64) -> Version -> f Version
- Salve: makeVersion :: Word -> Word -> Word -> [PreRelease] -> [Build] -> Version
+ Salve: makeVersion :: Word64 -> Word64 -> Word64 -> [PreRelease] -> [Build] -> Version
- Salve: minorLens :: Functor f => (Word -> f Word) -> Version -> f Version
+ Salve: minorLens :: Functor f => (Word64 -> f Word64) -> Version -> f Version
- Salve: patchLens :: Functor f => (Word -> f Word) -> Version -> f Version
+ Salve: patchLens :: Functor f => (Word64 -> f Word64) -> Version -> f Version
- Salve.Internal: PreReleaseNumeric :: Word -> PreRelease
+ Salve.Internal: PreReleaseNumeric :: Word64 -> PreRelease
- Salve.Internal: Version :: Word -> Word -> Word -> [PreRelease] -> [Build] -> Version
+ Salve.Internal: Version :: Word64 -> Word64 -> Word64 -> [PreRelease] -> [Build] -> Version
- Salve.Internal: WildcardMinor :: Word -> Wildcard
+ Salve.Internal: WildcardMinor :: Word64 -> Wildcard
- Salve.Internal: WildcardPatch :: Word -> Word -> Wildcard
+ Salve.Internal: WildcardPatch :: Word64 -> Word64 -> Wildcard
- Salve.Internal: [versionMajor] :: Version -> Word
+ Salve.Internal: [versionMajor] :: Version -> Word64
- Salve.Internal: [versionMinor] :: Version -> Word
+ Salve.Internal: [versionMinor] :: Version -> Word64
- Salve.Internal: [versionPatch] :: Version -> Word
+ Salve.Internal: [versionPatch] :: Version -> Word64
- Salve.Internal: majorLens :: Functor f => (Word -> f Word) -> Version -> f Version
+ Salve.Internal: majorLens :: Functor f => (Word64 -> f Word64) -> Version -> f Version
- Salve.Internal: makeVersion :: Word -> Word -> Word -> [PreRelease] -> [Build] -> Version
+ Salve.Internal: makeVersion :: Word64 -> Word64 -> Word64 -> [PreRelease] -> [Build] -> Version
- Salve.Internal: minorLens :: Functor f => (Word -> f Word) -> Version -> f Version
+ Salve.Internal: minorLens :: Functor f => (Word64 -> f Word64) -> Version -> f Version
- Salve.Internal: mkV :: Word -> Word -> Word -> Version
+ Salve.Internal: mkV :: Word64 -> Word64 -> Word64 -> Version
- Salve.Internal: nonZeroP :: ReadP Word
+ Salve.Internal: nonZeroP :: ReadP Word64
- Salve.Internal: numberP :: ReadP Word
+ Salve.Internal: numberP :: ReadP Word64
- Salve.Internal: patchLens :: Functor f => (Word -> f Word) -> Version -> f Version
+ Salve.Internal: patchLens :: Functor f => (Word64 -> f Word64) -> Version -> f Version
- Salve.Internal: zeroP :: ReadP Word
+ Salve.Internal: zeroP :: ReadP Word64
Files
- library/Salve.hs +37/−12
- library/Salve/Internal.hs +77/−52
- salve.cabal +6/−6
- tests/doctest.hs +4/−1
library/Salve.hs view
@@ -1,7 +1,7 @@ -- | This module defines types and functions for working with versions as -- defined by [Semantic Versioning](http://semver.org/spec/v2.0.0.html). It -- also provides types and functions for working with version constraints as--- described by [npm](https://docs.npmjs.com/misc/semver#ranges).+-- described by [npm](https://github.com/npm/npm/blob/d081cc6/doc/misc/semver.md#ranges). module Salve ( -- | This module doesn't export anything that conflicts with the "Prelude", so -- you can import it unqualified.@@ -9,7 +9,8 @@ -- >>> import Salve -- -- This module provides lenses for modifying versions. If you want to modify--- versions, consider importing a lens library like "Lens.Micro".+-- versions, consider importing a lens library like+-- [microlens](https://www.stackage.org/lts-9.3/package/microlens-0.4.8.1). -- -- The 'Version' data type is the core of this module. Use 'parseVersion' to -- make versions and 'renderVersion' to convert them into strings.@@ -56,8 +57,9 @@ -- * Rationale -- ** The PVP--- | Haskell's <https://pvp.haskell.org/ Package Versioning Policy> (PVP)--- defines three things:+-- | Haskell's+-- [Package Versioning Policy](https://github.com/haskell/pvp/blob/176bb14/pvp-specification.md)+-- (PVP) defines three things: -- -- 1. A spec for versioning your package, which includes how version numbers -- look and how to encode breaking changes.@@ -72,7 +74,7 @@ -- -- Looking at the first point, why might you want to use SemVer instead of the -- PVP? The PVP has many problems, as described by the--- <http://taylor.fausak.me/2016/12/28/problematic-versioning-policy/ Problematic versioning policy>+-- [Problematic versioning policy](http://taylor.fausak.me/2016/12/28/problematic-versioning-policy/) -- blog post. In short, the PVP is too flexible and it's unique to Haskell, -- which causes unnecessary friction with developers from other languages. --@@ -86,7 +88,7 @@ -- | There are already a few modules that provide version numbers. Why do we -- need another one? Let's take a look at the options. ----- - <https://www.stackage.org/haddock/lts-9.3/base-4.9.1.0/Data-Version.html Data.Version>+-- - [Data.Version](https://www.stackage.org/haddock/lts-9.3/base-4.9.1.0/Data-Version.html) -- from the @base@ package: -- -- - Exposes constructors, which allows creating versions that cannot be@@ -96,7 +98,7 @@ -- - Does not support build metadata on versions. -- - Does not support constraints. ----- - <https://www.stackage.org/haddock/lts-9.3/Cabal-1.24.2.0/Distribution-Version.html Distribution.Version>+-- - [Distribution.Version](https://www.stackage.org/haddock/lts-9.3/Cabal-1.24.2.0/Distribution-Version.html) -- from the @Cabal@ package: -- -- - Has the same problems as Data.Version because it re-uses that@@ -105,13 +107,13 @@ -- @deepseq@, @directory@, @filepath@, @pretty@, @process@, @time@, and -- @unix@ packages. ----- - <https://www.stackage.org/haddock/lts-9.3/semver-0.3.3.1/Data-SemVer.html Data.SemVer>+-- - [Data.SemVer](https://www.stackage.org/haddock/lts-9.3/semver-0.3.3.1/Data-SemVer.html) -- from the @semver@ package: -- -- - Depends on the @attoparsec@, @deepseq@, and @text@ packages. -- - Does not support version constraints. ----- - <https://hackage.haskell.org/package/semver-range-0.2.2/docs/Data-SemVer.html Data.SemVer>+-- - [Data.SemVer](https://hackage.haskell.org/package/semver-range-0.2.2/docs/Data-SemVer.html) -- from the @semver-range@ package: -- -- - Depends on the @classy-prelude@, @parsec@, @text@, and@@ -119,7 +121,7 @@ -- - Module name collides with the @semver@ package. -- - Supports constraints, but does not provide a way to render them. ----- - <https://www.stackage.org/haddock/lts-9.3/versions-3.1.1/Data-Versions.html Data.Versions>+-- - [Data.Versions](https://www.stackage.org/haddock/lts-9.3/versions-3.1.1/Data-Versions.html) -- from the @versions@ package: -- -- - Depends on the @deepseq@, @hashable@, @megaparsec@, and @text@@@ -192,8 +194,7 @@ -- Don't be scared by these type signatures. They are provided in full to avoid -- the @RankNTypes@ language extension. The type signature -- @'Functor' f => (a -> f a) -> 'Version' -> f 'Version'@ is the same as--- @'Lens.Micro.Lens'' 'Version' a@ (from "Lens.Micro"), which you may already--- be familiar with.+-- @Lens' 'Version' a@, which you may already be familiar with. majorLens, minorLens, patchLens,@@ -253,6 +254,27 @@ -- Nothing -- >>> parseVersion "0.0.0 " -- Nothing+--+-- Each version component cannot be larger than a 64-bit unsigned integer.+--+-- >>> parseVersion "18446744073709551615.0.0"+-- Just (Version {versionMajor = 18446744073709551615, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+-- >>> parseVersion "18446744073709551616.0.0"+-- Nothing+--+-- Numeric pre-releases tags cannot be larger than a 64-bit unsigned integer.+--+-- >>> parseVersion "0.0.0-18446744073709551615"+-- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [PreReleaseNumeric 18446744073709551615], versionBuilds = []})+-- >>> parseVersion "0.0.0-18446744073709551616"+-- Nothing+--+-- Build metadata is not numeric so it does not have any limit.+--+-- >>> parseVersion "0.0.0+18446744073709551615"+-- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = [Build "18446744073709551615"]})+-- >>> parseVersion "0.0.0+18446744073709551616"+-- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = [Build "18446744073709551616"]}) -- ** Constraints -- | Partial version numbers are not allowed.@@ -576,3 +598,6 @@ ) where import Salve.Internal++-- $setup+-- >>> import Control.Applicative
library/Salve/Internal.hs view
@@ -7,13 +7,15 @@ import qualified Data.Char as Char import qualified Data.List as List import qualified Data.Maybe as Maybe+import qualified Data.Monoid as Monoid import qualified Data.Ord as Ord+import qualified Data.Word as Word import qualified Text.ParserCombinators.ReadP as ReadP-import qualified Text.Read as Read -- $setup+-- >>> import Control.Applicative -- >>> import Lens.Micro--- >>> import Lens.Micro.Extras+-- >>> let view l x = x ^. l -- * Public @@ -27,14 +29,14 @@ -- -- Use 'parseVersion' to create versions. data Version = Version- { versionMajor :: Word- , versionMinor :: Word- , versionPatch :: Word+ { versionMajor :: Word.Word64+ , versionMinor :: Word.Word64+ , versionPatch :: Word.Word64 , versionPreReleases :: [PreRelease] , versionBuilds :: [Build] } deriving (Eq, Show) --- | In general, 'Version's compare in the way that you would expect. First the+-- | In general, versions compare in the way that you would expect. First the -- major version numbers are compared, then the minors, then the patches. -- -- >>> compare <$> parseVersion "1.2.3" <*> parseVersion "2.0.0"@@ -69,7 +71,7 @@ -- >>> (==) <$> parseVersion "1.2.3+a" <*> parseVersion "1.2.3+b" -- Just False instance Ord Version where- compare x y = mconcat+ compare x y = Monoid.mconcat [ Ord.comparing versionMajor x y , Ord.comparing versionMinor x y , Ord.comparing versionPatch x y@@ -93,7 +95,7 @@ -- -- Use 'parsePreRelease' to create pre-releases. data PreRelease- = PreReleaseNumeric Word+ = PreReleaseNumeric Word.Word64 | PreReleaseTextual String deriving (Eq, Show) @@ -132,7 +134,7 @@ -- | Constrains allowable version numbers. -- -- Use 'parseConstraint' to create constraints and 'satisfiesConstraint' to see--- if a version number satisfiesConstraint a constraint.+-- if a version number satisfies a constraint. data Constraint = ConstraintOperator Operator Version | ConstraintHyphen Version Version@@ -143,12 +145,12 @@ -- | Makes a new version number. ----- >>> makeVersion 0 0 0 [] []--- Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []}+-- >>> makeVersion 1 2 3 [unsafeParsePreRelease "pre"] [unsafeParseBuild "build"]+-- Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [PreReleaseTextual "pre"], versionBuilds = [Build "build"]} -- -- This can be a useful alternative to 'parseVersion' if you want a total way -- to create a version.-makeVersion :: Word -> Word -> Word -> [PreRelease] -> [Build] -> Version+makeVersion :: Word.Word64 -> Word.Word64 -> Word.Word64 -> [PreRelease] -> [Build] -> Version makeVersion major minor patch preReleases builds = Version { versionMajor = major , versionMinor = minor@@ -164,8 +166,8 @@ initialVersion :: Version initialVersion = makeVersion 0 0 0 [] [] --- | Attempts to parse a version. This parser follows [SemVer's--- BNF](https://github.com/mojombo/semver/blob/eb9aac5/semver.md#backusnaur-form-grammar-for-valid-semver-versions).+-- | Attempts to parse a version. This parser follows+-- [SemVer's BNF](https://github.com/mojombo/semver/blob/eb9aac5/semver.md#backusnaur-form-grammar-for-valid-semver-versions). -- -- >>> parseVersion "1.2.3-p.4+b.5" -- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [PreReleaseTextual "p",PreReleaseNumeric 4], versionBuilds = [Build "b",Build "5"]})@@ -220,10 +222,8 @@ parseBuild :: String -> Maybe Build parseBuild s = parse buildP s --- | Attempts to parse a constraint. This parser follows [npm's--- BNF](https://github.com/npm/npm/blob/d081cc6/doc/misc/semver.md#range-grammar),--- except that neither the so-called "x-ranges" nor partial version numbers are--- not supported. So you cannot use @1.2.x@ or @>1.2@ as version constraints.+-- | Attempts to parse a constraint. This parser mostly follows+-- [npm's BNF](https://github.com/npm/npm/blob/d081cc6/doc/misc/semver.md#range-grammar). -- -- >>> parseConstraint ">1.2.3" -- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))@@ -232,6 +232,19 @@ -- -- >>> parseConstraint "wrong" -- Nothing+--+-- The two departures from npm's BNF are that x-ranges cannot be used with+-- other operators and partial version numbers are not allowed.+--+-- >>> parseConstraint "1.2.x"+-- Just (ConstraintWildcard (WildcardPatch 1 2))+-- >>> parseConstraint ">=1.2.x"+-- Nothing+--+-- >>> parseConstraint "1.2"+-- Nothing+-- >>> parseConstraint ">=1.2"+-- Nothing parseConstraint :: String -> Maybe Constraint parseConstraint s = parse constraintsP s @@ -308,7 +321,7 @@ -- >>> renderVersion <$> parseVersion "1.2.3-p.4+b.5" -- Just "1.2.3-p.4+b.5" renderVersion :: Version -> String-renderVersion v = mconcat+renderVersion v = concat [ show (versionMajor v) , "." , show (versionMinor v)@@ -460,7 +473,7 @@ -- Just 1 -- >>> set majorLens 4 <$> parseVersion "1.2.3" -- Just (Version {versionMajor = 4, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})-majorLens :: Functor f => (Word -> f Word) -> Version -> f Version+majorLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version majorLens f v = fmap (\ m -> v { versionMajor = m }) (f (versionMajor v))@@ -471,7 +484,7 @@ -- Just 2 -- >>> set minorLens 4 <$> parseVersion "1.2.3" -- Just (Version {versionMajor = 1, versionMinor = 4, versionPatch = 3, versionPreReleases = [], versionBuilds = []})-minorLens :: Functor f => (Word -> f Word) -> Version -> f Version+minorLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version minorLens f v = fmap (\ n -> v { versionMinor = n }) (f (versionMinor v))@@ -482,7 +495,7 @@ -- Just 3 -- >>> set patchLens 4 <$> parseVersion "1.2.3" -- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 4, versionPreReleases = [], versionBuilds = []})-patchLens :: Functor f => (Word -> f Word) -> Version -> f Version+patchLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version patchLens f v = fmap (\ p -> v { versionPatch = p }) (f (versionPatch v))@@ -525,8 +538,8 @@ data Wildcard = WildcardMajor- | WildcardMinor Word- | WildcardPatch Word Word+ | WildcardMinor Word.Word64+ | WildcardPatch Word.Word64 Word.Word64 deriving (Eq, Show) -- ** Constructors@@ -635,7 +648,7 @@ patch <- numberP preReleases <- preReleasesP builds <- buildsP- pure (makeVersion major minor patch preReleases builds)+ return (makeVersion major minor patch preReleases builds) preReleasesP :: ReadP.ReadP [PreRelease] preReleasesP = ReadP.option [] (do@@ -648,14 +661,14 @@ preReleaseNumberP :: ReadP.ReadP PreRelease preReleaseNumberP = do n <- numberP- pure (PreReleaseNumeric n)+ return (PreReleaseNumeric n) preReleaseStringP :: ReadP.ReadP PreRelease preReleaseStringP = do s <- ReadP.munch1 isIdentifier if all Char.isDigit s then ReadP.pfail- else pure (PreReleaseTextual s)+ else return (PreReleaseTextual s) buildsP :: ReadP.ReadP [Build] buildsP = ReadP.option [] (do@@ -665,21 +678,21 @@ buildP :: ReadP.ReadP Build buildP = do b <- ReadP.munch1 isIdentifier- pure (Build b)+ return (Build b) -numberP :: ReadP.ReadP Word-numberP = nonZeroP ReadP.<++ zeroP+numberP :: ReadP.ReadP Word.Word64+numberP = zeroP ReadP.<++ nonZeroP -zeroP :: ReadP.ReadP Word+zeroP :: ReadP.ReadP Word.Word64 zeroP = do Monad.void (ReadP.char '0')- pure 0+ return 0 -nonZeroP :: ReadP.ReadP Word+nonZeroP :: ReadP.ReadP Word.Word64 nonZeroP = do x <- ReadP.satisfy isAsciiDigitNonZero ys <- ReadP.munch Char.isDigit- case Read.readMaybe (x : ys) of+ case toWord64 (stringToIntegral (x : ys)) of Nothing -> ReadP.pfail Just n -> pure n @@ -688,19 +701,19 @@ spacesP cs <- ReadP.sepBy1 constraintP orP spacesP- pure (foldr1 constraintOr cs)+ return (foldr1 constraintOr cs) constraintP :: ReadP.ReadP Constraint constraintP = do cs <- ReadP.sepBy1 simpleP spaces1P- pure (foldr1 constraintAnd cs)+ return (foldr1 constraintAnd cs) hyphenatedP :: ReadP.ReadP Constraint hyphenatedP = do v <- versionP hyphenP w <- versionP- pure (constraintHyphen v w)+ return (constraintHyphen v w) simpleP :: ReadP.ReadP Constraint simpleP = ReadP.choice [hyphenatedP, wildcardConstraintP, primitiveP]@@ -709,7 +722,7 @@ wildcardConstraintP = do ReadP.optional (ReadP.char '=') w <- wildcardP- pure (ConstraintWildcard w)+ return (ConstraintWildcard w) wildcardP :: ReadP.ReadP Wildcard wildcardP = ReadP.choice [wildcardPatchP, wildcardMinorP, wildcardMajorP]@@ -721,7 +734,7 @@ n <- numberP Monad.void (ReadP.char '.') Monad.void (ReadP.satisfy isWildcard)- pure (WildcardPatch m n)+ return (WildcardPatch m n) wildcardMinorP :: ReadP.ReadP Wildcard wildcardMinorP = do@@ -730,7 +743,7 @@ Monad.void (ReadP.satisfy isWildcard) Monad.void (ReadP.char '.') Monad.void (ReadP.satisfy isWildcard)- pure (WildcardMinor m)+ return (WildcardMinor m) wildcardMajorP :: ReadP.ReadP Wildcard wildcardMajorP = do@@ -739,25 +752,25 @@ Monad.void (ReadP.satisfy isWildcard) Monad.void (ReadP.char '.') Monad.void (ReadP.satisfy isWildcard)- pure WildcardMajor+ return WildcardMajor primitiveP :: ReadP.ReadP Constraint primitiveP = do o <- operatorP spacesP v <- versionP- pure (ConstraintOperator o v)+ return (ConstraintOperator o v) operatorP :: ReadP.ReadP Operator operatorP = ReadP.choice- [ ReadP.string "<=" *> pure OperatorLE- , ReadP.string ">=" *> pure OperatorGE- , ReadP.char '<' *> pure OperatorLT- , ReadP.char '>' *> pure OperatorGT- , ReadP.char '=' *> pure OperatorEQ- , ReadP.char '^' *> pure OperatorCaret- , ReadP.char '~' *> pure OperatorTilde- , pure OperatorEQ+ [ ReadP.string "<=" >> return OperatorLE+ , ReadP.string ">=" >> return OperatorGE+ , ReadP.char '<' >> return OperatorLT+ , ReadP.char '>' >> return OperatorGT+ , ReadP.char '=' >> return OperatorEQ+ , ReadP.char '^' >> return OperatorCaret+ , ReadP.char '~' >> return OperatorTilde+ , return OperatorEQ ] hyphenP :: ReadP.ReadP ()@@ -809,8 +822,20 @@ let p' = ReadP.readP_to_S p in Maybe.listToMaybe (do (x, "") <- p' s- pure x)+ return x) +stringToIntegral :: Integral a => String -> a+stringToIntegral s = foldl+ (\ n d -> (n * 10) + (fromIntegral (fromEnum d) - 48)) 0 s++toWord64 :: Integer -> Maybe Word.Word64+toWord64 n =+ if n < 0+ then Nothing+ else if n > fromIntegral (maxBound :: Word.Word64)+ then Nothing+ else Just (fromIntegral n)+ -- * Simple constraints -- | Simple constraints are just as expressive as 'Constraint's, but they are -- easier to reason about. You can think of them as the desugared version of@@ -824,7 +849,7 @@ | SCOr SimpleConstraint SimpleConstraint deriving (Eq, Show) -mkV :: Word -> Word -> Word -> Version+mkV :: Word.Word64 -> Word.Word64 -> Word.Word64 -> Version mkV m n p = makeVersion m n p [] [] satisfiesSC :: SimpleConstraint -> Version -> Bool
salve.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: salve-version: 0.0.6+version: 0.0.7 synopsis: Semantic version numbers and constraints. description: Salve provides semantic version (SemVer) numbers and constraints (ranges). category: Distribution@@ -28,7 +28,7 @@ library ghc-options: -Wall build-depends:- base >=4.8.2 && <4.11+ base >=4.7.0 && <4.11 exposed-modules: Salve Salve.Internal@@ -41,9 +41,9 @@ tests ghc-options: -Wall build-depends:- base >=4.8.2 && <4.11- , doctest >=0.10.1 && <0.14- , microlens >=0.3.5 && <0.5+ base >=4.7.0 && <4.11+ , doctest >=0.9.11 && <0.14+ , microlens >=0.2.0 && <0.5 default-language: Haskell2010 benchmark benchmark@@ -53,7 +53,7 @@ benchmarks ghc-options: -Wall build-depends:- base >=4.8.2 && <4.11+ base >=4.7.0 && <4.11 , criterion >=1.1.0 && <1.3 , deepseq >=1.4.1 && <1.5 , salve
tests/doctest.hs view
@@ -1,4 +1,7 @@ import Test.DocTest main :: IO ()-main = doctest ["library"]+main = doctest+ [ "library/Salve/Internal.hs"+ , "library/Salve.hs"+ ]