packages feed

seihou-cli-0.4.0.0: src-exe/Seihou/CLI/SchemaUpgrade.hs

module Seihou.CLI.SchemaUpgrade
  ( handleSchemaUpgrade,
  )
where

import Data.Text qualified as T
import Data.Text.IO qualified as TIO
import Seihou.CLI.Commands (SchemaUpgradeOpts (..))
import Seihou.CLI.SchemaVersion (schemaHash, schemaUrl)
import Seihou.CLI.Style (dim, green, yellow)
import Seihou.Core.Module (DiscoveredModule (..), defaultSearchPaths, discoverAllModules)
import Seihou.Core.SchemaUpgrade
import Seihou.Prelude
import System.Directory (doesFileExist, getCurrentDirectory)
import System.Exit (exitFailure)

handleSchemaUpgrade :: SchemaUpgradeOpts -> IO ()
handleSchemaUpgrade opts
  | opts.schemaUpgradeAll = do
      searchPaths <- defaultSearchPaths
      modules <- discoverAllModules searchPaths
      let paths = [dir </> "module.dhall" | DiscoveredModule {discoveredDir = dir} <- modules]
      results <- mapM (processModule opts.schemaUpgradeDryRun) paths
      printSummary results
  | otherwise = do
      moduleDir <- case opts.schemaUpgradePath of
        Just p -> pure p
        Nothing -> getCurrentDirectory
      let dhallFile = moduleDir </> "module.dhall"
      exists <- doesFileExist dhallFile
      if not exists
        then do
          TIO.putStrLn $ "Error: " <> T.pack dhallFile <> " not found."
          exitFailure
        else do
          results <- sequence [processModule opts.schemaUpgradeDryRun dhallFile]
          printSummary results

data ProcessResult
  = UpToDate FilePath
  | Fixed FilePath [UpgradeIssue]
  | WouldFix FilePath [UpgradeIssue]
  | ProcessError FilePath Text

processModule :: Bool -> FilePath -> IO ProcessResult
processModule dryRun path = do
  exists <- doesFileExist path
  if not exists
    then pure (ProcessError path "file not found")
    else do
      content <- TIO.readFile path
      case upgradeModuleText schemaUrl schemaHash content of
        AlreadyCurrent -> pure (UpToDate path)
        Upgraded newContent issues
          | dryRun -> do
              printDryRun path issues
              pure (WouldFix path issues)
          | otherwise -> do
              TIO.writeFile path newContent
              printFixed path issues
              pure (Fixed path issues)

printDryRun :: FilePath -> [UpgradeIssue] -> IO ()
printDryRun path issues = do
  TIO.putStrLn $ yellow "[dry run] " <> T.pack path
  mapM_ (\i -> TIO.putStrLn $ "  Would fix: " <> issueMessage i) issues

printFixed :: FilePath -> [UpgradeIssue] -> IO ()
printFixed path issues = do
  let count = length issues
      label = if count == 1 then " issue" else " issues"
  TIO.putStrLn $ green "  ✓ " <> T.pack path <> " — " <> T.pack (show count) <> label <> " fixed"
  mapM_ (\i -> TIO.putStrLn $ "    + " <> issueMessage i) issues

printSummary :: [ProcessResult] -> IO ()
printSummary results = do
  let total = length results
      fixed = length [() | Fixed {} <- results]
      wouldFix = length [() | WouldFix {} <- results]
      upToDate = length [() | UpToDate {} <- results]
      errors = length [() | ProcessError {} <- results]
  TIO.putStrLn ""
  if fixed > 0
    then TIO.putStrLn $ green (T.pack (show fixed) <> " upgraded") <> dim (", " <> T.pack (show upToDate) <> " already current, " <> T.pack (show total) <> " total")
    else
      if wouldFix > 0
        then TIO.putStrLn $ yellow (T.pack (show wouldFix) <> " would be upgraded") <> dim (", " <> T.pack (show upToDate) <> " already current, " <> T.pack (show total) <> " total")
        else TIO.putStrLn $ green "All modules up to date." <> dim (" (" <> T.pack (show total) <> " checked)")
  if errors > 0
    then do
      TIO.putStrLn $ T.pack (show errors) <> " errors:"
      mapM_ (\case ProcessError p msg -> TIO.putStrLn $ "  " <> T.pack p <> ": " <> msg; _ -> pure ()) results
    else pure ()