seihou-core-0.4.0.0: src/Seihou/Core/Context.hs
module Seihou.Core.Context
( resolveContext,
validateContextName,
)
where
import Control.Applicative ((<|>))
import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Data.Text.IO qualified as TIO
import Seihou.Prelude
import System.Directory (XdgDirectory (..), doesFileExist, getCurrentDirectory, getXdgDirectory)
-- | Resolve the active context name from multiple sources in precedence order:
--
-- 1. CLI flag (@--context@)
-- 2. @SEIHOU_CONTEXT@ environment variable
-- 3. Project file @.seihou\/context@ (plain text, single line)
-- 4. Global default @~\/.config\/seihou\/default-context@ (plain text, single line)
--
-- Returns 'Nothing' if no context is active.
resolveContext ::
Maybe Text ->
Map Text Text ->
IO (Maybe Text)
resolveContext cliFlag envVars =
case nonEmpty cliFlag <|> nonEmpty (Map.lookup "SEIHOU_CONTEXT" envVars) of
Just ctx -> pure (Just ctx)
Nothing -> do
projectCtx <- readProjectContext
case projectCtx of
Just ctx -> pure (Just ctx)
Nothing -> readGlobalDefaultContext
where
nonEmpty (Just t) | not (T.null (T.strip t)) = Just (T.strip t)
nonEmpty _ = Nothing
-- | Validate a context name. Returns 'Nothing' if valid, 'Just errorMsg' if invalid.
validateContextName :: Text -> Maybe Text
validateContextName ctx
| T.null ctx = Just "context name cannot be empty"
| ".." `T.isInfixOf` ctx = Just "context name must not contain '..'"
| "/" `T.isInfixOf` ctx = Just "context name must not contain '/'"
| otherwise = Nothing
-- Internal helpers
readTextFileMaybe :: FilePath -> IO (Maybe Text)
readTextFileMaybe path = do
exists <- doesFileExist path
if exists
then do
content <- T.strip <$> TIO.readFile path
pure $ if T.null content then Nothing else Just content
else pure Nothing
readProjectContext :: IO (Maybe Text)
readProjectContext = do
cwd <- getCurrentDirectory
readTextFileMaybe (cwd </> ".seihou" </> "context")
readGlobalDefaultContext :: IO (Maybe Text)
readGlobalDefaultContext = do
base <- getXdgDirectory XdgConfig "seihou"
readTextFileMaybe (base </> "default-context")