module Data.Versions
(
Versioning(..)
, SemVer(..)
, Version(..)
, Mess(..)
, VUnit(..)
, VChunk
, VSep(..)
, VParser(..)
, ParsingError
, semver
, version
, mess
, parseV
, semverP
, versionP
, messP
, semver'
, version'
, mess'
, prettyV
, prettySemVer
, prettyVer
, prettyMess
, parseErrorPretty
, _Versioning
, _SemVer
, _Version
, _Ideal
, _General
, _Complex
, svMajor
, svMinor
, svPatch
, svPreRel
, svMeta
, vEpoch
, vChunks
, vRel
, _Digits
, _Str ) where
import Control.DeepSeq
import Data.Hashable
import Data.List (intersperse)
import Data.Monoid
import Data.Text (Text,pack,snoc)
import GHC.Generics
import Text.Megaparsec
import Text.Megaparsec.Text
data Versioning = Ideal SemVer | General Version | Complex Mess
deriving (Eq,Show,Generic,NFData,Hashable)
instance Ord Versioning where
compare (Ideal s) (Ideal s') = compare s s'
compare (General v) (General v') = compare v v'
compare (Complex m) (Complex m') = compare m m'
compare (Ideal s) (General v) = compare (vFromS s) v
compare (General v) (Ideal s) = opposite $ compare (vFromS s) v
compare (General v) (Complex m) = compare (mFromV v) m
compare (Complex m) (General v) = opposite $ compare (mFromV v) m
compare (Ideal s) m@(Complex _) = compare (General $ vFromS s) m
compare m@(Complex _) (Ideal s) = compare m (General $ vFromS s)
vFromS :: SemVer -> Version
vFromS (SemVer m i p r _) = Version Nothing [[Digits m], [Digits i], [Digits p]] r
mFromV :: Version -> Mess
mFromV (Version e v r) = maybe affix (\a -> VNode [showt a] VColon affix) e
where affix = VNode (chunksAsT v) VHyphen $ VLeaf (chunksAsT r)
_Versioning :: Applicative f => (Versioning -> f Versioning) -> Text -> f Text
_Versioning f t = either (const (pure t)) (fmap prettyV . f) $ parseV t
_SemVer :: Applicative f => (SemVer -> f SemVer) -> Text -> f Text
_SemVer f t = either (const (pure t)) (fmap prettySemVer . f) $ semver t
_Version :: Applicative f => (Version -> f Version) -> Text -> f Text
_Version f t = either (const (pure t)) (fmap prettyVer . f) $ version t
_Ideal :: Applicative f => (SemVer -> f SemVer) -> Versioning -> f Versioning
_Ideal f (Ideal s) = Ideal <$> f s
_Ideal _ v = pure v
_General :: Applicative f => (Version -> f Version) -> Versioning -> f Versioning
_General f (General v) = General <$> f v
_General _ v = pure v
_Complex :: Applicative f => (Mess -> f Mess) -> Versioning -> f Versioning
_Complex f (Complex m) = Complex <$> f m
_Complex _ v = pure v
data SemVer = SemVer { _svMajor :: Int
, _svMinor :: Int
, _svPatch :: Int
, _svPreRel :: [VChunk]
, _svMeta :: [VChunk] } deriving (Show,Generic,NFData,Hashable)
instance Eq SemVer where
(SemVer ma mi pa pr _) == (SemVer ma' mi' pa' pr' _) =
(ma,mi,pa,pr) == (ma',mi',pa',pr')
instance Ord SemVer where
compare (SemVer ma mi pa pr _) (SemVer ma' mi' pa' pr' _) =
case compare (ma,mi,pa) (ma',mi',pa') of
LT -> LT
GT -> GT
EQ -> case (pr,pr') of
([],[]) -> EQ
([],_) -> GT
(_,[]) -> LT
_ -> compare pr pr'
instance Monoid SemVer where
mempty = SemVer 0 0 0 [] []
SemVer mj mn pa p m `mappend` SemVer mj' mn' pa' p' m' =
SemVer (mj + mj') (mn + mn') (pa + pa') (p ++ p') (m ++ m')
svMajor :: Functor f => (Int -> f Int) -> SemVer -> f SemVer
svMajor f sv = fmap (\ma -> sv { _svMajor = ma }) (f $ _svMajor sv)
svMinor :: Functor f => (Int -> f Int) -> SemVer -> f SemVer
svMinor f sv = fmap (\mi -> sv { _svMinor = mi }) (f $ _svMinor sv)
svPatch :: Functor f => (Int -> f Int) -> SemVer -> f SemVer
svPatch f sv = fmap (\pa -> sv { _svPatch = pa }) (f $ _svPatch sv)
svPreRel :: Functor f => ([VChunk] -> f [VChunk]) -> SemVer -> f SemVer
svPreRel f sv = fmap (\pa -> sv { _svPreRel = pa }) (f $ _svPreRel sv)
svMeta :: Functor f => ([VChunk] -> f [VChunk]) -> SemVer -> f SemVer
svMeta f sv = fmap (\pa -> sv { _svMeta = pa }) (f $ _svMeta sv)
data VUnit = Digits Int | Str Text deriving (Eq,Show,Read,Ord,Generic,NFData,Hashable)
_Digits :: Applicative f => (Int -> f Int) -> VUnit -> f VUnit
_Digits f (Digits i) = Digits <$> f i
_Digits _ v = pure v
_Str :: Applicative f => (Text -> f Text) -> VUnit -> f VUnit
_Str f (Str t) = Str <$> f t
_Str _ v = pure v
type VChunk = [VUnit]
data Version = Version { _vEpoch :: Maybe Int
, _vChunks :: [VChunk]
, _vRel :: [VChunk] } deriving (Eq,Show,Generic,NFData,Hashable)
wipe :: Version -> Version
wipe v = v { _vEpoch = Nothing }
instance Ord Version where
compare (Version _ [] []) (Version _ [] []) = EQ
compare v0@(Version (Just 0) _ _) v1@(Version Nothing _ _) = compare (wipe v0) v1
compare v0@(Version Nothing _ _) v1@(Version (Just 0) _ _) = compare v0 (wipe v1)
compare (Version (Just _) _ _) (Version Nothing _ _) = GT
compare (Version Nothing _ _) (Version (Just _) _ _) = LT
compare v0@(Version (Just n) _ _) v1@(Version (Just m) _ _) | n == m = compare (wipe v0) (wipe v1)
| otherwise = compare n m
compare (Version _ [] rs) (Version _ [] rs') = compare (Version Nothing rs []) (Version Nothing rs' [])
compare (Version _ _ _) (Version _ [] _) = GT
compare (Version _ [] _) (Version _ _ _) = LT
compare (Version _ (a:as) rs) (Version _ (b:bs) rs') = case f a b of
EQ -> compare (Version Nothing as rs) (Version Nothing bs rs')
res -> res
where f [] [] = EQ
f [] _ = GT
f _ [] = LT
f (Digits n:ns) (Digits m:ms) | n > m = GT
| n < m = LT
| otherwise = f ns ms
f (Str n:ns) (Str m:ms) | n > m = GT
| n < m = LT
| otherwise = f ns ms
f (Digits _ :_) (Str _ :_) = GT
f (Str _ :_ ) (Digits _ :_) = LT
vEpoch :: Functor f => (Maybe Int -> f (Maybe Int)) -> Version -> f Version
vEpoch f v = fmap (\ve -> v { _vEpoch = ve }) (f $ _vEpoch v)
vChunks :: Functor f => ([VChunk] -> f [VChunk]) -> Version -> f Version
vChunks f v = fmap (\vc -> v { _vChunks = vc }) (f $ _vChunks v)
vRel :: Functor f => ([VChunk] -> f [VChunk]) -> Version -> f Version
vRel f v = fmap (\vr -> v { _vRel = vr }) (f $ _vRel v)
data Mess = VLeaf [Text] | VNode [Text] VSep Mess deriving (Eq,Show,Generic,NFData,Hashable)
instance Ord Mess where
compare (VLeaf l1) (VLeaf l2) = compare l1 l2
compare (VNode t1 _ _) (VLeaf t2) = compare t1 t2
compare (VLeaf t1) (VNode t2 _ _) = compare t1 t2
compare (VNode t1 _ v1) (VNode t2 _ v2) | t1 < t2 = LT
| t1 > t2 = GT
| otherwise = compare v1 v2
data VSep = VColon | VHyphen | VPlus | VUnder deriving (Eq,Show,Generic,NFData,Hashable)
type ParsingError = ParseError (Token Text) Dec
newtype VParser = VParser { runVP :: Text -> Either ParsingError Versioning }
instance Monoid VParser where
mempty = VParser $ \_ -> Ideal <$> semver ""
(VParser f) `mappend` (VParser g) = VParser h
where h t = either (const (g t)) Right $ f t
parseV :: Text -> Either ParsingError Versioning
parseV = runVP $ semverP <> versionP <> messP
semverP :: VParser
semverP = VParser $ fmap Ideal . semver
semver :: Text -> Either ParsingError SemVer
semver = parse (semver' <* eof) "Semantic Version"
semver' :: Parser SemVer
semver' = SemVer <$> major <*> minor <*> patch <*> preRel <*> metaData
digits :: Parser Int
digits = read <$> (string "0" <|> some digitChar)
major :: Parser Int
major = digits <* char '.'
minor :: Parser Int
minor = major
patch :: Parser Int
patch = digits
preRel :: Parser [VChunk]
preRel = (char '-' *> chunks) <|> pure []
metaData :: Parser [VChunk]
metaData = (char '+' *> chunks) <|> pure []
chunks :: Parser [VChunk]
chunks = chunk `sepBy` char '.'
chunk :: Parser VChunk
chunk = try zeroWithLetters <|> oneZero <|> many (iunit <|> sunit)
where oneZero = (:[]) . Digits . read <$> string "0"
zeroWithLetters = do
z <- Digits . read <$> string "0"
s <- some sunit
c <- chunk
pure $ (z : s) ++ c
iunit :: Parser VUnit
iunit = Digits . read <$> some digitChar
sunit :: Parser VUnit
sunit = Str . pack <$> some letterChar
versionP :: VParser
versionP = VParser $ fmap General . version
version :: Text -> Either ParsingError Version
version = parse (version' <* eof) "Version"
version' :: Parser Version
version' = Version <$> optional (try epoch) <*> chunks <*> preRel
epoch :: Parser Int
epoch = read <$> (some digitChar <* char ':')
messP :: VParser
messP = VParser $ fmap Complex . mess
mess :: Text -> Either ParsingError Mess
mess = parse (mess' <* eof) "Mess"
mess' :: Parser Mess
mess' = try node <|> leaf
leaf :: Parser Mess
leaf = VLeaf <$> tchunks
node :: Parser Mess
node = VNode <$> tchunks <*> sep <*> mess'
tchunks :: Parser [Text]
tchunks = (pack <$> some (letterChar <|> digitChar)) `sepBy` char '.'
sep :: Parser VSep
sep = choice [ VColon <$ char ':'
, VHyphen <$ char '-'
, VPlus <$ char '+'
, VUnder <$ char '_' ]
sepCh :: VSep -> Char
sepCh VColon = ':'
sepCh VHyphen = '-'
sepCh VPlus = '+'
sepCh VUnder = '_'
prettyV :: Versioning -> Text
prettyV (Ideal sv) = prettySemVer sv
prettyV (General v) = prettyVer v
prettyV (Complex m) = prettyMess m
prettySemVer :: SemVer -> Text
prettySemVer (SemVer ma mi pa pr me) = mconcat $ ver <> pr' <> me'
where ver = intersperse "." [ showt ma, showt mi, showt pa ]
pr' = foldable [] ("-" :) $ intersperse "." (chunksAsT pr)
me' = foldable [] ("+" :) $ intersperse "." (chunksAsT me)
prettyVer :: Version -> Text
prettyVer (Version ep cs pr) = ep' <> mconcat (ver <> pr')
where ver = intersperse "." $ chunksAsT cs
pr' = foldable [] ("-" :) $ intersperse "." (chunksAsT pr)
ep' = maybe "" (\e -> showt e <> ":") ep
prettyMess :: Mess -> Text
prettyMess (VLeaf t) = mconcat $ intersperse "." t
prettyMess (VNode t s v) = snoc t' (sepCh s) <> prettyMess v
where t' = mconcat $ intersperse "." t
chunksAsT :: [VChunk] -> [Text]
chunksAsT = map (mconcat . map f)
where f (Digits i) = showt i
f (Str s) = s
foldable :: Foldable f => f b -> (f a -> f b) -> f a -> f b
foldable d g f | null f = d
| otherwise = g f
opposite :: Ordering -> Ordering
opposite EQ = EQ
opposite LT = GT
opposite GT = LT
showt :: Show a => a -> Text
showt = pack . show