module Shebanger.Cli
  ( parseCliOpts
  , Command (..)
  , TranslateArgs (..)
  , ExecArgs (..)
  ) where

import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as ByteString.Char8
import Data.ByteString.Base64 (decode)
import Data.Version (showVersion)
import Control.Applicative ((<**>), Alternative (many, (<|>)))
import Options.Applicative
    ( action, argument, command, eitherReader, fullDesc, header, help, info,
      metavar, progDesc, strArgument, execParser, helper, hsubparser,
      simpleVersioner, CommandFields, Mod, Parser, ParserInfo
    )
import Paths_shebanger (version)

data TranslateArgs = TranslateArgs { TranslateArgs -> String
scriptFilePath :: FilePath }
  deriving stock Int -> TranslateArgs -> ShowS
[TranslateArgs] -> ShowS
TranslateArgs -> String
(Int -> TranslateArgs -> ShowS)
-> (TranslateArgs -> String)
-> ([TranslateArgs] -> ShowS)
-> Show TranslateArgs
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TranslateArgs -> ShowS
showsPrec :: Int -> TranslateArgs -> ShowS
$cshow :: TranslateArgs -> String
show :: TranslateArgs -> String
$cshowList :: [TranslateArgs] -> ShowS
showList :: [TranslateArgs] -> ShowS
Show

data ExecArgs = ExecArgs
  { ExecArgs -> ByteString
shebangScriptPart :: ByteString
    -- ^ The base64-encoded part of the original script.
    --
    -- Example: @\"ICAgZWNobyAkaQpkb25lCmVjaG8KCiMgTGlzdCBzeXN0ZW0gaW5mb3JtYXRpb24KdW4=\"@
  , ExecArgs -> String
shebangScriptFilePath :: FilePath
    -- ^ The path of this shebanged script.  This is normally passed
    -- automatically by the kernel when calling a script with a shebang line.
    --
    -- Example: @\"./test.sh.shebanged.7\"@
  , ExecArgs -> [String]
additionalArgs :: [String]
    -- ^ Additional arguments that have been passed to the script on the command line.
    --
    -- Example @[\"-l\", \"-a\", \"somedirectory/\"]@
  }
  deriving stock Int -> ExecArgs -> ShowS
[ExecArgs] -> ShowS
ExecArgs -> String
(Int -> ExecArgs -> ShowS)
-> (ExecArgs -> String) -> ([ExecArgs] -> ShowS) -> Show ExecArgs
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ExecArgs -> ShowS
showsPrec :: Int -> ExecArgs -> ShowS
$cshow :: ExecArgs -> String
show :: ExecArgs -> String
$cshowList :: [ExecArgs] -> ShowS
showList :: [ExecArgs] -> ShowS
Show

data Command
  = Translate TranslateArgs
  | Exec ExecArgs
  deriving stock Int -> Command -> ShowS
[Command] -> ShowS
Command -> String
(Int -> Command -> ShowS)
-> (Command -> String) -> ([Command] -> ShowS) -> Show Command
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Command -> ShowS
showsPrec :: Int -> Command -> ShowS
$cshow :: Command -> String
show :: Command -> String
$cshowList :: [Command] -> ShowS
showList :: [Command] -> ShowS
Show

parseCliOpts :: IO Command
parseCliOpts :: IO Command
parseCliOpts = ParserInfo Command -> IO Command
forall a. ParserInfo a -> IO a
execParser ParserInfo Command
cliCmdParserInfo

cliCmdParserInfo :: ParserInfo Command
cliCmdParserInfo :: ParserInfo Command
cliCmdParserInfo =
  Parser Command -> InfoMod Command -> ParserInfo Command
forall a. Parser a -> InfoMod a -> ParserInfo a
info
    ( Parser Command
cliCmdParser Parser Command -> Parser (Command -> Command) -> Parser Command
forall (f :: * -> *) a b. Applicative f => f a -> f (a -> b) -> f b
<**>
      Parser (Command -> Command)
forall a. Parser (a -> a)
helper Parser Command -> Parser (Command -> Command) -> Parser Command
forall (f :: * -> *) a b. Applicative f => f a -> f (a -> b) -> f b
<**>
      String -> Parser (Command -> Command)
forall a. String -> Parser (a -> a)
simpleVersioner (Version -> String
showVersion Version
version)
    )
    ( InfoMod Command
forall a. InfoMod a
fullDesc InfoMod Command -> InfoMod Command -> InfoMod Command
forall a. Semigroup a => a -> a -> a
<>
      String -> InfoMod Command
forall a. String -> InfoMod a
header String
"shebanger - translate a shell script into a series of shebang lines"
    )

cliCmdParser :: Parser Command
cliCmdParser :: Parser Command
cliCmdParser = Mod CommandFields Command -> Parser Command
forall a. Mod CommandFields a -> Parser a
hsubparser (Mod CommandFields Command
translateCommandMod Mod CommandFields Command
-> Mod CommandFields Command -> Mod CommandFields Command
forall a. Semigroup a => a -> a -> a
<> Mod CommandFields Command
execCommandMod) Parser Command -> Parser Command -> Parser Command
forall a. Parser a -> Parser a -> Parser a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parser Command
translateCommandParser
  where
    translateCommandMod :: Mod CommandFields Command
    translateCommandMod :: Mod CommandFields Command
translateCommandMod =
      String -> ParserInfo Command -> Mod CommandFields Command
forall a. String -> ParserInfo a -> Mod CommandFields a
command
        String
"translate"
        ( Parser Command -> InfoMod Command -> ParserInfo Command
forall a. Parser a -> InfoMod a -> ParserInfo a
info
            Parser Command
translateCommandParser
            (String -> InfoMod Command
forall a. String -> InfoMod a
progDesc String
"Translate a shell script into a series of scripts with only shebang lines")
        )

    execCommandMod :: Mod CommandFields Command
    execCommandMod :: Mod CommandFields Command
execCommandMod =
      String -> ParserInfo Command -> Mod CommandFields Command
forall a. String -> ParserInfo a -> Mod CommandFields a
command
        String
"exec"
        ( Parser Command -> InfoMod Command -> ParserInfo Command
forall a. Parser a -> InfoMod a -> ParserInfo a
info
            ((ExecArgs -> Command) -> Parser ExecArgs -> Parser Command
forall a b. (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ExecArgs -> Command
Exec Parser ExecArgs
execArgsParser)
            (String -> InfoMod Command
forall a. String -> InfoMod a
progDesc String
"Execute shebanged shell scripts")
        )

    translateCommandParser :: Parser Command
    translateCommandParser :: Parser Command
translateCommandParser = (TranslateArgs -> Command)
-> Parser TranslateArgs -> Parser Command
forall a b. (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap TranslateArgs -> Command
Translate Parser TranslateArgs
translateArgsParser

translateArgsParser :: Parser TranslateArgs
translateArgsParser :: Parser TranslateArgs
translateArgsParser = String -> TranslateArgs
TranslateArgs (String -> TranslateArgs) -> Parser String -> Parser TranslateArgs
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Parser String
inputFileParser

inputFileParser :: Parser FilePath
inputFileParser :: Parser String
inputFileParser =
  Mod ArgumentFields String -> Parser String
forall s. IsString s => Mod ArgumentFields s -> Parser s
strArgument
    ( String -> Mod ArgumentFields String
forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
metavar String
"FILE" Mod ArgumentFields String
-> Mod ArgumentFields String -> Mod ArgumentFields String
forall a. Semigroup a => a -> a -> a
<>
      String -> Mod ArgumentFields String
forall (f :: * -> *) a. String -> Mod f a
help String
"Input shell scripts to shebang" Mod ArgumentFields String
-> Mod ArgumentFields String -> Mod ArgumentFields String
forall a. Semigroup a => a -> a -> a
<>
      String -> Mod ArgumentFields String
forall (f :: * -> *) a. HasCompleter f => String -> Mod f a
action String
"file"
    )

execArgsParser :: Parser ExecArgs
execArgsParser :: Parser ExecArgs
execArgsParser =
  ByteString -> String -> [String] -> ExecArgs
ExecArgs
    (ByteString -> String -> [String] -> ExecArgs)
-> Parser ByteString -> Parser (String -> [String] -> ExecArgs)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Parser ByteString
shebangedScriptParser
    Parser (String -> [String] -> ExecArgs)
-> Parser String -> Parser ([String] -> ExecArgs)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Parser String
shebangScriptFilePathParser
    Parser ([String] -> ExecArgs) -> Parser [String] -> Parser ExecArgs
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Parser [String]
additionalArgsParser


shebangedScriptParser :: Parser ByteString
shebangedScriptParser :: Parser ByteString
shebangedScriptParser =
  ReadM ByteString
-> Mod ArgumentFields ByteString -> Parser ByteString
forall a. ReadM a -> Mod ArgumentFields a -> Parser a
argument
    ((String -> Either String ByteString) -> ReadM ByteString
forall a. (String -> Either String a) -> ReadM a
eitherReader String -> Either String ByteString
b64Reader)
    ( String -> Mod ArgumentFields ByteString
forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
metavar String
"SHEBANG_SCRIPT_PART" Mod ArgumentFields ByteString
-> Mod ArgumentFields ByteString -> Mod ArgumentFields ByteString
forall a. Semigroup a => a -> a -> a
<>
      String -> Mod ArgumentFields ByteString
forall (f :: * -> *) a. String -> Mod f a
help String
"Part of a shebang translated script file.  Expected to be in base 64."
    )
  where
    b64Reader :: String -> Either String ByteString
    b64Reader :: String -> Either String ByteString
b64Reader String
b64Str = ByteString -> Either String ByteString
decode (ByteString -> Either String ByteString)
-> ByteString -> Either String ByteString
forall a b. (a -> b) -> a -> b
$ String -> ByteString
ByteString.Char8.pack String
b64Str

shebangScriptFilePathParser :: Parser FilePath
shebangScriptFilePathParser :: Parser String
shebangScriptFilePathParser =
  Mod ArgumentFields String -> Parser String
forall s. IsString s => Mod ArgumentFields s -> Parser s
strArgument
    ( String -> Mod ArgumentFields String
forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
metavar String
"FILE" Mod ArgumentFields String
-> Mod ArgumentFields String -> Mod ArgumentFields String
forall a. Semigroup a => a -> a -> a
<>
      String -> Mod ArgumentFields String
forall (f :: * -> *) a. String -> Mod f a
help String
"Input shebanged script name.  Normally passed automatically by kernel." Mod ArgumentFields String
-> Mod ArgumentFields String -> Mod ArgumentFields String
forall a. Semigroup a => a -> a -> a
<>
      String -> Mod ArgumentFields String
forall (f :: * -> *) a. HasCompleter f => String -> Mod f a
action String
"file"
    )

additionalArgsParser :: Parser [String]
additionalArgsParser :: Parser [String]
additionalArgsParser =
  Parser String -> Parser [String]
forall a. Parser a -> Parser [a]
forall (f :: * -> *) a. Alternative f => f a -> f [a]
many (Parser String -> Parser [String])
-> Parser String -> Parser [String]
forall a b. (a -> b) -> a -> b
$
    Mod ArgumentFields String -> Parser String
forall s. IsString s => Mod ArgumentFields s -> Parser s
strArgument
      ( String -> Mod ArgumentFields String
forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
metavar String
"ARG" Mod ArgumentFields String
-> Mod ArgumentFields String -> Mod ArgumentFields String
forall a. Semigroup a => a -> a -> a
<>
        String -> Mod ArgumentFields String
forall (f :: * -> *) a. String -> Mod f a
help String
"arguments to pass to the underlying script being called"
      )