-- | 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