kiroku-cli-0.3.0.0: src/Kiroku/Cli/Run.hs
module Kiroku.Cli.Run
( runKirokuCommand,
runKirokuCommandWithStore,
renderKirokuCommandWithStore,
KirokuCommandOutcome (..),
executeKirokuCommandWithStore,
)
where
import Control.Monad (when)
import Data.Text (Text)
import Data.Text.IO qualified as TIO
import Kiroku.Cli.Command
import Kiroku.Cli.Compaction
import Kiroku.Cli.Subscription.Status (renderRemoteSubscriptionStatus, renderSubscriptionStatusRows, subscriptionStatusRows)
import Kiroku.Store (KirokuStore)
import Kiroku.Store.Compaction qualified as Store
import Kiroku.Store.Effect (runStoreIO)
import Kiroku.Store.Read (storeIdentity)
import Kiroku.Store.Subscription (subscriptionStates)
import System.Exit (ExitCode (..), exitWith)
data KirokuCommandOutcome = KirokuCommandOutcome {output :: !Text, exitCode :: !ExitCode}
deriving stock (Eq, Show)
runKirokuCommand :: KirokuCommand -> IO ()
runKirokuCommand KirokuNoCommand = putStrLn "No Kiroku operator command was selected."
runKirokuCommand (KirokuSubscriptions _) = putStrLn storeRequired
runKirokuCommand (KirokuCompaction _) = putStrLn storeRequired
runKirokuCommand (KirokuStore _) = putStrLn storeRequired
storeRequired :: String
storeRequired = "This command needs a live KirokuStore. Use runKirokuCommandWithStore from the embeddable library API."
runKirokuCommandWithStore :: KirokuStore -> KirokuCommand -> IO ()
runKirokuCommandWithStore store command = do
KirokuCommandOutcome {output, exitCode} <- executeKirokuCommandWithStore store command
TIO.putStrLn output
when (exitCode /= ExitSuccess) (exitWith exitCode)
renderKirokuCommandWithStore :: KirokuStore -> KirokuCommand -> IO Text
renderKirokuCommandWithStore store command = do
KirokuCommandOutcome {output} <- executeKirokuCommandWithStore store command
pure output
-- | Structured execution for hosts. Refusals use exit 2, load/store errors use
-- exit 1, and success/already-applied use exit 0. Legacy status behavior stays
-- compatible, including its rendered remote-fetch errors with exit 0.
executeKirokuCommandWithStore :: KirokuStore -> KirokuCommand -> IO KirokuCommandOutcome
executeKirokuCommandWithStore _ KirokuNoCommand = pure (success "No Kiroku operator command was selected.")
executeKirokuCommandWithStore store (KirokuSubscriptions (SubscriptionStatus (StatusOptions format endpoint))) =
success <$> case endpoint of
Just ep -> renderRemoteSubscriptionStatus ep format
Nothing -> renderSubscriptionStatusRows format . subscriptionStatusRows <$> subscriptionStates store
executeKirokuCommandWithStore store (KirokuStore (ShowStoreIdentity (IdentityOptions format))) = do
result <- runStoreIO store storeIdentity
pure $ either (failure format 1 . StoreFailure) (success . renderStoreIdentity format) result
executeKirokuCommandWithStore store (KirokuCompaction command) = case command of
CompactionPreview (CompactionOptions path format) -> withManifest path format $ \manifest -> do
result <- runStoreIO store (Store.previewCompaction manifest)
pure $ case result of
Left err -> failure format 1 (StoreFailure err)
Right (Left refusals) -> KirokuCommandOutcome (renderCompactionRefusals format refusals) (ExitFailure 2)
Right (Right (Store.CompactionPreviewReady report)) -> success (renderCompactionReport format Nothing report)
Right (Right (Store.CompactionPreviewAlreadyApplied record)) -> success (renderCompactionResult format True record)
CompactionApply (ApplyOptions path typedDigest format) -> withManifest path format $ \manifest ->
case Store.parseCompactionDigestHex typedDigest of
Nothing -> pure (failure format 1 (ConfirmDigestUnparseable typedDigest))
Just digest
| digest /= Store.compactionManifestDigest manifest ->
pure (failure format 2 (ConfirmDigestMismatch typedDigest (Store.compactionDigestHex (Store.compactionManifestDigest manifest))))
Just _ -> do
result <- runStoreIO store (Store.applyCompaction manifest)
pure $ case result of
Left err -> failure format 1 (StoreFailure err)
Right (Left refusals) -> KirokuCommandOutcome (renderCompactionRefusals format refusals) (ExitFailure 2)
Right (Right (Store.CompactionAppliedNow record)) -> success (renderCompactionResult format False record)
Right (Right (Store.CompactionAlreadyApplied record)) -> success (renderCompactionResult format True record)
CompactionLedger (LedgerOptions limit format) -> case Store.mkCompactionLedgerLimit limit of
Left _ -> pure (failure format 1 (LedgerLimitOutOfRange limit))
Right validated -> do
result <- runStoreIO store (Store.compactionLedger (Store.CompactionLedgerQuery validated))
pure $ either (failure format 1 . StoreFailure) (success . renderCompactionRecords format) result
withManifest :: ManifestPath -> OutputFormat -> (Store.CompactionManifest -> IO KirokuCommandOutcome) -> IO KirokuCommandOutcome
withManifest path format continuation = loadCompactionManifest path >>= either (pure . failure format 1) continuation
success :: Text -> KirokuCommandOutcome
success text = KirokuCommandOutcome text ExitSuccess
failure :: OutputFormat -> Int -> CompactionCommandError -> KirokuCommandOutcome
failure format code err = KirokuCommandOutcome (renderCompactionCommandError format err) (ExitFailure code)