packages feed

cabal-cache-1.2.0.0: app/App/Commands/Plan.hs

module App.Commands.Plan
  ( cmdPlan,
  )
where

import Amazonka.Data qualified as AWS
import App.Commands.Options.Types (PlanOptions (PlanOptions))
import App.Commands.Options.Types qualified as Z
import App.Run
import App.Static qualified as AS
import Control.Lens (Each (each), (%~))
import Data.Aeson qualified as J
import Data.ByteString.Lazy qualified as LBS
import Data.Text qualified as T
import Effectful.Zoo.Core.Error.Static
import HaskellWorks.CabalCache.Core qualified as Z
import HaskellWorks.CabalCache.Error (DecodeError, ExitFailure (..))
import HaskellWorks.CabalCache.Hash qualified as H
import HaskellWorks.CabalCache.IO.Console qualified as CIO
import HaskellWorks.CabalCache.Location (Location (..), (<.>), (</>))
import HaskellWorks.CabalCache.Version (archiveVersion)
import HaskellWorks.Prelude
import Options.Applicative (CommandFields, Mod, Parser)
import Options.Applicative qualified as OA
import System.IO qualified as IO

{- HLINT ignore "Monoid law, left identity" -}
{- HLINT ignore "Redundant do"              -}
{- HLINT ignore "Reduce duplication"        -}

runPlan :: Z.PlanOptions -> IO ()
runPlan opts = runApp do
  let storePath = opts.storePath
  let archiveUris = [LocalFile ""]
  let storePathHash = opts.storePathHash & fromMaybe (H.hashStorePath storePath)
  let versionedArchiveUris = archiveUris & each %~ (</> archiveVersion)
  let outputFile = opts.outputFile

  CIO.putStrLn $ "Store path: " <> AWS.toText storePath
  CIO.putStrLn $ "Store path hash: " <> T.pack storePathHash
  CIO.putStrLn $ "Archive URIs: " <> tshow archiveUris
  CIO.putStrLn $ "Archive version: " <> archiveVersion

  planJson <-
    Z.loadPlan (opts.path </> opts.buildPath)
      & do
        trap @DecodeError \e -> do
          CIO.hPutStrLn IO.stderr $ "ERROR: Unable to parse plan.json file: " <> tshow e
          throw ExitFailure

  packages <- liftIO $ Z.getPackages storePath planJson

  plan <- forM packages $ \pInfo -> do
    let archiveFileBasename = pInfo.packageDir <.> ".tar.gz"
    let archiveFiles = versionedArchiveUris <&> (</> T.pack archiveFileBasename)
    let scopedArchiveFiles = versionedArchiveUris <&> (</> T.pack storePathHash </> T.pack archiveFileBasename)

    return $ archiveFiles <> scopedArchiveFiles

  if outputFile == "-"
    then liftIO $ LBS.putStr $ J.encode (fmap (fmap AWS.toText) plan)
    else liftIO $ LBS.writeFile outputFile $ J.encode (fmap (fmap AWS.toText) plan)

optsPlan :: Parser PlanOptions
optsPlan =
  PlanOptions
    <$> OA.strOption
      ( OA.long "path"
          <> OA.help "Path to cabal project.  Defaults to \".\""
          <> OA.metavar "DIRECTORY"
          <> OA.value AS.path
      )
    <*> OA.strOption
      ( OA.long "build-path"
          <> OA.help ("Path to cabal build directory.  Defaults to " <> show AS.buildPath)
          <> OA.metavar "DIRECTORY"
          <> OA.value AS.buildPath
      )
    <*> OA.strOption
      ( OA.long "store-path"
          <> OA.help "Path to cabal store"
          <> OA.metavar "DIRECTORY"
          <> OA.value AS.cabalStoreDirectory
      )
    <*> optional
      ( OA.strOption
          ( OA.long "store-path-hash"
              <> OA.help "Store path hash (do not use)"
              <> OA.metavar "HASH"
          )
      )
    <*> OA.strOption
      ( OA.long "output-file"
          <> OA.help "Output file"
          <> OA.metavar "FILE"
          <> OA.value "-"
      )

cmdPlan :: Mod CommandFields (IO ())
cmdPlan = OA.command "plan" $ flip OA.info OA.idm $ runPlan <$> optsPlan