salve 1.0.9 → 1.0.10
raw patch · 11 files changed
+1772/−1726 lines, 11 filesdep +HUnitdep +salvedep −doctestdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit, salve
Dependencies removed: doctest
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- CHANGELOG.markdown +0/−7
- README.markdown +3/−5
- library/Salve.hs +0/−611
- library/Salve/Internal.hs +0/−1006
- package.yaml +0/−41
- salve.cabal +50/−48
- src/lib/Salve.hs +611/−0
- src/lib/Salve/Internal.hs +1006/−0
- src/test/Main.hs +102/−0
- stack.yaml +0/−1
- tests/Main.hs +0/−7
− CHANGELOG.markdown
@@ -1,7 +0,0 @@-# Change log--Salve uses [Semantic Versioning][].-The change log is available through the [releases on GitHub][].--[Semantic Versioning]: http://semver.org/spec/v2.0.0.html-[releases on GitHub]: https://github.com/tfausak/salve/releases
README.markdown view
@@ -1,11 +1,9 @@ # Salve--[![Build badge][]][build status]+[](https://travis-ci.org/tfausak/salve)+[](https://hackage.haskell.org/package/salve)+[](https://www.stackage.org/package/salve) Salve provides semantic version numbers and constraints for Haskell. Salve has extensive documentation available on Hackage: <https://hackage.haskell.org/package/salve>.--[Build badge]: https://travis-ci.org/tfausak/salve.svg?branch=master-[build status]: https://travis-ci.org/tfausak/salve
− library/Salve.hs
@@ -1,611 +0,0 @@--- | 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://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.------ >>> import Salve------ This module provides lenses for modifying versions. If you want to modify--- 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.------ >>> renderVersion <$> parseVersion "1.2.3"--- Just "1.2.3"------ The 'Constraint' data type allows you to specify version constraints. Use--- 'parseConstraint' to make constraints and 'renderConstraint' to convert them--- into strings.------ >>> renderConstraint <$> parseConstraint ">1.2.0"--- Just ">1.2.0"------ Use 'satisfiesConstraint' to see if a version satisfiesConstraint a--- constraint.------ >>> satisfiesConstraint <$> parseConstraint ">1.2.0" <*> parseVersion "1.2.3"--- Just True---- * Cheat sheet--- | If you're coming from Cabal, you might not be familiar with npm's version--- range syntax. This table shows you how npm version ranges map to Cabal's--- version constraints.------ > Salve | Cabal | Notes--- > ----- | ----- | -------- > <1.2.3 | <1.2.3 | ---- > <=1.2.3 | <=1.2.3 | ---- > =1.2.3 | ==1.2.3 | equals sign is optional--- > >=1.2.3 | >=1.2.3 | ---- > >1.2.3 | >1.2.3 | ---- > 1.2.3 || >1.2.3 | ==1.2.3 || >1.2.3 | lower precedence than and--- > >=1.2.3 <2.0.0 | >=1.2.3 && <2.0.0 | higher precedence than or--- > 1.2.3 - 2.3.4 | >=1.2.3 && <=2.3.4 | inclusive ranges--- > 1.2.x | ==1.2.* | can use X or * instead of x--- > 1.x.x | ==1.* | ---- > x.x.x | ==* | same as -any--- > ~1.2.3 | ^>=1.2.3 | same as >=1.2.3 && <1.3.0--- > ^1.2.3 | >=1.2.3 && <2 | ---- > ^0.2.3 | >=0.2.3 && <0.3 | ---- > ^0.0.3 | >=0.0.3 && <0.0.4 | ----- * Rationale---- ** The PVP--- | 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.--- 2. A spec for constraining the versions of your dependencies, which--- incldues how version ranges look.--- 3. A prescription for how to constrain the versions of your dependencies,--- which includes how the ranges of your dependencies should be.------ By comparison, Semantic Versioning only deals with the first thing. npm's--- version ranges only deal with the second thing. This module deals with the--- first and second things but leaves the third up to you.------ 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--- [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.------ Moving on to the second point, why should we use npm's version ranges--- instead of the PVP's? This is a less clear cut. The two syntaxes are broadly--- compatible. Really the only gains here are compatibility with a widely-used--- syntax and convenient shorthand for common constraints (like hyphens--- @1.2.3 - 2.3.4@, tildes @~1.2.3@, and carets @^1.2.3@).---- ** Other modules--- | There are already a few modules that provide version numbers. Why do we--- need another one? Let's take a look at the options.------ - [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--- parsed.--- - Allows any number of components, from zero to inifinity.--- - Deprecated tags on versions.--- - Does not support build metadata on versions.--- - Does not support constraints.------ - [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--- version type.--- - Depends on the @array@, @binary@, @bytestring@, @containers@,--- @deepseq@, @directory@, @filepath@, @pretty@, @process@, @time@, and--- @unix@ packages.------ - [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.------ - [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--- @unordered-containers@ packages.--- - Module name collides with the @semver@ package.--- - Supports constraints, but does not provide a way to render them.------ - [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@--- packages.--- - Intentially allows weird versions.--- - Does not support constraints.------ By comparison, this module:------ - Does not expose constructors. Any version you create can be rendered and--- parsed without issue.--- - Requires exactly three components. You won't have to wonder if version--- @1.2.0@ is greater than @1.2@.--- - Allows pre-release identifiers on versions. Go ahead and release version--- @1.0.0-alpha@ for early adopters.--- - Allows build metadata on versions. Show when a release was made with--- versions like @1.0.0+2001-02-03@.--- - Supports version constraints. Just like versions, rendering and parsing--- constraints is no problem.--- - Only depends on the @base@ package. You can use all the functionality--- without installing any other packages.--- - Has a unique module name. You won't have to use the @PackageImports@--- extension simply to deal with version numbers.---- * Types-Salve.Version,-Salve.PreRelease,-Salve.Build,-Salve.Constraint,---- * Constructors-Salve.makeVersion,-Salve.initialVersion,---- * Parsing-Salve.parseVersion,-Salve.parsePreRelease,-Salve.parseBuild,-Salve.parseConstraint,---- ** Unsafe--- | These functions can be used to unsafely parse strings. Instead of--- returning 'Nothing', they raise an exception. Only use these if you are sure--- the string can be successfully parsed!-Salve.unsafeParseVersion,-Salve.unsafeParsePreRelease,-Salve.unsafeParseBuild,-Salve.unsafeParseConstraint,---- * Rendering-Salve.renderVersion,-Salve.renderPreRelease,-Salve.renderBuild,-Salve.renderConstraint,---- * Predicates-Salve.isUnstable,-Salve.isStable,---- * Conversions-Salve.fromBaseVersion,-Salve.toBaseVersion,---- * Helpers-Salve.bumpMajor,-Salve.bumpMinor,-Salve.bumpPatch,-Salve.satisfiesConstraint,---- * Lenses--- | These lenses can be used to access and modify specific parts of a--- 'Version'.------ 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' 'Version' a@, which you may already be familiar with.-Salve.majorLens,-Salve.minorLens,-Salve.patchLens,-Salve.preReleasesLens,-Salve.buildsLens,---- * Examples--- | These examples are provided to showcase functionality and explain weird--- behavior. If something isn't clear, please open a pull request adding an--- example!---- ** Versions--- | Leading zeros are not allowed.------ >>> parseVersion "01.0.0"--- Nothing--- >>> parseVersion "0.01.0"--- Nothing--- >>> parseVersion "0.0.01"--- Nothing------ Negative numbers are not allowed.------ >>> parseVersion "-1.0.0"--- Nothing--- >>> parseVersion "0.-1.0"--- Nothing--- >>> parseVersion "0.0.-1"--- Nothing------ Non-digits are not allowed.------ >>> parseVersion "a.0.0"--- Nothing--- >>> parseVersion "0.a.0"--- Nothing--- >>> parseVersion "0.0.a"--- Nothing------ Partial version numbers are not allowed.------ >>> parseVersion "0.0"--- Nothing------ Extra version numbers are not allowed.------ >>> parseVersion "0.0.0.0"--- Nothing------ Spaces are allowed------ >>> parseVersion " 0.0.0"--- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})--- >>> parseVersion "0.0.0 "--- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})------ Interior spaces are not allowed.------ >>> parseVersion "0 .0.0"--- 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.------ >>> parseConstraint "1.2"--- Nothing------ Wildcards (also known as "x-ranges") are allowed. The exact character used--- for the wildcard is not round-tripped.------ >>> renderConstraint <$> parseConstraint "1.2.x"--- Just "1.2.x"--- >>> renderConstraint <$> parseConstraint "1.2.X"--- Just "1.2.x"--- >>> renderConstraint <$> parseConstraint "1.2.*"--- Just "1.2.x"------ An optional equals sign can be included with wildcard constraints.------ >>> renderConstraint <$> parseConstraint "=1.2.x"--- Just "1.2.x"------ Wildcards can be combined with other constraints.------ >>> renderConstraint <$> parseConstraint "1.2.x 2.3.4"--- Just "1.2.x 2.3.4"--- >>> renderConstraint <$> parseConstraint "1.2.x || 2.3.4"--- Just "1.2.x || 2.3.4"------ Wildcards are allowed at any position.------ >>> renderConstraint <$> parseConstraint "1.2.x"--- Just "1.2.x"--- >>> renderConstraint <$> parseConstraint "1.x.x"--- Just "1.x.x"--- >>> renderConstraint <$> parseConstraint "x.x.x"--- Just "x.x.x"------ Non-wildcards cannot come after wildcards.------ >>> parseConstraint "1.x.3"--- Nothing--- >>> parseConstraint "x.2.3"--- Nothing--- >>> parseConstraint "x.x.3"--- Nothing--- >>> parseConstraint "x.2.x"--- Nothing------ Wildcards cannot be used with other operators.------ >>> parseConstraint "<1.2.x"--- Nothing--- >>> parseConstraint "<=1.2.x"--- Nothing--- >>> parseConstraint ">=1.2.x"--- Nothing--- >>> parseConstraint ">1.2.x"--- Nothing--- >>> parseConstraint "~1.2.x"--- Nothing--- >>> parseConstraint "^1.2.x"--- Nothing--- >>> parseConstraint "1.2.x - 2.3.4"--- Nothing--- >>> parseConstraint "1.2.3 - 2.3.x"--- Nothing------ Spaces are allowed in most places. Extra spaces are not round-tripped.------ >>> renderConstraint <$> parseConstraint " 1.2.3 "--- Just "1.2.3"--- >>> renderConstraint <$> parseConstraint "> 1.2.3"--- Just ">1.2.3"--- >>> renderConstraint <$> parseConstraint "1.2.3 - 2.3.4"--- Just "1.2.3 - 2.3.4"--- >>> renderConstraint <$> parseConstraint "1.2.3 2.3.4"--- Just "1.2.3 2.3.4"--- >>> renderConstraint <$> parseConstraint "1.2.3 || 2.3.4"--- Just "1.2.3 || 2.3.4"------ Parentheses are not allowed. Note that combining two constraints with a--- space (and) has higher precedence than combining them with pipes (or). In--- other words, @"a b || c"@ parses as @"(a b) || c"@, not @"a (b || c)"@.------ >>> parseConstraint "(1.2.3)"--- Nothing--- >>> parseConstraint "(1.2.3 || >1.2.3) <1.3.0"--- Nothing--- >>> parseConstraint "(>1.2.3 <1.3.0) || 1.2.3"--- Nothing------ Most constraints can be round-tripped through parsing and rendering.------ >>> renderConstraint <$> parseConstraint "<1.2.3"--- Just "<1.2.3"--- >>> renderConstraint <$> parseConstraint "<=1.2.3"--- Just "<=1.2.3"--- >>> renderConstraint <$> parseConstraint "1.2.3"--- Just "1.2.3"--- >>> renderConstraint <$> parseConstraint ">=1.2.3"--- Just ">=1.2.3"--- >>> renderConstraint <$> parseConstraint ">1.2.3"--- Just ">1.2.3"--- >>> renderConstraint <$> parseConstraint "1.2.3 - 2.3.4"--- Just "1.2.3 - 2.3.4"--- >>> renderConstraint <$> parseConstraint "~1.2.3"--- Just "~1.2.3"--- >>> renderConstraint <$> parseConstraint "^1.2.3"--- Just "^1.2.3"--- >>> renderConstraint <$> parseConstraint ">1.2.3 <2.0.0"--- Just ">1.2.3 <2.0.0"--- >>> renderConstraint <$> parseConstraint "1.2.3 || >1.2.3"--- Just "1.2.3 || >1.2.3"------ Explicit equal signs do not get round-tripped.------ >>> renderConstraint <$> parseConstraint "=1.2.3"--- Just "1.2.3"------ Pre-releases and builds are allowed on any constraints except wildcards.------ >>> renderConstraint <$> parseConstraint "1.2.3-p+b"--- Just "1.2.3-p+b"--- >>> renderConstraint <$> parseConstraint ">1.2.3-p+b"--- Just ">1.2.3-p+b"--- >>> renderConstraint <$> parseConstraint "1.2.3-p+b - 2.3.4-p+b"--- Just "1.2.3-p+b - 2.3.4-p+b"--- >>> renderConstraint <$> parseConstraint "~1.2.3-p+b"--- Just "~1.2.3-p+b"--- >>> renderConstraint <$> parseConstraint "^1.2.3-p+b"--- Just "^1.2.3-p+b"------ >>> parseConstraint "1.2.x-p+b"--- Nothing------ These examples show every type of constraint in a single expression.------ >>> renderConstraint <$> parseConstraint "<1.2.0 <=1.2.1 =1.2.2 >=1.2.3 >1.2.4 1.2.5 1.2.6 - 1.2.7 ~1.2.8 ^1.2.9 1.2.x"--- Just "<1.2.0 <=1.2.1 1.2.2 >=1.2.3 >1.2.4 1.2.5 1.2.6 - 1.2.7 ~1.2.8 ^1.2.9 1.2.x"--- >>> renderConstraint <$> parseConstraint "<1.2.0 <=1.2.1 || =1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9 1.2.x"--- Just "<1.2.0 <=1.2.1 || 1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9 1.2.x"--- >>> renderConstraint <$> parseConstraint "<1.2.0 || <=1.2.1 =1.2.2 || >=1.2.3 >1.2.4 || 1.2.5 1.2.6 - 1.2.7 || ~1.2.8 ^1.2.9 || 1.2.x"--- Just "<1.2.0 || <=1.2.1 1.2.2 || >=1.2.3 >1.2.4 || 1.2.5 1.2.6 - 1.2.7 || ~1.2.8 ^1.2.9 || 1.2.x"--- >>> renderConstraint <$> parseConstraint "<1.2.0 || <=1.2.1 || =1.2.2 || >=1.2.3 || >1.2.4 || 1.2.5 || 1.2.6 - 1.2.7 || ~1.2.8 || ^1.2.9 || 1.2.x"--- Just "<1.2.0 || <=1.2.1 || 1.2.2 || >=1.2.3 || >1.2.4 || 1.2.5 || 1.2.6 - 1.2.7 || ~1.2.8 || ^1.2.9 || 1.2.x"---- ** Satisfying constraints--- | Although in general you should use 'satisfiesConstraint', 'parseVersion',--- and 'parseConstraint', doing that here makes it hard to tell what the--- examples are doing. An operator makes things clearer.------ >>> satisfiesConstraint <$> parseConstraint "=1.2.3" <*> parseVersion "1.2.3"--- Just True--- >>> let version ? constraint = satisfiesConstraint (unsafeParseConstraint constraint) (unsafeParseVersion version)--- >>> "1.2.3" ? "=1.2.3"--- True------ - Less than:------ >>> "1.2.2" ? "<1.2.3"--- True--- >>> "1.2.3" ? "<1.2.3"--- False--- >>> "1.2.4" ? "<1.2.3"--- False--- >>> "1.2.3-pre" ? "<1.2.3"--- True------ - Less than or equal to:------ >>> "1.2.2" ? "<=1.2.3"--- True--- >>> "1.2.3" ? "<=1.2.3"--- True--- >>> "1.2.4" ? "<=1.2.3"--- False------ - Equal to:------ >>> "1.2.2" ? "=1.2.3"--- False--- >>> "1.2.3" ? "=1.2.3"--- True--- >>> "1.2.4" ? "=1.2.3"--- False--- >>> "1.2.3-pre" ? "=1.2.3"--- False--- >>> "1.2.3+build" ? "=1.2.3"--- True------ - Greater than or equal to:------ >>> "1.2.2" ? ">=1.2.3"--- False--- >>> "1.2.3" ? ">=1.2.3"--- True--- >>> "1.2.4" ? ">=1.2.3"--- True------ - Greater than:------ >>> "1.2.2" ? ">1.2.3"--- False--- >>> "1.2.3" ? ">1.2.3"--- False--- >>> "1.2.4" ? ">1.2.3"--- True--- >>> "1.2.4-pre" ? ">1.2.3"--- True------ >>> "1.2.4" ? ">1.2.3-pre"--- True------ - And:------ >>> "1.2.3" ? ">1.2.3 <1.2.5"--- False--- >>> "1.2.4" ? ">1.2.3 <1.2.5"--- True--- >>> "1.2.5" ? ">1.2.3 <1.2.5"--- False------ - Or:------ >>> "1.2.2" ? "1.2.3 || 1.2.4"--- False--- >>> "1.2.3" ? "1.2.3 || 1.2.4"--- True--- >>> "1.2.4" ? "1.2.3 || 1.2.4"--- True--- >>> "1.2.5" ? "1.2.3 || 1.2.4"--- False------ - And & or:------ >>> "1.2.2" ? "1.2.2 || >1.2.3 <1.3.0"--- True--- >>> "1.2.3" ? "1.2.2 || >1.2.3 <1.3.0"--- False--- >>> "1.2.4" ? "1.2.2 || >1.2.3 <1.3.0"--- True--- >>> "1.3.0" ? "1.2.2 || >1.2.3 <1.3.0"--- False------ - Hyphen:------ >>> "1.2.2" ? "1.2.3 - 1.2.4"--- False--- >>> "1.2.3" ? "1.2.3 - 1.2.4"--- True--- >>> "1.2.4" ? "1.2.3 - 1.2.4"--- True--- >>> "1.2.5" ? "1.2.3 - 1.2.4"--- False------ - Tilde:------ >>> "1.2.2" ? "~1.2.3"--- False--- >>> "1.2.3" ? "~1.2.3"--- True--- >>> "1.2.4" ? "~1.2.3"--- True--- >>> "1.3.0" ? "~1.2.3"--- False------ - Caret:------ >>> "1.2.2" ? "^1.2.3"--- False--- >>> "1.2.3" ? "^1.2.3"--- True--- >>> "1.2.4" ? "^1.2.3"--- True--- >>> "1.3.0" ? "^1.2.3"--- True--- >>> "2.0.0" ? "^1.2.3"--- False------ >>> "0.2.2" ? "^0.2.3"--- False--- >>> "0.2.3" ? "^0.2.3"--- True--- >>> "0.2.4" ? "^0.2.3"--- True--- >>> "0.3.0" ? "^0.2.3"--- False------ >>> "0.0.2" ? "^0.0.3"--- False--- >>> "0.0.3" ? "^0.0.3"--- True--- >>> "0.0.4" ? "^0.0.3"--- False------ - Wildcard:------ >>> "1.1.0" ? "1.2.x"--- False--- >>> "1.2.3" ? "1.2.x"--- True--- >>> "1.3.0" ? "1.2.x"--- False------ >>> "0.1.0" ? "1.x.x"--- False--- >>> "1.0.0" ? "1.x.x"--- True--- >>> "1.2.3" ? "1.x.x"--- True--- >>> "2.0.0" ? "1.x.x"--- False------ >>> "0.0.0" ? "x.x.x"--- True--- >>> "1.2.3" ? "x.x.x"--- True--- >>> "2.0.0" ? "x.x.x"--- True-) where--import qualified Salve.Internal as Salve---- $setup--- >>> import Control.Applicative--- >>> import Salve
− library/Salve/Internal.hs
@@ -1,1006 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}---- | WARNING: This module should be considered private! If you find yourself--- wanting to import something from this module, please open an issue to get--- that thing exported from "Salve".-module Salve.Internal- ( Version(..)- , PreRelease(..)- , Build(..)- , Constraint(..)- , makeVersion- , initialVersion- , parseVersion- , parsePreRelease- , parseBuild- , parseConstraint- , unsafeParseVersion- , unsafeParsePreRelease- , unsafeParseBuild- , unsafeParseConstraint- , renderVersion- , renderPreRelease- , renderBuild- , renderConstraint- , isUnstable- , isStable- , fromBaseVersion- , toBaseVersion- , bumpMajor- , bumpMinor- , bumpPatch- , satisfiesConstraint- , majorLens- , minorLens- , patchLens- , preReleasesLens- , buildsLens- , Operator(..)- , Wildcard(..)- , constraintLT- , constraintLE- , constraintEQ- , constraintGE- , constraintGT- , constraintAnd- , constraintOr- , constraintHyphen- , constraintTilde- , constraintCaret- ) where--import qualified Control.Monad as Monad-import qualified Data.Char as Char-import qualified Data.Data as Data-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.Version as Version-import qualified Data.Word as Word-import qualified GHC.Generics as Generics-import qualified Text.ParserCombinators.ReadP as ReadP---- $setup------ >>> import Control.Applicative (Const(..))--- >>> let view lens record = getConst (lens Const record)------ >>> import Data.Functor.Identity (Identity(..))--- >>> let set lens field record = runIdentity (lens (const (Identity field)) record)------ >>> import Control.Applicative ((<$>), (<*>))---- * Public---- | A semantic version number. Versions have five parts:------ 1. 'majorLens': The major version number.--- 2. 'minorLens': The minor version number.--- 3. 'patchLens': The patch version number.--- 4. 'preReleasesLens': A list of pre-release identifiers.--- 5. 'buildsLens': A list of build metadata.------ Use 'parseVersion' to create versions.-data Version = Version- { versionMajor :: Word.Word64- , versionMinor :: Word.Word64- , versionPatch :: Word.Word64- , versionPreReleases :: [PreRelease]- , versionBuilds :: [Build]- } deriving (Data.Data, Eq, Generics.Generic, Read, Show)---- | 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"--- Just LT--- >>> compare <$> parseVersion "1.2.3" <*> parseVersion "1.3.0"--- Just LT--- >>> compare <$> parseVersion "1.2.3" <*> parseVersion "1.2.4"--- Just LT------ Numbers are compared numerically, not alphabetically.------ >>> compare <$> parseVersion "0.0.9" <*> parseVersion "0.0.10"--- Just LT------ If all the numbers are the same, the pre-releases are compared.------ >>> compare <$> parseVersion "1.2.3-a" <*> parseVersion "1.2.3-b"--- Just LT------ A version with a pre-release is always less than a version without one as--- long as the other parts are the same.------ >>> compare <$> parseVersion "1.2.3-pre" <*> parseVersion "1.2.3"--- Just LT--- >>> compare <$> parseVersion "1.2.4-pre" <*> parseVersion "1.2.3"--- Just GT------ Builds are not considered when comparing versions.------ >>> compare <$> parseVersion "1.2.3+a" <*> parseVersion "1.2.3+b"--- Just EQ--- >>> (==) <$> parseVersion "1.2.3+a" <*> parseVersion "1.2.3+b"--- Just False-instance Ord Version where- compare x y = Monoid.mconcat- [ Ord.comparing versionMajor x y- , Ord.comparing versionMinor x y- , Ord.comparing versionPatch x y- , case both versionPreReleases (x, y) of- ([], []) -> EQ- ([], _) -> GT- (_, []) -> LT- (p, q) -> compare p q- ]---- | Pre-release information attached to a version. These can either be numeric--- or textual. They must not be empty.------ - Numeric: Can be any non-negative integer. Cannot have leading zeros.------ - Textual: Can be any string of ASCII digits, letters, or hyphens. Cannot be--- all digits, as that would be numeric.------ In general, pre-releases must match the regular expression--- @\/^[-0-9A-Za-z]+$\/@.------ Use 'parsePreRelease' to create pre-releases.-data PreRelease- = PreReleaseNumeric Word.Word64- | PreReleaseTextual String- deriving (Data.Data, Eq, Generics.Generic, Read, Show)---- | Numeric pre-releases are always less than textual pre-releases.------ >>> compare <$> parsePreRelease "1" <*> parsePreRelease "a"--- Just LT------ Numeric pre-releases are compared numerically.------ >>> compare <$> parsePreRelease "9" <*> parsePreRelease "10"--- Just LT------ Textual pre-releases are compared alphabetically.------ >>> compare <$> parsePreRelease "p10" <*> parsePreRelease "p9"--- Just LT-instance Ord PreRelease where- compare x y = case (x, y) of- (PreReleaseNumeric n, PreReleaseNumeric m) -> compare n m- (PreReleaseNumeric _, PreReleaseTextual _) -> LT- (PreReleaseTextual _, PreReleaseNumeric _) -> GT- (PreReleaseTextual s, PreReleaseTextual t) -> compare s t---- | Build metadata attached to a version. These are similar to--- 'PreRelease's with some key differences:------ 1. There is no such thing as numeric builds. Even though builds can look--- like numbers, all builds are textual.--- 2. As a result, builds that look numeric are allowed to have leading zeros.--- 3. Builds cannot be compared. That is, they do not have an 'Ord' instance.------ Use 'parseBuild' to create builds.-newtype Build = Build String- deriving (Data.Data, Eq, Generics.Generic, Read, Show)---- | Constrains allowable version numbers.------ Use 'parseConstraint' to create constraints and 'satisfiesConstraint' to see--- if a version number satisfies a constraint.-data Constraint- = ConstraintOperator Operator Version- | ConstraintHyphen Version Version- | ConstraintWildcard Wildcard- | ConstraintAnd Constraint Constraint- | ConstraintOr Constraint Constraint- deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)---- | Makes a new version number.------ >>> 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.Word64 -> Word.Word64 -> Word.Word64 -> [PreRelease] -> [Build] -> Version-makeVersion major minor patch preReleases builds = Version- { versionMajor = major- , versionMinor = minor- , versionPatch = patch- , versionPreReleases = preReleases- , versionBuilds = builds- }---- | The initial version number for development.------ >>> initialVersion--- Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []}-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).------ >>> 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"]})------ Returns 'Nothing' if the parse fails.------ >>> parseVersion "wrong"--- Nothing------ Whitespace is allowed.------ >>> parseVersion " 1.2.3 "--- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})-parseVersion :: String -> Maybe Version-parseVersion s = parse- (do- ReadP.skipSpaces- version <- versionP- ReadP.skipSpaces- return version)- s---- | Attempts to parse a pre-release.------ >>> parsePreRelease "pre"--- Just (PreReleaseTextual "pre")--- >>> parsePreRelease "1"--- Just (PreReleaseNumeric 1)------ Returns 'Nothing' if the parse fails.------ >>> parsePreRelease "wrong!"--- Nothing------ Numeric pre-releases cannot contain leading zeros.------ >>> parsePreRelease "01"--- Nothing-parsePreRelease :: String -> Maybe PreRelease-parsePreRelease s = parse preReleaseP s---- | Attempts to parse a build.------ >>> parseBuild "build"--- Just (Build "build")--- >>> parseBuild "1"--- Just (Build "1")------ Returns 'Nothing' if the parse fails.------ >>> parseBuild "wrong!"--- Nothing------ Unlike pre-releases, numeric builds can have leading zeros.------ >>> parseBuild "01"--- Just (Build "01")-parseBuild :: String -> Maybe Build-parseBuild s = parse buildP s---- | 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 = []}))------ Returns 'Nothing' if the parse fails.------ >>> 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---- | Parses a version.------ >>> unsafeParseVersion "1.2.3-p.4+b.5"--- Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [PreReleaseTextual "p",PreReleaseNumeric 4], versionBuilds = [Build "b",Build "5"]}------ Raises an exception if the parse fails.------ >>> unsafeParseVersion "wrong"--- *** Exception: unsafeParseVersion: invalid version: "wrong"--- ...------ See 'parseVersion' for a safe version of this function.-unsafeParseVersion :: String -> Version-unsafeParseVersion s = case parseVersion s of- Nothing -> error ("unsafeParseVersion: invalid version: " ++ show s)- Just v -> v---- | Parses a pre-release.------ >>> unsafeParsePreRelease "pre"--- PreReleaseTextual "pre"------ Raises an exception if the parse fails.------ >>> unsafeParsePreRelease "wrong!"--- *** Exception: unsafeParsePreRelease: invalid pre-release: "wrong!"--- ...------ See 'parsePreRelease' for a safe version of this function.-unsafeParsePreRelease :: String -> PreRelease-unsafeParsePreRelease s = case parsePreRelease s of- Nothing -> error ("unsafeParsePreRelease: invalid pre-release: " ++ show s)- Just p -> p---- | Parses a build.------ >>> unsafeParseBuild "build"--- Build "build"------ Raises an exception if the parse fails.------ >>> unsafeParseBuild "wrong!"--- Build "*** Exception: unsafeParseBuild: invalid build: "wrong!"--- ...------ See 'parseBuild' for a safe version of this function.-unsafeParseBuild :: String -> Build-unsafeParseBuild s = case parseBuild s of- Nothing -> error ("unsafeParseBuild: invalid build: " ++ show s)- Just b -> b---- | Parses a constraint.------ >>> unsafeParseConstraint ">1.2.3"--- ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})------ Raises an exception if the parse fails.------ >>> unsafeParseConstraint "wrong"--- *** Exception: unsafeParseConstraint: invalid constraint: "wrong"--- ...------ See 'parseConstraint' for a safe version of this function.-unsafeParseConstraint :: String -> Constraint-unsafeParseConstraint s = case parseConstraint s of- Nothing -> error ("unsafeParseConstraint: invalid constraint: " ++ show s)- Just c -> c---- | Renders a version.------ >>> renderVersion <$> parseVersion "1.2.3-p.4+b.5"--- Just "1.2.3-p.4+b.5"-renderVersion :: Version -> String-renderVersion v = concat- [ show (versionMajor v)- , "."- , show (versionMinor v)- , "."- , show (versionPatch v)- , renderPreReleases (versionPreReleases v)- , renderBuilds (versionBuilds v)- ]---- | Renders a pre-release.------ >>> renderPreRelease <$> parsePreRelease "pre"--- Just "pre"--- >>> renderPreRelease <$> parsePreRelease "1"--- Just "1"-renderPreRelease :: PreRelease -> String-renderPreRelease p = case p of- PreReleaseNumeric n -> show n- PreReleaseTextual s -> s---- | Renders a build.------ >>> renderBuild <$> parseBuild "build"--- Just "build"--- >>> renderBuild <$> parseBuild "1"--- Just "1"-renderBuild :: Build -> String-renderBuild (Build b) = b---- | Renders a constraint.------ >>> renderConstraint <$> parseConstraint ">1.2.3"--- Just ">1.2.3"------ Parsing and rendering a constraint doesn't always return what you started--- with.------ >>> renderConstraint <$> parseConstraint "=1.2.3"--- Just "1.2.3"-renderConstraint :: Constraint -> String-renderConstraint c = case c of- ConstraintOperator o v ->- let s = renderVersion v- in case o of- OperatorLT -> '<' : s- OperatorLE -> '<' : '=' : s- OperatorEQ -> s- OperatorGE -> '>' : '=' : s- OperatorGT -> '>' : s- OperatorTilde -> '~' : s- OperatorCaret -> '^' : s- ConstraintHyphen l r -> unwords [renderVersion l, "-", renderVersion r]- ConstraintWildcard w -> case w of- WildcardMajor -> "x.x.x"- WildcardMinor m -> show m ++ ".x.x"- WildcardPatch m n -> List.intercalate "." [show m, show n, "x"]- ConstraintAnd l r -> unwords (map renderConstraint [l, r])- ConstraintOr l r -> unwords [renderConstraint l, "||", renderConstraint r]---- | Returns 'True' if the major version number is zero, 'False' otherwise.------ >>> isUnstable <$> parseVersion "0.1.2"--- Just True--- >>> isUnstable <$> parseVersion "1.0.0"--- Just False-isUnstable :: Version -> Bool-isUnstable v = versionMajor v == 0---- | Returns 'True' if the major version number is not zero, 'False' otherwise.------ >>> isStable <$> parseVersion "1.0.0"--- Just True--- >>> isStable <$> parseVersion "0.1.2"--- Just False-isStable :: Version -> Bool-isStable v = not (isUnstable v)---- | Converts from a 'Version.Version' from the @base@ package.------ >>> renderVersion . fromBaseVersion $ Version.makeVersion [1, 2, 3]--- "1.2.3"------ Missing version components are set to zero.------ >>> renderVersion . fromBaseVersion $ Version.makeVersion []--- "0.0.0"--- >>> renderVersion . fromBaseVersion $ Version.makeVersion [1]--- "1.0.0"--- >>> renderVersion . fromBaseVersion $ Version.makeVersion [1, 2]--- "1.2.0"------ Extra version components are ignored.------ >>> renderVersion . fromBaseVersion $ Version.makeVersion [1, 2, 3, 4]--- "1.2.3"------ Tags are ignored.------ >>> renderVersion . fromBaseVersion $ Version.Version [] ["ignored"]--- "0.0.0"-fromBaseVersion :: Version.Version -> Version-fromBaseVersion v = case Version.versionBranch v of- (m : n : p : _) -> mkV (fromIntegral m) (fromIntegral n) (fromIntegral p)- (m : n : _) -> mkV (fromIntegral m) (fromIntegral n) 0- (m : _) -> mkV (fromIntegral m) 0 0- _ -> mkV 0 0 0---- | Converts to a 'Version.Version' from the @base@ package.------ >>> toBaseVersion <$> parseVersion "1.2.3"--- Just (Version {versionBranch = [1,2,3], versionTags = []})------ Pre-releases and builds are converted to tags.------ >>> toBaseVersion <$> parseVersion "1.2.3-pre+build"--- Just (Version {versionBranch = [1,2,3], versionTags = ["pre","build"]})-toBaseVersion :: Version -> Version.Version-toBaseVersion v = Version.Version- (map fromIntegral- [ versionMajor v- , versionMinor v- , versionPatch v- ])- (concat- [ map renderPreRelease (versionPreReleases v)- , map renderBuild (versionBuilds v)- ])---- | Increments the major version number.------ >>> bumpMajor <$> parseVersion "0.0.0"--- Just (Version {versionMajor = 1, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})------ The minor and patch numbers are reset to zero.------ >>> bumpMajor <$> parseVersion "1.2.3"--- Just (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})------ The pre-releases and builds are removed.------ >>> bumpMajor <$> parseVersion "0.0.0-pre+build"--- Just (Version {versionMajor = 1, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})------ Consider using 'majorLens' if you want to arbitrarily change the major--- number, or if you don't want the other parts of the version to change.-bumpMajor :: Version -> Version-bumpMajor v = makeVersion (versionMajor v + 1) 0 0 [] []---- | Increments the minor version number.------ >>> bumpMinor <$> parseVersion "0.0.0"--- Just (Version {versionMajor = 0, versionMinor = 1, versionPatch = 0, versionPreReleases = [], versionBuilds = []})------ The patch number is reset to zero.------ >>> bumpMinor <$> parseVersion "1.2.3"--- Just (Version {versionMajor = 1, versionMinor = 3, versionPatch = 0, versionPreReleases = [], versionBuilds = []})------ The pre-releases and builds are removed.------ >>> bumpMinor <$> parseVersion "0.0.0-pre+build"--- Just (Version {versionMajor = 0, versionMinor = 1, versionPatch = 0, versionPreReleases = [], versionBuilds = []})------ Consider using 'minorLens' if you want to arbitrarily change the minor--- number, or if you don't want the other parts of the version to change.-bumpMinor :: Version -> Version-bumpMinor v = makeVersion (versionMajor v) (versionMinor v + 1) 0 [] []---- | Increments the patch number.------ >>> bumpPatch <$> parseVersion "0.0.0"--- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 1, versionPreReleases = [], versionBuilds = []})------ The major and minor numbers are not changed.------ >>> bumpPatch <$> parseVersion "1.2.3"--- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 4, versionPreReleases = [], versionBuilds = []})------ The pre-releases and builds are removed.------ >>> bumpPatch <$> parseVersion "0.0.0-pre+build"--- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 1, versionPreReleases = [], versionBuilds = []})------ Consider using 'patchLens' if you want to arbitrarily change the patch--- number, or if you don't want the other parts of the version to change.-bumpPatch :: Version -> Version-bumpPatch v = makeVersion- (versionMajor v) (versionMinor v) (versionPatch v + 1) [] []---- | Returns 'True' if the version satisfies the constraint, 'False' otherwise.------ >>> satisfiesConstraint <$> parseConstraint ">1.2.0" <*> parseVersion "1.2.3"--- Just True-satisfiesConstraint :: Constraint -> Version -> Bool-satisfiesConstraint c v = satisfiesSC (toSC c) v---- | Focuses on the major version number.------ >>> view majorLens <$> parseVersion "1.2.3-pre.4+build.5"--- Just 1--- >>> set majorLens 4 <$> parseVersion "1.2.3"--- Just (Version {versionMajor = 4, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})-majorLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version-majorLens f v = fmap- (\ m -> v { versionMajor = m })- (f (versionMajor v))---- | Focuses on the minor version number.------ >>> view minorLens <$> parseVersion "1.2.3-pre.4+build.5"--- Just 2--- >>> set minorLens 4 <$> parseVersion "1.2.3"--- Just (Version {versionMajor = 1, versionMinor = 4, versionPatch = 3, versionPreReleases = [], versionBuilds = []})-minorLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version-minorLens f v = fmap- (\ n -> v { versionMinor = n })- (f (versionMinor v))---- | Focuses on the patch version number.------ >>> view patchLens <$> parseVersion "1.2.3-pre.4+build.5"--- Just 3--- >>> set patchLens 4 <$> parseVersion "1.2.3"--- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 4, versionPreReleases = [], versionBuilds = []})-patchLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version-patchLens f v = fmap- (\ p -> v { versionPatch = p })- (f (versionPatch v))---- | Focuses on the pre-release identifiers.------ >>> view preReleasesLens <$> parseVersion "1.2.3-pre.4+build.5"--- Just [PreReleaseTextual "pre",PreReleaseNumeric 4]--- >>> set preReleasesLens [] <$> parseVersion "1.2.3-pre"--- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})-preReleasesLens :: Functor f => ([PreRelease] -> f [PreRelease]) -> Version -> f Version-preReleasesLens f v = fmap- (\ ps -> v { versionPreReleases = ps })- (f (versionPreReleases v))---- | Focuses on the build metadata.------ >>> view buildsLens <$> parseVersion "1.2.3-pre.4+build.5"--- Just [Build "build",Build "5"]--- >>> set buildsLens [] <$> parseVersion "1.2.3+build"--- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})-buildsLens :: Functor f => ([Build] -> f [Build]) -> Version -> f Version-buildsLens f v = fmap- (\ bs -> v { versionBuilds = bs })- (f (versionBuilds v))---- * Private---- ** Types--data Operator- = OperatorLT- | OperatorLE- | OperatorEQ- | OperatorGE- | OperatorGT- | OperatorTilde- | OperatorCaret- deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)--data Wildcard- = WildcardMajor- | WildcardMinor Word.Word64- | WildcardPatch Word.Word64 Word.Word64- deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)---- ** Constructors---- | Makes a new constraint that must be less than the version number.------ >>> constraintLT <$> parseVersion "1.2.3"--- Just (ConstraintOperator OperatorLT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))--- >>> parseConstraint "<1.2.3"--- Just (ConstraintOperator OperatorLT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))-constraintLT :: Version -> Constraint-constraintLT v = ConstraintOperator OperatorLT v---- | Makes a new constraint that must be less than or euqal to the version--- number.------ >>> constraintLE <$> parseVersion "1.2.3"--- Just (ConstraintOperator OperatorLE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))--- >>> parseConstraint "<=1.2.3"--- Just (ConstraintOperator OperatorLE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))-constraintLE :: Version -> Constraint-constraintLE v = ConstraintOperator OperatorLE v---- | Makes a new constraint that must be equal to the version number.------ >>> constraintEQ <$> parseVersion "1.2.3"--- Just (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))--- >>> parseConstraint "=1.2.3"--- Just (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))-constraintEQ :: Version -> Constraint-constraintEQ v = ConstraintOperator OperatorEQ v---- | Makes a new constraint that must be greater than or equal to the version--- number.------ >>> constraintGE <$> parseVersion "1.2.3"--- Just (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))--- >>> parseConstraint ">=1.2.3"--- Just (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))-constraintGE :: Version -> Constraint-constraintGE v = ConstraintOperator OperatorGE v---- | Makes a new constraint that must be greater than the version number.------ >>> constraintGT <$> parseVersion "1.2.3"--- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))--- >>> parseConstraint ">1.2.3"--- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))-constraintGT :: Version -> Constraint-constraintGT v = ConstraintOperator OperatorGT v---- | Makes a new constraint that must satisfy both constraints.------ >>> constraintAnd <$> (constraintGE <$> parseVersion "1.2.3") <*> (constraintLT <$> parseVersion "2.0.0")--- Just (ConstraintAnd (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorLT (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})))--- >>> parseConstraint ">=1.2.3 <2.0.0"--- Just (ConstraintAnd (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorLT (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})))-constraintAnd :: Constraint -> Constraint -> Constraint-constraintAnd l r = ConstraintAnd l r---- | Makes a new constraint that must satisfy either constraint.------ >>> constraintOr <$> (constraintEQ <$> parseVersion "1.2.3") <*> (constraintGT <$> parseVersion "1.2.3")--- Just (ConstraintOr (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})))--- >>> parseConstraint "=1.2.3 || >1.2.3"--- Just (ConstraintOr (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})))-constraintOr :: Constraint -> Constraint -> Constraint-constraintOr l r = ConstraintOr l r---- | Makes a new constraint that must be between the versions, inclusive.------ >>> constraintHyphen <$> parseVersion "1.2.3" <*> parseVersion "2.3.4"--- Just (ConstraintHyphen (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}) (Version {versionMajor = 2, versionMinor = 3, versionPatch = 4, versionPreReleases = [], versionBuilds = []}))--- >>> parseConstraint "1.2.3 - 2.3.4"--- Just (ConstraintHyphen (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}) (Version {versionMajor = 2, versionMinor = 3, versionPatch = 4, versionPreReleases = [], versionBuilds = []}))-constraintHyphen :: Version -> Version -> Constraint-constraintHyphen v w = ConstraintHyphen v w---- | Makes a new constraint that allows changes to the patch version number.------ >>> constraintTilde <$> parseVersion "1.2.3"--- Just (ConstraintOperator OperatorTilde (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))--- >>> parseConstraint "~1.2.3"--- Just (ConstraintOperator OperatorTilde (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))-constraintTilde :: Version -> Constraint-constraintTilde v = ConstraintOperator OperatorTilde v---- | Makes a new constraint that allows changes that do not modify the--- left-most non-zero version number.------ >>> constraintCaret <$> parseVersion "1.2.3"--- Just (ConstraintOperator OperatorCaret (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))--- >>> parseConstraint "^1.2.3"--- Just (ConstraintOperator OperatorCaret (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))-constraintCaret :: Version -> Constraint-constraintCaret v = ConstraintOperator OperatorCaret v---- ** Parsing--versionP :: ReadP.ReadP Version-versionP = do- major <- numberP- Monad.void (ReadP.char '.')- minor <- numberP- Monad.void (ReadP.char '.')- patch <- numberP- preReleases <- preReleasesP- builds <- buildsP- return (makeVersion major minor patch preReleases builds)--preReleasesP :: ReadP.ReadP [PreRelease]-preReleasesP = ReadP.option [] (do- Monad.void (ReadP.char '-')- ReadP.sepBy1 preReleaseP (ReadP.char '.'))--preReleaseP :: ReadP.ReadP PreRelease-preReleaseP = preReleaseNumberP ReadP.<++ preReleaseStringP--preReleaseNumberP :: ReadP.ReadP PreRelease-preReleaseNumberP = do- n <- numberP- return (PreReleaseNumeric n)--preReleaseStringP :: ReadP.ReadP PreRelease-preReleaseStringP = do- s <- ReadP.munch1 isIdentifier- if all Char.isDigit s- then ReadP.pfail- else return (PreReleaseTextual s)--buildsP :: ReadP.ReadP [Build]-buildsP = ReadP.option [] (do- Monad.void (ReadP.char '+')- ReadP.sepBy1 buildP (ReadP.char '.'))--buildP :: ReadP.ReadP Build-buildP = do- b <- ReadP.munch1 isIdentifier- return (Build b)--numberP :: ReadP.ReadP Word.Word64-numberP = zeroP ReadP.<++ nonZeroP--zeroP :: ReadP.ReadP Word.Word64-zeroP = do- Monad.void (ReadP.char '0')- return 0--nonZeroP :: ReadP.ReadP Word.Word64-nonZeroP = do- x <- ReadP.satisfy isAsciiDigitNonZero- ys <- ReadP.munch Char.isDigit- case toWord64 (stringToIntegral (x : ys)) of- Nothing -> ReadP.pfail- Just n -> return n--constraintsP :: ReadP.ReadP Constraint-constraintsP = do- spacesP- cs <- ReadP.sepBy1 constraintP orP- spacesP- return (foldr1 constraintOr cs)--constraintP :: ReadP.ReadP Constraint-constraintP = do- cs <- ReadP.sepBy1 simpleP spaces1P- return (foldr1 constraintAnd cs)--hyphenatedP :: ReadP.ReadP Constraint-hyphenatedP = do- v <- versionP- hyphenP- w <- versionP- return (constraintHyphen v w)--simpleP :: ReadP.ReadP Constraint-simpleP = ReadP.choice [hyphenatedP, wildcardConstraintP, primitiveP]--wildcardConstraintP :: ReadP.ReadP Constraint-wildcardConstraintP = do- ReadP.optional (ReadP.char '=')- w <- wildcardP- return (ConstraintWildcard w)--wildcardP :: ReadP.ReadP Wildcard-wildcardP = ReadP.choice [wildcardPatchP, wildcardMinorP, wildcardMajorP]--wildcardPatchP :: ReadP.ReadP Wildcard-wildcardPatchP = do- m <- numberP- Monad.void (ReadP.char '.')- n <- numberP- Monad.void (ReadP.char '.')- Monad.void (ReadP.satisfy isWildcard)- return (WildcardPatch m n)--wildcardMinorP :: ReadP.ReadP Wildcard-wildcardMinorP = do- m <- numberP- Monad.void (ReadP.char '.')- Monad.void (ReadP.satisfy isWildcard)- Monad.void (ReadP.char '.')- Monad.void (ReadP.satisfy isWildcard)- return (WildcardMinor m)--wildcardMajorP :: ReadP.ReadP Wildcard-wildcardMajorP = do- Monad.void (ReadP.satisfy isWildcard)- Monad.void (ReadP.char '.')- Monad.void (ReadP.satisfy isWildcard)- Monad.void (ReadP.char '.')- Monad.void (ReadP.satisfy isWildcard)- return WildcardMajor--primitiveP :: ReadP.ReadP Constraint-primitiveP = do- o <- operatorP- spacesP- v <- versionP- return (ConstraintOperator o v)--operatorP :: ReadP.ReadP Operator-operatorP = ReadP.choice- [ 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 ()-hyphenP = do- spaces1P- Monad.void (ReadP.char '-')- spaces1P--orP :: ReadP.ReadP ()-orP = do- spaces1P- Monad.void (ReadP.string "||")- spaces1P--spaces1P :: ReadP.ReadP ()-spaces1P = Monad.void (ReadP.munch1 (== ' '))--spacesP :: ReadP.ReadP ()-spacesP = Monad.void (ReadP.munch (== ' '))---- ** Rendering--renderPreReleases :: [PreRelease] -> String-renderPreReleases ps = if null ps- then ""- else '-' : List.intercalate "." (map renderPreRelease ps)--renderBuilds :: [Build] -> String-renderBuilds bs = if null bs- then ""- else '+' : List.intercalate "." (map renderBuild bs)---- ** Helpers--both :: (a -> b) -> (a, a) -> (b, b)-both f (x, y) = (f x, f y)--isAsciiDigitNonZero :: Char -> Bool-isAsciiDigitNonZero c = Char.isDigit c && (c /= '0')--isIdentifier :: Char -> Bool-isIdentifier c = (Char.isAscii c && Char.isAlphaNum c) || (c == '-')--isWildcard :: Char -> Bool-isWildcard c = (c == 'x') || (c == '*') || (c == 'X')--parse :: ReadP.ReadP a -> String -> Maybe a-parse p s =- let p' = ReadP.readP_to_S p- in Maybe.listToMaybe (do- (x, "") <- p' s- 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--- 'Constraint's.--data SimpleConstraint- = SCLT Version- | SCEQ Version- | SCGT Version- | SCAnd SimpleConstraint SimpleConstraint- | SCOr SimpleConstraint SimpleConstraint- deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)--mkV :: Word.Word64 -> Word.Word64 -> Word.Word64 -> Version-mkV m n p = makeVersion m n p [] []--satisfiesSC :: SimpleConstraint -> Version -> Bool-satisfiesSC c v = case c of- SCLT u -> v < u- -- This uses `compare` rather than `==` to ignore build metadata.- SCEQ u -> compare v u == EQ- SCGT u -> v > u- SCAnd l r -> satisfiesSC l v && satisfiesSC r v- SCOr l r -> satisfiesSC l v || satisfiesSC r v--scLE :: Version -> SimpleConstraint-scLE v = SCOr (SCLT v) (SCEQ v)--scGE :: Version -> SimpleConstraint-scGE v = SCOr (SCGT v) (SCEQ v)--toSC :: Constraint -> SimpleConstraint-toSC c = case c of- ConstraintOperator o v -> case o of- OperatorLT -> SCLT v- OperatorLE -> scLE v- OperatorEQ -> SCEQ v- OperatorGE -> scGE v- OperatorGT -> SCGT v- OperatorTilde -> SCAnd- (scGE v)- (SCLT (mkV (versionMajor v) (versionMinor v + 1) 0))- OperatorCaret -> SCAnd- (scGE v)- (SCLT (case (versionMajor v, versionMinor v, versionPatch v) of- (0, 0, p) -> mkV 0 0 (p + 1)- (0, n, _) -> mkV 0 (n + 1) 0- (m, _, _) -> mkV (m + 1) 0 0))- ConstraintHyphen l h -> SCAnd (scGE l) (scLE h)- ConstraintWildcard w -> case w of- WildcardMajor -> scGE initialVersion- WildcardMinor m -> SCAnd (scGE (mkV m 0 0)) (SCLT (mkV (m + 1) 0 0))- WildcardPatch m n -> SCAnd (scGE (mkV m n 0)) (SCLT (mkV m (n + 1) 0))- ConstraintAnd l r -> SCAnd (toSC l) (toSC r)- ConstraintOr l r -> SCOr (toSC l) (toSC r)
− package.yaml
@@ -1,41 +0,0 @@-name: salve-version: 1.0.9--category: Distribution-description:- Salve provides semantic version (SemVer) numbers and constraints (ranges).-extra-source-files:- - CHANGELOG.markdown- - package.yaml- - README.markdown- - stack.yaml-github: tfausak/salve-license-file: LICENSE.markdown-license: MIT-maintainer: Taylor Fausak-synopsis: Semantic version numbers and constraints.--dependencies:- base: '>= 4.9.0 && < 4.15'-ghc-options:- - -Weverything- - -Wno-implicit-prelude- - -Wno-safe- - -Wno-unsafe--when:- - condition: impl(ghc >= 8.8.1)- ghc-options: -Wno-missing-deriving-strategies--library:- source-dirs: library--tests:- doctest:- dependencies:- doctest: '>= 0.11.0 && < 0.17'- ghc-options:- - -rtsopts- - -threaded- main: Main.hs- source-dirs: tests
salve.cabal view
@@ -1,58 +1,60 @@-cabal-version: 1.12+cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.33.0.------ see: https://github.com/sol/hpack------ hash: 819ccef7644498bebc5302d80b59de266c93ebf52b3037e2d7f3f3433db20512+name: salve+version: 1.0.10 -name: salve-version: 1.0.9-synopsis: Semantic version numbers and constraints.-description: Salve provides semantic version (SemVer) numbers and constraints (ranges).-category: Distribution-homepage: https://github.com/tfausak/salve#readme-bug-reports: https://github.com/tfausak/salve/issues-maintainer: Taylor Fausak-license: MIT-license-file: LICENSE.markdown-build-type: Simple-extra-source-files:- CHANGELOG.markdown- package.yaml- README.markdown- stack.yaml+synopsis: Semantic version numbers and constraints.+description:+ Salve provides semantic version (SemVer) numbers and constraints (ranges). +build-type: Simple+category: Distribution+extra-source-files: README.markdown+license-file: LICENSE.markdown+license: MIT+maintainer: Taylor Fausak+ source-repository head- type: git location: https://github.com/tfausak/salve+ type: git +common basics+ default-language: Haskell2010+ ghc-options:+ -Weverything+ -Wno-all-missed-specialisations+ -Wno-implicit-prelude+ -Wno-missing-exported-signatures+ -Wno-missing-import-lists+ -Wno-safe+ -Wno-unsafe++ if impl(ghc >= 8.8)+ ghc-options:+ -Wno-missing-deriving-strategies++ if impl(ghc >= 8.10)+ ghc-options:+ -Wno-missing-safe-haskell-mode+ -Wno-prepositive-qualified-module+ library- exposed-modules:- Salve- Salve.Internal- other-modules:- Paths_salve- hs-source-dirs:- library- ghc-options: -Weverything -Wno-implicit-prelude -Wno-safe -Wno-unsafe+ import: basics+ build-depends:- base >=4.9.0 && <4.15- if impl(ghc >= 8.8.1)- ghc-options: -Wno-missing-deriving-strategies- default-language: Haskell2010+ base >= 4.9.0 && < 4.15+ exposed-modules:+ Salve+ Salve.Internal+ hs-source-dirs: src/lib -test-suite doctest- type: exitcode-stdio-1.0- main-is: Main.hs- other-modules:- Paths_salve- hs-source-dirs:- tests- ghc-options: -Weverything -Wno-implicit-prelude -Wno-safe -Wno-unsafe -rtsopts -threaded+test-suite test+ import: basics+ build-depends:- base >=4.9.0 && <4.15- , doctest >=0.11.0 && <0.17- if impl(ghc >= 8.8.1)- ghc-options: -Wno-missing-deriving-strategies- default-language: Haskell2010+ base -any+ , HUnit >= 1.6.0 && < 1.7+ , salve -any+ hs-source-dirs: src/test+ main-is: Main.hs+ type: exitcode-stdio-1.0
+ src/lib/Salve.hs view
@@ -0,0 +1,611 @@+-- | 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://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.+--+-- >>> import Salve+--+-- This module provides lenses for modifying versions. If you want to modify+-- 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.+--+-- >>> renderVersion <$> parseVersion "1.2.3"+-- Just "1.2.3"+--+-- The 'Constraint' data type allows you to specify version constraints. Use+-- 'parseConstraint' to make constraints and 'renderConstraint' to convert them+-- into strings.+--+-- >>> renderConstraint <$> parseConstraint ">1.2.0"+-- Just ">1.2.0"+--+-- Use 'satisfiesConstraint' to see if a version satisfiesConstraint a+-- constraint.+--+-- >>> satisfiesConstraint <$> parseConstraint ">1.2.0" <*> parseVersion "1.2.3"+-- Just True++-- * Cheat sheet+-- | If you're coming from Cabal, you might not be familiar with npm's version+-- range syntax. This table shows you how npm version ranges map to Cabal's+-- version constraints.+--+-- > Salve | Cabal | Notes+-- > ----- | ----- | -----+-- > <1.2.3 | <1.2.3 | -+-- > <=1.2.3 | <=1.2.3 | -+-- > =1.2.3 | ==1.2.3 | equals sign is optional+-- > >=1.2.3 | >=1.2.3 | -+-- > >1.2.3 | >1.2.3 | -+-- > 1.2.3 || >1.2.3 | ==1.2.3 || >1.2.3 | lower precedence than and+-- > >=1.2.3 <2.0.0 | >=1.2.3 && <2.0.0 | higher precedence than or+-- > 1.2.3 - 2.3.4 | >=1.2.3 && <=2.3.4 | inclusive ranges+-- > 1.2.x | ==1.2.* | can use X or * instead of x+-- > 1.x.x | ==1.* | -+-- > x.x.x | ==* | same as -any+-- > ~1.2.3 | ^>=1.2.3 | same as >=1.2.3 && <1.3.0+-- > ^1.2.3 | >=1.2.3 && <2 | -+-- > ^0.2.3 | >=0.2.3 && <0.3 | -+-- > ^0.0.3 | >=0.0.3 && <0.0.4 | -++-- * Rationale++-- ** The PVP+-- | 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.+-- 2. A spec for constraining the versions of your dependencies, which+-- incldues how version ranges look.+-- 3. A prescription for how to constrain the versions of your dependencies,+-- which includes how the ranges of your dependencies should be.+--+-- By comparison, Semantic Versioning only deals with the first thing. npm's+-- version ranges only deal with the second thing. This module deals with the+-- first and second things but leaves the third up to you.+--+-- 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+-- [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.+--+-- Moving on to the second point, why should we use npm's version ranges+-- instead of the PVP's? This is a less clear cut. The two syntaxes are broadly+-- compatible. Really the only gains here are compatibility with a widely-used+-- syntax and convenient shorthand for common constraints (like hyphens+-- @1.2.3 - 2.3.4@, tildes @~1.2.3@, and carets @^1.2.3@).++-- ** Other modules+-- | There are already a few modules that provide version numbers. Why do we+-- need another one? Let's take a look at the options.+--+-- - [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+-- parsed.+-- - Allows any number of components, from zero to inifinity.+-- - Deprecated tags on versions.+-- - Does not support build metadata on versions.+-- - Does not support constraints.+--+-- - [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+-- version type.+-- - Depends on the @array@, @binary@, @bytestring@, @containers@,+-- @deepseq@, @directory@, @filepath@, @pretty@, @process@, @time@, and+-- @unix@ packages.+--+-- - [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.+--+-- - [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+-- @unordered-containers@ packages.+-- - Module name collides with the @semver@ package.+-- - Supports constraints, but does not provide a way to render them.+--+-- - [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@+-- packages.+-- - Intentially allows weird versions.+-- - Does not support constraints.+--+-- By comparison, this module:+--+-- - Does not expose constructors. Any version you create can be rendered and+-- parsed without issue.+-- - Requires exactly three components. You won't have to wonder if version+-- @1.2.0@ is greater than @1.2@.+-- - Allows pre-release identifiers on versions. Go ahead and release version+-- @1.0.0-alpha@ for early adopters.+-- - Allows build metadata on versions. Show when a release was made with+-- versions like @1.0.0+2001-02-03@.+-- - Supports version constraints. Just like versions, rendering and parsing+-- constraints is no problem.+-- - Only depends on the @base@ package. You can use all the functionality+-- without installing any other packages.+-- - Has a unique module name. You won't have to use the @PackageImports@+-- extension simply to deal with version numbers.++-- * Types+Salve.Version,+Salve.PreRelease,+Salve.Build,+Salve.Constraint,++-- * Constructors+Salve.makeVersion,+Salve.initialVersion,++-- * Parsing+Salve.parseVersion,+Salve.parsePreRelease,+Salve.parseBuild,+Salve.parseConstraint,++-- ** Unsafe+-- | These functions can be used to unsafely parse strings. Instead of+-- returning 'Nothing', they raise an exception. Only use these if you are sure+-- the string can be successfully parsed!+Salve.unsafeParseVersion,+Salve.unsafeParsePreRelease,+Salve.unsafeParseBuild,+Salve.unsafeParseConstraint,++-- * Rendering+Salve.renderVersion,+Salve.renderPreRelease,+Salve.renderBuild,+Salve.renderConstraint,++-- * Predicates+Salve.isUnstable,+Salve.isStable,++-- * Conversions+Salve.fromBaseVersion,+Salve.toBaseVersion,++-- * Helpers+Salve.bumpMajor,+Salve.bumpMinor,+Salve.bumpPatch,+Salve.satisfiesConstraint,++-- * Lenses+-- | These lenses can be used to access and modify specific parts of a+-- 'Version'.+--+-- 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' 'Version' a@, which you may already be familiar with.+Salve.majorLens,+Salve.minorLens,+Salve.patchLens,+Salve.preReleasesLens,+Salve.buildsLens,++-- * Examples+-- | These examples are provided to showcase functionality and explain weird+-- behavior. If something isn't clear, please open a pull request adding an+-- example!++-- ** Versions+-- | Leading zeros are not allowed.+--+-- >>> parseVersion "01.0.0"+-- Nothing+-- >>> parseVersion "0.01.0"+-- Nothing+-- >>> parseVersion "0.0.01"+-- Nothing+--+-- Negative numbers are not allowed.+--+-- >>> parseVersion "-1.0.0"+-- Nothing+-- >>> parseVersion "0.-1.0"+-- Nothing+-- >>> parseVersion "0.0.-1"+-- Nothing+--+-- Non-digits are not allowed.+--+-- >>> parseVersion "a.0.0"+-- Nothing+-- >>> parseVersion "0.a.0"+-- Nothing+-- >>> parseVersion "0.0.a"+-- Nothing+--+-- Partial version numbers are not allowed.+--+-- >>> parseVersion "0.0"+-- Nothing+--+-- Extra version numbers are not allowed.+--+-- >>> parseVersion "0.0.0.0"+-- Nothing+--+-- Spaces are allowed+--+-- >>> parseVersion " 0.0.0"+-- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+-- >>> parseVersion "0.0.0 "+-- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+--+-- Interior spaces are not allowed.+--+-- >>> parseVersion "0 .0.0"+-- 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.+--+-- >>> parseConstraint "1.2"+-- Nothing+--+-- Wildcards (also known as "x-ranges") are allowed. The exact character used+-- for the wildcard is not round-tripped.+--+-- >>> renderConstraint <$> parseConstraint "1.2.x"+-- Just "1.2.x"+-- >>> renderConstraint <$> parseConstraint "1.2.X"+-- Just "1.2.x"+-- >>> renderConstraint <$> parseConstraint "1.2.*"+-- Just "1.2.x"+--+-- An optional equals sign can be included with wildcard constraints.+--+-- >>> renderConstraint <$> parseConstraint "=1.2.x"+-- Just "1.2.x"+--+-- Wildcards can be combined with other constraints.+--+-- >>> renderConstraint <$> parseConstraint "1.2.x 2.3.4"+-- Just "1.2.x 2.3.4"+-- >>> renderConstraint <$> parseConstraint "1.2.x || 2.3.4"+-- Just "1.2.x || 2.3.4"+--+-- Wildcards are allowed at any position.+--+-- >>> renderConstraint <$> parseConstraint "1.2.x"+-- Just "1.2.x"+-- >>> renderConstraint <$> parseConstraint "1.x.x"+-- Just "1.x.x"+-- >>> renderConstraint <$> parseConstraint "x.x.x"+-- Just "x.x.x"+--+-- Non-wildcards cannot come after wildcards.+--+-- >>> parseConstraint "1.x.3"+-- Nothing+-- >>> parseConstraint "x.2.3"+-- Nothing+-- >>> parseConstraint "x.x.3"+-- Nothing+-- >>> parseConstraint "x.2.x"+-- Nothing+--+-- Wildcards cannot be used with other operators.+--+-- >>> parseConstraint "<1.2.x"+-- Nothing+-- >>> parseConstraint "<=1.2.x"+-- Nothing+-- >>> parseConstraint ">=1.2.x"+-- Nothing+-- >>> parseConstraint ">1.2.x"+-- Nothing+-- >>> parseConstraint "~1.2.x"+-- Nothing+-- >>> parseConstraint "^1.2.x"+-- Nothing+-- >>> parseConstraint "1.2.x - 2.3.4"+-- Nothing+-- >>> parseConstraint "1.2.3 - 2.3.x"+-- Nothing+--+-- Spaces are allowed in most places. Extra spaces are not round-tripped.+--+-- >>> renderConstraint <$> parseConstraint " 1.2.3 "+-- Just "1.2.3"+-- >>> renderConstraint <$> parseConstraint "> 1.2.3"+-- Just ">1.2.3"+-- >>> renderConstraint <$> parseConstraint "1.2.3 - 2.3.4"+-- Just "1.2.3 - 2.3.4"+-- >>> renderConstraint <$> parseConstraint "1.2.3 2.3.4"+-- Just "1.2.3 2.3.4"+-- >>> renderConstraint <$> parseConstraint "1.2.3 || 2.3.4"+-- Just "1.2.3 || 2.3.4"+--+-- Parentheses are not allowed. Note that combining two constraints with a+-- space (and) has higher precedence than combining them with pipes (or). In+-- other words, @"a b || c"@ parses as @"(a b) || c"@, not @"a (b || c)"@.+--+-- >>> parseConstraint "(1.2.3)"+-- Nothing+-- >>> parseConstraint "(1.2.3 || >1.2.3) <1.3.0"+-- Nothing+-- >>> parseConstraint "(>1.2.3 <1.3.0) || 1.2.3"+-- Nothing+--+-- Most constraints can be round-tripped through parsing and rendering.+--+-- >>> renderConstraint <$> parseConstraint "<1.2.3"+-- Just "<1.2.3"+-- >>> renderConstraint <$> parseConstraint "<=1.2.3"+-- Just "<=1.2.3"+-- >>> renderConstraint <$> parseConstraint "1.2.3"+-- Just "1.2.3"+-- >>> renderConstraint <$> parseConstraint ">=1.2.3"+-- Just ">=1.2.3"+-- >>> renderConstraint <$> parseConstraint ">1.2.3"+-- Just ">1.2.3"+-- >>> renderConstraint <$> parseConstraint "1.2.3 - 2.3.4"+-- Just "1.2.3 - 2.3.4"+-- >>> renderConstraint <$> parseConstraint "~1.2.3"+-- Just "~1.2.3"+-- >>> renderConstraint <$> parseConstraint "^1.2.3"+-- Just "^1.2.3"+-- >>> renderConstraint <$> parseConstraint ">1.2.3 <2.0.0"+-- Just ">1.2.3 <2.0.0"+-- >>> renderConstraint <$> parseConstraint "1.2.3 || >1.2.3"+-- Just "1.2.3 || >1.2.3"+--+-- Explicit equal signs do not get round-tripped.+--+-- >>> renderConstraint <$> parseConstraint "=1.2.3"+-- Just "1.2.3"+--+-- Pre-releases and builds are allowed on any constraints except wildcards.+--+-- >>> renderConstraint <$> parseConstraint "1.2.3-p+b"+-- Just "1.2.3-p+b"+-- >>> renderConstraint <$> parseConstraint ">1.2.3-p+b"+-- Just ">1.2.3-p+b"+-- >>> renderConstraint <$> parseConstraint "1.2.3-p+b - 2.3.4-p+b"+-- Just "1.2.3-p+b - 2.3.4-p+b"+-- >>> renderConstraint <$> parseConstraint "~1.2.3-p+b"+-- Just "~1.2.3-p+b"+-- >>> renderConstraint <$> parseConstraint "^1.2.3-p+b"+-- Just "^1.2.3-p+b"+--+-- >>> parseConstraint "1.2.x-p+b"+-- Nothing+--+-- These examples show every type of constraint in a single expression.+--+-- >>> renderConstraint <$> parseConstraint "<1.2.0 <=1.2.1 =1.2.2 >=1.2.3 >1.2.4 1.2.5 1.2.6 - 1.2.7 ~1.2.8 ^1.2.9 1.2.x"+-- Just "<1.2.0 <=1.2.1 1.2.2 >=1.2.3 >1.2.4 1.2.5 1.2.6 - 1.2.7 ~1.2.8 ^1.2.9 1.2.x"+-- >>> renderConstraint <$> parseConstraint "<1.2.0 <=1.2.1 || =1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9 1.2.x"+-- Just "<1.2.0 <=1.2.1 || 1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9 1.2.x"+-- >>> renderConstraint <$> parseConstraint "<1.2.0 || <=1.2.1 =1.2.2 || >=1.2.3 >1.2.4 || 1.2.5 1.2.6 - 1.2.7 || ~1.2.8 ^1.2.9 || 1.2.x"+-- Just "<1.2.0 || <=1.2.1 1.2.2 || >=1.2.3 >1.2.4 || 1.2.5 1.2.6 - 1.2.7 || ~1.2.8 ^1.2.9 || 1.2.x"+-- >>> renderConstraint <$> parseConstraint "<1.2.0 || <=1.2.1 || =1.2.2 || >=1.2.3 || >1.2.4 || 1.2.5 || 1.2.6 - 1.2.7 || ~1.2.8 || ^1.2.9 || 1.2.x"+-- Just "<1.2.0 || <=1.2.1 || 1.2.2 || >=1.2.3 || >1.2.4 || 1.2.5 || 1.2.6 - 1.2.7 || ~1.2.8 || ^1.2.9 || 1.2.x"++-- ** Satisfying constraints+-- | Although in general you should use 'satisfiesConstraint', 'parseVersion',+-- and 'parseConstraint', doing that here makes it hard to tell what the+-- examples are doing. An operator makes things clearer.+--+-- >>> satisfiesConstraint <$> parseConstraint "=1.2.3" <*> parseVersion "1.2.3"+-- Just True+-- >>> let version ? constraint = satisfiesConstraint (unsafeParseConstraint constraint) (unsafeParseVersion version)+-- >>> "1.2.3" ? "=1.2.3"+-- True+--+-- - Less than:+--+-- >>> "1.2.2" ? "<1.2.3"+-- True+-- >>> "1.2.3" ? "<1.2.3"+-- False+-- >>> "1.2.4" ? "<1.2.3"+-- False+-- >>> "1.2.3-pre" ? "<1.2.3"+-- True+--+-- - Less than or equal to:+--+-- >>> "1.2.2" ? "<=1.2.3"+-- True+-- >>> "1.2.3" ? "<=1.2.3"+-- True+-- >>> "1.2.4" ? "<=1.2.3"+-- False+--+-- - Equal to:+--+-- >>> "1.2.2" ? "=1.2.3"+-- False+-- >>> "1.2.3" ? "=1.2.3"+-- True+-- >>> "1.2.4" ? "=1.2.3"+-- False+-- >>> "1.2.3-pre" ? "=1.2.3"+-- False+-- >>> "1.2.3+build" ? "=1.2.3"+-- True+--+-- - Greater than or equal to:+--+-- >>> "1.2.2" ? ">=1.2.3"+-- False+-- >>> "1.2.3" ? ">=1.2.3"+-- True+-- >>> "1.2.4" ? ">=1.2.3"+-- True+--+-- - Greater than:+--+-- >>> "1.2.2" ? ">1.2.3"+-- False+-- >>> "1.2.3" ? ">1.2.3"+-- False+-- >>> "1.2.4" ? ">1.2.3"+-- True+-- >>> "1.2.4-pre" ? ">1.2.3"+-- True+--+-- >>> "1.2.4" ? ">1.2.3-pre"+-- True+--+-- - And:+--+-- >>> "1.2.3" ? ">1.2.3 <1.2.5"+-- False+-- >>> "1.2.4" ? ">1.2.3 <1.2.5"+-- True+-- >>> "1.2.5" ? ">1.2.3 <1.2.5"+-- False+--+-- - Or:+--+-- >>> "1.2.2" ? "1.2.3 || 1.2.4"+-- False+-- >>> "1.2.3" ? "1.2.3 || 1.2.4"+-- True+-- >>> "1.2.4" ? "1.2.3 || 1.2.4"+-- True+-- >>> "1.2.5" ? "1.2.3 || 1.2.4"+-- False+--+-- - And & or:+--+-- >>> "1.2.2" ? "1.2.2 || >1.2.3 <1.3.0"+-- True+-- >>> "1.2.3" ? "1.2.2 || >1.2.3 <1.3.0"+-- False+-- >>> "1.2.4" ? "1.2.2 || >1.2.3 <1.3.0"+-- True+-- >>> "1.3.0" ? "1.2.2 || >1.2.3 <1.3.0"+-- False+--+-- - Hyphen:+--+-- >>> "1.2.2" ? "1.2.3 - 1.2.4"+-- False+-- >>> "1.2.3" ? "1.2.3 - 1.2.4"+-- True+-- >>> "1.2.4" ? "1.2.3 - 1.2.4"+-- True+-- >>> "1.2.5" ? "1.2.3 - 1.2.4"+-- False+--+-- - Tilde:+--+-- >>> "1.2.2" ? "~1.2.3"+-- False+-- >>> "1.2.3" ? "~1.2.3"+-- True+-- >>> "1.2.4" ? "~1.2.3"+-- True+-- >>> "1.3.0" ? "~1.2.3"+-- False+--+-- - Caret:+--+-- >>> "1.2.2" ? "^1.2.3"+-- False+-- >>> "1.2.3" ? "^1.2.3"+-- True+-- >>> "1.2.4" ? "^1.2.3"+-- True+-- >>> "1.3.0" ? "^1.2.3"+-- True+-- >>> "2.0.0" ? "^1.2.3"+-- False+--+-- >>> "0.2.2" ? "^0.2.3"+-- False+-- >>> "0.2.3" ? "^0.2.3"+-- True+-- >>> "0.2.4" ? "^0.2.3"+-- True+-- >>> "0.3.0" ? "^0.2.3"+-- False+--+-- >>> "0.0.2" ? "^0.0.3"+-- False+-- >>> "0.0.3" ? "^0.0.3"+-- True+-- >>> "0.0.4" ? "^0.0.3"+-- False+--+-- - Wildcard:+--+-- >>> "1.1.0" ? "1.2.x"+-- False+-- >>> "1.2.3" ? "1.2.x"+-- True+-- >>> "1.3.0" ? "1.2.x"+-- False+--+-- >>> "0.1.0" ? "1.x.x"+-- False+-- >>> "1.0.0" ? "1.x.x"+-- True+-- >>> "1.2.3" ? "1.x.x"+-- True+-- >>> "2.0.0" ? "1.x.x"+-- False+--+-- >>> "0.0.0" ? "x.x.x"+-- True+-- >>> "1.2.3" ? "x.x.x"+-- True+-- >>> "2.0.0" ? "x.x.x"+-- True+) where++import qualified Salve.Internal as Salve++-- $setup+-- >>> import Control.Applicative+-- >>> import Salve
+ src/lib/Salve/Internal.hs view
@@ -0,0 +1,1006 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}++-- | WARNING: This module should be considered private! If you find yourself+-- wanting to import something from this module, please open an issue to get+-- that thing exported from "Salve".+module Salve.Internal+ ( Version(..)+ , PreRelease(..)+ , Build(..)+ , Constraint(..)+ , makeVersion+ , initialVersion+ , parseVersion+ , parsePreRelease+ , parseBuild+ , parseConstraint+ , unsafeParseVersion+ , unsafeParsePreRelease+ , unsafeParseBuild+ , unsafeParseConstraint+ , renderVersion+ , renderPreRelease+ , renderBuild+ , renderConstraint+ , isUnstable+ , isStable+ , fromBaseVersion+ , toBaseVersion+ , bumpMajor+ , bumpMinor+ , bumpPatch+ , satisfiesConstraint+ , majorLens+ , minorLens+ , patchLens+ , preReleasesLens+ , buildsLens+ , Operator(..)+ , Wildcard(..)+ , constraintLT+ , constraintLE+ , constraintEQ+ , constraintGE+ , constraintGT+ , constraintAnd+ , constraintOr+ , constraintHyphen+ , constraintTilde+ , constraintCaret+ ) where++import qualified Control.Monad as Monad+import qualified Data.Char as Char+import qualified Data.Data as Data+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.Version as Version+import qualified Data.Word as Word+import qualified GHC.Generics as Generics+import qualified Text.ParserCombinators.ReadP as ReadP++-- $setup+--+-- >>> import Control.Applicative (Const(..))+-- >>> let view lens record = getConst (lens Const record)+--+-- >>> import Data.Functor.Identity (Identity(..))+-- >>> let set lens field record = runIdentity (lens (const (Identity field)) record)+--+-- >>> import Control.Applicative ((<$>), (<*>))++-- * Public++-- | A semantic version number. Versions have five parts:+--+-- 1. 'majorLens': The major version number.+-- 2. 'minorLens': The minor version number.+-- 3. 'patchLens': The patch version number.+-- 4. 'preReleasesLens': A list of pre-release identifiers.+-- 5. 'buildsLens': A list of build metadata.+--+-- Use 'parseVersion' to create versions.+data Version = Version+ { versionMajor :: Word.Word64+ , versionMinor :: Word.Word64+ , versionPatch :: Word.Word64+ , versionPreReleases :: [PreRelease]+ , versionBuilds :: [Build]+ } deriving (Data.Data, Eq, Generics.Generic, Read, Show)++-- | 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"+-- Just LT+-- >>> compare <$> parseVersion "1.2.3" <*> parseVersion "1.3.0"+-- Just LT+-- >>> compare <$> parseVersion "1.2.3" <*> parseVersion "1.2.4"+-- Just LT+--+-- Numbers are compared numerically, not alphabetically.+--+-- >>> compare <$> parseVersion "0.0.9" <*> parseVersion "0.0.10"+-- Just LT+--+-- If all the numbers are the same, the pre-releases are compared.+--+-- >>> compare <$> parseVersion "1.2.3-a" <*> parseVersion "1.2.3-b"+-- Just LT+--+-- A version with a pre-release is always less than a version without one as+-- long as the other parts are the same.+--+-- >>> compare <$> parseVersion "1.2.3-pre" <*> parseVersion "1.2.3"+-- Just LT+-- >>> compare <$> parseVersion "1.2.4-pre" <*> parseVersion "1.2.3"+-- Just GT+--+-- Builds are not considered when comparing versions.+--+-- >>> compare <$> parseVersion "1.2.3+a" <*> parseVersion "1.2.3+b"+-- Just EQ+-- >>> (==) <$> parseVersion "1.2.3+a" <*> parseVersion "1.2.3+b"+-- Just False+instance Ord Version where+ compare x y = Monoid.mconcat+ [ Ord.comparing versionMajor x y+ , Ord.comparing versionMinor x y+ , Ord.comparing versionPatch x y+ , case both versionPreReleases (x, y) of+ ([], []) -> EQ+ ([], _) -> GT+ (_, []) -> LT+ (p, q) -> compare p q+ ]++-- | Pre-release information attached to a version. These can either be numeric+-- or textual. They must not be empty.+--+-- - Numeric: Can be any non-negative integer. Cannot have leading zeros.+--+-- - Textual: Can be any string of ASCII digits, letters, or hyphens. Cannot be+-- all digits, as that would be numeric.+--+-- In general, pre-releases must match the regular expression+-- @\/^[-0-9A-Za-z]+$\/@.+--+-- Use 'parsePreRelease' to create pre-releases.+data PreRelease+ = PreReleaseNumeric Word.Word64+ | PreReleaseTextual String+ deriving (Data.Data, Eq, Generics.Generic, Read, Show)++-- | Numeric pre-releases are always less than textual pre-releases.+--+-- >>> compare <$> parsePreRelease "1" <*> parsePreRelease "a"+-- Just LT+--+-- Numeric pre-releases are compared numerically.+--+-- >>> compare <$> parsePreRelease "9" <*> parsePreRelease "10"+-- Just LT+--+-- Textual pre-releases are compared alphabetically.+--+-- >>> compare <$> parsePreRelease "p10" <*> parsePreRelease "p9"+-- Just LT+instance Ord PreRelease where+ compare x y = case (x, y) of+ (PreReleaseNumeric n, PreReleaseNumeric m) -> compare n m+ (PreReleaseNumeric _, PreReleaseTextual _) -> LT+ (PreReleaseTextual _, PreReleaseNumeric _) -> GT+ (PreReleaseTextual s, PreReleaseTextual t) -> compare s t++-- | Build metadata attached to a version. These are similar to+-- 'PreRelease's with some key differences:+--+-- 1. There is no such thing as numeric builds. Even though builds can look+-- like numbers, all builds are textual.+-- 2. As a result, builds that look numeric are allowed to have leading zeros.+-- 3. Builds cannot be compared. That is, they do not have an 'Ord' instance.+--+-- Use 'parseBuild' to create builds.+newtype Build = Build String+ deriving (Data.Data, Eq, Generics.Generic, Read, Show)++-- | Constrains allowable version numbers.+--+-- Use 'parseConstraint' to create constraints and 'satisfiesConstraint' to see+-- if a version number satisfies a constraint.+data Constraint+ = ConstraintOperator Operator Version+ | ConstraintHyphen Version Version+ | ConstraintWildcard Wildcard+ | ConstraintAnd Constraint Constraint+ | ConstraintOr Constraint Constraint+ deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)++-- | Makes a new version number.+--+-- >>> 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.Word64 -> Word.Word64 -> Word.Word64 -> [PreRelease] -> [Build] -> Version+makeVersion major minor patch preReleases builds = Version+ { versionMajor = major+ , versionMinor = minor+ , versionPatch = patch+ , versionPreReleases = preReleases+ , versionBuilds = builds+ }++-- | The initial version number for development.+--+-- >>> initialVersion+-- Version {versionMajor = 0, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []}+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).+--+-- >>> 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"]})+--+-- Returns 'Nothing' if the parse fails.+--+-- >>> parseVersion "wrong"+-- Nothing+--+-- Whitespace is allowed.+--+-- >>> parseVersion " 1.2.3 "+-- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})+parseVersion :: String -> Maybe Version+parseVersion s = parse+ (do+ ReadP.skipSpaces+ version <- versionP+ ReadP.skipSpaces+ return version)+ s++-- | Attempts to parse a pre-release.+--+-- >>> parsePreRelease "pre"+-- Just (PreReleaseTextual "pre")+-- >>> parsePreRelease "1"+-- Just (PreReleaseNumeric 1)+--+-- Returns 'Nothing' if the parse fails.+--+-- >>> parsePreRelease "wrong!"+-- Nothing+--+-- Numeric pre-releases cannot contain leading zeros.+--+-- >>> parsePreRelease "01"+-- Nothing+parsePreRelease :: String -> Maybe PreRelease+parsePreRelease s = parse preReleaseP s++-- | Attempts to parse a build.+--+-- >>> parseBuild "build"+-- Just (Build "build")+-- >>> parseBuild "1"+-- Just (Build "1")+--+-- Returns 'Nothing' if the parse fails.+--+-- >>> parseBuild "wrong!"+-- Nothing+--+-- Unlike pre-releases, numeric builds can have leading zeros.+--+-- >>> parseBuild "01"+-- Just (Build "01")+parseBuild :: String -> Maybe Build+parseBuild s = parse buildP s++-- | 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 = []}))+--+-- Returns 'Nothing' if the parse fails.+--+-- >>> 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++-- | Parses a version.+--+-- >>> unsafeParseVersion "1.2.3-p.4+b.5"+-- Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [PreReleaseTextual "p",PreReleaseNumeric 4], versionBuilds = [Build "b",Build "5"]}+--+-- Raises an exception if the parse fails.+--+-- >>> unsafeParseVersion "wrong"+-- *** Exception: unsafeParseVersion: invalid version: "wrong"+-- ...+--+-- See 'parseVersion' for a safe version of this function.+unsafeParseVersion :: String -> Version+unsafeParseVersion s = case parseVersion s of+ Nothing -> error ("unsafeParseVersion: invalid version: " ++ show s)+ Just v -> v++-- | Parses a pre-release.+--+-- >>> unsafeParsePreRelease "pre"+-- PreReleaseTextual "pre"+--+-- Raises an exception if the parse fails.+--+-- >>> unsafeParsePreRelease "wrong!"+-- *** Exception: unsafeParsePreRelease: invalid pre-release: "wrong!"+-- ...+--+-- See 'parsePreRelease' for a safe version of this function.+unsafeParsePreRelease :: String -> PreRelease+unsafeParsePreRelease s = case parsePreRelease s of+ Nothing -> error ("unsafeParsePreRelease: invalid pre-release: " ++ show s)+ Just p -> p++-- | Parses a build.+--+-- >>> unsafeParseBuild "build"+-- Build "build"+--+-- Raises an exception if the parse fails.+--+-- >>> unsafeParseBuild "wrong!"+-- Build "*** Exception: unsafeParseBuild: invalid build: "wrong!"+-- ...+--+-- See 'parseBuild' for a safe version of this function.+unsafeParseBuild :: String -> Build+unsafeParseBuild s = case parseBuild s of+ Nothing -> error ("unsafeParseBuild: invalid build: " ++ show s)+ Just b -> b++-- | Parses a constraint.+--+-- >>> unsafeParseConstraint ">1.2.3"+-- ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})+--+-- Raises an exception if the parse fails.+--+-- >>> unsafeParseConstraint "wrong"+-- *** Exception: unsafeParseConstraint: invalid constraint: "wrong"+-- ...+--+-- See 'parseConstraint' for a safe version of this function.+unsafeParseConstraint :: String -> Constraint+unsafeParseConstraint s = case parseConstraint s of+ Nothing -> error ("unsafeParseConstraint: invalid constraint: " ++ show s)+ Just c -> c++-- | Renders a version.+--+-- >>> renderVersion <$> parseVersion "1.2.3-p.4+b.5"+-- Just "1.2.3-p.4+b.5"+renderVersion :: Version -> String+renderVersion v = concat+ [ show (versionMajor v)+ , "."+ , show (versionMinor v)+ , "."+ , show (versionPatch v)+ , renderPreReleases (versionPreReleases v)+ , renderBuilds (versionBuilds v)+ ]++-- | Renders a pre-release.+--+-- >>> renderPreRelease <$> parsePreRelease "pre"+-- Just "pre"+-- >>> renderPreRelease <$> parsePreRelease "1"+-- Just "1"+renderPreRelease :: PreRelease -> String+renderPreRelease p = case p of+ PreReleaseNumeric n -> show n+ PreReleaseTextual s -> s++-- | Renders a build.+--+-- >>> renderBuild <$> parseBuild "build"+-- Just "build"+-- >>> renderBuild <$> parseBuild "1"+-- Just "1"+renderBuild :: Build -> String+renderBuild (Build b) = b++-- | Renders a constraint.+--+-- >>> renderConstraint <$> parseConstraint ">1.2.3"+-- Just ">1.2.3"+--+-- Parsing and rendering a constraint doesn't always return what you started+-- with.+--+-- >>> renderConstraint <$> parseConstraint "=1.2.3"+-- Just "1.2.3"+renderConstraint :: Constraint -> String+renderConstraint c = case c of+ ConstraintOperator o v ->+ let s = renderVersion v+ in case o of+ OperatorLT -> '<' : s+ OperatorLE -> '<' : '=' : s+ OperatorEQ -> s+ OperatorGE -> '>' : '=' : s+ OperatorGT -> '>' : s+ OperatorTilde -> '~' : s+ OperatorCaret -> '^' : s+ ConstraintHyphen l r -> unwords [renderVersion l, "-", renderVersion r]+ ConstraintWildcard w -> case w of+ WildcardMajor -> "x.x.x"+ WildcardMinor m -> show m ++ ".x.x"+ WildcardPatch m n -> List.intercalate "." [show m, show n, "x"]+ ConstraintAnd l r -> unwords (map renderConstraint [l, r])+ ConstraintOr l r -> unwords [renderConstraint l, "||", renderConstraint r]++-- | Returns 'True' if the major version number is zero, 'False' otherwise.+--+-- >>> isUnstable <$> parseVersion "0.1.2"+-- Just True+-- >>> isUnstable <$> parseVersion "1.0.0"+-- Just False+isUnstable :: Version -> Bool+isUnstable v = versionMajor v == 0++-- | Returns 'True' if the major version number is not zero, 'False' otherwise.+--+-- >>> isStable <$> parseVersion "1.0.0"+-- Just True+-- >>> isStable <$> parseVersion "0.1.2"+-- Just False+isStable :: Version -> Bool+isStable v = not (isUnstable v)++-- | Converts from a 'Version.Version' from the @base@ package.+--+-- >>> renderVersion . fromBaseVersion $ Version.makeVersion [1, 2, 3]+-- "1.2.3"+--+-- Missing version components are set to zero.+--+-- >>> renderVersion . fromBaseVersion $ Version.makeVersion []+-- "0.0.0"+-- >>> renderVersion . fromBaseVersion $ Version.makeVersion [1]+-- "1.0.0"+-- >>> renderVersion . fromBaseVersion $ Version.makeVersion [1, 2]+-- "1.2.0"+--+-- Extra version components are ignored.+--+-- >>> renderVersion . fromBaseVersion $ Version.makeVersion [1, 2, 3, 4]+-- "1.2.3"+--+-- Tags are ignored.+--+-- >>> renderVersion . fromBaseVersion $ Version.Version [] ["ignored"]+-- "0.0.0"+fromBaseVersion :: Version.Version -> Version+fromBaseVersion v = case Version.versionBranch v of+ (m : n : p : _) -> mkV (fromIntegral m) (fromIntegral n) (fromIntegral p)+ (m : n : _) -> mkV (fromIntegral m) (fromIntegral n) 0+ (m : _) -> mkV (fromIntegral m) 0 0+ _ -> mkV 0 0 0++-- | Converts to a 'Version.Version' from the @base@ package.+--+-- >>> toBaseVersion <$> parseVersion "1.2.3"+-- Just (Version {versionBranch = [1,2,3], versionTags = []})+--+-- Pre-releases and builds are converted to tags.+--+-- >>> toBaseVersion <$> parseVersion "1.2.3-pre+build"+-- Just (Version {versionBranch = [1,2,3], versionTags = ["pre","build"]})+toBaseVersion :: Version -> Version.Version+toBaseVersion v = Version.Version+ (map fromIntegral+ [ versionMajor v+ , versionMinor v+ , versionPatch v+ ])+ (concat+ [ map renderPreRelease (versionPreReleases v)+ , map renderBuild (versionBuilds v)+ ])++-- | Increments the major version number.+--+-- >>> bumpMajor <$> parseVersion "0.0.0"+-- Just (Version {versionMajor = 1, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+--+-- The minor and patch numbers are reset to zero.+--+-- >>> bumpMajor <$> parseVersion "1.2.3"+-- Just (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+--+-- The pre-releases and builds are removed.+--+-- >>> bumpMajor <$> parseVersion "0.0.0-pre+build"+-- Just (Version {versionMajor = 1, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+--+-- Consider using 'majorLens' if you want to arbitrarily change the major+-- number, or if you don't want the other parts of the version to change.+bumpMajor :: Version -> Version+bumpMajor v = makeVersion (versionMajor v + 1) 0 0 [] []++-- | Increments the minor version number.+--+-- >>> bumpMinor <$> parseVersion "0.0.0"+-- Just (Version {versionMajor = 0, versionMinor = 1, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+--+-- The patch number is reset to zero.+--+-- >>> bumpMinor <$> parseVersion "1.2.3"+-- Just (Version {versionMajor = 1, versionMinor = 3, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+--+-- The pre-releases and builds are removed.+--+-- >>> bumpMinor <$> parseVersion "0.0.0-pre+build"+-- Just (Version {versionMajor = 0, versionMinor = 1, versionPatch = 0, versionPreReleases = [], versionBuilds = []})+--+-- Consider using 'minorLens' if you want to arbitrarily change the minor+-- number, or if you don't want the other parts of the version to change.+bumpMinor :: Version -> Version+bumpMinor v = makeVersion (versionMajor v) (versionMinor v + 1) 0 [] []++-- | Increments the patch number.+--+-- >>> bumpPatch <$> parseVersion "0.0.0"+-- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 1, versionPreReleases = [], versionBuilds = []})+--+-- The major and minor numbers are not changed.+--+-- >>> bumpPatch <$> parseVersion "1.2.3"+-- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 4, versionPreReleases = [], versionBuilds = []})+--+-- The pre-releases and builds are removed.+--+-- >>> bumpPatch <$> parseVersion "0.0.0-pre+build"+-- Just (Version {versionMajor = 0, versionMinor = 0, versionPatch = 1, versionPreReleases = [], versionBuilds = []})+--+-- Consider using 'patchLens' if you want to arbitrarily change the patch+-- number, or if you don't want the other parts of the version to change.+bumpPatch :: Version -> Version+bumpPatch v = makeVersion+ (versionMajor v) (versionMinor v) (versionPatch v + 1) [] []++-- | Returns 'True' if the version satisfies the constraint, 'False' otherwise.+--+-- >>> satisfiesConstraint <$> parseConstraint ">1.2.0" <*> parseVersion "1.2.3"+-- Just True+satisfiesConstraint :: Constraint -> Version -> Bool+satisfiesConstraint c v = satisfiesSC (toSC c) v++-- | Focuses on the major version number.+--+-- >>> view majorLens <$> parseVersion "1.2.3-pre.4+build.5"+-- Just 1+-- >>> set majorLens 4 <$> parseVersion "1.2.3"+-- Just (Version {versionMajor = 4, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})+majorLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version+majorLens f v = fmap+ (\ m -> v { versionMajor = m })+ (f (versionMajor v))++-- | Focuses on the minor version number.+--+-- >>> view minorLens <$> parseVersion "1.2.3-pre.4+build.5"+-- Just 2+-- >>> set minorLens 4 <$> parseVersion "1.2.3"+-- Just (Version {versionMajor = 1, versionMinor = 4, versionPatch = 3, versionPreReleases = [], versionBuilds = []})+minorLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version+minorLens f v = fmap+ (\ n -> v { versionMinor = n })+ (f (versionMinor v))++-- | Focuses on the patch version number.+--+-- >>> view patchLens <$> parseVersion "1.2.3-pre.4+build.5"+-- Just 3+-- >>> set patchLens 4 <$> parseVersion "1.2.3"+-- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 4, versionPreReleases = [], versionBuilds = []})+patchLens :: Functor f => (Word.Word64 -> f Word.Word64) -> Version -> f Version+patchLens f v = fmap+ (\ p -> v { versionPatch = p })+ (f (versionPatch v))++-- | Focuses on the pre-release identifiers.+--+-- >>> view preReleasesLens <$> parseVersion "1.2.3-pre.4+build.5"+-- Just [PreReleaseTextual "pre",PreReleaseNumeric 4]+-- >>> set preReleasesLens [] <$> parseVersion "1.2.3-pre"+-- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})+preReleasesLens :: Functor f => ([PreRelease] -> f [PreRelease]) -> Version -> f Version+preReleasesLens f v = fmap+ (\ ps -> v { versionPreReleases = ps })+ (f (versionPreReleases v))++-- | Focuses on the build metadata.+--+-- >>> view buildsLens <$> parseVersion "1.2.3-pre.4+build.5"+-- Just [Build "build",Build "5"]+-- >>> set buildsLens [] <$> parseVersion "1.2.3+build"+-- Just (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})+buildsLens :: Functor f => ([Build] -> f [Build]) -> Version -> f Version+buildsLens f v = fmap+ (\ bs -> v { versionBuilds = bs })+ (f (versionBuilds v))++-- * Private++-- ** Types++data Operator+ = OperatorLT+ | OperatorLE+ | OperatorEQ+ | OperatorGE+ | OperatorGT+ | OperatorTilde+ | OperatorCaret+ deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)++data Wildcard+ = WildcardMajor+ | WildcardMinor Word.Word64+ | WildcardPatch Word.Word64 Word.Word64+ deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)++-- ** Constructors++-- | Makes a new constraint that must be less than the version number.+--+-- >>> constraintLT <$> parseVersion "1.2.3"+-- Just (ConstraintOperator OperatorLT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+-- >>> parseConstraint "<1.2.3"+-- Just (ConstraintOperator OperatorLT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+constraintLT :: Version -> Constraint+constraintLT v = ConstraintOperator OperatorLT v++-- | Makes a new constraint that must be less than or euqal to the version+-- number.+--+-- >>> constraintLE <$> parseVersion "1.2.3"+-- Just (ConstraintOperator OperatorLE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+-- >>> parseConstraint "<=1.2.3"+-- Just (ConstraintOperator OperatorLE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+constraintLE :: Version -> Constraint+constraintLE v = ConstraintOperator OperatorLE v++-- | Makes a new constraint that must be equal to the version number.+--+-- >>> constraintEQ <$> parseVersion "1.2.3"+-- Just (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+-- >>> parseConstraint "=1.2.3"+-- Just (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+constraintEQ :: Version -> Constraint+constraintEQ v = ConstraintOperator OperatorEQ v++-- | Makes a new constraint that must be greater than or equal to the version+-- number.+--+-- >>> constraintGE <$> parseVersion "1.2.3"+-- Just (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+-- >>> parseConstraint ">=1.2.3"+-- Just (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+constraintGE :: Version -> Constraint+constraintGE v = ConstraintOperator OperatorGE v++-- | Makes a new constraint that must be greater than the version number.+--+-- >>> constraintGT <$> parseVersion "1.2.3"+-- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+-- >>> parseConstraint ">1.2.3"+-- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+constraintGT :: Version -> Constraint+constraintGT v = ConstraintOperator OperatorGT v++-- | Makes a new constraint that must satisfy both constraints.+--+-- >>> constraintAnd <$> (constraintGE <$> parseVersion "1.2.3") <*> (constraintLT <$> parseVersion "2.0.0")+-- Just (ConstraintAnd (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorLT (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})))+-- >>> parseConstraint ">=1.2.3 <2.0.0"+-- Just (ConstraintAnd (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorLT (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})))+constraintAnd :: Constraint -> Constraint -> Constraint+constraintAnd l r = ConstraintAnd l r++-- | Makes a new constraint that must satisfy either constraint.+--+-- >>> constraintOr <$> (constraintEQ <$> parseVersion "1.2.3") <*> (constraintGT <$> parseVersion "1.2.3")+-- Just (ConstraintOr (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})))+-- >>> parseConstraint "=1.2.3 || >1.2.3"+-- Just (ConstraintOr (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})))+constraintOr :: Constraint -> Constraint -> Constraint+constraintOr l r = ConstraintOr l r++-- | Makes a new constraint that must be between the versions, inclusive.+--+-- >>> constraintHyphen <$> parseVersion "1.2.3" <*> parseVersion "2.3.4"+-- Just (ConstraintHyphen (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}) (Version {versionMajor = 2, versionMinor = 3, versionPatch = 4, versionPreReleases = [], versionBuilds = []}))+-- >>> parseConstraint "1.2.3 - 2.3.4"+-- Just (ConstraintHyphen (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}) (Version {versionMajor = 2, versionMinor = 3, versionPatch = 4, versionPreReleases = [], versionBuilds = []}))+constraintHyphen :: Version -> Version -> Constraint+constraintHyphen v w = ConstraintHyphen v w++-- | Makes a new constraint that allows changes to the patch version number.+--+-- >>> constraintTilde <$> parseVersion "1.2.3"+-- Just (ConstraintOperator OperatorTilde (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+-- >>> parseConstraint "~1.2.3"+-- Just (ConstraintOperator OperatorTilde (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+constraintTilde :: Version -> Constraint+constraintTilde v = ConstraintOperator OperatorTilde v++-- | Makes a new constraint that allows changes that do not modify the+-- left-most non-zero version number.+--+-- >>> constraintCaret <$> parseVersion "1.2.3"+-- Just (ConstraintOperator OperatorCaret (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+-- >>> parseConstraint "^1.2.3"+-- Just (ConstraintOperator OperatorCaret (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))+constraintCaret :: Version -> Constraint+constraintCaret v = ConstraintOperator OperatorCaret v++-- ** Parsing++versionP :: ReadP.ReadP Version+versionP = do+ major <- numberP+ Monad.void (ReadP.char '.')+ minor <- numberP+ Monad.void (ReadP.char '.')+ patch <- numberP+ preReleases <- preReleasesP+ builds <- buildsP+ return (makeVersion major minor patch preReleases builds)++preReleasesP :: ReadP.ReadP [PreRelease]+preReleasesP = ReadP.option [] (do+ Monad.void (ReadP.char '-')+ ReadP.sepBy1 preReleaseP (ReadP.char '.'))++preReleaseP :: ReadP.ReadP PreRelease+preReleaseP = preReleaseNumberP ReadP.<++ preReleaseStringP++preReleaseNumberP :: ReadP.ReadP PreRelease+preReleaseNumberP = do+ n <- numberP+ return (PreReleaseNumeric n)++preReleaseStringP :: ReadP.ReadP PreRelease+preReleaseStringP = do+ s <- ReadP.munch1 isIdentifier+ if all Char.isDigit s+ then ReadP.pfail+ else return (PreReleaseTextual s)++buildsP :: ReadP.ReadP [Build]+buildsP = ReadP.option [] (do+ Monad.void (ReadP.char '+')+ ReadP.sepBy1 buildP (ReadP.char '.'))++buildP :: ReadP.ReadP Build+buildP = do+ b <- ReadP.munch1 isIdentifier+ return (Build b)++numberP :: ReadP.ReadP Word.Word64+numberP = zeroP ReadP.<++ nonZeroP++zeroP :: ReadP.ReadP Word.Word64+zeroP = do+ Monad.void (ReadP.char '0')+ return 0++nonZeroP :: ReadP.ReadP Word.Word64+nonZeroP = do+ x <- ReadP.satisfy isAsciiDigitNonZero+ ys <- ReadP.munch Char.isDigit+ case toWord64 (stringToIntegral (x : ys)) of+ Nothing -> ReadP.pfail+ Just n -> return n++constraintsP :: ReadP.ReadP Constraint+constraintsP = do+ spacesP+ cs <- ReadP.sepBy1 constraintP orP+ spacesP+ return (foldr1 constraintOr cs)++constraintP :: ReadP.ReadP Constraint+constraintP = do+ cs <- ReadP.sepBy1 simpleP spaces1P+ return (foldr1 constraintAnd cs)++hyphenatedP :: ReadP.ReadP Constraint+hyphenatedP = do+ v <- versionP+ hyphenP+ w <- versionP+ return (constraintHyphen v w)++simpleP :: ReadP.ReadP Constraint+simpleP = ReadP.choice [hyphenatedP, wildcardConstraintP, primitiveP]++wildcardConstraintP :: ReadP.ReadP Constraint+wildcardConstraintP = do+ ReadP.optional (ReadP.char '=')+ w <- wildcardP+ return (ConstraintWildcard w)++wildcardP :: ReadP.ReadP Wildcard+wildcardP = ReadP.choice [wildcardPatchP, wildcardMinorP, wildcardMajorP]++wildcardPatchP :: ReadP.ReadP Wildcard+wildcardPatchP = do+ m <- numberP+ Monad.void (ReadP.char '.')+ n <- numberP+ Monad.void (ReadP.char '.')+ Monad.void (ReadP.satisfy isWildcard)+ return (WildcardPatch m n)++wildcardMinorP :: ReadP.ReadP Wildcard+wildcardMinorP = do+ m <- numberP+ Monad.void (ReadP.char '.')+ Monad.void (ReadP.satisfy isWildcard)+ Monad.void (ReadP.char '.')+ Monad.void (ReadP.satisfy isWildcard)+ return (WildcardMinor m)++wildcardMajorP :: ReadP.ReadP Wildcard+wildcardMajorP = do+ Monad.void (ReadP.satisfy isWildcard)+ Monad.void (ReadP.char '.')+ Monad.void (ReadP.satisfy isWildcard)+ Monad.void (ReadP.char '.')+ Monad.void (ReadP.satisfy isWildcard)+ return WildcardMajor++primitiveP :: ReadP.ReadP Constraint+primitiveP = do+ o <- operatorP+ spacesP+ v <- versionP+ return (ConstraintOperator o v)++operatorP :: ReadP.ReadP Operator+operatorP = ReadP.choice+ [ 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 ()+hyphenP = do+ spaces1P+ Monad.void (ReadP.char '-')+ spaces1P++orP :: ReadP.ReadP ()+orP = do+ spaces1P+ Monad.void (ReadP.string "||")+ spaces1P++spaces1P :: ReadP.ReadP ()+spaces1P = Monad.void (ReadP.munch1 (== ' '))++spacesP :: ReadP.ReadP ()+spacesP = Monad.void (ReadP.munch (== ' '))++-- ** Rendering++renderPreReleases :: [PreRelease] -> String+renderPreReleases ps = if null ps+ then ""+ else '-' : List.intercalate "." (map renderPreRelease ps)++renderBuilds :: [Build] -> String+renderBuilds bs = if null bs+ then ""+ else '+' : List.intercalate "." (map renderBuild bs)++-- ** Helpers++both :: (a -> b) -> (a, a) -> (b, b)+both f (x, y) = (f x, f y)++isAsciiDigitNonZero :: Char -> Bool+isAsciiDigitNonZero c = Char.isDigit c && (c /= '0')++isIdentifier :: Char -> Bool+isIdentifier c = (Char.isAscii c && Char.isAlphaNum c) || (c == '-')++isWildcard :: Char -> Bool+isWildcard c = (c == 'x') || (c == '*') || (c == 'X')++parse :: ReadP.ReadP a -> String -> Maybe a+parse p s =+ let p' = ReadP.readP_to_S p+ in Maybe.listToMaybe (do+ (x, "") <- p' s+ 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+-- 'Constraint's.++data SimpleConstraint+ = SCLT Version+ | SCEQ Version+ | SCGT Version+ | SCAnd SimpleConstraint SimpleConstraint+ | SCOr SimpleConstraint SimpleConstraint+ deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)++mkV :: Word.Word64 -> Word.Word64 -> Word.Word64 -> Version+mkV m n p = makeVersion m n p [] []++satisfiesSC :: SimpleConstraint -> Version -> Bool+satisfiesSC c v = case c of+ SCLT u -> v < u+ -- This uses `compare` rather than `==` to ignore build metadata.+ SCEQ u -> compare v u == EQ+ SCGT u -> v > u+ SCAnd l r -> satisfiesSC l v && satisfiesSC r v+ SCOr l r -> satisfiesSC l v || satisfiesSC r v++scLE :: Version -> SimpleConstraint+scLE v = SCOr (SCLT v) (SCEQ v)++scGE :: Version -> SimpleConstraint+scGE v = SCOr (SCGT v) (SCEQ v)++toSC :: Constraint -> SimpleConstraint+toSC c = case c of+ ConstraintOperator o v -> case o of+ OperatorLT -> SCLT v+ OperatorLE -> scLE v+ OperatorEQ -> SCEQ v+ OperatorGE -> scGE v+ OperatorGT -> SCGT v+ OperatorTilde -> SCAnd+ (scGE v)+ (SCLT (mkV (versionMajor v) (versionMinor v + 1) 0))+ OperatorCaret -> SCAnd+ (scGE v)+ (SCLT (case (versionMajor v, versionMinor v, versionPatch v) of+ (0, 0, p) -> mkV 0 0 (p + 1)+ (0, n, _) -> mkV 0 (n + 1) 0+ (m, _, _) -> mkV (m + 1) 0 0))+ ConstraintHyphen l h -> SCAnd (scGE l) (scLE h)+ ConstraintWildcard w -> case w of+ WildcardMajor -> scGE initialVersion+ WildcardMinor m -> SCAnd (scGE (mkV m 0 0)) (SCLT (mkV (m + 1) 0 0))+ WildcardPatch m n -> SCAnd (scGE (mkV m n 0)) (SCLT (mkV m (n + 1) 0))+ ConstraintAnd l r -> SCAnd (toSC l) (toSC r)+ ConstraintOr l r -> SCOr (toSC l) (toSC r)
+ src/test/Main.hs view
@@ -0,0 +1,102 @@+-- import qualified Control.Applicative as Applicative+import qualified Control.Monad as Monad+-- import qualified Data.Functor.Identity as Identity+import qualified Salve+import qualified System.Exit as Exit+import qualified Test.HUnit as Test++main :: IO ()+main = do+ counts <- Test.runTestTT $ Test.TestList+ [ (compare <$> Salve.parseVersion "1.2.3" <*> Salve.parseVersion "2.0.0") Test.~?= Just LT+ , (compare <$> Salve.parseVersion "1.2.3" <*> Salve.parseVersion "1.3.0") Test.~?= Just LT+ , (compare <$> Salve.parseVersion "1.2.3" <*> Salve.parseVersion "1.2.4") Test.~?= Just LT+ , (compare <$> Salve.parseVersion "0.0.9" <*> Salve.parseVersion "0.0.10") Test.~?= Just LT+ , (compare <$> Salve.parseVersion "1.2.3-a" <*> Salve.parseVersion "1.2.3-b") Test.~?= Just LT+ , (compare <$> Salve.parseVersion "1.2.3-pre" <*> Salve.parseVersion "1.2.3") Test.~?= Just LT+ , (compare <$> Salve.parseVersion "1.2.4-pre" <*> Salve.parseVersion "1.2.3") Test.~?= Just GT+ , (compare <$> Salve.parseVersion "1.2.3+a" <*> Salve.parseVersion "1.2.3+b") Test.~?= Just EQ+ , ((==) <$> Salve.parseVersion "1.2.3+a" <*> Salve.parseVersion "1.2.3+b") Test.~?= Just False+ , (compare <$> Salve.parsePreRelease "1" <*> Salve.parsePreRelease "a") Test.~?= Just LT+ , (compare <$> Salve.parsePreRelease "9" <*> Salve.parsePreRelease "10") Test.~?= Just LT+ , (compare <$> Salve.parsePreRelease "p10" <*> Salve.parsePreRelease "p9") Test.~?= Just LT+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "<1.2.3" <*> Salve.parseVersion "1.2.2") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "<1.2.3" <*> Salve.parseVersion "1.2.3") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "<1.2.3" <*> Salve.parseVersion "1.2.4") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "<1.2.3" <*> Salve.parseVersion "1.2.3-pre") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "<=1.2.3" <*> Salve.parseVersion "1.2.2") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "<=1.2.3" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "<=1.2.3" <*> Salve.parseVersion "1.2.4") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "=1.2.3" <*> Salve.parseVersion "1.2.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "=1.2.3" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "=1.2.3" <*> Salve.parseVersion "1.2.4") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "=1.2.3" <*> Salve.parseVersion "1.2.3-pre") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "=1.2.3" <*> Salve.parseVersion "1.2.3+build") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">=1.2.3" <*> Salve.parseVersion "1.2.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">=1.2.3" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">=1.2.3" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">1.2.3" <*> Salve.parseVersion "1.2.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">1.2.3" <*> Salve.parseVersion "1.2.3") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">1.2.3" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">1.2.3" <*> Salve.parseVersion "1.2.4-pre") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">1.2.3-pre" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">1.2.3 <1.2.5" <*> Salve.parseVersion "1.2.3") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">1.2.3 <1.2.5" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint ">1.2.3 <1.2.5" <*> Salve.parseVersion "1.2.5") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.3 || 1.2.4" <*> Salve.parseVersion "1.2.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.3 || 1.2.4" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.3 || 1.2.4" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.3 || 1.2.4" <*> Salve.parseVersion "1.2.5") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.2 || >1.2.3 <1.3.0" <*> Salve.parseVersion "1.2.2") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.2 || >1.2.3 <1.3.0" <*> Salve.parseVersion "1.2.3") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.2 || >1.2.3 <1.3.0" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.2 || >1.2.3 <1.3.0" <*> Salve.parseVersion "1.3.0") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.3 - 1.2.4" <*> Salve.parseVersion "1.2.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.3 - 1.2.4" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.3 - 1.2.4" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.3 - 1.2.4" <*> Salve.parseVersion "1.2.5") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "~1.2.3" <*> Salve.parseVersion "1.2.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "~1.2.3" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "~1.2.3" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "~1.2.3" <*> Salve.parseVersion "1.3.0") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^1.2.3" <*> Salve.parseVersion "1.2.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^1.2.3" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^1.2.3" <*> Salve.parseVersion "1.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^1.2.3" <*> Salve.parseVersion "1.3.0") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^1.2.3" <*> Salve.parseVersion "2.0.0") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^0.2.3" <*> Salve.parseVersion "0.2.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^0.2.3" <*> Salve.parseVersion "0.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^0.2.3" <*> Salve.parseVersion "0.2.4") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^0.2.3" <*> Salve.parseVersion "0.3.0") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^0.0.3" <*> Salve.parseVersion "0.0.2") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^0.0.3" <*> Salve.parseVersion "0.0.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "^0.0.3" <*> Salve.parseVersion "0.0.4") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.x" <*> Salve.parseVersion "1.1.0") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.x" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.2.x" <*> Salve.parseVersion "1.3.0") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.x.x" <*> Salve.parseVersion "0.1.0") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.x.x" <*> Salve.parseVersion "1.0.0") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.x.x" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "1.x.x" <*> Salve.parseVersion "2.0.0") Test.~?= Just False+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "x.x.x" <*> Salve.parseVersion "0.0.0") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "x.x.x" <*> Salve.parseVersion "1.2.3") Test.~?= Just True+ , (Salve.satisfiesConstraint <$> Salve.parseConstraint "x.x.x" <*> Salve.parseVersion "2.0.0") Test.~?= Just True+ ]++ let+ hasErrors = Test.errors counts /= 0+ hasFailures = Test.failures counts /= 0+ Monad.when (hasErrors || hasFailures) Exit.exitFailure++-- view+-- :: ((record -> Applicative.Const record field) -> record -> Applicative.Const field field)+-- -> record+-- -> field+-- view lens = Applicative.getConst . lens Applicative.Const++-- set+-- :: ((record -> Identity.Identity field) -> record -> Identity.Identity record)+-- -> field+-- -> record+-- -> record+-- set lens field = Identity.runIdentity . lens (const $ Identity.Identity field)
− stack.yaml
@@ -1,1 +0,0 @@-resolver: lts-15.4
− tests/Main.hs
@@ -1,7 +0,0 @@-import qualified Test.DocTest as Doctest--main :: IO ()-main = Doctest.doctest- [ "library/Salve/Internal.hs"- , "library/Salve.hs"- ]