packages feed

dojang-0.2.0: src/Dojang/Types/EnvironmentPredicate/Evaluate.hs

{-# LANGUAGE OverloadedRecordDot #-}

module Dojang.Types.EnvironmentPredicate.Evaluate
  ( EvaluationWarning (..)
  , evaluate
  , evaluate'
  ) where

import Prelude hiding (length, lookup, take)

import Data.CaseInsensitive (mk, original)
import Data.HashMap.Strict (lookup)
import Data.Text (Text, length, take, takeEnd)

import Dojang.Types.Environment
  ( Architecture (Etc)
  , Environment (..)
  , Kernel (..)
  , OperatingSystem (OtherOS)
  )
import Dojang.Types.EnvironmentPredicate (EnvironmentPredicate (..))
import Dojang.Types.MonikerMap (MonikerMap, MonikerResolver)
import Dojang.Types.MonikerName (MonikerName)


-- $setup
-- >>> :seti -XOverloadedStrings
-- >>> :seti -XOverloadedLists


-- | A warning that occurred during evaluation.
data EvaluationWarning
  = -- | A moniker was referenced that was not defined.
    UndefinedMoniker MonikerName
  | -- | The operating system was not recognized.
    UnrecognizedOperatingSystem OperatingSystem
  | -- | The architecture was not recognized.
    UnrecognizedArchitecture Architecture
  deriving (Eq, Show)


-- | Evaluates the given predicate against the given environment.
--
-- >>> import Dojang.Types.Environment (Environment (..))
-- >>> import Dojang.Types.MonikerMap (MonikerMap)
-- >>> import Dojang.Types.MonikerName (MonikerName, parseMonikerName)
-- >>> let (Right linuxAmd64) = parseMonikerName "linux-amd64"
-- >>> :{
-- let monikerMap =
--       [ (linuxAmd64, And [OperatingSystem "linux", Architecture "x86_64"])
--       ] :: MonikerMap
-- :}
--
-- >>> let environment = Environment "linux" "x86_64"
-- >>> evaluate environment monikerMap (OperatingSystem "linux")
-- (True,[])
-- >>> evaluate environment monikerMap (Architecture "aarch64")
-- (False,[])
--
-- The 'snd' of the result is a list of warnings that occurred during
-- evaluation:
--
-- >>> let (Right nonExistentMoniker) = parseMonikerName "non-existent"
-- >>> evaluate environment monikerMap (Moniker nonExistentMoniker)
-- (False,[UndefinedMoniker (MonikerName "non-existent")])
evaluate
  :: Environment
  -- ^ The 'Environment' to evaluate against.
  -> MonikerMap
  -- ^ The 'MonikerMap' to resolve 'MonikerName's against.
  -> EnvironmentPredicate
  -- ^ The 'EnvironmentPredicate' to evaluate.
  -> (Bool, [EvaluationWarning])
  -- ^ The result of the evaluation. The 'snd' of the result is a list of
  -- warnings that occurred during evaluation.  Note that warnings can be
  -- present even if the result is 'True'.
evaluate environment monikerMap =
  evaluate' environment (`lookup` monikerMap)


-- | Same as 'evaluate', but takes a resolver function instead of a
-- 'MonikerMap'.
evaluate'
  :: Environment
  -- ^ The 'Environment' to evaluate against.
  -> MonikerResolver
  -- ^ A function that resolves 'MonikerName's to 'EnvironmentPredicate's.
  -> EnvironmentPredicate
  -- ^ The 'EnvironmentPredicate' to evaluate.
  -> (Bool, [EvaluationWarning])
  -- ^ The result of the evaluation. The 'snd' of the result is a list of
  -- warnings that occurred during evaluation.  Note that warnings can be
  -- present even if the result is 'True'.
evaluate' _ _ Always =
  (True, [])
evaluate' environment resolver (Not predicate) =
  let (result, warnings) = evaluate' environment resolver predicate
  in (not result, warnings)
evaluate' environment resolver (And predicates) =
  let results = fmap (evaluate' environment resolver) predicates
  in (all fst results, concatMap snd results)
evaluate' environment resolver (Or predicates) =
  let results = fmap (evaluate' environment resolver) predicates
  in (any fst results, concatMap snd results)
evaluate' environment resolver (Moniker monikerName) =
  case resolver monikerName of
    Nothing -> (False, [UndefinedMoniker monikerName])
    Just predicate -> evaluate' environment resolver predicate
evaluate' environment _ (OperatingSystem os) =
  ( os == environment.operatingSystem
  , case os of
      OtherOS _ -> [UnrecognizedOperatingSystem os]
      _ -> []
  )
evaluate' environment _ (Architecture arch) =
  ( arch == environment.architecture
  , case arch of
      Etc _ -> [UnrecognizedArchitecture arch]
      _ -> []
  )
evaluate' environment _ (KernelName kernel) =
  (kernel == environment.kernel.name, [])
evaluate' environment _ (KernelRelease ver) =
  (ver == environment.kernel.release, [])
evaluate' environment _ (KernelReleasePrefix prefix) =
  (length rel >= prefixLen && mk (take prefixLen rel) == prefix, [])
 where
  rel :: Text
  rel = original environment.kernel.release
  prefixLen :: Int
  prefixLen = length $ original prefix
evaluate' environment _ (KernelReleaseSuffix suffix) =
  (length rel >= suffixLen && mk (takeEnd suffixLen rel) == suffix, [])
 where
  rel :: Text
  rel = original environment.kernel.release
  suffixLen :: Int
  suffixLen = length $ original suffix