packages feed

versions 3.1.1 → 6.0.8

raw patch · 6 files changed

Files

CHANGELOG.md view
@@ -1,36 +1,314 @@-Changelog-=========+# Changelog -3.1.1------+## 6.0.8 (2025-02-03)++#### Changed++- Bumped upper bound on `base` (support for GHC 9.12).++## 6.0.7 (2024-06-03)++#### Changed++- Bumped upper bound on `base`.++## 6.0.6 (2024-03-08)++#### Fixed++- Account for large numbers when parsing on 32-bit (or smaller) systems.++## 6.0.5 (2024-01-24)++#### Fixed++- Certain illegal versions were parsing as PVP.++## 6.0.4 (2023-12-29)++#### Changed++- Bump dependencies to support GHC 9.8.++## 6.0.3 (2023-10-23)++#### Added++- `Data` instances for the various data types.+- Simple conversion functions between the main version types.+- Compile-time constructors via Template Haskell, like `versioningQ`.++## 6.0.2 (2023-10-12)++#### Added++- `Lift` instances for the various types, which allows parsing version numbers+  at compile time within Template Haskell quotes. Currently there is no exported+  function that supports this directly, but you could write one like:++```haskell+-- | Parse a `Versioning` at compile time.+thVer :: Text -> Q Exp+thVer nm =+  case versioning nm of+    Left err -> fail (errorBundlePretty err)+    Right v  -> lift v+```++#### Changed++- Due to the new dependency on `template-haskell`, GHC 8.8 is now the lowest+  supported compiler version.++## 6.0.1 (2023-05-08)++#### Fixed++- Restored the ability to compile with GHC versions earlier than 9.++## 6.0.0 (2023-04-29)++A number of type changes have been made to improve parsing and comparison logic.+Doing so fixed several bugs and made the code cleaner overall.++If you're just doing basic parsing and comparisons and not actually inspecting+the types themselves, you shouldn't notice a difference.++#### Added++- New types `Release`, `Chunks`, and `Chunk`.++#### Changed++- Both `SemVer` and `Version` now contain a better-behaving `Release` type for their prerelease info.+- Similarly, `Version` now also has a better-behaving `Chunks` type for its main+  version number sections.+- The `release` traversal now yields a `Maybe Release`.+- Versions with `~` in their metadata will now parse as a `Mess`. Example: `12.0.0-3ubuntu1~20.04.5`++#### Removed++- The various `Semigroup` instances. Adding version numbers together is a+  nonsensical operation and should never have been added in the first place.+- The `VChunk` and `VUnit` types and their associated functions.++#### Fixed++- Leading zeroes are handled a little better in `SemVer` pre-release data.++## 5.0.5 (2023-03-23)++#### Changed++- Bumped `base` bound to support GHC 9.6.++## 5.0.4 (2022-10-18)++#### Changed++- Bumped `base` bound to support GHC 9.4.++## 5.0.3 (2022-02-25)++#### Fixed++- A bug in `prettyVer` that flipped the order of the `preRel` and `meta` fields.++## 5.0.2 (2022-01-21)++#### Added++- `text-2.0` support.++## 5.0.1 (2021-12-08)++#### Changed++- Support for GHC 9.2.++#### Fixed++- Remove redundant pattern match.++## 5.0.0 (2021-04-14)++This release brings `versions` in line with version `2.0.0` of the SemVer spec.+The main addition to the spec is the allowance of hyphens in both the prerelease+and metadata sections. As such, **certain versions like `1.2.3+1-1` which+previously would not parse as SemVer now do.**++To accomodate this and other small spec updates, the `SemVer` and `Version`+types have received breaking changes here.++#### Changed++- **Breaking:** The `_svMeta` field of `SemVer` is now parsed as a dumber `Maybe Text` instead of `[VChunk]`, due to metadata now being allowed to possess+  leading zeroes.+- **Breaking:** Like the above, the `_vMeta` field of `Version` is now `Maybe Text`.+- **Breaking: The `_vRel` and `_vMeta` fields of `Version` have had their order+  flipped.** Further, the prelease and meta sections are now expected in the+  same order as `SemVer` when parsing (prerel first, meta second). `Version` is+  thus now a quite similar to `SemVer`, except allowing letters in more+  permissive positions.+- **Breaking:** The `meta` traversal has been altered to accomodate the metadata+  field changes.++#### Fixed++- Parsing certain legal SemVers specified in the spec.++## 4.0.3 (2021-02-23)++#### Changed++- Support for GHC 9.++## 4.0.2 (2021-01-23)++#### Fixed++- A bug in zero parsing within SemVer prereleases. [#42]++[#42]: https://github.com/fosskers/versions/issues/42++## 4.0.1 (2020-10-22)++#### Fixed++- An infinite loop in `Version` comparison. [aura#652]++[aura#652]: https://github.com/fosskers/aura/issues/652++## 4.0.0 (2020-10-20)++#### Changed++- **Breaking:** `VChunk` now cannot be empty.+- **Breaking:** A `Version` now guarantees `NonEmpty` chunks.+- **Breaking:** A `Mess` now guarantees `NonEmpty` chunks, and its structure has+  been significantly changed. Particularly, `Mess` values are now aware of the+  `Int` values they hold (when they do), as well as "revision" values of the+  pattern `rXYZ`.+- Comparison of `Version` values is more memory efficient.++#### Added++- `Version` now has an extra field, `_vMeta :: [VChunk]` for capturing+  "metadata" like Semver. This prevents otherwise nice-looking versions from+  being demoted to `Mess`.+- The `MChunk` type to accomodate the changes to `Mess` mentioned above.++#### Removed++- **Breaking:** `Version` no longer has a `Monoid` instance.++#### Fixed++- `""` no longer parses in any way. [#32]+- Version strings with trailing whitespace no longer parse via `versioning`. [#33]+- Particular edge cases involving `Mess` comparisons. [aura#646]+- A particular edge case involving prereleases in `Version` comparisons. [aura#586]++[#32]: https://github.com/fosskers/versions/issues/32+[#33]: https://github.com/fosskers/versions/issues/33+[aura#646]: https://github.com/fosskers/aura/issues/646+[aura#586]: https://github.com/fosskers/aura/issues/586++## 3.5.4 (2020-05-12)++#### Added++- The functions `isIdeal`, `isGeneral`, and `isComplex` for `Bool`-based+  inspection of parse results.+- `messMajor`, `messMinor`, `messPatch`, and `messPatchChunk` for improved+  introspection into `Mess` values.++#### Changed++- Improved `Mess` comparison logic.++## 3.5.3++- GHC 8.10 support.++## 3.5.2++- Added a new `PVP` type and parsers.++## 3.5.1.1++- GHC 8.8 compatibility.++## 3.5.0++- Updated to `megaparsec-7`. Our `ParsingError` type alias has changed to match+  Megaparsec's new error model, and `errorBundlePretty` is now exposed instead of+  the old `parseErrorPretty`.++## 3.4.0.1++- Enhanced the whitespace handling in `semver'`, `version'`, and `mess'`.++## 3.4.0++- Removed `ParseV` and surrounding machinery.+  Use `versioning` now instead of the `parseV` function.++## 3.3.2++- GHC 8.4.1 compatibility.++## 3.3.0++- New `Semantic` typeclass that provides Traversals for SemVer-like data out+  of all the version types. `Text` was also given an instance, so its much+  easier to manipulate directly:++```+λ "1.2.3" & minor %~ (+ 1)+"1.3.3"+```++Some Lenses and Traversals had their names changed or were removed entirely+to accomodate this new typeclass.++- `SemVer` and `Version` should never contain negative values, so their numeric+  components were changed from `Int` to `Word`.++## 3.2.0++- Updated for `megaparsec-6` and GHC 8.2.++## 3.1.1+ - Added instances for common typeclasses: `Generic`, `NFData`, and   `Hashable`. This is to avoid having users define these instances themselves   as orphans. If there are more instances you want added, please let me know.   `Data` was left out on purpose. -3.1.0-------- Added support for *epoch* numbers in the `Version` type. These are numbers+## 3.1.0++- Added support for _epoch_ numbers in the `Version` type. These are numbers   like the `1:` in `1:2.3.4`. These are used in Arch Linux in rare cases where   packages change their versioning scheme, but need a reliable integer prefix   to establish ordering. The `Version` type has been given a new field,   `_vEpoch :: Maybe Int`, and a corresponding lens, `vEpoch`. -3.0.2------+## 3.0.2+ - Expose internal parsers so that they could be used in other parser programs   that parse version numbers in larger files. -3.0.0------+## 3.0.0+ - Updated for `megaparsec-5` and `ghc-8` -2.0.0------+## 2.0.0+ - Switched to `megaparsec` to perform all parsing as `Text` - Support for legacy `String` removed - Added more Traversals and INLINE'd all Lenses/Traversals -1.1.0------+## 1.1.0+ - Added Lenses and Traversals (no `lens` dependency)
Data/Versions.hs view
@@ -1,545 +1,1041 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}---- |--- Module    : Data.Versions--- Copyright : (c) Colin Woodbury, 2015 - 2017--- License   : BSD3--- Maintainer: Colin Woodbury <colingw@gmail.com>------ A library for parsing and comparing software version numbers.------ We like to give version numbers to our software in a myriad of different--- ways. Some ways follow strict guidelines for incrementing and comparison.--- Some follow conventional wisdom and are generally self-consistent.--- Some are just plain asinine. This library provides a means of parsing--- and comparing /any/ style of versioning, be it a nice Semantic Version--- like this:------ > 1.2.3-r1+git123------ ...or a monstrosity like this:------ > 2:10.2+0.0093r3+1-1------ Please switch to <http://semver.org Semantic Versioning> if you--- aren't currently using it. It provides consistency in version--- incrementing and has the best constraints on comparisons.------ == Using the Parsers--- In general, `parseV` is the function you want. It attempts to parse--- a given @Text@ using the three individual parsers, `semver`, `version`--- and `mess`. If one fails, it tries the next. If you know you only want--- to parse one specific version type, use that parser directly--- (e.g. `semver`).--module Data.Versions-    (-      -- * Types-      Versioning(..)-    , SemVer(..)-    , Version(..)-    , Mess(..)-    , VUnit(..)-    , VChunk-    , VSep(..)-    , VParser(..)-    , ParsingError-      -- * Parsers-    , semver-    , version-    , mess-      -- ** Wrapped Parsers-    , parseV-    , semverP-    , versionP-    , messP-      -- ** Megaparsec Parsers-    , semver'-    , version'-    , mess'-      -- * Pretty Printing-    , prettyV-    , prettySemVer-    , prettyVer-    , prettyMess-    , parseErrorPretty-      -- * Lenses-      -- **  Traversing Text-    , _Versioning-    , _SemVer-    , _Version-      -- ** Versioning Traversals-    , _Ideal-    , _General-    , _Complex-      -- ** (Ideal) SemVer Lenses-    , svMajor-    , svMinor-    , svPatch-    , svPreRel-    , svMeta-      -- ** (General) Version Lenses-    , vEpoch-    , vChunks-    , vRel-      -- ** Misc. Lenses / Traversals-    , _Digits-    , _Str ) where--import Control.DeepSeq-import Data.Hashable-import Data.List (intersperse)-import Data.Monoid-import Data.Text (Text,pack,snoc)-import GHC.Generics-import Text.Megaparsec-import Text.Megaparsec.Text--------- | A top-level Versioning type. Acts as a wrapper for the more specific--- types. This allows each subtype to have its own parser, and for said--- parsers to be composed. This is useful for specifying custom behaviour--- for when a certain parser fails.-data Versioning = Ideal SemVer | General Version | Complex Mess-  deriving (Eq,Show,Generic,NFData,Hashable)---- | Comparison of @Ideal@s is always well defined.------ If comparison of @General@s is well-defined, then comparison--- of @Ideal@ and @General@ is well-defined, as there exists a perfect--- mapping from @Ideal@ to @General@.------ If comparison of @Complex@es is well-defined, then comparison of @General@--- and @Complex@ is well defined for the same reason.--- This implies comparison of @Ideal@ and @Complex@ is also well-defined.-instance Ord Versioning where-  compare (Ideal s)     (Ideal s')    = compare s s'-  compare (General v)   (General v')  = compare v v'-  compare (Complex m)   (Complex m')  = compare m m'-  compare (Ideal s)     (General v)   = compare (vFromS s) v-  compare (General v)   (Ideal s)     = opposite $ compare (vFromS s) v-  compare (General v)   (Complex m)   = compare (mFromV v) m-  compare (Complex m)   (General v)   = opposite $ compare (mFromV v) m-  compare (Ideal s)     m@(Complex _) = compare (General $ vFromS s) m-  compare m@(Complex _) (Ideal s)     = compare m (General $ vFromS s)---- | Convert a `SemVer` to a `Version`.-vFromS :: SemVer -> Version-vFromS (SemVer m i p r _) = Version Nothing [[Digits m], [Digits i], [Digits p]] r---- | Convert a `Version` to a `Mess`.-mFromV :: Version -> Mess-mFromV (Version e v r) = maybe affix (\a -> VNode [showt a] VColon affix) e-  where affix = VNode (chunksAsT v) VHyphen $ VLeaf (chunksAsT r)---- | Traverse some Text for its inner versioning.------ > _Versioning :: Traversal' Text Versioning------ > ("1.2.3" & _Versioning . _Ideal . svPatch %~ (+ 1)) == "1.2.4"-_Versioning :: Applicative f => (Versioning -> f Versioning) -> Text -> f Text-_Versioning f t = either (const (pure t)) (fmap prettyV . f) $ parseV t-{-# INLINE _Versioning #-}---- | Traverse some Text for its inner SemVer.------ > _SemVer :: Traversal' Text SemVer-_SemVer :: Applicative f => (SemVer -> f SemVer) -> Text -> f Text-_SemVer f t = either (const (pure t)) (fmap prettySemVer . f) $ semver t-{-# INLINE _SemVer #-}---- | Traverse some Text for its inner Version.------ > _Version :: Traversal' Text Version-_Version :: Applicative f => (Version -> f Version) -> Text -> f Text-_Version f t = either (const (pure t)) (fmap prettyVer . f) $ version t-{-# INLINE _Version #-}---- | > _Ideal :: Traversal' Versioning SemVer-_Ideal :: Applicative f => (SemVer -> f SemVer) -> Versioning -> f Versioning-_Ideal f (Ideal s) = Ideal <$> f s-_Ideal _ v = pure v-{-# INLINE _Ideal #-}---- | > _General :: Traversal' Versioning Version-_General :: Applicative f => (Version -> f Version) -> Versioning -> f Versioning-_General f (General v) = General <$> f v-_General _ v = pure v-{-# INLINE _General #-}---- | > _Complex :: Traversal' Versioning Mess-_Complex :: Applicative f => (Mess -> f Mess) -> Versioning -> f Versioning-_Complex f (Complex m) = Complex <$> f m-_Complex _ v = pure v-{-# INLINE _Complex #-}---- | An (Ideal) version number that conforms to Semantic Versioning.--- This is a /prescriptive/ parser, meaning it follows the SemVer standard.------ Legal semvers are of the form: MAJOR.MINOR.PATCH-PREREL+META------ Example: 1.2.3-r1+commithash------ Extra Rules:------ 1. Pre-release versions have /lower/ precedence than normal versions.------ 2. Build metadata does not affect version precedence.------ For more information, see http://semver.org-data SemVer = SemVer { _svMajor  :: Int-                     , _svMinor  :: Int-                     , _svPatch  :: Int-                     , _svPreRel :: [VChunk]-                     , _svMeta   :: [VChunk] } deriving (Show,Generic,NFData,Hashable)---- | Two SemVers are equal if all fields except metadata are equal.-instance Eq SemVer where-  (SemVer ma mi pa pr _) == (SemVer ma' mi' pa' pr' _) =-    (ma,mi,pa,pr) == (ma',mi',pa',pr')---- | Build metadata does not affect version precedence.-instance Ord SemVer where-  compare (SemVer ma mi pa pr _) (SemVer ma' mi' pa' pr' _) =-    case compare (ma,mi,pa) (ma',mi',pa') of-     LT -> LT-     GT -> GT-     EQ -> case (pr,pr') of-            ([],[]) -> EQ-            ([],_)  -> GT-            (_,[])  -> LT-            _       -> compare pr pr'--instance Monoid SemVer where-  mempty = SemVer 0 0 0 [] []-  SemVer mj mn pa p m `mappend` SemVer mj' mn' pa' p' m' =-    SemVer (mj + mj') (mn + mn') (pa + pa') (p ++ p') (m ++ m')---- | > svMajor :: Lens' SemVer Int-svMajor :: Functor f => (Int -> f Int) -> SemVer -> f SemVer-svMajor f sv = fmap (\ma -> sv { _svMajor = ma }) (f $ _svMajor sv)-{-# INLINE svMajor #-}---- | > svMinor :: Lens' SemVer Int-svMinor :: Functor f => (Int -> f Int) -> SemVer -> f SemVer-svMinor f sv = fmap (\mi -> sv { _svMinor = mi }) (f $ _svMinor sv)-{-# INLINE svMinor #-}---- | > svPatch :: Lens' SemVer Int-svPatch :: Functor f => (Int -> f Int) -> SemVer -> f SemVer-svPatch f sv = fmap (\pa -> sv { _svPatch = pa }) (f $ _svPatch sv)-{-# INLINE svPatch #-}---- | > svPreRel :: Lens' SemVer Int-svPreRel :: Functor f => ([VChunk] -> f [VChunk]) -> SemVer -> f SemVer-svPreRel f sv = fmap (\pa -> sv { _svPreRel = pa }) (f $ _svPreRel sv)-{-# INLINE svPreRel #-}---- | > svMeta :: Lens' SemVer Int-svMeta :: Functor f => ([VChunk] -> f [VChunk]) -> SemVer -> f SemVer-svMeta f sv = fmap (\pa -> sv { _svMeta = pa }) (f $ _svMeta sv)-{-# INLINE svMeta #-}---- | A single unit of a Version. May be digits or a string of characters.--- Groups of these are called `VChunk`s, and are the identifiers separated--- by periods in the source.-data VUnit = Digits Int | Str Text deriving (Eq,Show,Read,Ord,Generic,NFData,Hashable)---- | > _Digits :: Traversal' VUnit Int-_Digits :: Applicative f => (Int -> f Int) -> VUnit -> f VUnit-_Digits f (Digits i) = Digits <$> f i-_Digits _ v = pure v-{-# INLINE _Digits #-}---- | > _Str :: Traversal' VUnit Text-_Str :: Applicative f => (Text -> f Text) -> VUnit -> f VUnit-_Str f (Str t) = Str <$> f t-_Str _ v = pure v-{-# INLINE _Str #-}---- | A logical unit of a version number. Can consist of multiple letters--- and numbers.-type VChunk = [VUnit]---- | A (General) Version.--- Not quite as ideal as a `SemVer`, but has some internal consistancy--- from version to version.--- Generally conforms to the @x.x.x-x@ pattern, and may optionally have an /epoch/.--- These are prefixes marked by a colon, like in @1:2.3.4@.------ Examples of @Version@ that are not @SemVer@: 0.25-2, 8.u51-1, 20150826-1, 1:2.3.4-data Version = Version { _vEpoch  :: Maybe Int-                       , _vChunks :: [VChunk]-                       , _vRel    :: [VChunk] } deriving (Eq,Show,Generic,NFData,Hashable)---- | Set a `Version`'s epoch to `Nothing`.-wipe :: Version -> Version-wipe v = v { _vEpoch = Nothing }---- | Customized.-instance Ord Version where-  -- | The obvious base case.-  compare (Version _ [] []) (Version _ [] []) = EQ--  -- | For the purposes of Versions with epochs, `Nothing` is the same as `Just 0`,-  -- so we need to compare their actual version numbers.-  compare v0@(Version (Just 0) _ _) v1@(Version Nothing _ _) = compare (wipe v0) v1-  compare v0@(Version Nothing _ _) v1@(Version (Just 0) _ _) = compare v0 (wipe v1)--  compare (Version (Just _) _ _) (Version Nothing _ _) = GT-  compare (Version Nothing _ _) (Version (Just _) _ _) = LT--  -- | If two epochs are equal, we need to compare their actual version numbers.-  -- Otherwise, the comparison of the epochs is the only thing that matters.-  compare v0@(Version (Just n) _ _) v1@(Version (Just m) _ _) | n == m = compare (wipe v0) (wipe v1)-                                                              | otherwise = compare n m--  -- | If the two Versions were otherwise equal and recursed down this far,-  -- we need to compare them by their "release" values.-  compare (Version _ [] rs) (Version _ [] rs') = compare (Version Nothing rs []) (Version Nothing rs' [])--  -- | If one side has run out of chunks to compare but the other hasn't,-  -- the other must be newer.-  compare (Version _ _ _)  (Version _ [] _) = GT-  compare (Version _ [] _) (Version _ _ _)  = LT--  -- | The usual case. If first VChunks of each Version is equal, then we-  -- keep recursing. Otherwise, we don't need to check further. Consider @1.2@-  -- compared to @1.1.3.4.5.6@.-  compare (Version _ (a:as) rs) (Version _ (b:bs) rs') = case f a b of-    EQ  -> compare (Version Nothing as rs) (Version Nothing bs rs')-    res -> res-    where f [] [] = EQ--          -- | Opposite of the above. If we've recursed this far and one side has-          -- fewer chunks, it must be the "greater" version. A Chunk break only occurs in-          -- a switch from digits to letters and vice versa, so anything "extra" must be-          -- an @rc@ marking or similar. Consider @1.1@ compared to @1.1rc1@.-          f [] _  = GT-          f _ []  = LT--          -- | The usual case.-          f (Digits n:ns) (Digits m:ms) | n > m = GT-                                        | n < m = LT-                                        | otherwise = f ns ms-          f (Str n:ns) (Str m:ms) | n > m = GT-                                  | n < m = LT-                                  | otherwise = f ns ms--          -- | An arbitrary decision to prioritize digits over letters.-          f (Digits _ :_) (Str _ :_) = GT-          f (Str _ :_ ) (Digits _ :_) = LT---- | > vEpoch :: Lens' Version (Maybe Int)-vEpoch :: Functor f => (Maybe Int -> f (Maybe Int)) -> Version -> f Version-vEpoch f v = fmap (\ve -> v { _vEpoch = ve }) (f $ _vEpoch v)---- | > vChunks :: Lens' Version [VChunk]-vChunks :: Functor f => ([VChunk] -> f [VChunk]) -> Version -> f Version-vChunks f v = fmap (\vc -> v { _vChunks = vc }) (f $ _vChunks v)-{-# INLINE vChunks #-}---- | > vRel :: Lens' Version [VChunk]-vRel :: Functor f => ([VChunk] -> f [VChunk]) -> Version -> f Version-vRel f v = fmap (\vr -> v { _vRel = vr }) (f $ _vRel v)-{-# INLINE vRel #-}---- | A (Complex) Mess.--- This is a /descriptive/ parser, based on examples of stupidly--- crafted version numbers used in the wild.------ Groups of letters/numbers, separated by a period, can be--- further separated by the symbols @_-+:@------ Unfortunately, @VChunk@s cannot be used here, as some developers have--- numbers like @1.003.04@ which make parsers quite sad.------ Not guaranteed to have well-defined ordering (@Ord@) behaviour,--- but so far internal tests show consistency.-data Mess = VLeaf [Text] | VNode [Text] VSep Mess deriving (Eq,Show,Generic,NFData,Hashable)--instance Ord Mess where-  compare (VLeaf l1) (VLeaf l2)     = compare l1 l2-  compare (VNode t1 _ _) (VLeaf t2) = compare t1 t2-  compare (VLeaf t1) (VNode t2 _ _) = compare t1 t2-  compare (VNode t1 _ v1) (VNode t2 _ v2) | t1 < t2 = LT-                                          | t1 > t2 = GT-                                          | otherwise = compare v1 v2---- | Developers use a number of symbols to seperate groups of digits/letters--- in their version numbers. These are:------ * A colon (:). Often denotes an "epoch".--- * A hyphen (-).--- * A plus (+). Stop using this outside of metadata if you are. Example: @10.2+0.93+1-1@--- * An underscore (_). Stop using this if you are.-data VSep = VColon | VHyphen | VPlus | VUnder deriving (Eq,Show,Generic,NFData,Hashable)---- | A synonym for the more verbose `megaparsec` error type.-type ParsingError = ParseError (Token Text) Dec---- | A wrapper for a parser function. Can be composed via their--- Monoid instance, such that a different parser can be tried--- if a previous one fails.-newtype VParser = VParser { runVP :: Text -> Either ParsingError Versioning }--instance Monoid VParser where-  -- | A parser which will always fail.-  mempty = VParser $ \_ -> Ideal <$> semver ""--  -- | Will attempt the right parser if the left one fails.-  (VParser f) `mappend` (VParser g) = VParser h-    where h t = either (const (g t)) Right $ f t---- | Parse a piece of @Text@ into either an (Ideal) SemVer, a (General)--- Version, or a (Complex) Mess.-parseV :: Text -> Either ParsingError Versioning-parseV = runVP $ semverP <> versionP <> messP---- | A wrapped `SemVer` parser. Can be composed with other parsers.-semverP :: VParser-semverP = VParser $ fmap Ideal . semver---- | Parse a (Ideal) Semantic Version.-semver :: Text -> Either ParsingError SemVer-semver = parse (semver' <* eof) "Semantic Version"---- | Internal megaparsec parser of 'semverP'.-semver' :: Parser SemVer-semver' = SemVer <$> major <*> minor <*> patch <*> preRel <*> metaData---- | Parse a group of digits, which can't be lead by a 0, unless it is 0.-digits :: Parser Int-digits = read <$> (string "0" <|> some digitChar)--major :: Parser Int-major = digits <* char '.'--minor :: Parser Int-minor = major--patch :: Parser Int-patch = digits--preRel :: Parser [VChunk]-preRel = (char '-' *> chunks) <|> pure []--metaData :: Parser [VChunk]-metaData = (char '+' *> chunks) <|> pure []--chunks :: Parser [VChunk]-chunks = chunk `sepBy` char '.'---- | Handling @0@ is a bit tricky. We can't allow runs of zeros in a chunk,--- since a version like @1.000.1@ would parse as @1.0.1@.-chunk :: Parser VChunk-chunk = try zeroWithLetters <|> oneZero <|> many (iunit <|> sunit)-  where oneZero = (:[]) . Digits . read <$> string "0"-        zeroWithLetters = do-          z <- Digits . read <$> string "0"-          s <- some sunit-          c <- chunk-          pure $ (z : s) ++ c--iunit :: Parser VUnit-iunit = Digits . read <$> some digitChar--sunit :: Parser VUnit-sunit = Str . pack <$> some letterChar---- | A wrapped `Version` parser. Can be composed with other parsers.-versionP :: VParser-versionP = VParser $ fmap General . version---- | Parse a (General) `Version`, as defined above.-version :: Text -> Either ParsingError Version-version = parse (version' <* eof) "Version"---- | Internal megaparsec parser of 'versionP'.-version' :: Parser Version-version' = Version <$> optional (try epoch) <*> chunks <*> preRel--epoch :: Parser Int-epoch = read <$> (some digitChar <* char ':')---- | A wrapped `Mess` parser. Can be composed with other parsers.-messP :: VParser-messP = VParser $ fmap Complex . mess---- | Parse a (Complex) `Mess`, as defined above.-mess :: Text -> Either ParsingError Mess-mess = parse (mess' <* eof) "Mess"---- | Internal megaparsec parser of 'messP'.-mess' :: Parser Mess-mess' = try node <|> leaf--leaf :: Parser Mess-leaf = VLeaf <$> tchunks--node :: Parser Mess-node = VNode <$> tchunks <*> sep <*> mess'--tchunks :: Parser [Text]-tchunks = (pack <$> some (letterChar <|> digitChar)) `sepBy` char '.'--sep :: Parser VSep-sep = choice [ VColon  <$ char ':'-             , VHyphen <$ char '-'-             , VPlus   <$ char '+'-             , VUnder  <$ char '_' ]--sepCh :: VSep -> Char-sepCh VColon  = ':'-sepCh VHyphen = '-'-sepCh VPlus   = '+'-sepCh VUnder  = '_'---- | Convert any parsed Versioning type to its textual representation.-prettyV :: Versioning -> Text-prettyV (Ideal sv)  = prettySemVer sv-prettyV (General v) = prettyVer v-prettyV (Complex m) = prettyMess m---- | Convert a `SemVer` back to its textual representation.-prettySemVer :: SemVer -> Text-prettySemVer (SemVer ma mi pa pr me) = mconcat $ ver <> pr' <> me'-  where ver = intersperse "." [ showt ma, showt mi, showt pa ]-        pr' = foldable [] ("-" :) $ intersperse "." (chunksAsT pr)-        me' = foldable [] ("+" :) $ intersperse "." (chunksAsT me)---- | Convert a `Version` back to its textual representation.-prettyVer :: Version -> Text-prettyVer (Version ep cs pr) = ep' <> mconcat (ver <> pr')-  where ver = intersperse "." $ chunksAsT cs-        pr' = foldable [] ("-" :) $ intersperse "." (chunksAsT pr)-        ep' = maybe "" (\e -> showt e <> ":") ep---- | Convert a `Mess` back to its textual representation.-prettyMess :: Mess -> Text-prettyMess (VLeaf t)     = mconcat $ intersperse "." t-prettyMess (VNode t s v) = snoc t' (sepCh s) <> prettyMess v-  where t' = mconcat $ intersperse "." t--chunksAsT :: [VChunk] -> [Text]-chunksAsT = map (mconcat . map f)-  where f (Digits i) = showt i-        f (Str s)    = s---- | Analogous to `maybe` and `either`. If a given Foldable is empty,--- a default value is returned. Else, a function is applied to that Foldable.-foldable :: Foldable f => f b -> (f a -> f b) -> f a -> f b-foldable d g f | null f    = d-               | otherwise = g f---- | Flip an Ordering.-opposite :: Ordering -> Ordering-opposite EQ = EQ-opposite LT = GT-opposite GT = LT---- Yes, `text-show` exists, but this reduces external dependencies.-showt :: Show a => a -> Text-showt = pack . show+{-# LANGUAGE CPP                #-}+{-# LANGUAGE DeriveAnyClass     #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric      #-}+{-# LANGUAGE DeriveLift         #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE Rank2Types         #-}++-- |+-- Module    : Data.Versions+-- Copyright : (c) Colin Woodbury, 2015 - 2023+-- License   : BSD3+-- Maintainer: Colin Woodbury <colin@fosskers.ca>+--+-- A library for parsing and comparing software version numbers.+--+-- We like to give version numbers to our software in a myriad of different+-- ways. Some ways follow strict guidelines for incrementing and comparison.+-- Some follow conventional wisdom and are generally self-consistent. Some are+-- just plain asinine. This library provides a means of parsing and comparing+-- /any/ style of versioning, be it a nice Semantic Version like this:+--+-- > 1.2.3-r1+git123+--+-- ...or a monstrosity like this:+--+-- > 2:10.2+0.0093r3+1-1+--+-- Please switch to <http://semver.org Semantic Versioning> if you aren't+-- currently using it. It provides consistency in version incrementing and has+-- the best constraints on comparisons.+--+-- __This library implements version @2.0.0@ of the SemVer spec.__+--+-- == Using the Parsers+-- In general, `versioning` is the function you want. It attempts to parse a+-- given `Text` using the three individual parsers, `semver`, `version` and+-- `mess`. If one fails, it tries the next. If you know you only want to parse+-- one specific version type, use that parser directly (e.g. `semver`).++module Data.Versions+  ( -- * Types+    Versioning(..), isIdeal, isGeneral, isComplex+  , SemVer(..)+  , PVP(..)+  , Version(..)+  , Mess(..), messMajor, messMinor, messPatch, messPatchChunk+  , Release(..)+  , Chunks(..)+  , Chunk(..)+  , MChunk(..)+  , VSep(..)+    -- ** Compile-time Constructors+  , versioningQ, semverQ, versionQ, messQ, pvpQ+    -- ** Conversions+  , semverToVersion, versionToMess, versionToPvp+    -- * Parsing Versions+  , ParsingError+  , versioning, semver, pvp, version, mess+    -- ** Megaparsec Parsers+    -- | For when you'd like to mix version parsing into some larger parser.+  , versioning', semver', pvp', version', mess'+    -- * Pretty Printing+  , prettyV, prettySemVer, prettyPVP, prettyVer, prettyMess, errorBundlePretty+    -- * Lenses+  , Lens'+  , Traversal'+  , Semantic(..)+    -- ** Traversing Text+    -- | When traversing `Text`, leveraging its `Semantic` instance will+    -- likely benefit you more than using these Traversals directly.+  , _Versioning, _SemVer, _Version, _Mess+    -- ** Versioning Traversals+  , _Ideal, _General, _Complex+    -- ** (General) Version Lenses+  , epoch+  ) where++import qualified Control.Applicative.Combinators.NonEmpty as PC+import           Control.DeepSeq+import           Control.Monad (unless, void)+import           Data.Char (isAlpha, isAlphaNum)+import           Data.Data (Data)+import           Data.Foldable (fold)+import           Data.Hashable (Hashable)+import           Data.List (intersperse)+import           Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NEL+import           Data.Maybe (fromMaybe, listToMaybe, mapMaybe)+import           Data.Text (Text)+import qualified Data.Text as T+import           Data.Void (Void)+import           Data.Word (Word64)+import           GHC.Generics (Generic)+import           Language.Haskell.TH (Exp, Q)+import           Language.Haskell.TH.Syntax (Lift(..))+import           Text.Megaparsec hiding (chunk)+import           Text.Megaparsec.Char+import qualified Text.Megaparsec.Char.Lexer as L++---++-- | A top-level Versioning type. Acts as a wrapper for the more specific types.+-- This allows each subtype to have its own parser, and for said parsers to be+-- composed. This is useful for specifying custom behaviour for when a certain+-- parser fails.+data Versioning = Ideal !SemVer | General !Version | Complex !Mess+  deriving (Eq, Show, Generic, NFData, Hashable, Lift, Data)++-- | Short-hand for detecting a `SemVer`.+isIdeal :: Versioning -> Bool+isIdeal (Ideal _) = True+isIdeal _         = False++-- | Short-hand for detecting a `Version`.+isGeneral :: Versioning -> Bool+isGeneral (General _) = True+isGeneral _           = False++-- | Short-hand for detecting a `Mess`.+isComplex :: Versioning -> Bool+isComplex (Complex _) = True+isComplex _           = False++-- | Comparison of @Ideal@s is always well defined.+--+-- If comparison of @General@s is well-defined, then comparison of @Ideal@ and+-- @General@ is well-defined, as there exists a perfect mapping from @Ideal@ to+-- @General@.+--+-- If comparison of @Complex@es is well-defined, then comparison of @General@+-- and @Complex@ is well defined for the same reason. This implies comparison of+-- @Ideal@ and @Complex@ is also well-defined.+instance Ord Versioning where+  compare (Ideal s)   (Ideal s')   = compare s s'+  compare (General v) (General v') = compare v v'+  compare (Complex m) (Complex m') = compare m m'+  compare (Ideal s)   (General v)  = semverAndVer s v+  compare (General v) (Ideal s)    = opposite $ semverAndVer s v+  compare (General v) (Complex m)  = compare (versionToMess v) m+  compare (Complex m) (General v)  = opposite $ compare (versionToMess v) m+  compare (Ideal s)   (Complex m)  = semverAndMess s m+  compare (Complex m) (Ideal s)    = opposite $ semverAndMess s m++-- | Convert a `SemVer` to a `Version`.+semverToVersion :: SemVer -> Version+semverToVersion (SemVer ma mi pa re me) =+  Version+  { _vEpoch = Nothing+  , _vChunks = Chunks $ Numeric ma :| [Numeric mi, Numeric pa]+  , _vMeta = me+  , _vRel = re }++-- | Convert a `Version` to a `Mess`.+versionToMess :: Version -> Mess+versionToMess (Version me (Chunks v) r _) = case me of+  Nothing -> f+  Just e  ->+    let cs = (:| []) . MDigit e $ showt e+    in Mess cs $ Just (VColon, f)+  where+    f :: Mess+    f = Mess cs $ fmap g r+      where+        cs = NEL.map toMChunk v++    g :: Release -> (VSep, Mess)+    g (Release cs) = (VHyphen, Mess ms Nothing)+      where+        ms = NEL.map toMChunk cs++-- | Convert a `Version` to a `PVP`. Fails if there is an epoch present, but+-- otherwise ignores the `Release` and other metadata. Naturally it also fails+-- if any of the version components contain any non-digits.+versionToPvp :: Version -> Maybe PVP+versionToPvp (Version (Just _) _ _ _) = Nothing+versionToPvp (Version Nothing (Chunks cs) _ _) = PVP <$> traverse f cs+  where+    f :: Chunk -> Maybe Word+    f (Numeric w)  = Just w+    f (Alphanum _) = Nothing++semverAndVer :: SemVer -> Version -> Ordering+-- A `Version` with a non-zero epoch value is automatically greater than any+-- `SemVer`.+semverAndVer _ (Version (Just e) _ _ _) | e > 0 = LT+semverAndVer (SemVer ma mi pa sr _) (Version _ (Chunks vc) vr _) =+  case compare ma <$> (nth 0 vc' >>= singleDigitLenient) of+    Nothing -> GT+    Just GT -> GT+    Just LT -> LT+    Just EQ -> case compare mi <$> (nth 1 vc' >>= singleDigitLenient) of+      Nothing -> GT+      Just GT -> GT+      Just LT -> LT+      Just EQ -> case compare pa <$> (nth 2 vc' >>= singleDigitLenient) of+        Nothing -> GT+        Just GT -> GT+        Just LT -> LT+        -- By thes point, the major/minor/patch positions have all been equal.+        -- If there is a fourth position, its type, not its value, will+        -- determine which overall version is greater.+        Just EQ -> case nth 3 vc' of+          -- 1.2.3 > 1.2.3.git+          Just (Alphanum _) -> GT+          -- 1.2.3 < 1.2.3.0+          Just (Numeric _)  -> LT+          Nothing           -> compare sr vr+ where+   vc' :: [Chunk]+   vc' = NEL.toList vc++   nth :: Int -> [Chunk] -> Maybe Chunk+   nth _ []     = Nothing+   nth 0 (c:_)  = Just c+   nth n (_:cs) = nth (n - 1) cs++-- | Special logic for when semver-like values can be extracted from a `Mess`.+-- This avoids having to "downcast" the `SemVer` into a `Mess` before comparing,+-- and in some cases can offer better comparison results.+semverAndMess :: SemVer -> Mess -> Ordering+semverAndMess s@(SemVer ma mi pa _ _) m = case compare ma <$> messMajor m of+  Nothing -> fallback+  Just LT -> LT+  Just GT -> GT+  Just EQ -> case compare mi <$> messMinor m of+    Nothing -> fallback+    Just LT -> LT+    Just GT -> GT+    Just EQ -> case compare pa <$> messPatch m of+      Just LT -> LT+      Just GT -> GT+      -- If they've been equal up to this point, the `Mess`+      -- will by definition have more to it, meaning that+      -- it's more likely to be newer, despite its poor shape.+      Just EQ -> fallback+      -- Even if we weren't able to extract a standalone patch number, we might+      -- still be able to find a number at the head of the `Chunk` in that+      -- position.+      Nothing -> case messPatchChunk m >>= singleDigitLenient of+        -- We were very close, but in the end the `Mess` had a nonsensical value+        -- in its patch position.+        Nothing  -> fallback+        Just pa' -> case compare pa pa' of+          LT -> LT+          GT -> GT+          -- This follows semver's rule that pre-releases have lower precedence.+          EQ -> GT+  where+    fallback :: Ordering+    fallback = compare (General $ semverToVersion s) (Complex m)++instance Semantic Versioning where+  major f (Ideal v)   = Ideal   <$> major f v+  major f (General v) = General <$> major f v+  major f (Complex v) = Complex <$> major f v+  {-# INLINE major #-}++  minor f (Ideal v)   = Ideal   <$> minor f v+  minor f (General v) = General <$> minor f v+  minor f (Complex v) = Complex <$> minor f v+  {-# INLINE minor #-}++  patch f (Ideal v)   = Ideal   <$> patch f v+  patch f (General v) = General <$> patch f v+  patch f (Complex v) = Complex <$> patch f v+  {-# INLINE patch #-}++  release f (Ideal v)   = Ideal   <$> release f v+  release f (General v) = General <$> release f v+  release f (Complex v) = Complex <$> release f v+  {-# INLINE release #-}++  meta f (Ideal v)   = Ideal   <$> meta f v+  meta f (General v) = General <$> meta f v+  meta f (Complex v) = Complex <$> meta f v+  {-# INLINE meta #-}++  semantic f (Ideal v)   = Ideal   <$> semantic f v+  semantic f (General v) = General <$> semantic f v+  semantic f (Complex v) = Complex <$> semantic f v+  {-# INLINE semantic #-}++-- | Traverse some Text for its inner versioning.+--+-- @+-- λ "1.2.3" & _Versioning . _Ideal . patch %~ (+ 1)  -- or just: "1.2.3" & patch %~ (+ 1)+-- "1.2.4"+-- @+_Versioning :: Traversal' Text Versioning+_Versioning f t = either (const (pure t)) (fmap prettyV . f) $ versioning t+{-# INLINE _Versioning #-}++-- | Traverse some Text for its inner SemVer.+_SemVer :: Traversal' Text SemVer+_SemVer f t = either (const (pure t)) (fmap prettySemVer . f) $ semver t+{-# INLINE _SemVer #-}++-- | Traverse some Text for its inner Version.+_Version :: Traversal' Text Version+_Version f t = either (const (pure t)) (fmap prettyVer . f) $ version t+{-# INLINE _Version #-}++-- | Traverse some Text for its inner Mess.+_Mess :: Traversal' Text Mess+_Mess f t = either (const (pure t)) (fmap prettyMess . f) $ mess t+{-# INLINE _Mess #-}++-- | Possibly extract a `SemVer` from a `Versioning`.+_Ideal :: Traversal' Versioning SemVer+_Ideal f (Ideal s) = Ideal <$> f s+_Ideal _ v         = pure v+{-# INLINE _Ideal #-}++-- | Possibly extract a `Version` from a `Versioning`.+_General :: Traversal' Versioning Version+_General f (General v) = General <$> f v+_General _ v           = pure v+{-# INLINE _General #-}++-- | Possibly extract a `Mess` from a `Versioning`.+_Complex :: Traversal' Versioning Mess+_Complex f (Complex m) = Complex <$> f m+_Complex _ v           = pure v+{-# INLINE _Complex #-}++-- | Simple Lenses compatible with both lens and microlens.+type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s++-- | Simple Traversals compatible with both lens and microlens.+type Traversal' s a = forall f. Applicative f => (a -> f a) -> s -> f s++-- | Version types which sanely and safely yield `SemVer`-like information about+-- themselves. For instances other than `SemVer` itself however, these optics+-- may /not/ yield anything, depending on the actual value being traversed.+-- Hence, the optics here are all `Traversal'`s.+--+-- Consider the `Version` @1.2.3.4.5@. We can imagine wanting to increment the+-- minor number:+--+-- @+-- λ "1.2.3.4.5" & minor %~ (+ 1)+-- "1.3.3.4.5"+-- @+--+-- But of course something like this would fail:+--+-- @+-- λ "1.e.3.4.5" & minor %~ (+ 1)+-- "1.e.3.4.5"+-- @+--+-- However!+--+-- @+-- λ "1.e.3.4.5" & major %~ (+ 1)+-- "2.e.3.4.5"+-- @+class Semantic v where+  -- | @MAJOR.minor.patch-prerel+meta@+  major    :: Traversal' v Word+  -- | @major.MINOR.patch-prerel+meta@+  minor    :: Traversal' v Word+  -- | @major.minor.PATCH-prerel+meta@+  patch    :: Traversal' v Word+  -- | @major.minor.patch-PREREL+meta@+  release  :: Traversal' v (Maybe Release)+  -- | @major.minor.patch-prerel+META@+  meta     :: Traversal' v (Maybe Text)+  -- | A Natural Transformation into an proper `SemVer`.+  semantic :: Traversal' v SemVer++instance Semantic Text where+  major    = _Versioning . major+  minor    = _Versioning . minor+  patch    = _Versioning . patch+  release  = _Versioning . release+  meta     = _Versioning . meta+  semantic = _SemVer++--------------------------------------------------------------------------------+-- (Ideal) SemVer++-- | An (Ideal) version number that conforms to Semantic Versioning.+-- This is a /prescriptive/ parser, meaning it follows the SemVer standard.+--+-- Legal semvers are of the form: MAJOR.MINOR.PATCH-PREREL+META+--+-- Example: @1.2.3-r1+commithash@+--+-- Extra Rules:+--+-- 1. Pre-release versions have /lower/ precedence than normal versions.+--+-- 2. Build metadata does not affect version precedence.+--+-- 3. PREREL and META strings may only contain ASCII alphanumerics and hyphens.+--+-- For more information, see http://semver.org+data SemVer = SemVer+  { _svMajor  :: !Word+  , _svMinor  :: !Word+  , _svPatch  :: !Word+  , _svPreRel :: !(Maybe Release)+  , _svMeta   :: !(Maybe Text) }+  deriving stock (Show, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++-- | Two SemVers are equal if all fields except metadata are equal.+instance Eq SemVer where+  (SemVer ma mi pa pr _) == (SemVer ma' mi' pa' pr' _) =+    (ma,mi,pa,pr) == (ma',mi',pa',pr')++-- | Build metadata does not affect version precedence.+instance Ord SemVer where+  compare (SemVer ma mi pa pr _) (SemVer ma' mi' pa' pr' _) =+    case compare (ma,mi,pa) (ma',mi',pa') of+     LT -> LT+     GT -> GT+     EQ -> case (pr, pr') of+       (Nothing, Nothing) -> EQ+       (Nothing, _)       -> GT+       (_, Nothing)       -> LT+       (Just ap, Just bp) -> compare ap bp++instance Semantic SemVer where+  major f sv = fmap (\ma -> sv { _svMajor = ma }) (f $ _svMajor sv)+  {-# INLINE major #-}++  minor f sv = fmap (\mi -> sv { _svMinor = mi }) (f $ _svMinor sv)+  {-# INLINE minor #-}++  patch f sv = fmap (\pa -> sv { _svPatch = pa }) (f $ _svPatch sv)+  {-# INLINE patch #-}++  release f sv = fmap (\pa -> sv { _svPreRel = pa }) (f $ _svPreRel sv)+  {-# INLINE release #-}++  meta f sv = fmap (\pa -> sv { _svMeta = pa }) (f $ _svMeta sv)+  {-# INLINE meta #-}++  semantic = ($)+  {-# INLINE semantic #-}++-- | `Chunk`s have comparison behaviour according to SemVer's rules for preleases.+newtype Release = Release (NonEmpty Chunk)+  deriving stock (Eq, Show, Read, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++instance Ord Release where+  compare (Release as) (Release bs) =+    fromMaybe EQ . listToMaybe . mapMaybe f $ zipLongest (NEL.toList as) (NEL.toList bs)+    where+      f :: These Chunk Chunk -> Maybe Ordering+      f (Both a b) = case cmpSemVer a b of+        LT -> Just LT+        GT -> Just GT+        EQ -> Nothing+      f (This _)   = Just GT+      f (That _)   = Just LT++-- | A logical unit of a version number.+--+-- Either entirely numerical (with no leading zeroes) or entirely alphanumerical+-- (with a free mixture of numbers, letters, and hyphens.)+--+-- Groups of these (like `Release`) are separated by periods to form a full+-- section of a version number.+--+-- Examples:+--+-- @+-- 1+-- 20150826+-- r3+-- 0rc1-abc3+-- @+data Chunk = Numeric !Word | Alphanum !Text+  deriving stock (Eq, Show, Read, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++toMChunk :: Chunk -> MChunk+toMChunk (Numeric n)  = MDigit n $ showt n+toMChunk (Alphanum s) = MPlain s++-- | `Chunk` is used in multiple places but requires different comparison+-- semantics depending on the wrapping type. This function and `cmpLenient`+-- below provide this.+cmpSemVer :: Chunk -> Chunk -> Ordering+cmpSemVer (Numeric a) (Numeric b)   = compare a b+cmpSemVer (Numeric _) (Alphanum _)  = LT+cmpSemVer (Alphanum _) (Numeric _)  = GT+cmpSemVer (Alphanum a) (Alphanum b) = compare a b++-- | Like `cmpSemVer`, but for `Version`s. We need to be mindful of comparisons+-- like @1.2.0 > 1.2.0rc1@ which normally wouldn't occur in SemVer.+cmpLenient :: Chunk -> Chunk -> Ordering+cmpLenient (Numeric a) (Numeric b)       = compare a b+cmpLenient a@(Alphanum x) b@(Alphanum y) =+  case (singleDigitLenient a, singleDigitLenient b) of+    (Just i, Just j) -> compare i j+    _                -> compare x y+cmpLenient (Numeric n) b@(Alphanum _) =+  case singleDigitLenient b of+    Nothing -> GT+    Just m -> case compare n m of+      -- 1.2.0 > 1.2.0rc1+      EQ -> GT+      c  -> c+cmpLenient a@(Alphanum _) (Numeric n) =+  case singleDigitLenient a of+    Nothing -> LT+    Just m -> case compare m n of+      -- 1.2.0rc1 < 1.2.0+      EQ -> LT+      c  -> c++-- | Like `singleDigit` but will grab a leading `Word` even if followed by+-- letters.+singleDigitLenient :: Chunk -> Maybe Word+singleDigitLenient (Numeric n)  = Just n+singleDigitLenient (Alphanum s) = hush $ parse unsignedP "Single Digit Lenient" s++--------------------------------------------------------------------------------+-- (Haskell) PVP++-- | A PVP version number specific to the Haskell ecosystem. Like SemVer this is+-- a prescriptive scheme, and follows <https://pvp.haskell.org/ the PVP spec>.+--+-- Legal PVP values are of the form: MAJOR(.MAJOR.MINOR)+--+-- Example: @1.2.3@+--+-- Extra Rules:+--+-- 1. Each component must be a number.+--+-- 2. Only the first MAJOR component is actually necessary. Otherwise, there can+--    be any number of components. @1.2.3.4.5.6.7@ is legal.+--+-- 3. Unlike SemVer there are two MAJOR components, and both indicate a breaking+--    change. The spec otherwise designates no special meaning to components+--    past the MINOR position.+newtype PVP = PVP { _pComponents :: NonEmpty Word }+  deriving stock (Eq, Ord, Show, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++instance Semantic PVP where+  major f (PVP (m :| rs)) = (\ma -> PVP $ ma :| rs) <$> f m+  {-# INLINE major #-}++  minor f (PVP (m :| mi : rs)) = (\mi' -> PVP $ m :| mi' : rs) <$> f mi+  minor f (PVP (m :| []))      = (\mi' -> PVP $ m :| [mi']) <$> f 0+  {-# INLINE minor #-}++  patch f (PVP (m :| mi : pa : rs)) = (\pa' -> PVP $ m :| mi : pa' : rs) <$> f pa+  patch f (PVP (m :| [mi]))         = (\pa' -> PVP $ m :| mi : [pa']) <$> f 0+  patch f (PVP (m :| []))           = (\pa' -> PVP $ m :| 0 : [pa']) <$> f 0+  {-# INLINE patch #-}++  release f p = p <$ f Nothing+  {-# INLINE release #-}++  meta f p = p <$ f Nothing+  {-# INLINE meta #-}++  semantic f (PVP (m :| rs)) = (\(SemVer ma mi pa _ _) -> PVP $ ma :| [mi, pa]) <$> f s+    where+      s = case rs of+        mi : pa : _ -> SemVer m mi pa Nothing Nothing+        mi : _      -> SemVer m mi 0  Nothing Nothing+        []          -> SemVer m 0 0   Nothing Nothing+  {-# INLINE semantic #-}++--------------------------------------------------------------------------------+-- (General) Version++-- | A version number with decent structure and comparison logic.+--+-- This is a /descriptive/ scheme, meaning that it encapsulates the most common,+-- unconscious patterns that developers use when assigning version numbers to+-- their software. If not `SemVer`, most version numbers found in the wild will+-- parse as a `Version`. These generally conform to the @x.x.x-x@ pattern, and+-- may optionally have an /epoch/.+--+-- Epochs are prefixes marked by a colon, like in @1:2.3.4@. When comparing two+-- `Version` values, epochs take precedent. So @2:1.0.0 > 1:9.9.9@. If one of+-- the given `Version`s has no epoch, its epoch is assumed to be 0.+--+-- Examples of @Version@ that are not @SemVer@: 0.25-2, 8.u51-1, 20150826-1,+-- 1:2.3.4+data Version = Version+  { _vEpoch  :: !(Maybe Word)+  , _vChunks :: !Chunks+  , _vRel    :: !(Maybe Release)+  , _vMeta   :: !(Maybe Text) }+  deriving stock (Eq, Show, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++-- | Customized. As in SemVer, metadata is ignored for the purpose of+-- comparison.+instance Ord Version where+  -- If two epochs are equal, we need to compare their actual version numbers.+  -- Otherwise, the comparison of the epochs is the only thing that matters.+  compare (Version mae ac ar _) (Version mbe bc br _) =+    case compare ae be of+      EQ -> case compare ac bc of+        EQ  -> compare ar br+        ord -> ord+      ord -> ord+    where+      ae = fromMaybe 0 mae+      be = fromMaybe 0 mbe++instance Semantic Version where+  major f (Version e (Chunks (Numeric n :| cs)) me rs) =+    (\n' -> Version e (Chunks $ Numeric n' :| cs) me rs) <$> f n+  major _ v = pure v+  {-# INLINE major #-}++  minor f (Version e (Chunks (c :| Numeric n : cs)) me rs) =+    (\n' -> Version e (Chunks $ c :| Numeric n' : cs) me rs) <$> f n+  minor _ v = pure v+  {-# INLINE minor #-}++  patch f (Version e (Chunks (c :| d : Numeric n : cs)) me rs) =+    (\n' -> Version e (Chunks $ c :| d : Numeric n' : cs) me rs) <$> f n+  patch _ v = pure v+  {-# INLINE patch #-}++  -- | This will always succeed.+  release f v = fmap (\vr -> v { _vRel = vr }) (f $ _vRel v)+  {-# INLINE release #-}++  -- | This will always fail.+  meta _ v = pure v+  {-# INLINE meta #-}++  semantic f (Version _ (Chunks (Numeric a :| Numeric b : Numeric c : _)) rs me) =+    semverToVersion <$> f (SemVer a b c rs me)+  semantic _ v = pure v+  {-# INLINE semantic #-}++-- | A `Version`'s inner epoch `Word`.+epoch :: Lens' Version (Maybe Word)+epoch f v = fmap (\ve -> v { _vEpoch = ve }) (f $ _vEpoch v)+{-# INLINE epoch #-}++-- | `Chunk`s that have a comparison behaviour specific to `Version`.+newtype Chunks = Chunks (NonEmpty Chunk)+  deriving stock (Eq, Show, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++instance Ord Chunks where+  compare (Chunks as) (Chunks bs) =+    fromMaybe EQ . listToMaybe . mapMaybe f $ zipLongest (NEL.toList as) (NEL.toList bs)+    where+      f :: These Chunk Chunk -> Maybe Ordering+      f (Both a b) = case cmpLenient a b of+        LT -> Just LT+        GT -> Just GT+        EQ -> Nothing+      f (This _)   = Just GT+      f (That _)   = Just LT++--------------------------------------------------------------------------------+-- (Complex) Mess++-- | Possible values of a section of a `Mess`. A numeric value is extracted if+-- it could be, alongside the original text it came from. This preserves both+-- `Ord` and pretty-print behaviour for versions like @1.003.0@.+data MChunk+  = MDigit !Word !Text+  -- ^ A nice numeric value.+  | MRev !Word !Text+  -- ^ A numeric value preceeded by an @r@, indicating a revision.+  | MPlain !Text+  -- ^ Anything else.+  deriving stock (Eq, Show, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++instance Ord MChunk where+  compare (MDigit a _) (MDigit b _) = compare a b+  compare (MRev a _) (MRev b _)     = compare a b+  compare (MPlain a) (MPlain b)     = compare a b+  compare a b                       = compare (mchunkText a) (mchunkText b)++-- | A total extraction of the `Text` from an `MChunk`.+mchunkText :: MChunk -> Text+mchunkText (MDigit _ t) = t+mchunkText (MRev _ t)   = t+mchunkText (MPlain t)   = t++-- | A (Complex) Mess. This is a /descriptive/ parser, based on examples of+-- stupidly crafted version numbers used in the wild.+--+-- Groups of letters/numbers, separated by a period, can be further separated by+-- the symbols @_-+:@+--+-- Some `Mess` values have a shape that is tantalizingly close to a `SemVer`.+-- Example: @1.6.0a+2014+m872b87e73dfb-1@. For values like these, we can extract+-- the semver-compatible values out with `messMajor`, etc.+--+-- Not guaranteed to have well-defined ordering (@Ord@) behaviour, but so far+-- internal tests show consistency. `messMajor`, etc., are used internally where+-- appropriate to enhance accuracy.+data Mess = Mess !(NonEmpty MChunk) !(Maybe (VSep, Mess))+  deriving stock (Eq, Show, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++-- | Try to extract the "major" version number from `Mess`, as if it were a+-- `SemVer`.+messMajor :: Mess -> Maybe Word+messMajor (Mess (MDigit i _ :| _) _) = Just i+messMajor _                          = Nothing++-- | Try to extract the "minor" version number from `Mess`, as if it were a+-- `SemVer`.+messMinor :: Mess -> Maybe Word+messMinor (Mess (_ :| MDigit i _ : _) _) = Just i+messMinor _                              = Nothing++-- | Try to extract the "patch" version number from `Mess`, as if it were a+-- `SemVer`.+messPatch :: Mess -> Maybe Word+messPatch (Mess (_ :| _ : MDigit i _ : _) _) = Just i+messPatch _                                  = Nothing++-- | Okay, fine, say `messPatch` couldn't find a nice value. But some `Mess`es+-- have a "proper" patch-plus-release-candidate value in their patch position,+-- which is parsable as a `Chunk`.+--+-- Example: @1.6.0a+2014+m872b87e73dfb-1@ We should be able to extract @0a@ safely.+messPatchChunk :: Mess -> Maybe Chunk+messPatchChunk (Mess (_ :| _ : MPlain p : _) _) = hush $ parse chunkP "Chunk" p+messPatchChunk _                                = Nothing++instance Ord Mess where+  compare (Mess t1 m1) (Mess t2 m2) = case compare t1 t2 of+    EQ  -> case (m1, m2) of+      (Just (_, v1), Just (_, v2)) -> compare v1 v2+      (Just (_, _), Nothing)       -> GT+      (Nothing, Just (_, _))       -> LT+      (Nothing, Nothing)           -> EQ+    res -> res++instance Semantic Mess where+  major f (Mess (MDigit n _ :| ts) m) = (\n' -> Mess (MDigit n' (showt n') :| ts) m) <$> f n+  major _ v = pure v+  {-# INLINE major #-}++  minor f (Mess (t0 :| MDigit n _ : ts) m) = (\n' -> Mess (t0 :| MDigit n' (showt n') : ts) m) <$> f n+  minor _ v = pure v+  {-# INLINE minor #-}++  patch f (Mess (t0 :| t1 : MDigit n _ : ts) m) = (\n' -> Mess (t0 :| t1 : MDigit n' (showt n') : ts) m) <$> f n+  patch _ v = pure v+  {-# INLINE patch #-}++  -- | This will always fail.+  release _ v = pure v+  {-# INLINE release #-}++  -- | This will always fail.+  meta _ v = pure v+  {-# INLINE meta #-}++  -- | Good luck.+  semantic f (Mess (MDigit t0 _ :| MDigit t1 _ : MDigit t2 _ : _) _) =+    versionToMess . semverToVersion <$> f (SemVer t0 t1 t2 Nothing Nothing)+  semantic _ v = pure v+  {-# INLINE semantic #-}++-- | Developers use a number of symbols to seperate groups of digits/letters in+-- their version numbers. These are:+--+-- * A colon (:). Often denotes an "epoch".+-- * A hyphen (-).+-- * A tilde (~). Example: @12.0.0-3ubuntu1~20.04.5@+-- * A plus (+). Stop using this outside of metadata if you are. Example: @10.2+0.93+1-1@+-- * An underscore (_). Stop using this if you are.+data VSep = VColon | VHyphen | VPlus | VUnder | VTilde+  deriving stock (Eq, Show, Generic, Lift, Data)+  deriving anyclass (NFData, Hashable)++-- | Parse a `Versioning` at compile time.+versioningQ :: Text -> Q Exp+versioningQ nm =+  case versioning nm of+    Left err -> fail (errorBundlePretty err)+    Right v  -> lift v++-- | Parse a `SemVer` at compile time.+semverQ :: T.Text -> Q Exp+semverQ nm =+  case semver nm of+    Left err -> fail (errorBundlePretty err)+    Right v  -> lift v++-- | Parse a `Version` at compile time.+versionQ :: T.Text -> Q Exp+versionQ nm =+  case version nm of+    Left err -> fail (errorBundlePretty err)+    Right v  -> lift v++-- | Parse a `Mess` at compile time.+messQ :: T.Text -> Q Exp+messQ nm =+  case mess nm of+    Left err -> fail (errorBundlePretty err)+    Right v  -> lift v++-- | Parse a `PVP` at compile time.+pvpQ :: T.Text -> Q Exp+pvpQ nm =+  case pvp nm of+    Left err -> fail (errorBundlePretty err)+    Right v  -> lift v++--------------------------------------------------------------------------------+-- Parsing++-- | A synonym for the more verbose 'megaparsec' error type.+type ParsingError = ParseErrorBundle Text Void++-- | Parse a piece of `Text` into either an (Ideal) `SemVer`, a (General)+-- `Version`, or a (Complex) `Mess`.+versioning :: Text -> Either ParsingError Versioning+versioning = parse versioning' "versioning"++-- | Parse a `Versioning`. Assumes the version number is the last token in+-- the string.+versioning' :: Parsec Void Text Versioning+versioning' = choice [ try (fmap Ideal semver''    <* eof)+                     , try (fmap General version'' <* eof)+                     , fmap Complex mess''         <* eof ]++-- | Parse a (Ideal) Semantic Version.+semver :: Text -> Either ParsingError SemVer+semver = parse (semver'' <* eof) "Semantic Version"++-- | Internal megaparsec parser of `semver`.+semver' :: Parsec Void Text SemVer+semver' = L.lexeme space semver''++semver'' :: Parsec Void Text SemVer+semver'' = SemVer <$> majorP <*> minorP <*> patchP <*> optional releaseP <*> optional metaData++-- | Parse a group of digits, which can't be lead by a 0, unless it is 0.+unsignedP :: Parsec Void Text Word+unsignedP = asWord64 >>= convertOrFail+  where+    asWord64 :: Parsec Void Text Word64+    asWord64 = (0 <$ char '0') <|> L.decimal++    convertOrFail :: Word64 -> Parsec Void Text Word+    convertOrFail w | w > bound = fail $ "Value (" ++ show w ++ ") larger than Word size: " ++ show bound+                    | otherwise = pure $ fromIntegral w+      where+        bound :: Word64+        bound = fromIntegral (maxBound :: Word)++majorP :: Parsec Void Text Word+majorP = unsignedP <* char '.'++minorP :: Parsec Void Text Word+minorP = majorP++patchP :: Parsec Void Text Word+patchP = unsignedP++releaseP :: Parsec Void Text Release+releaseP = char '-' *> fmap Release (chunkP `PC.sepBy1` char '.')++chunkP :: Parsec Void Text Chunk+chunkP = try alphanumP <|> numericP++alphanumP :: Parsec Void Text Chunk+alphanumP = do+  ids <- takeWhile1P (Just "Hyphenated Alphanums") (\c -> isAlphaNum c || c == '-')+  -- It's okay for this to `fail` like this, since this fail is caught higher up+  -- in `chunkP` and another parser which should be guaranteed to succeed is+  -- called. It's guaranteed since by this point we /did/ parse something, but+  -- the test below proves it contains only numbers. Therefore the fallback call+  -- to `numericP` should succeed.+  unless (T.any (\c -> isAlpha c || c == '-') ids) $ fail "Only numeric!"+  pure $ Alphanum ids++alphanumWithoutHyphensP :: Parsec Void Text Chunk+alphanumWithoutHyphensP = do+  ids <- takeWhile1P (Just "Unhyphenated Alphanums") isAlphaNum+  unless (T.any isAlpha ids) $ fail "Only numeric!"+  pure $ Alphanum ids++numericP :: Parsec Void Text Chunk+numericP = Numeric <$> unsignedP++chunkWithoutHyphensP :: Parsec Void Text Chunk+chunkWithoutHyphensP = try alphanumWithoutHyphensP <|> numericP++metaData :: Parsec Void Text Text+metaData = do+  void $ char '+'+  fold . NEL.intersperse "." <$> section `PC.sepBy1` char '.'+  where+    section :: Parsec Void Text Text+    section = takeWhile1P (Just "Metadata char") (\c -> isAlphaNum c || c == '-')++-- | Parse a (Haskell) `PVP`, as defined above.+pvp :: Text -> Either ParsingError PVP+pvp = parse (pvp' <* eof) "PVP"++-- | Internal megaparsec parser of `pvp`.+pvp' :: Parsec Void Text PVP+pvp' = L.lexeme space (PVP <$> unsignedP `PC.sepBy1` char '.')++-- | Parse a (General) `Version`, as defined above.+version :: Text -> Either ParsingError Version+version = parse (version'' <* eof) "Version"++-- | Internal megaparsec parser of `version`.+version' :: Parsec Void Text Version+version' = L.lexeme space version''++version'' :: Parsec Void Text Version+version'' = Version <$> optional (try epochP) <*> chunksP <*> optional releaseP <*> optional metaData++epochP :: Parsec Void Text Word+epochP = unsignedP <* char ':'++chunksP :: Parsec Void Text Chunks+chunksP = Chunks <$> chunkWithoutHyphensP `PC.sepBy1` char '.'++-- | Parse a (Complex) `Mess`, as defined above.+mess :: Text -> Either ParsingError Mess+mess = parse (mess'' <* eof) "Mess"++-- | Internal megaparsec parser of `mess`.+mess' :: Parsec Void Text Mess+mess' = L.lexeme space mess''++mess'' :: Parsec Void Text Mess+mess'' = Mess <$> mchunks <*> optional ((,) <$> sep <*> mess')++mchunks :: Parsec Void Text (NonEmpty MChunk)+mchunks = mchunk `PC.sepBy1` char '.'++mchunk :: Parsec Void Text MChunk+mchunk = choice [ try $ (\(t, i) -> MDigit i t) <$> match (L.decimal <* next)+                , try $ (\(t, i) -> MRev i t) <$> match (single 'r' *> L.decimal <* next)+                , MPlain . T.pack <$> some (letterChar <|> digitChar) ]+  where+    next :: Parsec Void Text ()+    next = lookAhead (void (single '.') <|> void sep <|> eof)++sep :: Parsec Void Text VSep+sep = choice [ VColon  <$ char ':'+             , VHyphen <$ char '-'+             , VPlus   <$ char '+'+             , VUnder  <$ char '_'+             , VTilde  <$ char '~' ]++sepCh :: VSep -> Char+sepCh VColon  = ':'+sepCh VHyphen = '-'+sepCh VPlus   = '+'+sepCh VUnder  = '_'+sepCh VTilde  = '~'++-- | Convert any parsed Versioning type to its textual representation.+prettyV :: Versioning -> Text+prettyV (Ideal sv)  = prettySemVer sv+prettyV (General v) = prettyVer v+prettyV (Complex m) = prettyMess m++-- | Convert a `SemVer` back to its textual representation.+prettySemVer :: SemVer -> Text+prettySemVer (SemVer ma mi pa pr me) = mconcat $ ver <> pr' <> me'+  where+    ver = intersperse "." [ showt ma, showt mi, showt pa ]+    pr' = maybe [] (\m -> ["-", prettyRelease m]) pr+    me' = maybe [] (\m -> ["+", m]) me++-- | Convert a `PVP` back to its textual representation.+prettyPVP :: PVP -> Text+prettyPVP (PVP (m :| rs)) = T.intercalate "." . map showt $ m : rs++-- | Convert a `Version` back to its textual representation.+prettyVer :: Version -> Text+prettyVer (Version ep cs pr me) = mconcat $ ep' <> [ver] <> pr' <> me'+  where+    ver = prettyChunks cs+    me' = maybe [] (\m -> ["+", m]) me+    pr' = maybe [] (\m -> ["-", prettyRelease m]) pr+    ep' = maybe [] (\e -> [showt e, ":"]) ep++-- | Convert a `Mess` back to its textual representation.+prettyMess :: Mess -> Text+prettyMess (Mess t m) = case m of+  Nothing     -> t'+  Just (s, v) -> T.snoc t' (sepCh s) <> prettyMess v+  where+    t' :: Text+    t' = fold . NEL.intersperse "." $ NEL.map mchunkText t++prettyChunks :: Chunks -> Text+prettyChunks (Chunks cs) = T.intercalate "." . map prettyChunk $ NEL.toList cs++prettyRelease :: Release -> Text+prettyRelease (Release cs) = T.intercalate "." . map prettyChunk $ NEL.toList cs++prettyChunk :: Chunk -> Text+prettyChunk (Numeric n)  = showt n+prettyChunk (Alphanum s) = s++--------------------------------------------------------------------------------+-- Utilities++-- | Flip an Ordering.+opposite :: Ordering -> Ordering+opposite EQ = EQ+opposite LT = GT+opposite GT = LT++-- Yes, `text-show` exists, but this reduces external dependencies.+showt :: Show a => a -> Text+showt = T.pack . show++hush :: Either a b -> Maybe b+hush (Left _)  = Nothing+hush (Right b) = Just b++data These a b = This !a | That !b | Both !a !b++zipLongest :: [a] -> [b] -> [These a b]+zipLongest [] []         = []+zipLongest (a:as) (b:bs) = Both a b : zipLongest as bs+zipLongest (a:as) []     = This a : zipLongest as []+zipLongest [] (b:bs)     = That b : zipLongest [] bs
README.md view
@@ -1,7 +1,7 @@ versions ======== -[![Build Status](https://travis-ci.org/fosskers/versions.svg?branch=master)](https://travis-ci.org/fosskers/versions)+![](https://github.com/fosskers/versions/workflows/Tests/badge.svg) [![Hackage](https://img.shields.io/hackage/v/versions.svg?style=flat)](https://hackage.haskell.org/package/versions) [![Stackage Nightly](http://stackage.org/package/versions/badge/nightly)](http://stackage.org/nightly/package/versions) [![Stackage LTS](http://stackage.org/package/versions/badge/lts)](http://stackage.org/lts/package/versions)@@ -28,7 +28,7 @@  Usage ------In general, `parseV` is the function you want. It attempts to parse a given+In general, `versioning` is the function you want. It attempts to parse a given Text using the three individual parsers, `semver`, `version` and `mess`. If one fails, it tries the next. If you know you only want to parse one specific version type, use that parser directly (e.g. `semver`).@@ -40,15 +40,15 @@  ```haskell incPatch :: SemVer -> SemVer-incPatch s = s & svPatch %~ (+ 1)+incPatch s = s & patch %~ (+ 1) ```  Or, something more involved:  ```haskell -- | Get all major versions of legally parsed SemVers.-majors :: [Text] -> [Int]-majors vs = vs ^.. each . to semver . _Right . svMajor+majors :: [Text] -> [Word]+majors vs = vs ^.. each . to semver . _Right . major ```  The `to semver . _Right` is clunky, so we provide some direct `Text`@@ -57,27 +57,30 @@ [lens-aeson](http://hackage.haskell.org/package/lens-aeson):  ```haskell--- | Get all major versions of legally parsed SemVers.-majors :: [Text] -> [Int]-majors vs = vs ^.. each . _SemVer . svMajor-```--Note that `_SemVer` only attempts to parse as true Semantic Versioning. If-the package versions you're parsing don't agree to a standard, you can-achieve a similar result via:--```haskell--- | Get all major versions of potentially parsed SemVers.-majors :: [Text] -> [Int]-majors vs = vs ^.. each . _Versioning . _Ideal . svMajor+-- | Get the major version of any `Text` that has one.+majors :: [Text] -> [Word]+majors vs = vs ^.. each . major ```  We can also use these `Text` Traversals to increment versions, as above:  ```haskell incPatch :: Text -> Text-incPatch s = s & _SemVer . svPatch %~ (+ 1)+incPatch s = s & patch %~ (+ 1)  > incPatch "1.2.3" "1.2.4" ```++#### Caveats++The largest number that can be parsed as part of a version is:++``` haskell+ghci> maxBound :: Word64+18446744073709551615+```++However, on 32-bit systems (or smaller), the maximum is their `maxBound :: Word`. +A number larger than that, even if smaller than `maxBound :: Word64`,+will yield a parse error.
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
test/Test.hs view
@@ -1,42 +1,74 @@+{-# LANGUAGE OverloadedLists   #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell   #-} -module Main where+{-# OPTIONS_GHC -fno-warn-orphans #-} -import Data.Monoid ((<>))-import Data.Text (Text,unpack)-import Data.Versions-import Lens.Micro-import Test.Tasty-import Test.Tasty.HUnit+module Main ( main ) where +import           Data.Either (fromRight, isLeft)+import           Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Text as T+import           Data.Versions+import           Data.Void (Void)+import           Language.Haskell.TH (recover)+import           Lens.Micro+import           Test.Tasty+import           Test.Tasty.HUnit+import           Text.Megaparsec+import           Text.Megaparsec.Char+import           Text.Printf (printf)+ ---  -- | These don't need to parse as a SemVer.-goodVers :: [Text]+goodVers :: [T.Text] goodVers = [ "1", "1.2", "1.0rc0", "1.0rc1", "1.1rc1", "1.58.0-3",  "44.0.2403.157-1"            , "0.25-2",  "8.u51-1", "21-2", "7.1p1-1", "20150826-1", "1:0.10.16-3"+           , "1.11.0.git.20200404-1", "1.11.0+20200830-1", "1:3.20", "9.2.1.b-debug+lol"+           , "0:1.2.3"            ] -messes :: [Text]-messes = [ "10.2+0.93+1-1", "003.03-3", "002.000-7", "20.26.1_0-2" ]+badVers :: [T.Text]+badVers = ["", "1.2 "] -messComps :: [Text]+badPVP :: [T.Text]+badPVP = ["", "clc237", "abc"]++messes :: [T.Text]+messes = [ "10.2+0.93+1-1", "003.03-3", "002.000-7", "20.26.1_0-2", "1.6.0a+2014+m872b87e73dfb-1"+         , "1.3.00.16851-1", "5.2.458699.0906-1", "12.0.0-3ubuntu1~20.04.5" ]++messComps :: NonEmpty T.Text messComps = [ "10.2+0.93+1-1", "10.2+0.93+1-2", "10.2+0.93+2-1"             , "10.2+0.94+1-1", "10.3+0.93+1-1", "11.2+0.93+1-1", "12"             ] -badSemVs :: [Text]-badSemVs = [ "1", "1.2", "1.2.3+a1b2bc3.1-alpha.2", "a.b.c", "1.01.1"-           , "1.2.3+a1b!2c3.1"+badSemVs :: [T.Text]+badSemVs = [ -- Not enough version slots+           "1", "1.2", "a.b.c"+           -- Illegal characters+           , "1.01.1", "1.2.3+a1b!2c3.1", "", "1.2.3 "+           -- Really large version+           -- , "18446744073709551610000000000000000.0.0"            ] -goodSemVs :: [Text]+goodSemVs :: [T.Text] goodSemVs = [ "0.1.0", "1.2.3", "1.2.3-1", "1.2.3-alpha", "1.2.3-alpha.2"-            , "1.2.3+a1b2c3.1", "1.2.3-alpha.2+a1b2c3.1"+            , "1.2.3+a1b2c3.1", "1.2.3-alpha.2+a1b2c3.1", "2.2.1-b05"+            -- Weird Pre-releases+            , "1.0.0-x-y-z.-"+            -- Weird metadata+            , "1.0.0-alpha+001", "1.0.0+21AF26D3---117B344092BD"+            -- Zeroes+            , "1.2.2-00a"+            -- Really large version+            , "18446744073709551610.0.0"             ]  -- | The exact example from `http://semver.org`-semverOrd :: [Text]+semverOrd :: NonEmpty T.Text semverOrd = [ "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-alpha.beta"             , "1.0.0-beta", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1"             , "1.0.0"@@ -47,144 +79,232 @@ -- make this necessary, meaning `cabal` can't be simplified to ignore it. -- Logically, these are the same package, but for those 5 packages, they -- aren't.-cabalOrd :: [Text]-cabalOrd = [ "0.2", "0.2.0", "0.2.0.0" ]+cabalOrd :: NonEmpty T.Text+cabalOrd = [ "0", "0.2", "0.2.0", "0.2.0.0" ] -versionOrd :: [Text]+versionOrd :: NonEmpty T.Text versionOrd = [ "0.9.9.9", "1.0.0.0", "1.0.0.1", "2" ]  suite :: TestTree-suite = testGroup "Unit Tests"-  [ testGroup "(Ideal) Semantic Versioning"-    [ testGroup "Bad Versions (shouldn't parse)" $-      map (\s -> testCase (unpack s) $ assert $ isLeft $ semver s) badSemVs-    , testGroup "Good Versions (should parse)" $-      map (\s -> testCase (unpack s) $ isomorphSV s) goodSemVs-    , testGroup "Comparisons" $-      testCase "1.2.3-alpha.2 == 1.2.3-alpha.2+a1b2c3.1"-      (assert $ semver "1.2.3-alpha.2" == semver "1.2.3-alpha.2+a1b2c3.1") :-      map (\(a,b) -> testCase (unpack $ a <> " < " <> b) $ comp semver a b)-      (zip semverOrd $ tail semverOrd)-    ]-  , testGroup "(General) Versions"-    [ testGroup "Good Versions" $-      map (\s -> testCase (unpack s) $ isomorphV s) goodVers-    , testGroup "Comparisons" $-      testCase "1.2-5 < 1.2.3-1" (comp version "1.2-5" "1.2.3-1") :-      testCase "1.0rc1 < 1.0" (comp version "1.0rc1" "1.0") :-      testCase "1.0 < 1:1.0" (comp version "1.0" "1:1.0") :-      testCase "1.1 < 1:1.0" (comp version "1.1" "1:1.0") :-      testCase "1.1 < 1:1.1" (comp version "1.1" "1:1.1") :-      map (\(a,b) -> testCase (unpack $ a <> " < " <> b) $ comp version a b)-      (zip cabalOrd (tail cabalOrd) <> zip versionOrd (tail versionOrd))-    ]-  , testGroup "(Complex) Mess"-    [ testGroup "Good Versions" $-      map (\s -> testCase (unpack s) $ isomorphM s) messes-    , testGroup "Comparisons" $-      map (\(a,b) -> testCase (unpack $ a <> " < " <> b) $ comp mess a b) $-      zip messComps (tail messComps)-    ]-  , testGroup "Mixed Versioning"-    [ testGroup "Identification"-      [ testCase "1.2.3 is SemVer" $ check $ isSemVer <$> parseV "1.2.3"-      , testCase "1.2.3-1 is SemVer" $ check $ isSemVer <$> parseV "1.2.3-1"-      , testCase "1.2.3-1+1 is SemVer" $ check $ isSemVer <$> parseV "1.2.3-1+1"-      , testCase "1.2.3r1 is Version" $ check $ isVersion <$> parseV "1.2.3r1"-      , testCase "0.25-2 is Version" $ check $ isVersion <$> parseV "0.25-2"-      , testCase "1:1.2.3-1 is Version" $ check $ isVersion <$> parseV "1:1.2.3-1"-      , testCase "1.2.3+1-1 is Mess" $ check $ isMess <$> parseV "1.2.3+1-1"-      , testCase "000.007-1 is Mess" $ check $ isMess <$> parseV "000.007-1"-      , testCase "20.26.1_0-2 is Mess" $ check $ isMess <$> parseV "20.26.1_0-2"+suite = testGroup "Tests"+  [ testGroup "Unit Tests"+    [ testGroup "(Ideal) Semantic Versioning"+      [ testGroup "Bad Versions (shouldn't parse)" $ map bad badSemVs+      , testGroup "Good Versions (should parse)" $+        map (\s -> testCase (T.unpack s) $ isomorphSV s) goodSemVs+      , testGroup "Comparisons" $+        testCase "1.2.3-alpha.2 == 1.2.3-alpha.2+a1b2c3.1"+        (assertBool "Equality test of two complicated SemVers failed"+         $ semver "1.2.3-alpha.2" == semver "1.2.3-alpha.2+a1b2c3.1") :+        zipWith (\a b -> testCase (T.unpack $ a <> " < " <> b) $ comp semver a b) (NonEmpty.toList semverOrd) (NonEmpty.tail semverOrd)+      , testGroup "Whitespace Handling"+        [ testCase "1.2.3-1[ ]" $ parse semver' "semver whitespace" "1.2.3-1 " @?= Right (SemVer 1 2 3 (Just . Release $ Numeric 1 :| []) Nothing)+        ]+      , testGroup "Zero Handling"+        [ testCase "2.2.1-b05" $ semver "2.2.1-b05" @?= Right (SemVer 2 2 1 (Just . Release $ Alphanum "b05" :| []) Nothing)+        ]       ]-    , testGroup "Isomorphisms" $-      map (\s -> testCase (unpack s) $ isomorph s) $ goodSemVs ++ goodVers ++ messes-    , testGroup "Comparisons"-      [ testCase "1.2.2r1-1 < 1.2.3-1"   $ comp parseV "1.2.2r1-1" "1.2.3-1"-      , testCase "1.2.3-1   < 1.2.4r1-1" $ comp parseV "1.2.3-1" "1.2.4r1-1"-      , testCase "1.2.3-1   < 2+0007-1"  $ comp parseV "1.2.3-1" "2+0007-1"-      , testCase "1.2.3r1-1 < 2+0007-1"  $ comp parseV "1.2.3r1-1" "2+0007-1"-      , testCase "1.2-5 < 1.2.3-1"       $ comp parseV "1.2-5" "1.2.3-1"+    , testGroup "(Haskell) PVP"+      [ testGroup "Good PVPs" $+        map (\s -> testCase (T.unpack s) $ isomorphPVP s) (NonEmpty.toList cabalOrd)+      , testGroup "Bad PVP" $+        map (\s -> testCase (T.unpack s) $ assertBool "A bad PVP parsed" $ isLeft $ pvp s) badPVP+      , testGroup "Comparisons" $+        zipWith (\a b -> testCase (T.unpack $ a <> " < " <> b) $ comp pvp a b) (NonEmpty.toList cabalOrd) (NonEmpty.tail cabalOrd)       ]-    ]+    , testGroup "(General) Versions"+      [ testGroup "Good Versions" $+        map (\s -> testCase (T.unpack s) $ isomorphV s) goodVers+      , testGroup "Bad Versions (shouldn't parse)" $+        map (\s -> testCase (T.unpack s) $ assertBool "A bad version parsed" $ isLeft $ version s) badVers+      , testGroup "Comparisons" $+        testCase "1.2-5 < 1.2.3-1" (comp version "1.2-5" "1.2.3-1") :+        testCase "1.0rc1 < 1.0" (comp version "1.0rc1" "1.0") :+        testCase "1.0 < 1:1.0" (comp version "1.0" "1:1.0") :+        testCase "1.1 < 1:1.0" (comp version "1.1" "1:1.0") :+        testCase "1.1 < 1:1.1" (comp version "1.1" "1:1.1") :+        map (\(a,b) -> testCase (T.unpack $ a <> " < " <> b) $ comp version a b)+        (zip (NonEmpty.toList cabalOrd) (NonEmpty.tail cabalOrd) <> zip (NonEmpty.toList versionOrd) (NonEmpty.tail versionOrd))+      ]+    , testGroup "(Complex) Mess"+      [ testGroup "Good Versions" $+        map (\s -> testCase (T.unpack s) $ isomorphM s) messes+      , testGroup "Bad Versions (shouldn't parse)" $+        map (\s -> testCase (T.unpack s) $ assertBool "A bad version parsed" $ isLeft $ mess s) badVers+      , testGroup "Comparisons" $+        zipWith (\a b -> testCase (T.unpack $ a <> " < " <> b) $ comp mess a b) (NonEmpty.toList messComps) (NonEmpty.tail messComps)+      , testGroup "SemVer-like Value Extraction"+        [ testCase "messMajor" $+          (hush (mess "1.6.0a+2014+m872b87e73dfb-1") >>= messMajor) @?= Just 1+        , testCase "messMinor" $+          (hush (mess "1.6.0a+2014+m872b87e73dfb-1") >>= messMinor) @?= Just 6+        , testCase "messPatch - Good" $+          (hush (mess "1.6.0+2014+m872b87e73dfb-1") >>= messPatch) @?= Just 0+        , testCase "messPatch - Bad" $+          (hush (mess "1.6.0a+2014+m872b87e73dfb-1") >>= messPatch) @?= Nothing+        , testCase "messPatchChunk" $+          (hush (mess "1.6.0a+2014+m872b87e73dfb-1") >>= messPatchChunk) @?= Just (Alphanum "0a")+        ]+      ]+    , testGroup "Mixed Versioning"+      [ testGroup "Identification"+        [ testCase "1.2.3 is SemVer" $ check $ isSemVer <$> versioning "1.2.3"+        , testCase "1.2.3-1 is SemVer" $ check $ isSemVer <$> versioning "1.2.3-1"+        , testCase "1.2.3-1+1 is SemVer" $ check $ isSemVer <$> versioning "1.2.3-1+1"+        , testCase "1.2.3+1-1 is SemVer" $ check $ isSemVer <$> versioning "1.2.3+1-1"+        , testCase "1.2.3r1 is Version" $ check $ isVersion <$> versioning "1.2.3r1"+        , testCase "0.25-2 is Version" $ check $ isVersion <$> versioning "0.25-2"+        , testCase "1:1.2.3-1 is Version" $ check $ isVersion <$> versioning "1:1.2.3-1"+        , testCase "1:3.20.1-1 is Version" $ check $ isVersion <$> versioning "1:3.20.1-1"+        , testCase "000.007-1 is Mess" $ check $ isMess <$> versioning "000.007-1"+        , testCase "20.26.1_0-2 is Mess" $ check $ isMess <$> versioning "20.26.1_0-2"+        , testCase "1:3.20.1-1 is Version" $ check $ isVersion <$> versioning "1:3.20.1-1"+        ]+      , testGroup "Bad Versions" $+        map (\s -> testCase (T.unpack s) $ assertBool "A bad version parsed" $ isLeft $ versioning s) badVers+      , testGroup "Isomorphisms" $+        map (\s -> testCase (T.unpack s) $ isomorph s) $ goodSemVs ++ goodVers ++ messes+      , testGroup "Comparisons"+        [ compVer "1.2.2r1-1" "1.2.3-1"+        , compVer "1.2.3-1" "1.2.4r1-1"+        , compVer "1.2.3-1" "2+0007-1"+        , compVer "1.2.3r1-1" "2+0007-1"+        , compVer "1.2-5" "1.2.3-1"+        , compVer "1.6.0a+2014+m872b87e73dfb-1" "1.6.0-1"+        , compVer "1.11.0.git.20200404-1" "1.11.0+20200830-1"+        , compVer "0.17.0+r8+gc41db5f1-1" "0.17.0+r157+g584760cf-1"+        , compVer "0.4.8-1" "0.4.9-1"+        , compVer "7.42.13-4" "7.46.0-2"+        , compVer "1.15.2-1" "1.15.3-1"+        , compVer "2.1.16102-2" "2.1.17627-1"+        , compVer "8.64.0.81-1" "8.65.0.78-1"+        , compVer "1.3.00.16851-1" "1.3.00.25560-1"+        , compVer "10.0.4-1" "10.1.0-1"+        , compVer "1:3.20-1" "1:3.20.1-1"+        , compVer "5.2.458699.0906-1" "5.3.472687.1012-1"+        ]+      , testGroup "Equality"+        [ eqVer "1:3.20.1-1"+        , eqVer "1.3.00.25560-1"+        , eqVer "150_28-3"+        , eqVer "1.0.r15.g3fc772c-5"+        , eqVer "0.88-2"+        ]+      , testGroup "Conversions"+        [ testCase "Good Version -> PVP" $ versionToPvp $(versionQ "1.2.3") @?= Just $(pvpQ "1.2.3")+        , testCase "Bad  Version -> PVP" $ versionToPvp $(versionQ "1.e.3") @?= Nothing+        ]+      ]     , testGroup "Lenses and Traversals"       [ testCase "SemVer - Increment Patch" incPatch       , testCase "SemVer - Increment Patch from Text" incFromT       , testCase "SemVer - Get patches" patches-      , testCase "Traverse `General` as `Ideal`" noInc       ]+    , testGroup "Template Haskell"+      [ testCase "SemVer"  $ prettyV $(versioningQ "1.2.3") @?= "1.2.3"+      , testCase "Version" $ prettyV $(versioningQ "1.2.3.4") @?= "1.2.3.4"+      , testCase "Mess"    $ prettyV $(versioningQ "003.03-3") @?= "003.03-3"+      , testCase "Failure" $ $(recover [| () |] (versioningQ "!!!")) @?= ()+      ]+    , testGroup "Megaparsec Behaviour"+      [ testCase "manyTill" $ parse nameGrab "manyTill" "linux-firmware-3.2.14-1-x86_64.pkg.tar.xz" @?= Right "linux-firmware"+      , testCase "Extracting version" $ parse versionGrab "extraction" "linux-firmware-3.2.14-1-x86_64.pkg.tar.xz" @?= Right (Ideal $ SemVer 3 2 14 (Just . Release $ Alphanum "1-x86" :| []) Nothing)+      , testCase "Parser State" $ parse pvp'' "parser state" "1.2.3arst" @?= Right ($(pvpQ "1.2.3"), "arst")+      ]+    ]   ] +bad :: T.Text -> TestTree+bad s = testCase (T.unpack s) $ case semver s of+  Left _   -> pure ()+  Right s' -> assertFailure $ "A bad version parsed: " ++ T.unpack (prettySemVer s')++compVer :: T.Text -> T.Text -> TestTree+compVer a b = testCase (printf "%s < %s" a b) $ comp versioning a b++eqVer :: T.Text -> TestTree+eqVer a = testCase (T.unpack a) $ equal versioning a+ -- | Does pretty-printing return a Versioning to its original form?-isomorph :: Text -> Assertion-isomorph t = Right t @=? (prettyV <$> parseV t)+isomorph :: T.Text -> Assertion+isomorph t = case prettyV <$> versioning t of+  Right t' -> t @?= t'+  Left e   -> assertBool (errorBundlePretty e) False  -- | Does pretty-printing return a Version to its original form?-isomorphV :: Text -> Assertion-isomorphV t = Right t @=? (prettyVer <$> version t)+isomorphV :: T.Text -> Assertion+isomorphV t = case prettyVer <$> version t of+  Right t' -> t @?= t'+  Left e   -> assertBool (errorBundlePretty e) False  -- | Does pretty-printing return a SemVer to its original form?-isomorphSV :: Text -> Assertion-isomorphSV t = Right t @=? (prettySemVer <$> semver t)+isomorphSV :: T.Text -> Assertion+isomorphSV t = case prettySemVer <$> semver t of+  Right t' -> t @?= t'+  Left e   -> assertBool (errorBundlePretty e) False -isomorphM :: Text -> Assertion-isomorphM t =  Right t @=? (prettyMess <$> mess t)+isomorphPVP :: T.Text -> Assertion+isomorphPVP t = case prettyPVP <$> pvp t of+  Right t' -> t @?= t'+  Left e   -> assertBool (errorBundlePretty e) False -comp :: Ord b => (Text -> Either a b) -> Text -> Text -> Assertion+isomorphM :: T.Text -> Assertion+isomorphM t = case prettyMess <$> mess t of+  Right t' -> t @?= t'+  Left e   -> assertBool (errorBundlePretty e) False++comp :: Ord b => (T.Text -> Either a b) -> T.Text -> T.Text -> Assertion comp f a b = check $ (<) <$> f a <*> f b +equal :: Ord r => (T.Text -> Either l r) -> T.Text -> Assertion+equal f a = check $ (\r -> r == r) <$> f a+ check :: Either a Bool -> Assertion-check = assert . either (const False) id+check = assertBool "Some Either-based assertion failed" . fromRight False  isSemVer :: Versioning -> Bool isSemVer (Ideal _) = True-isSemVer _ = False+isSemVer _         = False  isVersion :: Versioning -> Bool isVersion (General _) = True-isVersion _ = False+isVersion _           = False  isMess :: Versioning -> Bool isMess (Complex _) = True-isMess _ = False+isMess _           = False  incPatch :: Assertion-incPatch = (v1 & _Ideal . svPatch %~ (+ 1)) @?= v2-  where v1 = Ideal $ SemVer 1 2 3 [] []-        v2 = Ideal $ SemVer 1 2 4 [] []---- | Nothing should happen.-noInc :: Assertion-noInc = (v & _Ideal . svPatch %~ (+ 1)) @?= v-  where v = General $ Version Nothing [] []+incPatch = (v1 & patch %~ (+ 1)) @?= v2+  where v1 = Ideal $ SemVer 1 2 3 Nothing Nothing+        v2 = Ideal $ SemVer 1 2 4 Nothing Nothing  incFromT :: Assertion-incFromT = ("1.2.3" & _Versioning . _Ideal . svPatch %~ (+ 1)) @?= "1.2.4"+incFromT = (("1.2.3" :: T.Text) & patch %~ (+ 1)) @?= "1.2.4"  patches :: Assertion patches = ps @?= [3,4,5]-  where ps = ["1.2.3","2.3.4","3.4.5"] ^.. each . _SemVer . svPatch--isLeft :: Either t1 t -> Bool-isLeft (Left _) = True-isLeft _ = False+  where ps = (["1.2.3","2.3.4","3.4.5"] :: [T.Text]) ^.. each . patch -{-}--- Need to submit patch for these, as well as Maybe instance.-assertRight :: String -> Either a b -> Assertion-assertRight _ (Right _)  = return ()-assertRight msg (Left _) = assertFailure msg+main :: IO ()+main = defaultMain suite -instance Assertable (Either a b) where-  assert = assertRight ""+nameGrab :: Parsec Void T.Text T.Text+nameGrab = T.pack <$> manyTill anySingle (try finished)+  where finished = char '-' *> lookAhead digitChar -assertMaybe :: String -> Maybe a -> Assertion-assertMaybe _ (Just _)  = return ()-assertMaybe msg Nothing = assertFailure msg+versionGrab :: Parsec Void T.Text Versioning+versionGrab = manyTill anySingle (try finished) *> ver+  where finished = char '-' *> lookAhead digitChar+        ver = fmap Ideal semver' <|> fmap General version' <|> fmap Complex mess' -instance Assertable (Maybe a) where-  assert = assertMaybe ""--}+hush :: Either a b -> Maybe b+hush (Left _)  = Nothing+hush (Right b) = Just b -main :: IO ()-main = defaultMain suite+-- | An attempt to squeeze out the remaining parser state.+pvp'' :: Parsec Void T.Text (PVP, T.Text)+pvp'' = do+  v <- pvp'+  s <- getParserState+  pure (v, stateInput s)
versions.cabal view
@@ -1,66 +1,66 @@--- This file has been generated from package.yaml by hpack version 0.17.1.------ see: https://github.com/sol/hpack--name:           versions-version:        3.1.1-synopsis:       Types and parsers for software version numbers.-description:    A library for parsing and comparing software version numbers.-                We like to give version numbers to our software in a myriad of-                ways. Some ways follow strict guidelines for incrementing and comparison.-                Some follow conventional wisdom and are generally self-consistent.-                Some are just plain asinine. This library provides a means of parsing-                and comparing /any/ style of versioning, be it a nice Semantic Version-                like this:-                .-                > 1.2.3-r1+git123-                .-                ...or a monstrosity like this:-                .-                > 2:10.2+0.0093r3+1-1-                .-                Please switch to <http://semver.org Semantic Versioning> if you-                aren't currently using it. It provides consistency in version-                incrementing and has the best constraints on comparisons.-license:        BSD3-license-file:   LICENSE-author:         Colin Woodbury-maintainer:     colingw@gmail.com-category:       Data-build-type:     Simple-cabal-version:  >= 1.10+cabal-version:      2.2+name:               versions+version:            6.0.8+synopsis:           Types and parsers for software version numbers.+description:+  A library for parsing and comparing software version numbers. We like to give+  version numbers to our software in a myriad of ways. Some ways follow strict+  guidelines for incrementing and comparison. Some follow conventional wisdom+  and are generally self-consistent. Some are just plain asinine. This library+  provides a means of parsing and comparing /any/ style of versioning, be it a+  nice Semantic Version like this:+  .+  > 1.2.3-r1+git123+  .+  ...or a monstrosity like this:+  .+  > 2:10.2+0.0093r3+1-1+  .+  Please switch to <http://semver.org Semantic Versioning> if you aren't+  currently using it. It provides consistency in version incrementing and has+  the best constraints on comparisons.+  .+  This library implements version @2.0.0@ of the SemVer spec. +category:           Data+homepage:           https://github.com/fosskers/versions+author:             Colin Woodbury+maintainer:         colin@fosskers.ca+license:            BSD-3-Clause+license-file:       LICENSE+build-type:         Simple extra-source-files:-    CHANGELOG.md-    README.md+  CHANGELOG.md+  README.md -source-repository head-  type: git-  location: git://github.com/fosskers/haskell-versions.git+common commons+  default-language: Haskell2010+  ghc-options:+    -Wall -Wcompat -Wincomplete-record-updates+    -Wincomplete-uni-patterns +  build-depends:+    , base        >=4.10 && <4.22+    , megaparsec  >=7+    , text        ^>=1.2 || >= 2.0 && < 2.2+    , template-haskell >= 2.15+ library-  exposed-modules:-      Data.Versions+  import:          commons+  exposed-modules: Data.Versions   build-depends:-      base >=4.8 && <4.10-    , text >=1.2 && <1.3-    , megaparsec >=4 && <6-    , deepseq >= 1.4 && < 1.5-    , hashable >= 1.2 && < 1.3-  default-language: Haskell2010-  ghc-options: -fwarn-unused-imports -fwarn-unused-binds -fwarn-name-shadowing -fwarn-unused-matches+    , deepseq   >=1.4+    , hashable  >=1.2+    , parser-combinators >= 1.0  test-suite versions-test-  type: exitcode-stdio-1.0+  import:         commons+  type:           exitcode-stdio-1.0+  main-is:        Test.hs+  hs-source-dirs: test+  ghc-options:    -threaded -with-rtsopts=-N   build-depends:-      base >=4.8 && <4.10-    , text >=1.2 && <1.3-    , microlens >=0.4 && <0.5-    , tasty >=0.10.1.2-    , tasty-hunit >=0.9.2+    , microlens         >=0.4+    , tasty             >=0.10.1.2+    , tasty-hunit       >=0.9.2     , versions-  hs-source-dirs:-      test-  main-is: Test.hs-  default-language: Haskell2010-  ghc-options: -fwarn-unused-imports -fwarn-unused-binds -fwarn-name-shadowing -fwarn-unused-matches -threaded