diff --git a/semver.cabal b/semver.cabal
--- a/semver.cabal
+++ b/semver.cabal
@@ -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
diff --git a/src/Data/SemVer.hs b/src/Data/SemVer.hs
--- a/src/Data/SemVer.hs
+++ b/src/Data/SemVer.hs
@@ -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 #-}
diff --git a/src/Data/SemVer/Delimited.hs b/src/Data/SemVer/Delimited.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/SemVer/Delimited.hs
@@ -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))
diff --git a/src/Data/SemVer/Internal.hs b/src/Data/SemVer/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/SemVer/Internal.hs
@@ -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 #-}
