stacker-0.0.1.0: src/Commands.hs
{-# LANGUAGE OverloadedStrings #-}
module Commands
( runCommand
) where
import Control.Monad (forM_, when)
import Data.List (sortBy)
import Data.Map.Strict qualified as Map
import Data.Ord (comparing)
import Data.Text (Text)
import Data.Text qualified as T
import System.Console.ANSI
import System.Directory (makeAbsolute)
import System.Exit (die)
import Text.Printf (printf)
import Types
import Config
import CSV
import Git
import Analysis
import StackYaml
import ColorOption
import License (licenseText)
import XDG qualified
-- | Run a command
runCommand :: Options -> IO ()
runCommand opts = do
useColor <- shouldUseColor (optColor opts)
case optCommand opts of
Version -> printVersion
NumericVersion -> putStrLn appVersion
PrintLicense -> putStrLn licenseText
Help -> do
-- Manually render help text
-- While not ideal, this ensures help works. The structure mirrors optparse-applicative's output.
putStrLn $ "stacker version " ++ appVersion
putStrLn ""
putStrLn "Usage: stacker [COMMAND | (-V|--version) | --numeric-version |"
putStrLn " --license | (-h|--help)] [--color WHEN]"
putStrLn ""
putStrLn " A tool to bump snapshots (resolvers) in stack*.yaml files"
putStrLn ""
putStrLn "Available commands:"
putStrLn " bump [FILES...] Update FILES (or all stack*.yaml files in current directory if none specified)"
putStrLn " Use --recursive/-r to search subdirectories"
putStrLn " dry-run [FILES...] Show what would be updated by 'bump' (default)"
putStrLn " Use --recursive/-r to search subdirectories"
putStrLn " update Update stackage snapshots database"
putStrLn " info [repo|snapshots] Print repository path and/or GHC version to snapshot mapping"
putStrLn " config Configure stacker"
putStrLn ""
putStrLn "Available options:"
putStrLn " --color WHEN Use colored output (always, never, auto)"
putStrLn " -r,--recursive Search subdirectories for stack*.yaml files"
putStrLn " (commands bump and dry-run)"
putStrLn ""
putStrLn "Inessential commands:"
putStrLn " version Print version information (also: -V, --version)"
putStrLn " numeric-version Print version number (also: --numeric-version)"
putStrLn " license Print license text (also: --license)"
putStrLn " help Print this help (also: -h, --help)"
putStrLn ""
putStrLn "Option form of inessential commands:"
putStrLn " -V,--version Print version information"
putStrLn " --numeric-version Print version number"
putStrLn " --license Print license text"
putStrLn " -h,--help Print help"
putStrLn ""
putStrLn "For more information, see the README"
Config configCmd -> runConfig configCmd -- Handle config first!
Info InfoRepo -> getRepoPath >>= putStrLn -- Does not need CSV files
cmd -> runEssentialCommand useColor cmd
-- | Run an essential command (requires CSV files)
runEssentialCommand :: Bool -> Command -> IO ()
runEssentialCommand useColor cmd = do
-- Ensure CSV files exist (copy from data dir if needed)
ensureCSVFiles
case cmd of
DryRun files recursive -> runDryRun useColor files recursive
Bump files recursive -> runBump files recursive
Update -> do
-- Only for Update command, ensure repo exists and update it
repoPath <- getRepoPath
ensureRepo repoPath
runUpdate repoPath
Info infoCmd -> runInfo infoCmd
_ -> return ()
-- | Print version information
printVersion :: IO ()
printVersion = do
putStrLn $ appName ++ " version " ++ appVersion
putStrLn copyright
-- | Print text with optional color formatting
withColor :: Bool -> [SGR] -> IO () -> IO ()
withColor useColor sgr action = do
when useColor $ setSGR sgr
action
when useColor $ setSGR [Reset]
-- | Run dry-run command
runDryRun :: Bool -> [FilePath] -> Bool -> IO ()
runDryRun useColor files recursive = do
-- Validate: recursive cannot be used with explicit files
when (recursive && not (null files)) $
die "Error: --recursive cannot be used with explicit file arguments"
db <- loadSnapshotDB
actions <- analyzeStackYamls db files recursive
-- Sort actions by filename
let sortedActions = sortBy (comparing actionFile) actions
-- Calculate the maximum filename length for proper alignment (add 2 for padding)
let maxFileLen = case sortedActions of
[] -> 20
acts -> max 20 (maximum (map (length . actionFile) acts) + 2)
forM_ sortedActions $ \action -> do
printActionWithWidth useColor maxFileLen action
-- | Print an action with a specified column width for filenames
printActionWithWidth :: Bool -> Int -> Action -> IO ()
printActionWithWidth useColor fileWidth action = do
let file = actionFile action
let oldSnap = actionOldSnapshot action
let bump = actionBump action
let symlinkTarget = actionSymlinkTarget action
-- Print with proper alignment (file padded to fileWidth chars, oldSnap to 25 chars)
withColor useColor [SetConsoleIntensity BoldIntensity] $
putStr $ padRight fileWidth file
putStr $ padRight 25 (T.unpack oldSnap)
case symlinkTarget of
Just target -> do
-- This is a symlink to another stack*.yaml file
withColor useColor [SetColor Foreground Dull White] $ do
putStr "= symlink to "
putStr target
Nothing ->
case bump of
UpToDate -> do
withColor useColor [SetColor Foreground Vivid Green] $
putStr "✓ up to date"
Unknown -> do
withColor useColor [SetColor Foreground Vivid Red] $
putStr "? unknown snapshot"
BumpTo new -> do
withColor useColor [SetColor Foreground Vivid Yellow] $ do
putStr "→ bump to "
putStr $ T.unpack new
putStrLn ""
-- | Pad string to the right
padRight :: Int -> String -> String
padRight n s = take n (s ++ repeat ' ')
-- | Run bump command
runBump :: [FilePath] -> Bool -> IO ()
runBump files recursive = do
-- Validate: recursive cannot be used with explicit files
when (recursive && not (null files)) $
die "Error: --recursive cannot be used with explicit file arguments"
db <- loadSnapshotDB
actions <- analyzeStackYamls db files recursive
mapM_ (applyAction True) actions
-- | Run update command
runUpdate :: FilePath -> IO ()
runUpdate repoPath = do
putStrLn $ "Repository: " ++ repoPath
updateRepo repoPath
generateCSVs repoPath
-- | Run info command
runInfo :: InfoCmd -> IO ()
runInfo = \case
InfoAll -> do
repoPath <- getRepoPath
putStrLn $ "repo: " ++ repoPath
putStrLn "snapshots:"
printSnapshots " "
InfoRepo -> getRepoPath >>= putStrLn
InfoSnapshots -> printSnapshots ""
where
printSnapshots indent = do
db <- loadSnapshotDB
forM_ (Map.toAscList (dbGHC db)) $ \(ghc, snapshot) ->
putStrLn $ indent ++ formatGHCVersionText ghc ++ ": " ++ T.unpack (formatSnapshotText snapshot)
-- | Format GHC version as text
formatGHCVersionText :: GHCVersion -> String
formatGHCVersionText (GHCVersion maj1 maj2 minV) =
show maj1 ++ "." ++ show maj2 ++ "." ++ show minV
-- | Format snapshot as text
formatSnapshotText :: Snapshot -> Text
formatSnapshotText (LTS (LTSVersion major minor)) =
"lts-" <> T.pack (show major) <> "." <> T.pack (show minor)
formatSnapshotText (Nightly (NightlyVersion year month day)) =
T.pack $ "nightly-" ++ printf "%d-%02d-%02d" year month day
-- | Run config command
runConfig :: ConfigCmd -> IO ()
runConfig (SetRepo path) = do
absPath <- makeAbsolute path
saveConfig $ AppConfig (Just absPath)
putStrLn $ "Repository path set to: " ++ absPath
configFile <- XDG.getConfigFile
putStrLn $ "Configuration saved to: " ++ configFile