packages feed

indigo-0.6.0: app/Main.hs

-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

module Main
  ( main
  ) where

import Data.Text (dropWhileEnd)
import Data.Text.IO.Utf8 qualified as Utf8
import Data.Version (showVersion)
import Options.Applicative
  (command, execParser, fullDesc, header, help, helper, info, infoOption, long, progDesc, subparser)
import Options.Applicative qualified as Opt
import Paths_indigo (version)
import Shelly qualified as S
import System.Environment qualified as SE
import System.Process

import Morley.Util.Main (wrapMain)

import FileGen qualified
import Helper
import Upgrade qualified

argParser :: Opt.Parser (IO ())
argParser =
  subparser $
    newSubCmd <>
    buildSubCmd <>
    runSubCmd <>
    replSubCmd <>
    upgradeSubCmd <>
    testSubCmd

  where
    mkCommandParser commandName desc parser =
      command commandName $
      info (helper <*> parser) $
      progDesc desc

    projectName = Opt.strArgument (Opt.metavar "PROJECT_NAME")

    revisionArg = optional $ Opt.strOption $
         long "revision"
      <> Opt.metavar "REVISION"
      <> help "Git commit to pull from"
      <> Opt.internal

    newArgs = (,) <$> projectName <*> revisionArg

    newSubCmd =
      mkCommandParser "new"
      "Create a new project" $
      newArgs <&> \(projName, gitRev) -> do
        case parsePackageName projName of
          Right _ -> do
            isNonUnique <- doesExistProjectName projName
            if isNonUnique then
              die "A project with this name already exists. Please choose another one."
            else
              FileGen.run projName gitRev

          Left err ->
            die $ toString err

    buildSubCmd =
      mkCommandParser "build"
      "Build the current project" $ pure $
        S.shelly . S.escaping False $ runStackWithArgs ["build", "--fast"]

    runSubCmd =
      command "run" $
      flip info (progDesc "Run the current project" <> Opt.noIntersperse <> Opt.forwardOptions) $
      many (Opt.strArgument $ Opt.metavar "ARGUMENTS") <&> \input -> do
        result <- Utf8.readFile "package.yaml"
        S.shelly . S.escaping False $ case getExecName (words result) of
          Just execName ->
            -- If we don't catch the exception here, 2 errors message will be print
            -- one by 'runStackWithArgs' and another by shelly itself.
            -- We still treat it as a fail command when the exception occurs by
            -- exit with error code 1.
            S.catch_sh @(S.ReThrownException SomeException)
              (runStackWithArgs (["run", execName, "--"] <> input))
              (\_ -> S.quietExit 1)
          Nothing ->
            die "Could not find executable name in package.yaml"

    replSubCmd =
      mkCommandParser "repl"
      "Access Indigo REPL" $ pure $ do
        mbIndigoInDocker <- SE.lookupEnv "INDIGO_IN_DOCKER"
        let dockerFlag = (maybe [] (const ["--allow-different-user"]) mbIndigoInDocker)
        result <- Utf8.readFile "package.yaml"
        target <- case getLibName (words result) of
          Just libName ->
            pure (libName <> ":lib")
          Nothing ->
            -- | Fallback to executable name in case of not finding the library name
            -- Needed especially in running the REPL for the tutorial, since we need the
            -- target to be @:indigo-repl@.
            case getExecName (words result) of
              Just execName -> pure (":" <> execName)
              Nothing ->
                die "Could not find library name in package.yaml"

        S.shelly $ S.liftIO $ callProcess "stack" $ dockerFlag <> ["repl", toString target]

    testSubCmd =
      mkCommandParser "test"
      "Run contract tests" $ pure $
        S.shelly . S.escaping False $ runStackWithArgs ["test", "--fast"]

    upgradeSubCmd =
      mkCommandParser "upgrade"
      "Upgrade indigo binary to the latest version" $
        Upgrade.run <$> revisionArg <*> Opt.switch (long "force" <> Opt.internal)

    runStackWithArgs :: [Text] -> S.Sh ()
    runStackWithArgs args = do
      mbIndigoInDocker <- liftIO $ SE.lookupEnv "INDIGO_IN_DOCKER"
      let dockerFlag = (maybe [] (const ["--allow-different-user"]) mbIndigoInDocker)
      S.shelly $ S.escaping False $ S.run_ "stack" $ dockerFlag <> args

    getExecName :: [Text] -> Maybe Text
    getExecName = \case
      "executables:":a:_ -> Just $ dropWhileEnd (==':') a
      _:xs -> getExecName xs
      _ -> Nothing

    getLibName :: [Text] -> Maybe Text
    getLibName = \case
      "name:":a:_ -> Just a
      _ -> Nothing

main :: IO ()
main = wrapMain $ do
  join $ execParser $ info (helper <*> versionOption <*> argParser) $
    mconcat
    [ fullDesc
    , progDesc (toString indigoDesc)
    , header (toString indigoTitle)
    ]
  where
    versionOption = infoOption ("indigo-" <> showVersion version)
      (long "version" <> help "Show version.")