packages feed

seihou-core-0.4.0.0: src/Seihou/Core/Version.hs

module Seihou.Core.Version
  ( Version (..),
    parseVersion,
    renderVersion,
  )
where

import Data.Text qualified as T
import GHC.Generics (Generic)
import Numeric.Natural (Natural)
import Seihou.Prelude

-- | A parsed version consisting of numeric segments (e.g. @[1, 2, 3]@ for "1.2.3").
newtype Version = Version {segments :: [Natural]}
  deriving stock (Show, Generic)

instance Eq Version where
  Version a == Version b =
    let maxLen = max (length a) (length b)
     in pad maxLen a == pad maxLen b

instance Ord Version where
  compare (Version a) (Version b) =
    let maxLen = max (length a) (length b)
     in compare (pad maxLen a) (pad maxLen b)

-- | Pad a list of naturals with trailing zeros to the given length.
pad :: Int -> [Natural] -> [Natural]
pad n xs = xs ++ replicate (n - length xs) 0

-- | Parse a dotted version string like @"1.2.3"@ into a 'Version'.
-- Returns 'Nothing' for empty strings or strings with non-numeric segments.
parseVersion :: Text -> Maybe Version
parseVersion t
  | T.null t = Nothing
  | otherwise =
      let parts = T.splitOn "." t
       in case traverse readNatural parts of
            Just ns@(_ : _) -> Just (Version ns)
            _ -> Nothing

-- | Try to read a 'Natural' from a 'Text' value.
readNatural :: Text -> Maybe Natural
readNatural t = case reads (T.unpack t) of
  [(n, "")] | (n :: Integer) >= 0 -> Just (fromIntegral n)
  _ -> Nothing

-- | Render a 'Version' back to dotted notation.
renderVersion :: Version -> Text
renderVersion (Version ns) = T.intercalate "." (map (T.pack . show) ns)