diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/Data/Versions.hs b/Data/Versions.hs
--- a/Data/Versions.hs
+++ b/Data/Versions.hs
@@ -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
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -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)
diff --git a/versions.cabal b/versions.cabal
--- a/versions.cabal
+++ b/versions.cabal
@@ -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
