packages feed

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