packages feed

cachix-0.7.0: src/Cachix/Client/OptionsParser.hs

module Cachix.Client.OptionsParser
  ( CachixCommand (..),
    CachixOptions (..),
    PushArguments (..),
    PushOptions (..),
    BinaryCacheName,
    getOpts,
  )
where

import qualified Cachix.Client.Config as Config
import qualified Cachix.Client.InstallationMode as InstallationMode
import Cachix.Client.URI (defaultCachixURI)
import qualified Cachix.Deploy.OptionsParser as DeployOptions
import Options.Applicative
import Protolude hiding (option, toS)
import Protolude.Conv
import URI.ByteString
  ( Absolute,
    URIRef,
    parseURI,
    serializeURIRef',
    strictURIParserOptions,
  )

data CachixOptions = CachixOptions
  { host :: URIRef Absolute,
    configPath :: Config.ConfigPath,
    verbose :: Bool
  }
  deriving (Show)

parserCachixOptions :: Config.ConfigPath -> Parser CachixOptions
parserCachixOptions defaultConfigPath =
  CachixOptions
    <$> option
      uriOption
      ( long "host"
          <> value defaultCachixURI
          <> metavar "URI"
          <> showDefaultWith (toS . serializeURIRef')
          <> help "Host to connect to"
      )
    <*> strOption
      ( long "config"
          <> short 'c'
          <> value defaultConfigPath
          <> metavar "CONFIGPATH"
          <> showDefault
          <> help "Cachix configuration file"
      )
    <*> switch
      ( long "verbose"
          <> short 'v'
          <> help "Verbose mode"
      )

uriOption :: ReadM (URIRef Absolute)
uriOption = eitherReader $ \s ->
  first show $ parseURI strictURIParserOptions $ toS s

type BinaryCacheName = Text

data CachixCommand
  = AuthToken (Maybe Text)
  | GenerateKeypair BinaryCacheName
  | Push PushArguments
  | WatchStore PushOptions Text
  | WatchExec PushOptions Text Text [Text]
  | Use BinaryCacheName InstallationMode.UseOptions
  | DeployCommand DeployOptions.DeployCommand
  | Version
  deriving (Show)

data PushArguments
  = PushPaths PushOptions Text [Text]
  | PushWatchStore PushOptions Text
  deriving (Show)

data PushOptions = PushOptions
  { compressionLevel :: Int,
    numJobs :: Int,
    omitDeriver :: Bool
  }
  deriving (Show)

parserCachixCommand :: Parser CachixCommand
parserCachixCommand =
  subparser $
    command "authtoken" (infoH authtoken (progDesc "Configure authentication token for communication to HTTP API"))
      <> command "generate-keypair" (infoH generateKeypair (progDesc "Generate signing key pair for a binary cache"))
      <> command "push" (infoH push (progDesc "Upload Nix store paths to a binary cache"))
      <> command "watch-exec" (infoH watchExec (progDesc "Run a command while it's running watch /nix/store for newly added store paths and upload them to a binary cache"))
      <> command "watch-store" (infoH watchStore (progDesc "Indefinitely watch /nix/store for newly added store paths and upload them to a binary cache"))
      <> command "use" (infoH use (progDesc "Configure a binary cache by writing nix.conf and netrc files"))
      <> command "deploy" (infoH (DeployCommand <$> DeployOptions.parser) (progDesc "Cachix Deploy commands"))
  where
    nameArg = strArgument (metavar "CACHE-NAME")
    authtoken = AuthToken <$> (stdinFlag <|> (Just <$> authTokenArg))
      where
        stdinFlag = flag' Nothing (long "stdin" <> help "Read the token from stdin rather than accepting it as an argument.")
        authTokenArg = strArgument (metavar "AUTH-TOKEN")
    generateKeypair = GenerateKeypair <$> nameArg
    validatedLevel l =
      l <$ unless (l `elem` [0 .. 9]) (readerError $ "value " <> show l <> " not in expected range: [0..9]")
    pushOptions :: Parser PushOptions
    pushOptions =
      PushOptions
        <$> option
          (auto >>= validatedLevel)
          ( long "compression-level"
              <> short 'c'
              <> metavar "[0..9]"
              <> help
                "The compression level for XZ compression.\
                \ Take compressor *and* decompressor memory usage into account before using [7..9]!"
              <> showDefault
              <> value 2
          )
        <*> option
          auto
          ( long "jobs"
              <> short 'j'
              <> help "Number of threads used for pushing store paths."
              <> showDefault
              <> value 4
          )
        <*> switch (long "omit-deriver" <> help "Do not publish which derivations built the store paths.")
    push = (\opts cache f -> Push $ f opts cache) <$> pushOptions <*> nameArg <*> (pushPaths <|> pushWatchStore)
    pushPaths =
      (\paths opts cache -> PushPaths opts cache paths)
        <$> many (strArgument (metavar "PATHS..."))
    watchExec = WatchExec <$> pushOptions <*> nameArg <*> strArgument (metavar "CMD") <*> many (strArgument (metavar "-- ARGS"))
    watchStore = WatchStore <$> pushOptions <*> nameArg
    pushWatchStore =
      (\() opts cache -> PushWatchStore opts cache)
        <$> flag'
          ()
          ( long "watch-store"
              <> short 'w'
              <> help "DEPRECATED: use watch-store command instead."
          )
    use =
      Use <$> nameArg
        <*> ( InstallationMode.UseOptions
                <$> optional
                  ( option
                      (maybeReader InstallationMode.fromString)
                      ( long "mode"
                          <> short 'm'
                          <> help "Mode in which to configure binary caches for Nix. Supported values: nixos, root-nixconf, user-nixconf"
                      )
                  )
                <*> strOption
                  ( long "nixos-folder"
                      <> short 'd'
                      <> help "Base directory for NixOS configuration generation"
                      <> value "/etc/nixos/"
                      <> showDefault
                  )
                <*> optional
                  ( strOption
                      ( long "output-directory"
                          <> short 'O'
                          <> help "Output directory where nix.conf and netrc will be updated."
                      )
                  )
            )

getOpts :: IO (CachixOptions, CachixCommand)
getOpts = do
  configpath <- Config.getDefaultFilename
  customExecParser (prefs showHelpOnEmpty) (optsInfo configpath)

optsInfo :: Config.ConfigPath -> ParserInfo (CachixOptions, CachixCommand)
optsInfo configpath = infoH parser desc
  where
    parser = (,) <$> parserCachixOptions configpath <*> (parserCachixCommand <|> versionParser)
    versionParser :: Parser CachixCommand
    versionParser =
      flag'
        Version
        ( long "version"
            <> short 'V'
            <> help "Show cachix version"
        )

desc :: InfoMod a
desc =
  fullDesc
    <> progDesc "To get started log in to https://app.cachix.org"
    <> header "https://cachix.org command line interface"

-- TODO: usage footer
infoH :: Parser a -> InfoMod a -> ParserInfo a
infoH a = info (helper <*> a)