hackport-0.6.6: Portage/Version.hs
{-# LANGUAGE CPP #-}
{-|
Author : Andres Loeh <kosmikus@gentoo.org>
Stability : provisional
Portability : haskell98
Version parser, according to Portage spec.
Shamelessly borrowed from exi, ported from Parsec to ReadP
-}
module Portage.Version (
Version(..),
Suffix(..),
fromCabalVersion,
toCabalVersion,
is_live
) where
import qualified Distribution.Version as Cabal
import Distribution.Pretty (Pretty(..))
import Distribution.Parsec (Parsec(..))
import qualified Distribution.Compat.CharParsing as P
import qualified Text.PrettyPrint as Disp
import Text.PrettyPrint ((<>))
import qualified Data.List.NonEmpty as NE
#if MIN_VERSION_base(4,11,0)
import Prelude hiding ((<>))
#endif
-- | Portage-style version type.
data Version = Version { versionNumber :: [Int] -- ^ @[1,42,3]@ ~= 1.42.3
, versionChar :: (Maybe Char) -- ^ optional letter
, versionSuffix :: [Suffix]
, versionRevision :: Int -- ^ revision, 0 means none
}
deriving (Eq, Ord, Show, Read)
-- | Prints a valid Portage 'Version' string.
instance Pretty Version where
pretty (Version ver c suf rev) =
dispVer ver <> dispC c <> dispSuf suf <> dispRev rev
where
dispVer = Disp.hcat . Disp.punctuate (Disp.char '.') . map Disp.int
dispC = maybe Disp.empty Disp.char
dispSuf = Disp.hcat . map pretty
dispRev 0 = Disp.empty
dispRev n = Disp.text "-r" <> Disp.int n
-- | 'Version' parser using 'Parsec'.
instance Parsec Version where
parsec = do
ver <- P.sepByNonEmpty digits (P.char '.')
c <- P.optional P.lower
suf <- P.many parsec
rev <- P.option 0 $ P.string "-r" *> digits
return $ Version (NE.toList ver) c suf rev
-- | Check if the ebuild is a live ebuild, i.e. if its 'Version' is @9999@.
--
-- foo-9999* is treated as live ebuild
-- Cabal-1.17.9999* as well
--
-- >>> let (c,s,r) = (Nothing,[],0)
-- >>> is_live (Version [1,0,0] c s r)
-- False
-- >>> is_live (Version [999] c s r)
-- False
-- >>> is_live (Version [1,0,0,9999] c s r)
-- True
-- >>> is_live (Version [9999] c s r)
-- True
--
-- $
-- prop> \verNum char rev -> is_live (Version verNum char [] rev) == if length verNum >= 1 && last verNum >= 9999 then True else False
is_live :: Version -> Bool
is_live v =
case vs of
-- nonempty
(_:_) | many_nines (last vs) -> True
_ -> False
where vs = versionNumber v
many_nines n = is_big n && all_nines n
is_big n = n >= 9999
all_nines n = (all (== '9') . show) n
-- | Various allowed suffixes in Portage versions.
data Suffix = Alpha Int | Beta Int | Pre Int | RC Int | P Int
deriving (Eq, Ord, Show, Read)
instance Pretty Suffix where
pretty suf = case suf of
Alpha n -> Disp.text "_alpha" <> dispPos n
Beta n -> Disp.text "_beta" <> dispPos n
Pre n -> Disp.text "_pre" <> dispPos n
RC n -> Disp.text "_rc" <> dispPos n
P n -> Disp.text "_p" <> dispPos n
where
dispPos :: Int -> Disp.Doc
dispPos 0 = Disp.empty
dispPos n = Disp.int n
instance Parsec Suffix where
parsec = P.char '_'
*> P.choice
[ P.string "alpha" >> fmap Alpha maybeDigits
, P.string "beta" >> fmap Beta maybeDigits
, P.try (P.string "pre") >> fmap Pre maybeDigits
, P.string "rc" >> fmap RC maybeDigits
, P.string "p" >> fmap P maybeDigits
]
where
maybeDigits = P.option 0 digits
-- | Convert from a 'Cabal.Version' to a Portage 'Version'.
--
-- prop> \verNum -> fromCabalVersion (Cabal.mkVersion verNum) == Version verNum Nothing [] 0
fromCabalVersion :: Cabal.Version -> Version
fromCabalVersion cabal_version = Version (Cabal.versionNumbers cabal_version) Nothing [] 0
-- | Convert from a Portage 'Version' to a 'Cabal.Version'.
-- $
-- prop> \verNum char rev -> toCabalVersion (Version verNum char [] rev) == if char == Nothing then Just (Cabal.mkVersion verNum) else Nothing
toCabalVersion :: Version -> Maybe Cabal.Version
toCabalVersion (Version nums Nothing [] _) = Just (Cabal.mkVersion nums)
toCabalVersion _ = Nothing
-- | Parser which munches digits.
digits :: P.CharParsing m => m Int
digits = read <$> P.some P.digit