packages feed

semver 0.2.1 → 0.3.0

raw patch · 4 files changed

+325/−277 lines, 4 files

Files

semver.cabal view
@@ -1,5 +1,5 @@ name:                  semver-version:               0.2.1+version:               0.3.0 synopsis:              Representation, manipulation, and de/serialisation of Semantic Versions. homepage:              https://github.com/brendanhay/semver license:               OtherLicense@@ -29,6 +29,10 @@      exposed-modules:           Data.SemVer+        , Data.SemVer.Delimited++    other-modules:+          Data.SemVer.Internal      build-depends:           attoparsec >= 0.10 && < 0.13
src/Data/SemVer.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}- -- Module      : Data.SemVer -- Copyright   : (c) 2014 Brendan Hay <brendan.g.hay@gmail.com> -- License     : This Source Code Form is subject to the terms of@@ -25,12 +22,13 @@     , version     , initial     -- ** Lenses-    , versionMajor-    , versionMinor-    , versionPatch-    , versionRelease-    , versionMeta+    , major+    , minor+    , patch+    , release+    , metadata     -- ** Incrementing+    -- $incrementing     , incrementMajor     , incrementMinor     , incrementPatch@@ -55,102 +53,17 @@     -- ** Prisms     , _Numeric     , _Textual--    -- * Delimiters-    -- $delimiters-    , Delimiters-    -- ** Constructor-    , delimiters-    -- ** Lenses-    , delimMinor-    , delimPatch-    , delimRelease-    , delimMeta-    , delimIdent-    -- ** Encoding-    , toDelimitedBuilder-    -- ** Decoding-    , delimitedParser     ) where  import           Control.Applicative-import           Control.DeepSeq-import           Control.Monad import           Data.Attoparsec.Text-import           Data.Char-import           Data.Function              (on)-import           Data.List                  (intersperse)-import           Data.Monoid-import           Data.Text                  (Text)-import qualified Data.Text                  as Text-import qualified Data.Text.Lazy             as LText-import           Data.Text.Lazy.Builder     (Builder)-import qualified Data.Text.Lazy.Builder     as Build-import qualified Data.Text.Lazy.Builder.Int as Build-import           Prelude                    hiding (takeWhile)---- | A type representing an individual identifier from the release--- or metadata components of a 'Version'.------ * The 'Ord' instance implements precedence according to the semantic version--- specification, with numeric identifiers being of /lower/ precedence than--- textual identifiers, otherwise lexicographic ordering is used.------ The functions 'numeric' and 'textual' can be used to construct an 'Identifier'.-data Identifier-    = INum  !Int-    | IText !Text-      deriving (Eq, Show)---- | Safely construct a numeric identifier.-numeric :: Int -> Identifier-numeric = INum-{-# INLINE numeric #-}---- | Construct an identifier from the given 'Text', returning 'Nothing' if--- neither a numeric or valid textual input is supplied.-textual :: Text -> Maybe Identifier-textual = either (const Nothing) Just-    . parseOnly (identifierParser endOfInput <* endOfInput)-{-# INLINE textual #-}--_Numeric :: Applicative f => (Int -> f Int) -> Identifier -> f Identifier-_Numeric f (INum x) = INum <$> f x-_Numeric _ x        = pure x-{-# INLINE _Numeric #-}--_Textual :: Applicative f => (Text -> f Text) -> Identifier -> f Identifier-_Textual f (IText x) = IText <$> f x-_Textual _ x         = pure x-{-# INLINE _Textual #-}--instance Ord Identifier where-    compare a b = case (a, b) of-        (INum  x, INum  y) -> x `compare` y-        (IText x, IText y) -> x `compare` y-        (INum  _, _)       -> LT-        (IText _, _)       -> GT--instance NFData Identifier where-    rnf (INum  n) = rnf n-    rnf (IText t) = rnf t---- | An opaque type representing a successfully decoded or constructed--- semantic version. See the related functions and lenses for modification and--- update.------ * The 'Eq' instance represents exhaustive equality with all--- components considered.------ * The 'Ord' instance implements the precedence rules from the semantic--- version specification with metadata being ignored.-data Version = Version-    { _versionMajor   :: !Int-    , _versionMinor   :: !Int-    , _versionPatch   :: !Int-    , _versionRelease :: [Identifier]-    , _versionMeta    :: [Identifier]-    } deriving (Eq, Show)+import qualified Data.SemVer.Delimited  as Delim+import           Data.SemVer.Internal+import           Data.Text              (Text)+import qualified Data.Text              as Text+import qualified Data.Text.Lazy         as LText+import           Data.Text.Lazy.Builder (Builder)+import qualified Data.Text.Lazy.Builder as Build  -- | Smart constructor fully specifying all available version components. version :: Int          -- ^ Major version component.@@ -168,143 +81,44 @@ initial :: Version initial = version 0 0 0 [] [] -instance Ord Version where-    compare a b = on compare versions a b <> on compare _versionRelease a b-      where-        versions Version{..} =-            [ _versionMajor-            , _versionMinor-            , _versionPatch-            ]--instance NFData Version where-    rnf Version{..} =-              rnf _versionMajor-        `seq` rnf _versionMinor-        `seq` rnf _versionPatch-        `seq` rnf _versionRelease-        `seq` rnf _versionMeta- -- | Lens for the major version component.-versionMajor :: Functor f => (Int -> f Int) -> Version -> f Version-versionMajor f x = (\y -> x { _versionMajor = y }) <$> f (_versionMajor x)-{-# INLINE versionMajor #-}+major :: Functor f => (Int -> f Int) -> Version -> f Version+major f x = (\y -> x { _versionMajor = y }) <$> f (_versionMajor x)+{-# INLINE major #-}  -- | Lens for minor version component.-versionMinor :: Functor f => (Int -> f Int) -> Version -> f Version-versionMinor f x = (\y -> x { _versionMinor = y }) <$> f (_versionMinor x)-{-# INLINE versionMinor #-}+minor :: Functor f => (Int -> f Int) -> Version -> f Version+minor f x = (\y -> x { _versionMinor = y }) <$> f (_versionMinor x)+{-# INLINE minor #-}  -- | Lens for the patch version component.-versionPatch :: Functor f => (Int -> f Int) -> Version -> f Version-versionPatch f x = (\y -> x { _versionPatch = y }) <$> f (_versionPatch x)-{-# INLINE versionPatch #-}+patch :: Functor f => (Int -> f Int) -> Version -> f Version+patch f x = (\y -> x { _versionPatch = y }) <$> f (_versionPatch x)+{-# INLINE patch #-}  -- | Lens for the list of release identifiers.-versionRelease :: Functor f+release :: Functor f                => ([Identifier] -> f [Identifier])                -> Version                -> f Version-versionRelease f x = (\y -> x { _versionRelease = y }) <$> f (_versionRelease x)-{-# INLINE versionRelease #-}+release f x = (\y -> x { _versionRelease = y }) <$> f (_versionRelease x)+{-# INLINE release #-}  -- | Lens for the list of metadata identifiers.-versionMeta :: Functor f+metadata :: Functor f             => ([Identifier] -> f [Identifier])             -> Version             -> f Version-versionMeta f x = (\y -> x { _versionMeta = y }) <$> f (_versionMeta x)-{-# INLINE versionMeta #-}---- $delimiters------ A set of delimiters is used to encode/decode a 'Version' and specify--- alternative serialisation strategies.------ Lenses can be used to modify the default delimiter set, as in the following--- example - using alpha characters to encode the version as a valid--- DNS CNAME:------ @--- let Right v = fromText "1.2.3+40"--- let alpha = delimiters & delimMajor .= \'m\' & delimPatch .= \'p\' & delimRelease .= \'r\' & delimMeta .= \'d\' & delimIdent .= \'i\'------ Data.Text.Lazy.Builder.toLazyText (\"app01-\" <> toDelimitedBuilder alpha v <> \".dmz.internal\")--- @------ Would result in the following 'LText.Text':------ @--- app01-1m2p3d40.dmz.internal--- @------ Using the same 'Delimiters' set with 'delimitedParser' would ensure--- correct decoding behaviour.---- | An opaque set representing the seperators used to delimit semantic--- version components.-data Delimiters = Delimiters-    { _delimMinor   :: !Char-    , _delimPatch   :: !Char-    , _delimRelease :: !Char-    , _delimMeta    :: !Char-    , _delimIdent   :: !Char-    } deriving (Eq, Ord, Show)+metadata f x = (\y -> x { _versionMeta = y }) <$> f (_versionMeta x)+{-# INLINE metadata #-} --- | The default set of delimiters used in the semantic version specification.------ Example: Given exhaustive version components would result in the--- following hypothetical version:+-- $incrementing ----- @--- 1.2.3-alpha.1+sha.exp.12ab3d9--- @-delimiters :: Delimiters-delimiters = Delimiters-    { _delimMinor   = '.'-    , _delimPatch   = '.'-    , _delimRelease = '-'-    , _delimMeta    = '+'-    , _delimIdent   = '.'-    }--instance NFData Delimiters where-    rnf Delimiters{..} =-              rnf _delimMinor-        `seq` rnf _delimPatch-        `seq` rnf _delimRelease-        `seq` rnf _delimMeta-        `seq` rnf _delimIdent---- | Lens for the minor version delimiter. Default: @.@-delimMinor :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters-delimMinor f x = (\y -> x { _delimMinor = y }) <$> f (_delimMinor x)-{-# INLINE delimMinor #-}---- | Lens for the patch version delimiter. Default: @.@-delimPatch :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters-delimPatch f x = (\y -> x { _delimPatch = y }) <$> f (_delimPatch x)-{-# INLINE delimPatch #-}---- | Lens for the release component delimiter. Default: @-@-delimRelease :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters-delimRelease f x = (\y -> x { _delimRelease = y }) <$> f (_delimRelease x)-{-# INLINE delimRelease #-}---- | Lens for the metadata component delimiter. Default: @+@-delimMeta :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters-delimMeta f x = (\y -> x { _delimMeta = y }) <$> f (_delimMeta x)-{-# INLINE delimMeta #-}---- | Lens for the individual identifier delimiter. Default: @.@-delimIdent :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters-delimIdent f x = (\y -> x { _delimIdent = y }) <$> f (_delimIdent x)-{-# INLINE delimIdent #-}---- $increments+-- The following increment functions are used to ensure that the related+-- version components are reset according to the specification. ----- Mention the usage of increments to correctly adjust version components--- according to spec.+-- See the individual function documentation for specifics regarding each+-- version component.  -- | Increment the major component of a 'Version' by 1, resetting the minor -- and patch components.@@ -375,10 +189,10 @@  -- | Convert a 'Version' to it's readable 'String' representation. ----- Note: This is optimised for cases where you wish to use a 'String' and+-- Note: This is optimised for cases where you require 'String' output, and -- as such is faster than the semantically equivalent @unpack . toLazyText@. toString :: Version -> String-toString = toMonoid (:[]) show Text.unpack delimiters+toString = toMonoid (:[]) show Text.unpack Delim.semantic  -- | Convert a 'Version' to a strict 'Text' representation. --@@ -397,35 +211,7 @@  -- | Convert a 'Version' to a 'Builder'. toBuilder :: Version -> Builder-toBuilder = toDelimitedBuilder delimiters---- | Convert a 'Version' to a 'Builder' using the specified 'Delimiters' set.-toDelimitedBuilder :: Delimiters -> Version -> Builder-toDelimitedBuilder = toMonoid Build.singleton Build.decimal Build.fromText--toMonoid :: Monoid m-         => (Char -> m)-         -> (Int  -> m)-         -> (Text -> m)-         -> Delimiters-         -> Version-         -> m-toMonoid del int txt Delimiters{..} Version{..} = mconcat-     [ int _versionMajor-     , del _delimMinor-     , int _versionMinor-     , del _delimPatch-     , int _versionPatch-     , f _delimRelease _versionRelease-     , f _delimMeta    _versionMeta-     ]-  where-    f _ [] = mempty-    f c xs = del c <> mconcat (intersperse (del _delimIdent) (map g xs))--    g (INum  n) = int n-    g (IText t) = txt t-{-# INLINE toMonoid #-}+toBuilder = Delim.toBuilder Delim.semantic  -- | Parse a 'Version' from 'Text', returning an attoparsec error message -- in the 'Left' case on failure.@@ -440,33 +226,31 @@ fromLazyText :: LText.Text -> Either String Version fromLazyText = fromText . LText.toStrict --- | A greedy attoparsec 'Parser' which requires the entire 'Text' input to match.+-- | A greedy attoparsec 'Parser' which requires the entire 'Text'+-- input to match. parser :: Parser Version-parser = delimitedParser delimiters+parser = Delim.parser Delim.semantic --- | A greedy attoparsec 'Parser' using the specified 'Delimiters' set--- which requires the entire 'Text' input to match.-delimitedParser :: Delimiters -> Parser Version-delimitedParser Delimiters{..} = Version-    <$> (nonNegative <* char _delimMinor)-    <*> (nonNegative <* char _delimPatch)-    <*> nonNegative-    <*> option [] (try (char _delimRelease) *> identifiers)-    <*> option [] (try (char _delimMeta)    *> identifiers)-    <*  endOfInput-  where-    identifiers :: Parser [Identifier]-    identifiers = many (identifierParser $ void (char _delimIdent))+-- | Safely construct a numeric identifier.+numeric :: Int -> Identifier+numeric = INum+{-# INLINE numeric #-} -identifierParser :: Parser () -> Parser Identifier-identifierParser end = num <|> text-  where-    num  = INum  <$> nonNegative <* (end <|> endOfInput)-    text = IText <$> takeWhile1 (inClass "0-9A-Za-z-") <* optional end+-- | Construct an identifier from the given 'Text', returning 'Nothing' if+-- neither a numeric or valid textual input is supplied.+textual :: Text -> Maybe Identifier+textual = either (const Nothing) (Just . IText)+    . parseOnly (textualParser endOfInput <* endOfInput)+{-# INLINE textual #-} -nonNegative :: (Show a, Integral a) => Parser a-nonNegative = do-    n <- decimal-    when (n < 0) $-        fail ("Numeric value must be non-negative: " ++ show n)-    return n+-- | A prism into the numeric branch of an 'Identifier'.+_Numeric :: Applicative f => (Int -> f Int) -> Identifier -> f Identifier+_Numeric f (INum x) = INum <$> f x+_Numeric _ x        = pure x+{-# INLINE _Numeric #-}++-- | A prism into the textual branch of an 'Identifier'.+_Textual :: Applicative f => (Text -> f Text) -> Identifier -> f (Maybe Identifier)+_Textual f (IText x) = textual <$> f x+_Textual _ x         = pure (Just x)+{-# INLINE _Textual #-}
+ src/Data/SemVer/Delimited.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}++-- Module      : Data.SemVer.Delimited+-- Copyright   : (c) 2014 Brendan Hay <brendan.g.hay@gmail.com>+-- License     : This Source Code Form is subject to the terms of+--               the Mozilla Public License, v. 2.0.+--               A copy of the MPL can be found in the LICENSE file or+--               you can obtain it at http://mozilla.org/MPL/2.0/.+-- Maintainer  : Brendan Hay <brendan.g.hay@gmail.com>+-- Stability   : experimental+-- Portability : non-portable (GHC extensions)++-- | A set of delimiters can be used to encode/decode a 'Version' and specify+-- alternative serialisation strategies.+--+-- Lenses can be used to modify the default delimiter set, as in the following+-- example - using alpha characters to encode the version as a valid+-- DNS CNAME (assuming operators from lens or lens-family-core):+--+-- @+-- let Right v = fromText "1.2.3+40"+-- let alpha = semantic & major .~ \'m\' & patch .~ \'p\' & release .~ \'r\' & metadata .~ \'d\' & identifier .~ \'i\'+--+-- Data.Text.Lazy.Builder.toLazyText (\"app01-\" <> toDelimitedBuilder alpha v <> \".dmz.internal\")+-- @+--+-- Would result in the following 'LText.Text':+--+-- @+-- app01-1m2p3d40.dmz.internal+-- @+--+-- Using the same 'Delimiters' set with 'delimitedParser' would ensure+-- correct decoding behaviour.+module Data.SemVer.Delimited+    (+    -- * Delimiters+      Delimiters+    -- ** Constructor+    , semantic+    -- ** Lenses+    , minor+    , patch+    , release+    , metadata+    , identifier+    -- ** Encoding+    , toBuilder+    -- ** Decoding+    , parser+    ) where++import           Control.Applicative+import           Control.Monad+import           Data.Attoparsec.Text+import           Data.SemVer.Internal+import           Data.Text.Lazy.Builder     (Builder)+import qualified Data.Text.Lazy.Builder     as Build+import qualified Data.Text.Lazy.Builder.Int as Build++-- | The default set of delimiters used in the semantic version specification.+--+-- Example: Given exhaustive version components would result in the+-- following hypothetical version:+--+-- @+-- 1.2.3-alpha.1+sha.exp.12ab3d9+-- @+semantic :: Delimiters+semantic = Delimiters+    { _delimMinor   = '.'+    , _delimPatch   = '.'+    , _delimRelease = '-'+    , _delimMeta    = '+'+    , _delimIdent   = '.'+    }++-- | Lens for the minor version delimiter. Default: @.@+minor :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters+minor f x = (\y -> x { _delimMinor = y }) <$> f (_delimMinor x)+{-# INLINE minor #-}++-- | Lens for the patch version delimiter. Default: @.@+patch :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters+patch f x = (\y -> x { _delimPatch = y }) <$> f (_delimPatch x)+{-# INLINE patch #-}++-- | Lens for the release component delimiter. Default: @-@+release :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters+release f x = (\y -> x { _delimRelease = y }) <$> f (_delimRelease x)+{-# INLINE release #-}++-- | Lens for the metadata component delimiter. Default: @+@+metadata :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters+metadata f x = (\y -> x { _delimMeta = y }) <$> f (_delimMeta x)+{-# INLINE metadata #-}++-- | Lens for the individual identifier delimiter. Default: @.@+identifier :: Functor f => (Char -> f Char) -> Delimiters -> f Delimiters+identifier f x = (\y -> x { _delimIdent = y }) <$> f (_delimIdent x)+{-# INLINE identifier #-}++-- | Convert a 'Version' to a 'Builder' using the specified 'Delimiters' set.+toBuilder :: Delimiters -> Version -> Builder+toBuilder = toMonoid Build.singleton Build.decimal Build.fromText++-- | A greedy attoparsec 'Parser' using the specified 'Delimiters' set+-- which requires the entire 'Text' input to match.+parser :: Delimiters -> Parser Version+parser Delimiters{..} = Version+    <$> (nonNegative <* char _delimMinor)+    <*> (nonNegative <* char _delimPatch)+    <*> nonNegative+    <*> option [] (try (char _delimRelease)  *> identifiers)+    <*> option [] (try (char _delimMeta) *> identifiers)+    <*  endOfInput+  where+    identifiers :: Parser [Identifier]+    identifiers = many (identifierParser $ void (char _delimIdent))
+ src/Data/SemVer/Internal.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}++-- Module      : Data.SemVer.Internal+-- Copyright   : (c) 2014 Brendan Hay <brendan.g.hay@gmail.com>+-- License     : This Source Code Form is subject to the terms of+--               the Mozilla Public License, v. 2.0.+--               A copy of the MPL can be found in the LICENSE file or+--               you can obtain it at http://mozilla.org/MPL/2.0/.+-- Maintainer  : Brendan Hay <brendan.g.hay@gmail.com>+-- Stability   : experimental+-- Portability : non-portable (GHC extensions)++module Data.SemVer.Internal where++import Control.Applicative+import Control.DeepSeq+import Control.Monad+import Data.Attoparsec.Text+import Data.Function        (on)+import Data.List            (intersperse)+import Data.Monoid+import Data.Text            (Text)++-- | An opaque type representing a successfully decoded or constructed+-- semantic version. See the related functions and lenses for modification and+-- update.+--+-- * The 'Eq' instance represents exhaustive equality with all+-- components considered.+--+-- * The 'Ord' instance implements the precedence rules from the semantic+-- version specification with metadata being ignored.+data Version = Version+    { _versionMajor   :: !Int+    , _versionMinor   :: !Int+    , _versionPatch   :: !Int+    , _versionRelease :: [Identifier]+    , _versionMeta    :: [Identifier]+    } deriving (Eq, Show)++instance Ord Version where+    compare a b = on compare versions a b <> on compare _versionRelease a b+      where+        versions Version{..} =+            [ _versionMajor+            , _versionMinor+            , _versionPatch+            ]++instance NFData Version where+    rnf Version{..} =+              rnf _versionMajor+        `seq` rnf _versionMinor+        `seq` rnf _versionPatch+        `seq` rnf _versionRelease+        `seq` rnf _versionMeta++-- | A type representing an individual identifier from the release+-- or metadata components of a 'Version'.+--+-- * The 'Ord' instance implements precedence according to the semantic version+-- specification, with numeric identifiers being of /lower/ precedence than+-- textual identifiers, otherwise lexicographic ordering is used.+--+-- The functions 'numeric' and 'textual' can be used to construct an 'Identifier'.+data Identifier+    = INum  !Int+    | IText !Text+      deriving (Eq, Show)++instance Ord Identifier where+    compare a b = case (a, b) of+        (INum  x, INum  y) -> x `compare` y+        (IText x, IText y) -> x `compare` y+        (INum  _, _)       -> LT+        (IText _, _)       -> GT++instance NFData Identifier where+    rnf (INum  n) = rnf n+    rnf (IText t) = rnf t++identifierParser :: Parser () -> Parser Identifier+identifierParser p =+    either INum IText <$> eitherP (numericParser p) (textualParser p)++numericParser :: Parser () -> Parser Int+numericParser p = nonNegative <* (p <|> endOfInput)++textualParser :: Parser () -> Parser Text+textualParser p = takeWhile1 (inClass "0-9A-Za-z-") <* optional p++nonNegative :: (Show a, Integral a) => Parser a+nonNegative = do+    n <- decimal+    when (n < 0) $+        fail ("Numeric value must be non-negative: " ++ show n)+    return n++-- | An opaque set representing the seperators used to delimit semantic+-- version components.+data Delimiters = Delimiters+    { _delimMinor   :: !Char+    , _delimPatch   :: !Char+    , _delimRelease :: !Char+    , _delimMeta    :: !Char+    , _delimIdent   :: !Char+    } deriving (Eq, Ord, Show)++instance NFData Delimiters where+    rnf Delimiters{..} =+              rnf _delimMinor+        `seq` rnf _delimPatch+        `seq` rnf _delimRelease+        `seq` rnf _delimMeta+        `seq` rnf _delimIdent++toMonoid :: Monoid m+         => (Char -> m)+         -> (Int  -> m)+         -> (Text -> m)+         -> Delimiters+         -> Version+         -> m+toMonoid del int txt Delimiters{..} Version{..} = mconcat+     [ int _versionMajor+     , del _delimMinor+     , int _versionMinor+     , del _delimPatch+     , int _versionPatch+     , f _delimRelease _versionRelease+     , f _delimMeta    _versionMeta+     ]+  where+    f _ [] = mempty+    f c xs = del c <> mconcat (intersperse (del _delimIdent) (map g xs))++    g (INum  n) = int n+    g (IText t) = txt t+{-# INLINE toMonoid #-}