packages feed

phino-0.0.0.50: src/Must.hs

-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT

module Must (Must (..), inRange, exceedsUpperBound, validateMust) where

import Data.List (isInfixOf)
import Text.Read (readMaybe)
import Text.Printf (printf)

data Must
  = MtDisabled
  | MtExact Integer
  | MtRange (Maybe Integer) (Maybe Integer)
  deriving (Eq)

instance Show Must where
  show MtDisabled = "0"
  show (MtExact n) = show n
  show (MtRange Nothing Nothing) = ".."
  show (MtRange Nothing (Just max)) = ".." ++ show max
  show (MtRange (Just min) Nothing) = show min ++ ".."
  show (MtRange (Just min) (Just max)) = show min ++ ".." ++ show max

instance Read Must where
  readsPrec _ "0" = [(MtDisabled, "")]
  readsPrec _ s
    | ".." `isInfixOf` s = parseRange s
    | otherwise = parseExact s
    where
      parseRange :: String -> [(Must, String)]
      parseRange str = case break (== '.') str of
        (minStr, '.' : '.' : maxStr) ->
          let minPart = if null minStr then Nothing else readMaybe minStr
              maxPart = if null maxStr then Nothing else readMaybe maxStr
           in case (minPart, maxPart, null minStr, null maxStr) of
                (Nothing, Nothing, False, False) -> [] -- Invalid range: non-numeric values
                (Nothing, Nothing, True, True) -> [] -- Invalid range: empty range '..'
                (Nothing, Just max, True, False) ->
                  [(MtRange Nothing (Just max), "") | max >= 0]
                (Just min, Nothing, False, True) ->
                  [(MtRange (Just min) Nothing, "") | min >= 0]
                (Just min, Just max, False, False) ->
                  [(MtRange (Just min) (Just max), "") | min >= 0 && max >= 0 && min <= max]
                _ -> [] -- Invalid range format
        _ -> [] -- Invalid range: expected format like '3..5', '3..', or '..5'
      parseExact :: String -> [(Must, String)]
      parseExact str = case readMaybe str of
        Just n | n >= 0 -> [(if n == 0 then MtDisabled else MtExact n, "")]
        Just _ -> [] -- Invalid value: must be non-negative
        Nothing -> [] -- Invalid value: expected integer

inRange :: Must -> Integer -> Bool
inRange MtDisabled _ = True
inRange (MtExact expected) actual = actual == expected
inRange (MtRange minVal maxVal) actual =
  checkMin && checkMax
  where
    checkMin = maybe True (<= actual) minVal
    checkMax = maybe True (>= actual) maxVal

-- | Check if a value exceeds the upper bound of the range
exceedsUpperBound :: Must -> Integer -> Bool
exceedsUpperBound MtDisabled _ = False
exceedsUpperBound (MtExact n) current = current > n
exceedsUpperBound (MtRange _ (Just max)) current = current > max
exceedsUpperBound (MtRange _ Nothing) _ = False

validateMust :: Must -> Maybe String
validateMust MtDisabled = Nothing
validateMust (MtExact n) | n <= 0 = Just "--must exact value must be positive"
validateMust (MtRange (Just minVal) _) | minVal < 0 = Just "--must minimum must be non-negative"
validateMust (MtRange _ (Just maxVal)) | maxVal < 0 = Just "--must maximum must be non-negative"
validateMust (MtRange Nothing _) = Nothing
validateMust (MtRange _ Nothing) = Nothing
validateMust (MtRange (Just min) (Just max))
  | min > max = Just (printf "--must range invalid: minimum (%d) is greater than maximum (%d)" min max)
  | otherwise = Nothing
validateMust _ = Nothing