{-# LANGUAGE DeriveDataTypeable #-}
# LANGUAGE DeriveGeneric #

-- | WARNING: This module should be considered private! If you find yourself
-- wanting to import something from this module, please open an issue to get
-- that thing exported from "Salve".
module Salve.Internal
  ( Version (..),
    PreRelease (..),
    Build (..),
    Constraint (..),
    makeVersion,
    initialVersion,
    parseVersion,
    parsePreRelease,
    parseBuild,
    parseConstraint,
    unsafeParseVersion,
    unsafeParsePreRelease,
    unsafeParseBuild,
    unsafeParseConstraint,
    renderVersion,
    renderPreRelease,
    renderBuild,
    renderConstraint,
    isUnstable,
    isStable,
    fromBaseVersion,
    toBaseVersion,
    bumpMajor,
    bumpMinor,
    bumpPatch,
    satisfiesConstraint,
    majorLens,
    minorLens,
    patchLens,
    preReleasesLens,
    buildsLens,
    Operator (..),
    Wildcard (..),
    constraintLT,
    constraintLE,
    constraintEQ,
    constraintGE,
    constraintGT,
    constraintAnd,
    constraintOr,
    constraintHyphen,
    constraintTilde,
    constraintCaret,
  )
where

import qualified Control.Monad as Monad
import qualified Data.Char as Char
import qualified Data.Data as Data
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import qualified Data.Monoid as Monoid
import qualified Data.Ord as Ord
import qualified Data.Version as Version
import qualified Data.Word as Word
import qualified GHC.Generics as Generics
import qualified Text.ParserCombinators.ReadP as ReadP

-- $setup
--
-- >>> import Control.Applicative (Const(..))
  > > > let view lens record =  ( lens Const record )
--
  > > > import Data . Functor . Identity ( Identity ( .. ) )
-- >>> let set lens field record = runIdentity (lens (const (Identity field)) record)
--
-- >>> import Control.Applicative ((<$>), (<*>))

-- * Public

  | A semantic version number . Versions have five parts :
--
  1 . ' majorLens ' : The major version number .
  2 . ' minorLens ' : The minor version number .
  3 . ' patchLens ' : The patch version number .
  4 . ' preReleasesLens ' : A list of pre - release identifiers .
  5 . ' buildsLens ' : A list of build metadata .
--
-- Use 'parseVersion' to create versions.
data Version = Version
  { versionMajor :: Word.Word64,
    versionMinor :: Word.Word64,
    versionPatch :: Word.Word64,
    versionPreReleases :: [PreRelease],
    versionBuilds :: [Build]
  }
  deriving (Data.Data, Eq, Generics.Generic, Read, Show)

  | In general , versions compare in the way that you would expect . First the
-- major version numbers are compared, then the minors, then the patches.
--
-- >>> compare <$> parseVersion "1.2.3" <*> parseVersion "2.0.0"
-- Just LT
-- >>> compare <$> parseVersion "1.2.3" <*> parseVersion "1.3.0"
-- Just LT
-- >>> compare <$> parseVersion "1.2.3" <*> parseVersion "1.2.4"
-- Just LT
--
-- Numbers are compared numerically, not alphabetically.
--
-- >>> compare <$> parseVersion "0.0.9" <*> parseVersion "0.0.10"
-- Just LT
--
-- If all the numbers are the same, the pre-releases are compared.
--
-- >>> compare <$> parseVersion "1.2.3-a" <*> parseVersion "1.2.3-b"
-- Just LT
--
  A version with a pre - release is always less than a version without one as
-- long as the other parts are the same.
--
-- >>> compare <$> parseVersion "1.2.3-pre" <*> parseVersion "1.2.3"
-- Just LT
-- >>> compare <$> parseVersion "1.2.4-pre" <*> parseVersion "1.2.3"
  Just GT
--
-- Builds are not considered when comparing versions.
--
  > > > compare < $ > parseVersion " 1.2.3+a " < * > parseVersion " 1.2.3+b "
-- Just EQ
  > > > (= =) < $ > parseVersion " 1.2.3+a " < * > parseVersion " 1.2.3+b "
-- Just False
instance Ord Version where
  compare x y =
    Monoid.mconcat
      [ Ord.comparing versionMajor x y,
        Ord.comparing versionMinor x y,
        Ord.comparing versionPatch x y,
        case both versionPreReleases (x, y) of
          ([], []) -> EQ
          ([], _) -> GT
          (_, []) -> LT
          (p, q) -> compare p q
      ]

-- | Pre-release information attached to a version. These can either be numeric
-- or textual. They must not be empty.
--
  - Numeric : Can be any non - negative integer . Can not have leading zeros .
--
-- - Textual: Can be any string of ASCII digits, letters, or hyphens. Cannot be
--   all digits, as that would be numeric.
--
-- In general, pre-releases must match the regular expression
  @\/^[-0 - 9A - Za - z]+$\/@.
--
-- Use 'parsePreRelease' to create pre-releases.
data PreRelease
  = PreReleaseNumeric Word.Word64
  | PreReleaseTextual String
  deriving (Data.Data, Eq, Generics.Generic, Read, Show)

-- | Numeric pre-releases are always less than textual pre-releases.
--
-- >>> compare <$> parsePreRelease "1" <*> parsePreRelease "a"
-- Just LT
--
-- Numeric pre-releases are compared numerically.
--
  > > > compare < $ > parsePreRelease " 9 " < * > parsePreRelease " 10 "
-- Just LT
--
-- Textual pre-releases are compared alphabetically.
--
-- >>> compare <$> parsePreRelease "p10" <*> parsePreRelease "p9"
-- Just LT
instance Ord PreRelease where
  compare x y = case (x, y) of
    (PreReleaseNumeric n, PreReleaseNumeric m) -> compare n m
    (PreReleaseNumeric _, PreReleaseTextual _) -> LT
    (PreReleaseTextual _, PreReleaseNumeric _) -> GT
    (PreReleaseTextual s, PreReleaseTextual t) -> compare s t

-- | Build metadata attached to a version. These are similar to
  ' PreRelease 's with some key differences :
--
  1 . There is no such thing as numeric builds . Even though builds can look
--    like numbers, all builds are textual.
  2 . As a result , builds that look numeric are allowed to have leading zeros .
  3 . Builds can not be compared . That is , they do not have an ' Ord ' instance .
--
  Use '  ' to create builds .
newtype Build = Build String
  deriving (Data.Data, Eq, Generics.Generic, Read, Show)

-- | Constrains allowable version numbers.
--
-- Use 'parseConstraint' to create constraints and 'satisfiesConstraint' to see
-- if a version number satisfies a constraint.
data Constraint
  = ConstraintOperator Operator Version
  | ConstraintHyphen Version Version
  | ConstraintWildcard Wildcard
  | ConstraintAnd Constraint Constraint
  | ConstraintOr Constraint Constraint
  deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)

-- | Makes a new version number.
--
-- >>> makeVersion 1 2 3 [unsafeParsePreRelease "pre"] [unsafeParseBuild "build"]
  Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ PreReleaseTextual " pre " ] , versionBuilds = [ Build " build " ] }
--
-- This can be a useful alternative to 'parseVersion' if you want a total way
-- to create a version.
makeVersion ::
  Word.Word64 ->
  Word.Word64 ->
  Word.Word64 ->
  [PreRelease] ->
  [Build] ->
  Version
makeVersion major minor patch preReleases builds =
  Version
    { versionMajor = major,
      versionMinor = minor,
      versionPatch = patch,
      versionPreReleases = preReleases,
      versionBuilds = builds
    }

-- | The initial version number for development.
--
-- >>> initialVersion
  Version { versionMajor = 0 ,  = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] }
initialVersion :: Version
initialVersion = makeVersion 0 0 0 [] []

-- | Attempts to parse a version. This parser follows
  [ SemVer 's BNF]( / mojombo / semver / blob / eb9aac5 / semver.md#backusnaur - form - grammar - for - valid - semver - versions ) .
--
-- >>> parseVersion "1.2.3-p.4+b.5"
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ PreReleaseTextual " p",PreReleaseNumeric 4 ] , versionBuilds = [ Build "  " 5 " ] } )
--
-- Returns 'Nothing' if the parse fails.
--
-- >>> parseVersion "wrong"
-- Nothing
--
  Whitespace is allowed .
--
-- >>> parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
parseVersion :: String -> Maybe Version
parseVersion =
  parse
    ( do
        ReadP.skipSpaces
        version <- versionP
        ReadP.skipSpaces
        pure version
    )

-- | Attempts to parse a pre-release.
--
-- >>> parsePreRelease "pre"
-- Just (PreReleaseTextual "pre")
  > > > parsePreRelease " 1 "
-- Just (PreReleaseNumeric 1)
--
-- Returns 'Nothing' if the parse fails.
--
-- >>> parsePreRelease "wrong!"
-- Nothing
--
  Numeric pre - releases can not contain leading zeros .
--
-- >>> parsePreRelease "01"
-- Nothing
parsePreRelease :: String -> Maybe PreRelease
parsePreRelease = parse preReleaseP

-- | Attempts to parse a build.
--
  > > >  " build "
-- Just (Build "build")
  > > >  " 1 "
-- Just (Build "1")
--
-- Returns 'Nothing' if the parse fails.
--
  > > >  " wrong ! "
-- Nothing
--
  Unlike pre - releases , numeric builds can have leading zeros .
--
  > > >  " 01 "
-- Just (Build "01")
parseBuild :: String -> Maybe Build
parseBuild = parse buildP

-- | Attempts to parse a constraint. This parser mostly follows
-- [npm's BNF](#range-grammar).
--
  > > > parseConstraint " > 1.2.3 "
  Just ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
--
-- Returns 'Nothing' if the parse fails.
--
-- >>> parseConstraint "wrong"
-- Nothing
--
  The two departures from npm 's BNF are that x - ranges can not be used with
-- other operators and partial version numbers are not allowed.
--
-- >>> parseConstraint "1.2.x"
  Just ( ConstraintWildcard ( WildcardPatch 1 2 ) )
-- >>> parseConstraint ">=1.2.x"
-- Nothing
--
  > > > parseConstraint " 1.2 "
-- Nothing
  > > > parseConstraint " > = 1.2 "
-- Nothing
parseConstraint :: String -> Maybe Constraint
parseConstraint = parse constraintsP

-- | Parses a version.
--
-- >>> unsafeParseVersion "1.2.3-p.4+b.5"
  Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ PreReleaseTextual " p",PreReleaseNumeric 4 ] , versionBuilds = [ Build "  " 5 " ] }
--
-- Raises an exception if the parse fails.
--
-- >>> unsafeParseVersion "wrong"
-- *** Exception: unsafeParseVersion: invalid version: "wrong"
-- ...
--
-- See 'parseVersion' for a safe version of this function.
unsafeParseVersion :: String -> Version
unsafeParseVersion s = case parseVersion s of
  Nothing -> error ("unsafeParseVersion: invalid version: " <> show s)
  Just v -> v

-- | Parses a pre-release.
--
-- >>> unsafeParsePreRelease "pre"
-- PreReleaseTextual "pre"
--
-- Raises an exception if the parse fails.
--
-- >>> unsafeParsePreRelease "wrong!"
-- *** Exception: unsafeParsePreRelease: invalid pre-release: "wrong!"
-- ...
--
-- See 'parsePreRelease' for a safe version of this function.
unsafeParsePreRelease :: String -> PreRelease
unsafeParsePreRelease s = case parsePreRelease s of
  Nothing -> error ("unsafeParsePreRelease: invalid pre-release: " <> show s)
  Just p -> p

-- | Parses a build.
--
-- >>> unsafeParseBuild "build"
-- Build "build"
--
-- Raises an exception if the parse fails.
--
-- >>> unsafeParseBuild "wrong!"
  Build " * * * Exception :  : invalid build : " wrong ! "
-- ...
--
  See '  ' for a safe version of this function .
unsafeParseBuild :: String -> Build
unsafeParseBuild s = case parseBuild s of
  Nothing -> error ("unsafeParseBuild: invalid build: " <> show s)
  Just b -> b

-- | Parses a constraint.
--
  > > > unsafeParseConstraint " > 1.2.3 "
  ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
--
-- Raises an exception if the parse fails.
--
-- >>> unsafeParseConstraint "wrong"
-- *** Exception: unsafeParseConstraint: invalid constraint: "wrong"
-- ...
--
-- See 'parseConstraint' for a safe version of this function.
unsafeParseConstraint :: String -> Constraint
unsafeParseConstraint s = case parseConstraint s of
  Nothing -> error ("unsafeParseConstraint: invalid constraint: " <> show s)
  Just c -> c

-- | Renders a version.
--
-- >>> renderVersion <$> parseVersion "1.2.3-p.4+b.5"
-- Just "1.2.3-p.4+b.5"
renderVersion :: Version -> String
renderVersion v =
  concat
    [ show (versionMajor v),
      ".",
      show (versionMinor v),
      ".",
      show (versionPatch v),
      renderPreReleases (versionPreReleases v),
      renderBuilds (versionBuilds v)
    ]

-- | Renders a pre-release.
--
-- >>> renderPreRelease <$> parsePreRelease "pre"
-- Just "pre"
  > > > renderPreRelease < $ > parsePreRelease " 1 "
  Just " 1 "
renderPreRelease :: PreRelease -> String
renderPreRelease p = case p of
  PreReleaseNumeric n -> show n
  PreReleaseTextual s -> s

-- | Renders a build.
--
  > > > renderBuild < $ >  " build "
-- Just "build"
  > > > renderBuild < $ >  " 1 "
  Just " 1 "
renderBuild :: Build -> String
renderBuild (Build b) = b

-- | Renders a constraint.
--
  > > > renderConstraint < $ > parseConstraint " > 1.2.3 "
  Just " > 1.2.3 "
--
-- Parsing and rendering a constraint doesn't always return what you started
-- with.
--
  > > > renderConstraint < $ > parseConstraint " = 1.2.3 "
-- Just "1.2.3"
renderConstraint :: Constraint -> String
renderConstraint c = case c of
  ConstraintOperator o v ->
    let s = renderVersion v
     in case o of
          OperatorLT -> '<' : s
          OperatorLE -> '<' : '=' : s
          OperatorEQ -> s
          OperatorGE -> '>' : '=' : s
          OperatorGT -> '>' : s
          OperatorTilde -> '~' : s
          OperatorCaret -> '^' : s
  ConstraintHyphen l r -> unwords [renderVersion l, "-", renderVersion r]
  ConstraintWildcard w -> case w of
    WildcardMajor -> "x.x.x"
    WildcardMinor m -> show m <> ".x.x"
    WildcardPatch m n -> List.intercalate "." [show m, show n, "x"]
  ConstraintAnd l r -> unwords (fmap renderConstraint [l, r])
  ConstraintOr l r -> unwords [renderConstraint l, "||", renderConstraint r]

  | Returns ' True ' if the major version number is zero , ' False ' otherwise .
--
  > > > isUnstable < $ > parseVersion " 0.1.2 "
-- Just True
-- >>> isUnstable <$> parseVersion "1.0.0"
-- Just False
isUnstable :: Version -> Bool
isUnstable v = versionMajor v == 0

  | Returns ' True ' if the major version number is not zero , ' False ' otherwise .
--
-- >>> isStable <$> parseVersion "1.0.0"
-- Just True
  > > > isStable < $ > parseVersion " 0.1.2 "
-- Just False
isStable :: Version -> Bool
isStable v = not (isUnstable v)

-- | Converts from a 'Version.Version' from the @base@ package.
--
  > > > renderVersion . fromBaseVersion $ Version.makeVersion [ 1 , 2 , 3 ]
-- "1.2.3"
--
  Missing version components are set to zero .
--
-- >>> renderVersion . fromBaseVersion $ Version.makeVersion []
  " 0.0.0 "
  > > > renderVersion . fromBaseVersion $ Version.makeVersion [ 1 ]
-- "1.0.0"
  > > > renderVersion . fromBaseVersion $ Version.makeVersion [ 1 , 2 ]
-- "1.2.0"
--
-- Extra version components are ignored.
--
  > > > renderVersion . fromBaseVersion $ Version.makeVersion [ 1 , 2 , 3 , 4 ]
-- "1.2.3"
--
-- Tags are ignored.
--
-- >>> renderVersion . fromBaseVersion $ Version.Version [] ["ignored"]
  " 0.0.0 "
fromBaseVersion :: Version.Version -> Version
fromBaseVersion v = case Version.versionBranch v of
  (m : n : p : _) -> mkV (fromIntegral m) (fromIntegral n) (fromIntegral p)
  (m : n : _) -> mkV (fromIntegral m) (fromIntegral n) 0
  (m : _) -> mkV (fromIntegral m) 0 0
  _ -> mkV 0 0 0

-- | Converts to a 'Version.Version' from the @base@ package.
--
-- >>> toBaseVersion <$> parseVersion "1.2.3"
-- Just (Version {versionBranch = [1,2,3], versionTags = []})
--
-- Pre-releases and builds are converted to tags.
--
-- >>> toBaseVersion <$> parseVersion "1.2.3-pre+build"
-- Just (Version {versionBranch = [1,2,3], versionTags = ["pre","build"]})
toBaseVersion :: Version -> Version.Version
toBaseVersion v =
  Version.Version
    (fmap fromIntegral [versionMajor v, versionMinor v, versionPatch v])
    ( mconcat
        [ fmap renderPreRelease (versionPreReleases v),
          fmap renderBuild (versionBuilds v)
        ]
    )

-- | Increments the major version number.
--
  > > >  < $ > parseVersion " 0.0.0 "
  Just ( Version { versionMajor = 1 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
--
  The minor and patch numbers are reset to zero .
--
  > > >  < $ > parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 2 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
--
-- The pre-releases and builds are removed.
--
  > > >  < $ > parseVersion " 0.0.0 - pre+build "
  Just ( Version { versionMajor = 1 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
--
-- Consider using 'majorLens' if you want to arbitrarily change the major
-- number, or if you don't want the other parts of the version to change.
bumpMajor :: Version -> Version
bumpMajor v = makeVersion (versionMajor v + 1) 0 0 [] []

-- | Increments the minor version number.
--
  > > > bumpMinor < $ > parseVersion " 0.0.0 "
  Just ( Version { versionMajor = 0 , versionMinor = 1 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
--
  The patch number is reset to zero .
--
-- >>> bumpMinor <$> parseVersion "1.2.3"
  Just ( Version { versionMajor = 1 , versionMinor = 3 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
--
-- The pre-releases and builds are removed.
--
-- >>> bumpMinor <$> parseVersion "0.0.0-pre+build"
  Just ( Version { versionMajor = 0 , versionMinor = 1 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
--
-- Consider using 'minorLens' if you want to arbitrarily change the minor
-- number, or if you don't want the other parts of the version to change.
bumpMinor :: Version -> Version
bumpMinor v = makeVersion (versionMajor v) (versionMinor v + 1) 0 [] []

-- | Increments the patch number.
--
  > > > bumpPatch < $ > parseVersion " 0.0.0 "
  Just ( Version { versionMajor = 0 ,  = 0 , versionPatch = 1 , versionPreReleases = [ ] ,  = [ ] } )
--
-- The major and minor numbers are not changed.
--
  > > > bumpPatch < $ > parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 4 , versionPreReleases = [ ] ,  = [ ] } )
--
-- The pre-releases and builds are removed.
--
  > > > bumpPatch < $ > parseVersion " 0.0.0 - pre+build "
  Just ( Version { versionMajor = 0 ,  = 0 , versionPatch = 1 , versionPreReleases = [ ] ,  = [ ] } )
--
-- Consider using 'patchLens' if you want to arbitrarily change the patch
-- number, or if you don't want the other parts of the version to change.
bumpPatch :: Version -> Version
bumpPatch v =
  makeVersion (versionMajor v) (versionMinor v) (versionPatch v + 1) [] []

-- | Returns 'True' if the version satisfies the constraint, 'False' otherwise.
--
  > > > satisfiesConstraint < $ > parseConstraint " > 1.2.0 " < * > parseVersion " 1.2.3 "
-- Just True
satisfiesConstraint :: Constraint -> Version -> Bool
satisfiesConstraint c = satisfiesSC (toSC c)

-- | Focuses on the major version number.
--
-- >>> view majorLens <$> parseVersion "1.2.3-pre.4+build.5"
-- Just 1
  > > > set majorLens 4 < $ > parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 4 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
majorLens ::
  (Functor f) => (Word.Word64 -> f Word.Word64) -> Version -> f Version
majorLens f v = fmap (\m -> v {versionMajor = m}) (f (versionMajor v))

-- | Focuses on the minor version number.
--
-- >>> view minorLens <$> parseVersion "1.2.3-pre.4+build.5"
  Just 2
-- >>> set minorLens 4 <$> parseVersion "1.2.3"
  Just ( Version { versionMajor = 1 , versionMinor = 4 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
minorLens ::
  (Functor f) => (Word.Word64 -> f Word.Word64) -> Version -> f Version
minorLens f v = fmap (\n -> v {versionMinor = n}) (f (versionMinor v))

-- | Focuses on the patch version number.
--
-- >>> view patchLens <$> parseVersion "1.2.3-pre.4+build.5"
  Just 3
  > > > set patchLens 4 < $ > parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 4 , versionPreReleases = [ ] ,  = [ ] } )
patchLens ::
  (Functor f) => (Word.Word64 -> f Word.Word64) -> Version -> f Version
patchLens f v = fmap (\p -> v {versionPatch = p}) (f (versionPatch v))

-- | Focuses on the pre-release identifiers.
--
-- >>> view preReleasesLens <$> parseVersion "1.2.3-pre.4+build.5"
  Just [ PreReleaseTextual " pre",PreReleaseNumeric 4 ]
-- >>> set preReleasesLens [] <$> parseVersion "1.2.3-pre"
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
preReleasesLens ::
  (Functor f) => ([PreRelease] -> f [PreRelease]) -> Version -> f Version
preReleasesLens f v =
  fmap (\ps -> v {versionPreReleases = ps}) (f (versionPreReleases v))

-- | Focuses on the build metadata.
--
-- >>> view buildsLens <$> parseVersion "1.2.3-pre.4+build.5"
-- Just [Build "build",Build "5"]
  > > > set buildsLens [ ] < $ > parseVersion " 1.2.3+build "
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
buildsLens :: (Functor f) => ([Build] -> f [Build]) -> Version -> f Version
buildsLens f v = fmap (\bs -> v {versionBuilds = bs}) (f (versionBuilds v))

-- * Private

-- ** Types

data Operator
  = OperatorLT
  | OperatorLE
  | OperatorEQ
  | OperatorGE
  | OperatorGT
  | OperatorTilde
  | OperatorCaret
  deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)

data Wildcard
  = WildcardMajor
  | WildcardMinor Word.Word64
  | WildcardPatch Word.Word64 Word.Word64
  deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)

-- ** Constructors

-- | Makes a new constraint that must be less than the version number.
--
-- >>> constraintLT <$> parseVersion "1.2.3"
  Just ( ConstraintOperator OperatorLT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " < 1.2.3 "
  Just ( ConstraintOperator OperatorLT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintLT :: Version -> Constraint
constraintLT = ConstraintOperator OperatorLT

-- | Makes a new constraint that must be less than or euqal to the version
-- number.
--
-- >>> constraintLE <$> parseVersion "1.2.3"
  Just ( ConstraintOperator OperatorLE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " < = 1.2.3 "
  Just ( ConstraintOperator OperatorLE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintLE :: Version -> Constraint
constraintLE = ConstraintOperator OperatorLE

-- | Makes a new constraint that must be equal to the version number.
--
  > > > constraintEQ < $ > parseVersion " 1.2.3 "
  Just ( ConstraintOperator OperatorEQ ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " = 1.2.3 "
  Just ( ConstraintOperator OperatorEQ ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintEQ :: Version -> Constraint
constraintEQ = ConstraintOperator OperatorEQ

-- | Makes a new constraint that must be greater than or equal to the version
-- number.
--
-- >>> constraintGE <$> parseVersion "1.2.3"
  Just ( ConstraintOperator OperatorGE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " > = 1.2.3 "
  Just ( ConstraintOperator OperatorGE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintGE :: Version -> Constraint
constraintGE = ConstraintOperator OperatorGE

-- | Makes a new constraint that must be greater than the version number.
--
-- >>> constraintGT <$> parseVersion "1.2.3"
  Just ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " > 1.2.3 "
  Just ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintGT :: Version -> Constraint
constraintGT = ConstraintOperator OperatorGT

-- | Makes a new constraint that must satisfy both constraints.
--
  > > > constraintAnd < $ > ( constraintGE < $ > parseVersion " 1.2.3 " ) < * > ( constraintLT < $ > parseVersion " 2.0.0 " )
  Just ( ConstraintAnd ( ConstraintOperator OperatorGE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) ( ConstraintOperator OperatorLT ( Version { versionMajor = 2 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } ) ) )
  > > > parseConstraint " > = 1.2.3 < 2.0.0 "
  Just ( ConstraintAnd ( ConstraintOperator OperatorGE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) ( ConstraintOperator OperatorLT ( Version { versionMajor = 2 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } ) ) )
constraintAnd :: Constraint -> Constraint -> Constraint
constraintAnd = ConstraintAnd

-- | Makes a new constraint that must satisfy either constraint.
--
  > > > constraintOr < $ > ( constraintEQ < $ > parseVersion " 1.2.3 " ) < * > ( constraintGT < $ > parseVersion " 1.2.3 " )
  Just ( ConstraintOr ( ConstraintOperator OperatorEQ ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) )
  > > > parseConstraint " = 1.2.3 || > 1.2.3 "
  Just ( ConstraintOr ( ConstraintOperator OperatorEQ ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) )
constraintOr :: Constraint -> Constraint -> Constraint
constraintOr = ConstraintOr

-- | Makes a new constraint that must be between the versions, inclusive.
--
-- >>> constraintHyphen <$> parseVersion "1.2.3" <*> parseVersion "2.3.4"
  Just ( ConstraintHyphen ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ( Version { versionMajor = 2 ,  = 3 , versionPatch = 4 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " 1.2.3 - 2.3.4 "
  Just ( ConstraintHyphen ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ( Version { versionMajor = 2 ,  = 3 , versionPatch = 4 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintHyphen :: Version -> Version -> Constraint
constraintHyphen = ConstraintHyphen

-- | Makes a new constraint that allows changes to the patch version number.
--
  > > >  < $ > parseVersion " 1.2.3 "
  Just ( ConstraintOperator OperatorTilde ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
-- >>> parseConstraint "~1.2.3"
  Just ( ConstraintOperator OperatorTilde ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintTilde :: Version -> Constraint
constraintTilde = ConstraintOperator OperatorTilde

-- | Makes a new constraint that allows changes that do not modify the
  left - most non - zero version number .
--
  > > >  < $ > parseVersion " 1.2.3 "
  Just ( ConstraintOperator OperatorCaret ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
-- >>> parseConstraint "^1.2.3"
  Just ( ConstraintOperator OperatorCaret ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintCaret :: Version -> Constraint
constraintCaret = ConstraintOperator OperatorCaret

-- ** Parsing

versionP :: ReadP.ReadP Version
versionP = do
  major <- numberP
  Monad.void (ReadP.char '.')
  minor <- numberP
  Monad.void (ReadP.char '.')
  patch <- numberP
  preReleases <- preReleasesP
  makeVersion major minor patch preReleases <$> buildsP

preReleasesP :: ReadP.ReadP [PreRelease]
preReleasesP =
  ReadP.option
    []
    ( do
        Monad.void (ReadP.char '-')
        ReadP.sepBy1 preReleaseP (ReadP.char '.')
    )

preReleaseP :: ReadP.ReadP PreRelease
preReleaseP = preReleaseNumberP ReadP.<++ preReleaseStringP

preReleaseNumberP :: ReadP.ReadP PreRelease
preReleaseNumberP = PreReleaseNumeric <$> numberP

preReleaseStringP :: ReadP.ReadP PreRelease
preReleaseStringP = do
  s <- ReadP.munch1 isIdentifier
  if all Char.isDigit s then ReadP.pfail else pure (PreReleaseTextual s)

buildsP :: ReadP.ReadP [Build]
buildsP =
  ReadP.option
    []
    ( do
        Monad.void (ReadP.char '+')
        ReadP.sepBy1 buildP (ReadP.char '.')
    )

buildP :: ReadP.ReadP Build
buildP = do
  b <- ReadP.munch1 isIdentifier
  pure (Build b)

numberP :: ReadP.ReadP Word.Word64
numberP = zeroP ReadP.<++ nonZeroP

zeroP :: ReadP.ReadP Word.Word64
zeroP = do
  Monad.void (ReadP.char '0')
  pure 0

nonZeroP :: ReadP.ReadP Word.Word64
nonZeroP = do
  x <- ReadP.satisfy isAsciiDigitNonZero
  ys <- ReadP.munch Char.isDigit
  maybe ReadP.pfail pure . toWord64 . stringToIntegral $ x : ys

constraintsP :: ReadP.ReadP Constraint
constraintsP = do
  spacesP
  cs <- ReadP.sepBy1 constraintP orP
  spacesP
  pure (foldr1 constraintOr cs)

constraintP :: ReadP.ReadP Constraint
constraintP = do
  cs <- ReadP.sepBy1 simpleP spaces1P
  pure (foldr1 constraintAnd cs)

hyphenatedP :: ReadP.ReadP Constraint
hyphenatedP = do
  v <- versionP
  hyphenP
  constraintHyphen v <$> versionP

simpleP :: ReadP.ReadP Constraint
simpleP = ReadP.choice [hyphenatedP, wildcardConstraintP, primitiveP]

wildcardConstraintP :: ReadP.ReadP Constraint
wildcardConstraintP = do
  ReadP.optional (ReadP.char '=')
  ConstraintWildcard <$> wildcardP

wildcardP :: ReadP.ReadP Wildcard
wildcardP = ReadP.choice [wildcardPatchP, wildcardMinorP, wildcardMajorP]

wildcardPatchP :: ReadP.ReadP Wildcard
wildcardPatchP = do
  m <- numberP
  Monad.void (ReadP.char '.')
  n <- numberP
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  pure (WildcardPatch m n)

wildcardMinorP :: ReadP.ReadP Wildcard
wildcardMinorP = do
  m <- numberP
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  pure (WildcardMinor m)

wildcardMajorP :: ReadP.ReadP Wildcard
wildcardMajorP = do
  Monad.void (ReadP.satisfy isWildcard)
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  pure WildcardMajor

primitiveP :: ReadP.ReadP Constraint
primitiveP = do
  o <- operatorP
  spacesP
  ConstraintOperator o <$> versionP

operatorP :: ReadP.ReadP Operator
operatorP =
  ReadP.choice
    [ ReadP.string "<=" >> pure OperatorLE,
      ReadP.string ">=" >> pure OperatorGE,
      ReadP.char '<' >> pure OperatorLT,
      ReadP.char '>' >> pure OperatorGT,
      ReadP.char '=' >> pure OperatorEQ,
      ReadP.char '^' >> pure OperatorCaret,
      ReadP.char '~' >> pure OperatorTilde,
      pure OperatorEQ
    ]

hyphenP :: ReadP.ReadP ()
hyphenP = do
  spaces1P
  Monad.void (ReadP.char '-')
  spaces1P

orP :: ReadP.ReadP ()
orP = do
  spaces1P
  Monad.void (ReadP.string "||")
  spaces1P

spaces1P :: ReadP.ReadP ()
spaces1P = Monad.void (ReadP.munch1 (== ' '))

spacesP :: ReadP.ReadP ()
spacesP = Monad.void (ReadP.munch (== ' '))

-- ** Rendering

renderPreReleases :: [PreRelease] -> String
renderPreReleases ps =
  if null ps then "" else '-' : List.intercalate "." (fmap renderPreRelease ps)

renderBuilds :: [Build] -> String
renderBuilds bs =
  if null bs then "" else '+' : List.intercalate "." (fmap renderBuild bs)

-- ** Helpers

both :: (a -> b) -> (a, a) -> (b, b)
both f (x, y) = (f x, f y)

isAsciiDigitNonZero :: Char -> Bool
isAsciiDigitNonZero c = Char.isDigit c && (c /= '0')

isIdentifier :: Char -> Bool
isIdentifier c = (Char.isAscii c && Char.isAlphaNum c) || (c == '-')

isWildcard :: Char -> Bool
isWildcard c = (c == 'x') || (c == '*') || (c == 'X')

parse :: ReadP.ReadP a -> String -> Maybe a
parse p s =
  let p' = ReadP.readP_to_S p
   in Maybe.listToMaybe
        ( do
            (x, "") <- p' s
            pure x
        )

stringToIntegral :: (Integral a) => String -> a
stringToIntegral =
  foldl (\n d -> (n * 10) + (fromIntegral (fromEnum d) - 48)) 0

toWord64 :: Integer -> Maybe Word.Word64
toWord64 n
  | n < 0 = Nothing
  | n > fromIntegral (maxBound :: Word.Word64) = Nothing
  | otherwise = Just (fromIntegral n)

-- * Simple constraints

  | Simple constraints are just as expressive as ' Constraint 's , but they are
-- easier to reason about. You can think of them as the desugared version of
  ' Constraint 's .
data SimpleConstraint
  = SCLT Version
  | SCEQ Version
  | SCGT Version
  | SCAnd SimpleConstraint SimpleConstraint
  | SCOr SimpleConstraint SimpleConstraint
  deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)

mkV :: Word.Word64 -> Word.Word64 -> Word.Word64 -> Version
mkV m n p = makeVersion m n p [] []

{- hlint ignore satisfiesSC "Redundant compare" -}
satisfiesSC :: SimpleConstraint -> Version -> Bool
satisfiesSC c v = case c of
  SCLT u -> v < u
  -- This uses `compare` rather than `==` to ignore build metadata.
  SCEQ u -> compare v u == EQ
  SCGT u -> v > u
  SCAnd l r -> satisfiesSC l v && satisfiesSC r v
  SCOr l r -> satisfiesSC l v || satisfiesSC r v

scLE :: Version -> SimpleConstraint
scLE v = SCOr (SCLT v) (SCEQ v)

scGE :: Version -> SimpleConstraint
scGE v = SCOr (SCGT v) (SCEQ v)

toSC :: Constraint -> SimpleConstraint
toSC c = case c of
  ConstraintOperator o v -> case o of
    OperatorLT -> SCLT v
    OperatorLE -> scLE v
    OperatorEQ -> SCEQ v
    OperatorGE -> scGE v
    OperatorGT -> SCGT v
    OperatorTilde ->
      SCAnd (scGE v) (SCLT (mkV (versionMajor v) (versionMinor v + 1) 0))
    OperatorCaret ->
      SCAnd
        (scGE v)
        ( SCLT
            ( case (versionMajor v, versionMinor v, versionPatch v) of
                (0, 0, p) -> mkV 0 0 (p + 1)
                (0, n, _) -> mkV 0 (n + 1) 0
                (m, _, _) -> mkV (m + 1) 0 0
            )
        )
  ConstraintHyphen l h -> SCAnd (scGE l) (scLE h)
  ConstraintWildcard w -> case w of
    WildcardMajor -> scGE initialVersion
    WildcardMinor m -> SCAnd (scGE (mkV m 0 0)) (SCLT (mkV (m + 1) 0 0))
    WildcardPatch m n -> SCAnd (scGE (mkV m n 0)) (SCLT (mkV m (n + 1) 0))
  ConstraintAnd l r -> SCAnd (toSC l) (toSC r)
  ConstraintOr l r -> SCOr (toSC l) (toSC r)

# LANGUAGE DeriveDataTypeable #
 | WARNING: This module should be considered private! If you find yourself
 wanting to import something from this module, please open an issue to get
 that thing exported from "Salve".
 $setup

 >>> import Control.Applicative (Const(..))

 >>> let set lens field record = runIdentity (lens (const (Identity field)) record)

 >>> import Control.Applicative ((<$>), (<*>))
 * Public


 Use 'parseVersion' to create versions.
 major version numbers are compared, then the minors, then the patches.

 >>> compare <$> parseVersion "1.2.3" <*> parseVersion "2.0.0"
 Just LT
 >>> compare <$> parseVersion "1.2.3" <*> parseVersion "1.3.0"
 Just LT
 >>> compare <$> parseVersion "1.2.3" <*> parseVersion "1.2.4"
 Just LT

 Numbers are compared numerically, not alphabetically.

 >>> compare <$> parseVersion "0.0.9" <*> parseVersion "0.0.10"
 Just LT

 If all the numbers are the same, the pre-releases are compared.

 >>> compare <$> parseVersion "1.2.3-a" <*> parseVersion "1.2.3-b"
 Just LT

 long as the other parts are the same.

 >>> compare <$> parseVersion "1.2.3-pre" <*> parseVersion "1.2.3"
 Just LT
 >>> compare <$> parseVersion "1.2.4-pre" <*> parseVersion "1.2.3"

 Builds are not considered when comparing versions.

 Just EQ
 Just False
 | Pre-release information attached to a version. These can either be numeric
 or textual. They must not be empty.


 - Textual: Can be any string of ASCII digits, letters, or hyphens. Cannot be
   all digits, as that would be numeric.

 In general, pre-releases must match the regular expression

 Use 'parsePreRelease' to create pre-releases.
 | Numeric pre-releases are always less than textual pre-releases.

 >>> compare <$> parsePreRelease "1" <*> parsePreRelease "a"
 Just LT

 Numeric pre-releases are compared numerically.

 Just LT

 Textual pre-releases are compared alphabetically.

 >>> compare <$> parsePreRelease "p10" <*> parsePreRelease "p9"
 Just LT
 | Build metadata attached to a version. These are similar to

    like numbers, all builds are textual.

 | Constrains allowable version numbers.

 Use 'parseConstraint' to create constraints and 'satisfiesConstraint' to see
 if a version number satisfies a constraint.
 | Makes a new version number.

 >>> makeVersion 1 2 3 [unsafeParsePreRelease "pre"] [unsafeParseBuild "build"]

 This can be a useful alternative to 'parseVersion' if you want a total way
 to create a version.
 | The initial version number for development.

 >>> initialVersion
 | Attempts to parse a version. This parser follows

 >>> parseVersion "1.2.3-p.4+b.5"

 Returns 'Nothing' if the parse fails.

 >>> parseVersion "wrong"
 Nothing


 >>> parseVersion " 1.2.3 "
 | Attempts to parse a pre-release.

 >>> parsePreRelease "pre"
 Just (PreReleaseTextual "pre")
 Just (PreReleaseNumeric 1)

 Returns 'Nothing' if the parse fails.

 >>> parsePreRelease "wrong!"
 Nothing


 >>> parsePreRelease "01"
 Nothing
 | Attempts to parse a build.

 Just (Build "build")
 Just (Build "1")

 Returns 'Nothing' if the parse fails.

 Nothing


 Just (Build "01")
 | Attempts to parse a constraint. This parser mostly follows
 [npm's BNF](#range-grammar).


 Returns 'Nothing' if the parse fails.

 >>> parseConstraint "wrong"
 Nothing

 other operators and partial version numbers are not allowed.

 >>> parseConstraint "1.2.x"
 >>> parseConstraint ">=1.2.x"
 Nothing

 Nothing
 Nothing
 | Parses a version.

 >>> unsafeParseVersion "1.2.3-p.4+b.5"

 Raises an exception if the parse fails.

 >>> unsafeParseVersion "wrong"
 *** Exception: unsafeParseVersion: invalid version: "wrong"
 ...

 See 'parseVersion' for a safe version of this function.
 | Parses a pre-release.

 >>> unsafeParsePreRelease "pre"
 PreReleaseTextual "pre"

 Raises an exception if the parse fails.

 >>> unsafeParsePreRelease "wrong!"
 *** Exception: unsafeParsePreRelease: invalid pre-release: "wrong!"
 ...

 See 'parsePreRelease' for a safe version of this function.
 | Parses a build.

 >>> unsafeParseBuild "build"
 Build "build"

 Raises an exception if the parse fails.

 >>> unsafeParseBuild "wrong!"
 ...

 | Parses a constraint.


 Raises an exception if the parse fails.

 >>> unsafeParseConstraint "wrong"
 *** Exception: unsafeParseConstraint: invalid constraint: "wrong"
 ...

 See 'parseConstraint' for a safe version of this function.
 | Renders a version.

 >>> renderVersion <$> parseVersion "1.2.3-p.4+b.5"
 Just "1.2.3-p.4+b.5"
 | Renders a pre-release.

 >>> renderPreRelease <$> parsePreRelease "pre"
 Just "pre"
 | Renders a build.

 Just "build"
 | Renders a constraint.


 Parsing and rendering a constraint doesn't always return what you started
 with.

 Just "1.2.3"

 Just True
 >>> isUnstable <$> parseVersion "1.0.0"
 Just False

 >>> isStable <$> parseVersion "1.0.0"
 Just True
 Just False
 | Converts from a 'Version.Version' from the @base@ package.

 "1.2.3"


 >>> renderVersion . fromBaseVersion $ Version.makeVersion []
 "1.0.0"
 "1.2.0"

 Extra version components are ignored.

 "1.2.3"

 Tags are ignored.

 >>> renderVersion . fromBaseVersion $ Version.Version [] ["ignored"]
 | Converts to a 'Version.Version' from the @base@ package.

 >>> toBaseVersion <$> parseVersion "1.2.3"
 Just (Version {versionBranch = [1,2,3], versionTags = []})

 Pre-releases and builds are converted to tags.

 >>> toBaseVersion <$> parseVersion "1.2.3-pre+build"
 Just (Version {versionBranch = [1,2,3], versionTags = ["pre","build"]})
 | Increments the major version number.




 The pre-releases and builds are removed.


 Consider using 'majorLens' if you want to arbitrarily change the major
 number, or if you don't want the other parts of the version to change.
 | Increments the minor version number.



 >>> bumpMinor <$> parseVersion "1.2.3"

 The pre-releases and builds are removed.

 >>> bumpMinor <$> parseVersion "0.0.0-pre+build"

 Consider using 'minorLens' if you want to arbitrarily change the minor
 number, or if you don't want the other parts of the version to change.
 | Increments the patch number.


 The major and minor numbers are not changed.


 The pre-releases and builds are removed.


 Consider using 'patchLens' if you want to arbitrarily change the patch
 number, or if you don't want the other parts of the version to change.
 | Returns 'True' if the version satisfies the constraint, 'False' otherwise.

 Just True
 | Focuses on the major version number.

 >>> view majorLens <$> parseVersion "1.2.3-pre.4+build.5"
 Just 1
 | Focuses on the minor version number.

 >>> view minorLens <$> parseVersion "1.2.3-pre.4+build.5"
 >>> set minorLens 4 <$> parseVersion "1.2.3"
 | Focuses on the patch version number.

 >>> view patchLens <$> parseVersion "1.2.3-pre.4+build.5"
 | Focuses on the pre-release identifiers.

 >>> view preReleasesLens <$> parseVersion "1.2.3-pre.4+build.5"
 >>> set preReleasesLens [] <$> parseVersion "1.2.3-pre"
 | Focuses on the build metadata.

 >>> view buildsLens <$> parseVersion "1.2.3-pre.4+build.5"
 Just [Build "build",Build "5"]
 * Private
 ** Types
 ** Constructors
 | Makes a new constraint that must be less than the version number.

 >>> constraintLT <$> parseVersion "1.2.3"
 | Makes a new constraint that must be less than or euqal to the version
 number.

 >>> constraintLE <$> parseVersion "1.2.3"
 | Makes a new constraint that must be equal to the version number.

 | Makes a new constraint that must be greater than or equal to the version
 number.

 >>> constraintGE <$> parseVersion "1.2.3"
 | Makes a new constraint that must be greater than the version number.

 >>> constraintGT <$> parseVersion "1.2.3"
 | Makes a new constraint that must satisfy both constraints.

 | Makes a new constraint that must satisfy either constraint.

 | Makes a new constraint that must be between the versions, inclusive.

 >>> constraintHyphen <$> parseVersion "1.2.3" <*> parseVersion "2.3.4"
 | Makes a new constraint that allows changes to the patch version number.

 >>> parseConstraint "~1.2.3"
 | Makes a new constraint that allows changes that do not modify the

 >>> parseConstraint "^1.2.3"
 ** Parsing
 ** Rendering
 ** Helpers
 * Simple constraints
 easier to reason about. You can think of them as the desugared version of
 hlint ignore satisfiesSC "Redundant compare" 
 This uses `compare` rather than `==` to ignore build metadata.
# LANGUAGE DeriveGeneric #

module Salve.Internal
  ( Version (..),
    PreRelease (..),
    Build (..),
    Constraint (..),
    makeVersion,
    initialVersion,
    parseVersion,
    parsePreRelease,
    parseBuild,
    parseConstraint,
    unsafeParseVersion,
    unsafeParsePreRelease,
    unsafeParseBuild,
    unsafeParseConstraint,
    renderVersion,
    renderPreRelease,
    renderBuild,
    renderConstraint,
    isUnstable,
    isStable,
    fromBaseVersion,
    toBaseVersion,
    bumpMajor,
    bumpMinor,
    bumpPatch,
    satisfiesConstraint,
    majorLens,
    minorLens,
    patchLens,
    preReleasesLens,
    buildsLens,
    Operator (..),
    Wildcard (..),
    constraintLT,
    constraintLE,
    constraintEQ,
    constraintGE,
    constraintGT,
    constraintAnd,
    constraintOr,
    constraintHyphen,
    constraintTilde,
    constraintCaret,
  )
where

import qualified Control.Monad as Monad
import qualified Data.Char as Char
import qualified Data.Data as Data
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import qualified Data.Monoid as Monoid
import qualified Data.Ord as Ord
import qualified Data.Version as Version
import qualified Data.Word as Word
import qualified GHC.Generics as Generics
import qualified Text.ParserCombinators.ReadP as ReadP

  > > > let view lens record =  ( lens Const record )
  > > > import Data . Functor . Identity ( Identity ( .. ) )


  | A semantic version number . Versions have five parts :
  1 . ' majorLens ' : The major version number .
  2 . ' minorLens ' : The minor version number .
  3 . ' patchLens ' : The patch version number .
  4 . ' preReleasesLens ' : A list of pre - release identifiers .
  5 . ' buildsLens ' : A list of build metadata .
data Version = Version
  { versionMajor :: Word.Word64,
    versionMinor :: Word.Word64,
    versionPatch :: Word.Word64,
    versionPreReleases :: [PreRelease],
    versionBuilds :: [Build]
  }
  deriving (Data.Data, Eq, Generics.Generic, Read, Show)

  | In general , versions compare in the way that you would expect . First the
  A version with a pre - release is always less than a version without one as
  Just GT
  > > > compare < $ > parseVersion " 1.2.3+a " < * > parseVersion " 1.2.3+b "
  > > > (= =) < $ > parseVersion " 1.2.3+a " < * > parseVersion " 1.2.3+b "
instance Ord Version where
  compare x y =
    Monoid.mconcat
      [ Ord.comparing versionMajor x y,
        Ord.comparing versionMinor x y,
        Ord.comparing versionPatch x y,
        case both versionPreReleases (x, y) of
          ([], []) -> EQ
          ([], _) -> GT
          (_, []) -> LT
          (p, q) -> compare p q
      ]

  - Numeric : Can be any non - negative integer . Can not have leading zeros .
  @\/^[-0 - 9A - Za - z]+$\/@.
data PreRelease
  = PreReleaseNumeric Word.Word64
  | PreReleaseTextual String
  deriving (Data.Data, Eq, Generics.Generic, Read, Show)

  > > > compare < $ > parsePreRelease " 9 " < * > parsePreRelease " 10 "
instance Ord PreRelease where
  compare x y = case (x, y) of
    (PreReleaseNumeric n, PreReleaseNumeric m) -> compare n m
    (PreReleaseNumeric _, PreReleaseTextual _) -> LT
    (PreReleaseTextual _, PreReleaseNumeric _) -> GT
    (PreReleaseTextual s, PreReleaseTextual t) -> compare s t

  ' PreRelease 's with some key differences :
  1 . There is no such thing as numeric builds . Even though builds can look
  2 . As a result , builds that look numeric are allowed to have leading zeros .
  3 . Builds can not be compared . That is , they do not have an ' Ord ' instance .
  Use '  ' to create builds .
newtype Build = Build String
  deriving (Data.Data, Eq, Generics.Generic, Read, Show)

data Constraint
  = ConstraintOperator Operator Version
  | ConstraintHyphen Version Version
  | ConstraintWildcard Wildcard
  | ConstraintAnd Constraint Constraint
  | ConstraintOr Constraint Constraint
  deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)

  Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ PreReleaseTextual " pre " ] , versionBuilds = [ Build " build " ] }
makeVersion ::
  Word.Word64 ->
  Word.Word64 ->
  Word.Word64 ->
  [PreRelease] ->
  [Build] ->
  Version
makeVersion major minor patch preReleases builds =
  Version
    { versionMajor = major,
      versionMinor = minor,
      versionPatch = patch,
      versionPreReleases = preReleases,
      versionBuilds = builds
    }

  Version { versionMajor = 0 ,  = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] }
initialVersion :: Version
initialVersion = makeVersion 0 0 0 [] []

  [ SemVer 's BNF]( / mojombo / semver / blob / eb9aac5 / semver.md#backusnaur - form - grammar - for - valid - semver - versions ) .
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ PreReleaseTextual " p",PreReleaseNumeric 4 ] , versionBuilds = [ Build "  " 5 " ] } )
  Whitespace is allowed .
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
parseVersion :: String -> Maybe Version
parseVersion =
  parse
    ( do
        ReadP.skipSpaces
        version <- versionP
        ReadP.skipSpaces
        pure version
    )

  > > > parsePreRelease " 1 "
  Numeric pre - releases can not contain leading zeros .
parsePreRelease :: String -> Maybe PreRelease
parsePreRelease = parse preReleaseP

  > > >  " build "
  > > >  " 1 "
  > > >  " wrong ! "
  Unlike pre - releases , numeric builds can have leading zeros .
  > > >  " 01 "
parseBuild :: String -> Maybe Build
parseBuild = parse buildP

  > > > parseConstraint " > 1.2.3 "
  Just ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  The two departures from npm 's BNF are that x - ranges can not be used with
  Just ( ConstraintWildcard ( WildcardPatch 1 2 ) )
  > > > parseConstraint " 1.2 "
  > > > parseConstraint " > = 1.2 "
parseConstraint :: String -> Maybe Constraint
parseConstraint = parse constraintsP

  Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ PreReleaseTextual " p",PreReleaseNumeric 4 ] , versionBuilds = [ Build "  " 5 " ] }
unsafeParseVersion :: String -> Version
unsafeParseVersion s = case parseVersion s of
  Nothing -> error ("unsafeParseVersion: invalid version: " <> show s)
  Just v -> v

unsafeParsePreRelease :: String -> PreRelease
unsafeParsePreRelease s = case parsePreRelease s of
  Nothing -> error ("unsafeParsePreRelease: invalid pre-release: " <> show s)
  Just p -> p

  Build " * * * Exception :  : invalid build : " wrong ! "
  See '  ' for a safe version of this function .
unsafeParseBuild :: String -> Build
unsafeParseBuild s = case parseBuild s of
  Nothing -> error ("unsafeParseBuild: invalid build: " <> show s)
  Just b -> b

  > > > unsafeParseConstraint " > 1.2.3 "
  ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
unsafeParseConstraint :: String -> Constraint
unsafeParseConstraint s = case parseConstraint s of
  Nothing -> error ("unsafeParseConstraint: invalid constraint: " <> show s)
  Just c -> c

renderVersion :: Version -> String
renderVersion v =
  concat
    [ show (versionMajor v),
      ".",
      show (versionMinor v),
      ".",
      show (versionPatch v),
      renderPreReleases (versionPreReleases v),
      renderBuilds (versionBuilds v)
    ]

  > > > renderPreRelease < $ > parsePreRelease " 1 "
  Just " 1 "
renderPreRelease :: PreRelease -> String
renderPreRelease p = case p of
  PreReleaseNumeric n -> show n
  PreReleaseTextual s -> s

  > > > renderBuild < $ >  " build "
  > > > renderBuild < $ >  " 1 "
  Just " 1 "
renderBuild :: Build -> String
renderBuild (Build b) = b

  > > > renderConstraint < $ > parseConstraint " > 1.2.3 "
  Just " > 1.2.3 "
  > > > renderConstraint < $ > parseConstraint " = 1.2.3 "
renderConstraint :: Constraint -> String
renderConstraint c = case c of
  ConstraintOperator o v ->
    let s = renderVersion v
     in case o of
          OperatorLT -> '<' : s
          OperatorLE -> '<' : '=' : s
          OperatorEQ -> s
          OperatorGE -> '>' : '=' : s
          OperatorGT -> '>' : s
          OperatorTilde -> '~' : s
          OperatorCaret -> '^' : s
  ConstraintHyphen l r -> unwords [renderVersion l, "-", renderVersion r]
  ConstraintWildcard w -> case w of
    WildcardMajor -> "x.x.x"
    WildcardMinor m -> show m <> ".x.x"
    WildcardPatch m n -> List.intercalate "." [show m, show n, "x"]
  ConstraintAnd l r -> unwords (fmap renderConstraint [l, r])
  ConstraintOr l r -> unwords [renderConstraint l, "||", renderConstraint r]

  | Returns ' True ' if the major version number is zero , ' False ' otherwise .
  > > > isUnstable < $ > parseVersion " 0.1.2 "
isUnstable :: Version -> Bool
isUnstable v = versionMajor v == 0

  | Returns ' True ' if the major version number is not zero , ' False ' otherwise .
  > > > isStable < $ > parseVersion " 0.1.2 "
isStable :: Version -> Bool
isStable v = not (isUnstable v)

  > > > renderVersion . fromBaseVersion $ Version.makeVersion [ 1 , 2 , 3 ]
  Missing version components are set to zero .
  " 0.0.0 "
  > > > renderVersion . fromBaseVersion $ Version.makeVersion [ 1 ]
  > > > renderVersion . fromBaseVersion $ Version.makeVersion [ 1 , 2 ]
  > > > renderVersion . fromBaseVersion $ Version.makeVersion [ 1 , 2 , 3 , 4 ]
  " 0.0.0 "
fromBaseVersion :: Version.Version -> Version
fromBaseVersion v = case Version.versionBranch v of
  (m : n : p : _) -> mkV (fromIntegral m) (fromIntegral n) (fromIntegral p)
  (m : n : _) -> mkV (fromIntegral m) (fromIntegral n) 0
  (m : _) -> mkV (fromIntegral m) 0 0
  _ -> mkV 0 0 0

toBaseVersion :: Version -> Version.Version
toBaseVersion v =
  Version.Version
    (fmap fromIntegral [versionMajor v, versionMinor v, versionPatch v])
    ( mconcat
        [ fmap renderPreRelease (versionPreReleases v),
          fmap renderBuild (versionBuilds v)
        ]
    )

  > > >  < $ > parseVersion " 0.0.0 "
  Just ( Version { versionMajor = 1 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
  The minor and patch numbers are reset to zero .
  > > >  < $ > parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 2 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
  > > >  < $ > parseVersion " 0.0.0 - pre+build "
  Just ( Version { versionMajor = 1 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
bumpMajor :: Version -> Version
bumpMajor v = makeVersion (versionMajor v + 1) 0 0 [] []

  > > > bumpMinor < $ > parseVersion " 0.0.0 "
  Just ( Version { versionMajor = 0 , versionMinor = 1 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
  The patch number is reset to zero .
  Just ( Version { versionMajor = 1 , versionMinor = 3 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
  Just ( Version { versionMajor = 0 , versionMinor = 1 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } )
bumpMinor :: Version -> Version
bumpMinor v = makeVersion (versionMajor v) (versionMinor v + 1) 0 [] []

  > > > bumpPatch < $ > parseVersion " 0.0.0 "
  Just ( Version { versionMajor = 0 ,  = 0 , versionPatch = 1 , versionPreReleases = [ ] ,  = [ ] } )
  > > > bumpPatch < $ > parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 4 , versionPreReleases = [ ] ,  = [ ] } )
  > > > bumpPatch < $ > parseVersion " 0.0.0 - pre+build "
  Just ( Version { versionMajor = 0 ,  = 0 , versionPatch = 1 , versionPreReleases = [ ] ,  = [ ] } )
bumpPatch :: Version -> Version
bumpPatch v =
  makeVersion (versionMajor v) (versionMinor v) (versionPatch v + 1) [] []

  > > > satisfiesConstraint < $ > parseConstraint " > 1.2.0 " < * > parseVersion " 1.2.3 "
satisfiesConstraint :: Constraint -> Version -> Bool
satisfiesConstraint c = satisfiesSC (toSC c)

  > > > set majorLens 4 < $ > parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 4 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
majorLens ::
  (Functor f) => (Word.Word64 -> f Word.Word64) -> Version -> f Version
majorLens f v = fmap (\m -> v {versionMajor = m}) (f (versionMajor v))

  Just 2
  Just ( Version { versionMajor = 1 , versionMinor = 4 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
minorLens ::
  (Functor f) => (Word.Word64 -> f Word.Word64) -> Version -> f Version
minorLens f v = fmap (\n -> v {versionMinor = n}) (f (versionMinor v))

  Just 3
  > > > set patchLens 4 < $ > parseVersion " 1.2.3 "
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 4 , versionPreReleases = [ ] ,  = [ ] } )
patchLens ::
  (Functor f) => (Word.Word64 -> f Word.Word64) -> Version -> f Version
patchLens f v = fmap (\p -> v {versionPatch = p}) (f (versionPatch v))

  Just [ PreReleaseTextual " pre",PreReleaseNumeric 4 ]
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
preReleasesLens ::
  (Functor f) => ([PreRelease] -> f [PreRelease]) -> Version -> f Version
preReleasesLens f v =
  fmap (\ps -> v {versionPreReleases = ps}) (f (versionPreReleases v))

  > > > set buildsLens [ ] < $ > parseVersion " 1.2.3+build "
  Just ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } )
buildsLens :: (Functor f) => ([Build] -> f [Build]) -> Version -> f Version
buildsLens f v = fmap (\bs -> v {versionBuilds = bs}) (f (versionBuilds v))



data Operator
  = OperatorLT
  | OperatorLE
  | OperatorEQ
  | OperatorGE
  | OperatorGT
  | OperatorTilde
  | OperatorCaret
  deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)

data Wildcard
  = WildcardMajor
  | WildcardMinor Word.Word64
  | WildcardPatch Word.Word64 Word.Word64
  deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)


  Just ( ConstraintOperator OperatorLT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " < 1.2.3 "
  Just ( ConstraintOperator OperatorLT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintLT :: Version -> Constraint
constraintLT = ConstraintOperator OperatorLT

  Just ( ConstraintOperator OperatorLE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " < = 1.2.3 "
  Just ( ConstraintOperator OperatorLE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintLE :: Version -> Constraint
constraintLE = ConstraintOperator OperatorLE

  > > > constraintEQ < $ > parseVersion " 1.2.3 "
  Just ( ConstraintOperator OperatorEQ ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " = 1.2.3 "
  Just ( ConstraintOperator OperatorEQ ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintEQ :: Version -> Constraint
constraintEQ = ConstraintOperator OperatorEQ

  Just ( ConstraintOperator OperatorGE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " > = 1.2.3 "
  Just ( ConstraintOperator OperatorGE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintGE :: Version -> Constraint
constraintGE = ConstraintOperator OperatorGE

  Just ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " > 1.2.3 "
  Just ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintGT :: Version -> Constraint
constraintGT = ConstraintOperator OperatorGT

  > > > constraintAnd < $ > ( constraintGE < $ > parseVersion " 1.2.3 " ) < * > ( constraintLT < $ > parseVersion " 2.0.0 " )
  Just ( ConstraintAnd ( ConstraintOperator OperatorGE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) ( ConstraintOperator OperatorLT ( Version { versionMajor = 2 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } ) ) )
  > > > parseConstraint " > = 1.2.3 < 2.0.0 "
  Just ( ConstraintAnd ( ConstraintOperator OperatorGE ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) ( ConstraintOperator OperatorLT ( Version { versionMajor = 2 , versionMinor = 0 , versionPatch = 0 , versionPreReleases = [ ] ,  = [ ] } ) ) )
constraintAnd :: Constraint -> Constraint -> Constraint
constraintAnd = ConstraintAnd

  > > > constraintOr < $ > ( constraintEQ < $ > parseVersion " 1.2.3 " ) < * > ( constraintGT < $ > parseVersion " 1.2.3 " )
  Just ( ConstraintOr ( ConstraintOperator OperatorEQ ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) )
  > > > parseConstraint " = 1.2.3 || > 1.2.3 "
  Just ( ConstraintOr ( ConstraintOperator OperatorEQ ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) ( ConstraintOperator OperatorGT ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ) )
constraintOr :: Constraint -> Constraint -> Constraint
constraintOr = ConstraintOr

  Just ( ConstraintHyphen ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ( Version { versionMajor = 2 ,  = 3 , versionPatch = 4 , versionPreReleases = [ ] ,  = [ ] } ) )
  > > > parseConstraint " 1.2.3 - 2.3.4 "
  Just ( ConstraintHyphen ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) ( Version { versionMajor = 2 ,  = 3 , versionPatch = 4 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintHyphen :: Version -> Version -> Constraint
constraintHyphen = ConstraintHyphen

  > > >  < $ > parseVersion " 1.2.3 "
  Just ( ConstraintOperator OperatorTilde ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  Just ( ConstraintOperator OperatorTilde ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintTilde :: Version -> Constraint
constraintTilde = ConstraintOperator OperatorTilde

  left - most non - zero version number .
  > > >  < $ > parseVersion " 1.2.3 "
  Just ( ConstraintOperator OperatorCaret ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
  Just ( ConstraintOperator OperatorCaret ( Version { versionMajor = 1 , versionMinor = 2 , versionPatch = 3 , versionPreReleases = [ ] ,  = [ ] } ) )
constraintCaret :: Version -> Constraint
constraintCaret = ConstraintOperator OperatorCaret


versionP :: ReadP.ReadP Version
versionP = do
  major <- numberP
  Monad.void (ReadP.char '.')
  minor <- numberP
  Monad.void (ReadP.char '.')
  patch <- numberP
  preReleases <- preReleasesP
  makeVersion major minor patch preReleases <$> buildsP

preReleasesP :: ReadP.ReadP [PreRelease]
preReleasesP =
  ReadP.option
    []
    ( do
        Monad.void (ReadP.char '-')
        ReadP.sepBy1 preReleaseP (ReadP.char '.')
    )

preReleaseP :: ReadP.ReadP PreRelease
preReleaseP = preReleaseNumberP ReadP.<++ preReleaseStringP

preReleaseNumberP :: ReadP.ReadP PreRelease
preReleaseNumberP = PreReleaseNumeric <$> numberP

preReleaseStringP :: ReadP.ReadP PreRelease
preReleaseStringP = do
  s <- ReadP.munch1 isIdentifier
  if all Char.isDigit s then ReadP.pfail else pure (PreReleaseTextual s)

buildsP :: ReadP.ReadP [Build]
buildsP =
  ReadP.option
    []
    ( do
        Monad.void (ReadP.char '+')
        ReadP.sepBy1 buildP (ReadP.char '.')
    )

buildP :: ReadP.ReadP Build
buildP = do
  b <- ReadP.munch1 isIdentifier
  pure (Build b)

numberP :: ReadP.ReadP Word.Word64
numberP = zeroP ReadP.<++ nonZeroP

zeroP :: ReadP.ReadP Word.Word64
zeroP = do
  Monad.void (ReadP.char '0')
  pure 0

nonZeroP :: ReadP.ReadP Word.Word64
nonZeroP = do
  x <- ReadP.satisfy isAsciiDigitNonZero
  ys <- ReadP.munch Char.isDigit
  maybe ReadP.pfail pure . toWord64 . stringToIntegral $ x : ys

constraintsP :: ReadP.ReadP Constraint
constraintsP = do
  spacesP
  cs <- ReadP.sepBy1 constraintP orP
  spacesP
  pure (foldr1 constraintOr cs)

constraintP :: ReadP.ReadP Constraint
constraintP = do
  cs <- ReadP.sepBy1 simpleP spaces1P
  pure (foldr1 constraintAnd cs)

hyphenatedP :: ReadP.ReadP Constraint
hyphenatedP = do
  v <- versionP
  hyphenP
  constraintHyphen v <$> versionP

simpleP :: ReadP.ReadP Constraint
simpleP = ReadP.choice [hyphenatedP, wildcardConstraintP, primitiveP]

wildcardConstraintP :: ReadP.ReadP Constraint
wildcardConstraintP = do
  ReadP.optional (ReadP.char '=')
  ConstraintWildcard <$> wildcardP

wildcardP :: ReadP.ReadP Wildcard
wildcardP = ReadP.choice [wildcardPatchP, wildcardMinorP, wildcardMajorP]

wildcardPatchP :: ReadP.ReadP Wildcard
wildcardPatchP = do
  m <- numberP
  Monad.void (ReadP.char '.')
  n <- numberP
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  pure (WildcardPatch m n)

wildcardMinorP :: ReadP.ReadP Wildcard
wildcardMinorP = do
  m <- numberP
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  pure (WildcardMinor m)

wildcardMajorP :: ReadP.ReadP Wildcard
wildcardMajorP = do
  Monad.void (ReadP.satisfy isWildcard)
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  Monad.void (ReadP.char '.')
  Monad.void (ReadP.satisfy isWildcard)
  pure WildcardMajor

primitiveP :: ReadP.ReadP Constraint
primitiveP = do
  o <- operatorP
  spacesP
  ConstraintOperator o <$> versionP

operatorP :: ReadP.ReadP Operator
operatorP =
  ReadP.choice
    [ ReadP.string "<=" >> pure OperatorLE,
      ReadP.string ">=" >> pure OperatorGE,
      ReadP.char '<' >> pure OperatorLT,
      ReadP.char '>' >> pure OperatorGT,
      ReadP.char '=' >> pure OperatorEQ,
      ReadP.char '^' >> pure OperatorCaret,
      ReadP.char '~' >> pure OperatorTilde,
      pure OperatorEQ
    ]

hyphenP :: ReadP.ReadP ()
hyphenP = do
  spaces1P
  Monad.void (ReadP.char '-')
  spaces1P

orP :: ReadP.ReadP ()
orP = do
  spaces1P
  Monad.void (ReadP.string "||")
  spaces1P

spaces1P :: ReadP.ReadP ()
spaces1P = Monad.void (ReadP.munch1 (== ' '))

spacesP :: ReadP.ReadP ()
spacesP = Monad.void (ReadP.munch (== ' '))


renderPreReleases :: [PreRelease] -> String
renderPreReleases ps =
  if null ps then "" else '-' : List.intercalate "." (fmap renderPreRelease ps)

renderBuilds :: [Build] -> String
renderBuilds bs =
  if null bs then "" else '+' : List.intercalate "." (fmap renderBuild bs)


both :: (a -> b) -> (a, a) -> (b, b)
both f (x, y) = (f x, f y)

isAsciiDigitNonZero :: Char -> Bool
isAsciiDigitNonZero c = Char.isDigit c && (c /= '0')

isIdentifier :: Char -> Bool
isIdentifier c = (Char.isAscii c && Char.isAlphaNum c) || (c == '-')

isWildcard :: Char -> Bool
isWildcard c = (c == 'x') || (c == '*') || (c == 'X')

parse :: ReadP.ReadP a -> String -> Maybe a
parse p s =
  let p' = ReadP.readP_to_S p
   in Maybe.listToMaybe
        ( do
            (x, "") <- p' s
            pure x
        )

stringToIntegral :: (Integral a) => String -> a
stringToIntegral =
  foldl (\n d -> (n * 10) + (fromIntegral (fromEnum d) - 48)) 0

toWord64 :: Integer -> Maybe Word.Word64
toWord64 n
  | n < 0 = Nothing
  | n > fromIntegral (maxBound :: Word.Word64) = Nothing
  | otherwise = Just (fromIntegral n)


  | Simple constraints are just as expressive as ' Constraint 's , but they are
  ' Constraint 's .
data SimpleConstraint
  = SCLT Version
  | SCEQ Version
  | SCGT Version
  | SCAnd SimpleConstraint SimpleConstraint
  | SCOr SimpleConstraint SimpleConstraint
  deriving (Data.Data, Eq, Generics.Generic, Ord, Read, Show)

mkV :: Word.Word64 -> Word.Word64 -> Word.Word64 -> Version
mkV m n p = makeVersion m n p [] []

satisfiesSC :: SimpleConstraint -> Version -> Bool
satisfiesSC c v = case c of
  SCLT u -> v < u
  SCEQ u -> compare v u == EQ
  SCGT u -> v > u
  SCAnd l r -> satisfiesSC l v && satisfiesSC r v
  SCOr l r -> satisfiesSC l v || satisfiesSC r v

scLE :: Version -> SimpleConstraint
scLE v = SCOr (SCLT v) (SCEQ v)

scGE :: Version -> SimpleConstraint
scGE v = SCOr (SCGT v) (SCEQ v)

toSC :: Constraint -> SimpleConstraint
toSC c = case c of
  ConstraintOperator o v -> case o of
    OperatorLT -> SCLT v
    OperatorLE -> scLE v
    OperatorEQ -> SCEQ v
    OperatorGE -> scGE v
    OperatorGT -> SCGT v
    OperatorTilde ->
      SCAnd (scGE v) (SCLT (mkV (versionMajor v) (versionMinor v + 1) 0))
    OperatorCaret ->
      SCAnd
        (scGE v)
        ( SCLT
            ( case (versionMajor v, versionMinor v, versionPatch v) of
                (0, 0, p) -> mkV 0 0 (p + 1)
                (0, n, _) -> mkV 0 (n + 1) 0
                (m, _, _) -> mkV (m + 1) 0 0
            )
        )
  ConstraintHyphen l h -> SCAnd (scGE l) (scLE h)
  ConstraintWildcard w -> case w of
    WildcardMajor -> scGE initialVersion
    WildcardMinor m -> SCAnd (scGE (mkV m 0 0)) (SCLT (mkV (m + 1) 0 0))
    WildcardPatch m n -> SCAnd (scGE (mkV m n 0)) (SCLT (mkV m (n + 1) 0))
  ConstraintAnd l r -> SCAnd (toSC l) (toSC r)
  ConstraintOr l r -> SCOr (toSC l) (toSC r)
