{- |
Module                  : Iris.Tool
Copyright               : (c) 2022 Dmitrii Kovanikov
SPDX-License-Identifier : MPL-2.0
Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
Stability               : Experimental
Portability             : Portable

Utilities to check required tools and their minimal version for a CLI app.

@since 0.0.0.0
-}

module Iris.Tool
    ( -- * Types describing executable requirements
      Tool (..)
    , ToolSelector (..)
    , defaultToolSelector

      -- * Tool requirements check
    , ToolCheckResult (..)
    , checkTool
    ) where

import Data.String (IsString (..))
import Data.Text (Text)
import System.Directory (findExecutable)
import System.Process (readProcess)

import qualified Data.Text as Text


{- |

@since 0.0.0.0
-}
data Tool cmd = Tool
    { -- | @since 0.0.0.0
      forall cmd. Tool cmd -> Text
toolName     :: Text

      -- | @since 0.0.0.0
    , forall cmd. Tool cmd -> Maybe (ToolSelector cmd)
toolSelector :: Maybe (ToolSelector cmd)
    }

{- |

@since 0.0.0.0
-}
instance IsString (Tool cmd) where
    fromString :: String -> Tool cmd
    fromString :: String -> Tool cmd
fromString String
s = Tool
        { toolName :: Text
toolName     = forall a. IsString a => String -> a
fromString String
s
        , toolSelector :: Maybe (ToolSelector cmd)
toolSelector = forall a. Maybe a
Nothing
        }

{- |

@since 0.0.0.0
-}
data ToolSelector cmd = ToolSelector
    { -- | @since 0.0.0.0
      forall cmd. ToolSelector cmd -> cmd -> Text -> Bool
toolSelectorFunction   :: cmd -> Text -> Bool

      -- | @since 0.0.0.0
    , forall cmd. ToolSelector cmd -> Maybe Text
toolSelectorVersionArg :: Maybe Text
    }

{- |

@since 0.0.0.0
-}
defaultToolSelector :: ToolSelector cmd
defaultToolSelector :: forall cmd. ToolSelector cmd
defaultToolSelector = ToolSelector
    { toolSelectorFunction :: cmd -> Text -> Bool
toolSelectorFunction   = \cmd
_cmd Text
_version -> Bool
True
    , toolSelectorVersionArg :: Maybe Text
toolSelectorVersionArg = forall a. Maybe a
Nothing
    }

{- |

@since 0.0.0.0
-}
data ToolCheckResult
    {- |

    @since 0.0.0.0
    -}
    = ToolNotFound Text

    {- |

    @since 0.0.0.0
    -}
    | ToolWrongVersion Text

    {- |

    @since 0.0.0.0
    -}
    | ToolOk
    deriving stock
        ( Int -> ToolCheckResult -> ShowS
[ToolCheckResult] -> ShowS
ToolCheckResult -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ToolCheckResult] -> ShowS
$cshowList :: [ToolCheckResult] -> ShowS
show :: ToolCheckResult -> String
$cshow :: ToolCheckResult -> String
showsPrec :: Int -> ToolCheckResult -> ShowS
$cshowsPrec :: Int -> ToolCheckResult -> ShowS
Show  -- ^ @since 0.0.0.0
        , ToolCheckResult -> ToolCheckResult -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ToolCheckResult -> ToolCheckResult -> Bool
$c/= :: ToolCheckResult -> ToolCheckResult -> Bool
== :: ToolCheckResult -> ToolCheckResult -> Bool
$c== :: ToolCheckResult -> ToolCheckResult -> Bool
Eq    -- ^ @since 0.0.0.0
        )

{- |

@since 0.0.0.0
-}
checkTool :: cmd -> Tool cmd -> IO ToolCheckResult
checkTool :: forall cmd. cmd -> Tool cmd -> IO ToolCheckResult
checkTool cmd
cmd Tool{Maybe (ToolSelector cmd)
Text
toolSelector :: Maybe (ToolSelector cmd)
toolName :: Text
toolSelector :: forall cmd. Tool cmd -> Maybe (ToolSelector cmd)
toolName :: forall cmd. Tool cmd -> Text
..} = String -> IO (Maybe String)
findExecutable (Text -> String
Text.unpack Text
toolName) forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Maybe String
Nothing  -> forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ Text -> ToolCheckResult
ToolNotFound Text
toolName
    Just String
exe -> case Maybe (ToolSelector cmd)
toolSelector of
        Maybe (ToolSelector cmd)
Nothing               -> forall (f :: * -> *) a. Applicative f => a -> f a
pure ToolCheckResult
ToolOk
        Just ToolSelector{Maybe Text
cmd -> Text -> Bool
toolSelectorVersionArg :: Maybe Text
toolSelectorFunction :: cmd -> Text -> Bool
toolSelectorVersionArg :: forall cmd. ToolSelector cmd -> Maybe Text
toolSelectorFunction :: forall cmd. ToolSelector cmd -> cmd -> Text -> Bool
..} -> case Maybe Text
toolSelectorVersionArg of
            Maybe Text
Nothing         -> forall (f :: * -> *) a. Applicative f => a -> f a
pure ToolCheckResult
ToolOk
            Just Text
versionArg -> do
                String
toolVersionOutput <- String -> [String] -> String -> IO String
readProcess String
exe [Text -> String
Text.unpack Text
versionArg] String
""
                let version :: Text
version = Text -> Text
Text.strip forall a b. (a -> b) -> a -> b
$ String -> Text
Text.pack String
toolVersionOutput

                if cmd -> Text -> Bool
toolSelectorFunction cmd
cmd Text
version
                then forall (f :: * -> *) a. Applicative f => a -> f a
pure ToolCheckResult
ToolOk
                else forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ Text -> ToolCheckResult
ToolWrongVersion Text
version