versions 1.1.0 → 2.0.0
raw patch · 5 files changed
+136/−26 lines, 5 filesdep +megaparsecdep −parsec
Dependencies added: megaparsec
Dependencies removed: parsec
Files
- CHANGELOG.md +6/−0
- Data/Versions.hs +40/−24
- README.md +81/−0
- test/Test.hs +5/−0
- versions.cabal +4/−2
CHANGELOG.md view
@@ -1,6 +1,12 @@ Changelog ========= +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 ----- - Added Lenses and Traversals (no `lens` dependency)
Data/Versions.hs view
@@ -45,11 +45,8 @@ , VParser(..) -- * Parsers , semver- , semver' , version- , version' , mess- , mess' -- ** Wrapped Parsers , parseV , semverP@@ -61,8 +58,11 @@ , prettyVer , prettyMess -- * Lenses- -- ** Top-level Traversals+ -- ** Traversing Text , _Versioning+ , _SemVer+ , _Version+ -- ** Versioning Traversals , _Ideal , _General , _Complex@@ -82,7 +82,8 @@ import Data.List (intersperse) import Data.Semigroup import Data.Text (Text,pack,unpack,snoc)-import Text.ParserCombinators.Parsec+import Text.Megaparsec.Text+import Text.Megaparsec --- @@ -100,21 +101,39 @@ -- > ("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 #-} -- | Comparison of @Ideal@s is always well defined. --@@ -167,22 +186,27 @@ -- | > 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 #-} -- | Two SemVers are equal if all fields except metadata are equal. instance Eq SemVer where@@ -210,11 +234,13 @@ _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.@@ -232,10 +258,12 @@ -- | > 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 (\vc -> v { _vRel = vc }) (f $ _vRel v)+{-# INLINE vRel #-} -- | A (Complex) Mess. -- This is a /descriptive/ parser, based on examples of stupidly@@ -288,11 +316,7 @@ -- | Parse a (Ideal) Semantic Version. semver :: Text -> Either ParseError SemVer-semver = semver' . unpack---- | Parse a Semantic Version, as a legacy String.-semver' :: String -> Either ParseError SemVer-semver' = parse semanticVersion "Semantic Version"+semver = parse semanticVersion "Semantic Version" semanticVersion :: Parser SemVer semanticVersion = p <* eof@@ -300,7 +324,7 @@ -- | Parse a group of digits, which can't be lead by a 0, unless it is 0. digits :: Parser Int-digits = read <$> (string "0" <|> many1 digit)+digits = read <$> (string "0" <|> some digitChar) major :: Parser Int major = digits <* char '.'@@ -322,10 +346,10 @@ where oneZero = (:[]) . Digits . read <$> string "0" iunit :: Parser VUnit-iunit = Digits . read <$> many1 digit+iunit = Digits . read <$> some digitChar sunit :: Parser VUnit-sunit = Str . pack <$> many1 letter+sunit = Str . pack <$> some letterChar -- | A wrapped `Version` parser. Can be composed with other parsers. versionP :: VParser@@ -333,11 +357,7 @@ -- | Parse a (General) `Version`, as defined above. version :: Text -> Either ParseError Version-version = version' . unpack---- | Parse a `Version`, where the input is a legacy String.-version' :: String -> Either ParseError Version-version' = parse versionNum "Version"+version = parse versionNum "Version" versionNum :: Parser Version versionNum = Version <$> chunks <*> preRel <* eof@@ -348,11 +368,7 @@ -- | Parse a (Complex) `Mess`, as defined above. mess :: Text -> Either ParseError Mess-mess = mess' . unpack---- | Parse a `Mess`, where the input is a legacy String.-mess' :: String -> Either ParseError Mess-mess' = parse messNumber "Mess"+mess = parse messNumber "Mess" messNumber :: Parser Mess messNumber = try node <|> leaf@@ -364,7 +380,7 @@ node = VNode <$> tchunks <*> sep <*> messNumber tchunks :: Parser [Text]-tchunks = (pack <$> many1 (letter <|> digit)) `sepBy` char '.'+tchunks = (pack <$> some (letterChar <|> digitChar)) `sepBy` char '.' sep :: Parser VSep sep = choice [ VColon <$ char ':'
+ README.md view
@@ -0,0 +1,81 @@+versions+========++[](https://travis-ci.org/aurapm/haskell-versions)+[](https://coveralls.io/github/aurapm/haskell-versions?branch=master)++A Haskell library for parsing and comparing software version numbers.++About+-----+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 [Semantic Versioning](http://semver.org) if you aren't+currently using it. It provides consistency in version incrementing and has+the best constraints on comparisons.++Usage+-----+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`).++#### Lenses and Traversals+The parse result types have Lenses/Traversals for accessing their data+fields. For instance, to increment the patch number of a parsed SemVer, you+could:++```haskell+incPatch :: SemVer -> SemVer+incPatch s = s & svPatch %~ (+ 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+```++The `to semver . _Right` is clunky, so we provide some direct `Text`+Traverals inspired by+([micro](http://hackage.haskell.org/package/microlens-aeson))+[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+```++We can also use these `Text` Traversals to increment versions, as above:++```haskell+incPatch :: Text -> Text+incPatch s = s & _SemVer . svPatch %~ (+ 1)++> incPatch "1.2.3"+"1.2.4"+```
test/Test.hs view
@@ -109,6 +109,7 @@ , 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 ] ]@@ -158,6 +159,10 @@ incFromT :: Assertion incFromT = ("1.2.3" & _Versioning . _Ideal . svPatch %~ (+ 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 {-} -- Need to submit patch for these, as well as Maybe instance.
versions.cabal view
@@ -1,5 +1,5 @@ name: versions-version: 1.1.0+version: 2.0.0 synopsis: Types and parsers for software version numbers. description: A library for parsing and comparing software version numbers. .@@ -29,6 +29,7 @@ cabal-version: >=1.10 extra-source-files: CHANGELOG.md+ , README.md source-repository head type: git@@ -40,7 +41,7 @@ other-extensions: OverloadedStrings build-depends: base >=4.8 && <4.9- , parsec >=3.1 && <3.2+ , megaparsec >= 4 && < 5 , semigroups >= 0.16.2.2 , text >=1.2 && <1.3 @@ -61,3 +62,4 @@ hs-source-dirs: test main-is: Test.hs default-language: Haskell2010+ ghc-options: -Wall -threaded