{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Parsing of stack command line arguments

module Data.Attoparsec.Args
    ( EscapingMode(..)
    , argsParser
    , parseArgs
    , parseArgsFromString
    ) where

import           Data.Attoparsec.Text ((<?>))
import qualified Data.Attoparsec.Text as P
import qualified Data.Text as T
import           Stack.Prelude

-- | Mode for parsing escape characters.
data EscapingMode
    = Escaping
    | NoEscaping
    deriving (Int -> EscapingMode -> ShowS
[EscapingMode] -> ShowS
EscapingMode -> String
(Int -> EscapingMode -> ShowS)
-> (EscapingMode -> String)
-> ([EscapingMode] -> ShowS)
-> Show EscapingMode
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [EscapingMode] -> ShowS
$cshowList :: [EscapingMode] -> ShowS
show :: EscapingMode -> String
$cshow :: EscapingMode -> String
showsPrec :: Int -> EscapingMode -> ShowS
$cshowsPrec :: Int -> EscapingMode -> ShowS
Show,EscapingMode -> EscapingMode -> Bool
(EscapingMode -> EscapingMode -> Bool)
-> (EscapingMode -> EscapingMode -> Bool) -> Eq EscapingMode
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: EscapingMode -> EscapingMode -> Bool
$c/= :: EscapingMode -> EscapingMode -> Bool
== :: EscapingMode -> EscapingMode -> Bool
$c== :: EscapingMode -> EscapingMode -> Bool
Eq,Int -> EscapingMode
EscapingMode -> Int
EscapingMode -> [EscapingMode]
EscapingMode -> EscapingMode
EscapingMode -> EscapingMode -> [EscapingMode]
EscapingMode -> EscapingMode -> EscapingMode -> [EscapingMode]
(EscapingMode -> EscapingMode)
-> (EscapingMode -> EscapingMode)
-> (Int -> EscapingMode)
-> (EscapingMode -> Int)
-> (EscapingMode -> [EscapingMode])
-> (EscapingMode -> EscapingMode -> [EscapingMode])
-> (EscapingMode -> EscapingMode -> [EscapingMode])
-> (EscapingMode -> EscapingMode -> EscapingMode -> [EscapingMode])
-> Enum EscapingMode
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
enumFromThenTo :: EscapingMode -> EscapingMode -> EscapingMode -> [EscapingMode]
$cenumFromThenTo :: EscapingMode -> EscapingMode -> EscapingMode -> [EscapingMode]
enumFromTo :: EscapingMode -> EscapingMode -> [EscapingMode]
$cenumFromTo :: EscapingMode -> EscapingMode -> [EscapingMode]
enumFromThen :: EscapingMode -> EscapingMode -> [EscapingMode]
$cenumFromThen :: EscapingMode -> EscapingMode -> [EscapingMode]
enumFrom :: EscapingMode -> [EscapingMode]
$cenumFrom :: EscapingMode -> [EscapingMode]
fromEnum :: EscapingMode -> Int
$cfromEnum :: EscapingMode -> Int
toEnum :: Int -> EscapingMode
$ctoEnum :: Int -> EscapingMode
pred :: EscapingMode -> EscapingMode
$cpred :: EscapingMode -> EscapingMode
succ :: EscapingMode -> EscapingMode
$csucc :: EscapingMode -> EscapingMode
Enum)

-- | Parse arguments using 'argsParser'.
parseArgs :: EscapingMode -> Text -> Either String [String]
parseArgs :: EscapingMode -> Text -> Either String [String]
parseArgs EscapingMode
mode = Parser [String] -> Text -> Either String [String]
forall a. Parser a -> Text -> Either String a
P.parseOnly (EscapingMode -> Parser [String]
argsParser EscapingMode
mode)

-- | Parse using 'argsParser' from a string.
parseArgsFromString :: EscapingMode -> String -> Either String [String]
parseArgsFromString :: EscapingMode -> String -> Either String [String]
parseArgsFromString EscapingMode
mode = Parser [String] -> Text -> Either String [String]
forall a. Parser a -> Text -> Either String a
P.parseOnly (EscapingMode -> Parser [String]
argsParser EscapingMode
mode) (Text -> Either String [String])
-> (String -> Text) -> String -> Either String [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack

-- | A basic argument parser. It supports space-separated text, and
-- string quotation with identity escaping: \x -> x.
argsParser :: EscapingMode -> P.Parser [String]
argsParser :: EscapingMode -> Parser [String]
argsParser EscapingMode
mode = Parser Text String -> Parser [String]
forall (f :: * -> *) a. Alternative f => f a -> f [a]
many (Parser ()
P.skipSpace Parser () -> Parser Text String -> Parser Text String
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> (Parser Text String
quoted Parser Text String -> Parser Text String -> Parser Text String
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parser Text String
unquoted)) Parser [String] -> Parser () -> Parser [String]
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<*
                  Parser ()
P.skipSpace Parser [String] -> Parser () -> Parser [String]
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* (Parser ()
forall t. Chunk t => Parser t ()
P.endOfInput Parser () -> String -> Parser ()
forall i a. Parser i a -> String -> Parser i a
<?> String
"unterminated string")
  where
    unquoted :: Parser Text String
unquoted = Parser Text Char -> Parser Text String
forall (f :: * -> *) a. Alternative f => f a -> f [a]
P.many1 Parser Text Char
naked
    quoted :: Parser Text String
quoted = Char -> Parser Text Char
P.char Char
'"' Parser Text Char -> Parser Text String -> Parser Text String
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Parser Text String
string Parser Text String -> Parser Text Char -> Parser Text String
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Char -> Parser Text Char
P.char Char
'"'
    string :: Parser Text String
string = Parser Text Char -> Parser Text String
forall (f :: * -> *) a. Alternative f => f a -> f [a]
many (case EscapingMode
mode of
                     EscapingMode
Escaping -> Parser Text Char
escaped Parser Text Char -> Parser Text Char -> Parser Text Char
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parser Text Char
nonquote
                     EscapingMode
NoEscaping -> Parser Text Char
nonquote)
    escaped :: Parser Text Char
escaped = Char -> Parser Text Char
P.char Char
'\\' Parser Text Char -> Parser Text Char -> Parser Text Char
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Parser Text Char
P.anyChar
    nonquote :: Parser Text Char
nonquote = (Char -> Bool) -> Parser Text Char
P.satisfy (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'"')
    naked :: Parser Text Char
naked = (Char -> Bool) -> Parser Text Char
P.satisfy (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> String -> Bool) -> String -> Char -> Bool
forall a b c. (a -> b -> c) -> b -> a -> c
flip Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem (String
"\" " :: String))