parse-help-0.0: System/Console/ParseHelp.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module System.Console.ParseHelp (
parseHelp, ParsedHelp, ParsedFlag(..)
) where
import Data.Attoparsec.Text
import Control.Applicative ((<|>), many)
import Data.Text (Text, unpack, pack)
import qualified Data.Text as T
import Language.Haskell.TH.Lift
import Prelude hiding (takeWhile)
instance Lift Text where lift = lift . unpack
data ParsedFlag = ParsedFlag {
parsedFlagShort :: Maybe Char,
parsedFlagLong :: Maybe Text,
parsedFlagType :: Maybe Text,
parsedFlagDescription :: Maybe Text
} deriving (Show)
deriveLift ''ParsedFlag
type ParsedMode = (Text, -- ^ program name
Text, -- ^ mode name
[ParsedFlag])
type ParsedCommonMode = (Text, [ParsedFlag]) -- no mode name
parseHelp :: String -> Either String ParsedHelp
parseHelp s =
case attoParser (pack s) of
Left e -> Left e
Right pHelp -> Right $ prepareParsedHelp pHelp
attoParser :: Text -> Either String (Maybe Text, -- ^ program description
Maybe ParsedCommonMode,
[ParsedMode])
attoParser input = parseOnly commandArgs input
where
commandArgs = do
description <- maybeManyTill anyChar blankLine
optional blankLines
common <- optionMaybe commonInvocation
optional blankLines
modes <- many modeInvocation
return (description, common, modes)
where
spaces = skipSpace
commonInvocation = do
-- sample [OPTIONS]
name <- commonProgName
optional blankLines
spaces
-- optional blankLines
-- optional $ takeWhile (notInClass "\r\n") >> eol'
optional blankLines
optional $ string "Common flags" >> takeWhile (notInClass "-\r\n") >> eol'
spaces
optional blankLines
opts <- flags
return (name, opts)
modeInvocation = do
(name, mode) <- progName
optional blankLines
opts <- flags
return (name, mode, opts)
betweenChars start end = do
_ <- char start
r <- takeWhile1 (/= end)
_ <- char end
return r
bracketed = betweenChars '[' ']'
commonProgName = do
spaces
t <- takeWhile1 notSpace
spaces
optional $ (bracketed <|> takeWhile1 notBracketed) >> return ()
spaces
optional $ string "..." >> return ()
spaces
_ <- bracketed
return t
notBracketed = notInClass $ '[':whiteSpaceChars
progName = do
spaces
t <- takeWhile1 notSpace
spaces
m <- takeWhile1 notBracketed
spaces
_ <- bracketed
return (t,m)
maybeText = fmap (\t -> if T.null t then Nothing else Just t)
flags = many1 flag
where
flag = do
spaces
ms <- optionMaybe shortFlag
spaces
ml <- optionMaybe longFlag
d <- maybeText (takeTill $ inClass "\r\n")
case (ms, ml) of
(Nothing, Nothing) -> fail "expected flags"
(_, Just (long,typ)) -> return $ ParsedFlag ms (Just long) typ d
(_, Nothing) -> return $ ParsedFlag ms Nothing Nothing d
where
shortFlag = do
_ <- char '-'
satisfy notSpace
longFlag = do
_ <- string "--"
long <- takeWhile1 (notInClass $ '=':whiteSpaceChars)
def <- optionMaybe $ char '=' >> takeWhile1 notSpace
return (long, def)
whiteSpaceChars = " \v\f\t\r\n"
notSpace = notInClass whiteSpaceChars
maybeManyTill p stop = do
r <- manyTill p stop
return $ if r == [] then Nothing else Just $ pack r
blankLines = many1 blankLine >> return ()
blankLine = eol >> eol
eol' = eol >> return ()
eol = endOfLine
optional = option ()
{-
showRest = do
rest <- restOfLine
error $ "rest: " ++ (show rest)
restOfLine = manyTill anyChar eol
-}
optionMaybe p = option Nothing $ (fmap Just) p
type ParsedHelp = (Text, Maybe Text, [ParsedFlag], [(Text, [ParsedFlag])])
prepareParsedHelp :: (Maybe Text, Maybe ParsedCommonMode, [ParsedMode])
-> ParsedHelp
prepareParsedHelp (_, Nothing, []) = error "No command flags found"
prepareParsedHelp (description, mCommon, parsedModes) =
let (name, common) = case mCommon of
Nothing -> (fst3 $ head parsedModes, defaultCommonFlags)
Just (commonName, flags) -> (commonName, flags)
in let modes = map (checkMode name) parsedModes
in (name, description, common, modes)
where
fst3 (x,_,_) = x
checkMode :: Text -> (Text, Text, [ParsedFlag]) -> (Text, [ParsedFlag])
checkMode commonProgName (progName, mode, flags) =
if progName /= commonProgName then error $ "inconsistent program name: expected "++unpack commonProgName++", got "++unpack progName
else (mode, flags)
defaultCommonFlags = [
ParsedFlag (Just '?') (Just $ pack "help") Nothing (Just $ pack "Display help message")
, ParsedFlag (Just 'V') (Just $ pack "version") Nothing (Just $ pack "Print version information")
]
-- makeCmdArgs $ prepareParsedHelp parseHelp
-- check indent level?
{-parseIndent :: Parser Int -}
{-parseIndent =-}
{-sum `fmap` many ((char ' ' >> return 1) <|> (char '\t' >> return 4)) -}