module GetOpt.Declarative.Interpret ( ParseResult(..) , parseCommandLineOptions , parse , interpretOptions ) where import Prelude () import Test.Hspec.Core.Compat import System.Console.GetOpt (OptDescr, ArgOrder(..), getOpt) import qualified System.Console.GetOpt as GetOpt import GetOpt.Declarative.Types import GetOpt.Declarative.Util (mkUsageInfo) data InvalidArgument = InvalidArgument String String data ParseResult config = Help String | Failure String | Success config parseCommandLineOptions :: [(String, [Option config])] -> String -> [String] -> config -> ParseResult config parseCommandLineOptions opts prog args config = case parseWithHelp (concatMap snd options) config args of Nothing -> Help usage Just (Right c) -> Success c Just (Left err) -> Failure $ prog ++ ": " ++ err ++ "\nTry `" ++ prog ++ " --help' for more information.\n" where options = addHelpFlag $ map (fmap interpretOptions) opts documentedOptions = addHelpFlag $ map (fmap $ interpretOptions . filter optionDocumented) opts usage :: String usage = "Usage: " ++ prog ++ " [OPTION]...\n\n" ++ intercalate "\n" (map (uncurry mkUsageInfo) documentedOptions) addHelpFlag :: [(a, [OptDescr a1])] -> [(a, [OptDescr (Maybe a1)])] addHelpFlag opts = case opts of (section, xs) : ys -> (section, GetOpt.Option [] ["help"] (GetOpt.NoArg help) "display this help and exit" : noHelp xs) : map (fmap noHelp) ys [] -> [] where help = Nothing noHelp :: [OptDescr a] -> [OptDescr (Maybe a)] noHelp = map (fmap Just) parseWithHelp :: [OptDescr (Maybe (config -> Either InvalidArgument config))] -> config -> [String] -> Maybe (Either String config) parseWithHelp options config args = case getOpt Permute options args of (opts, [], []) | _ : _ <- [() | Nothing <- opts] -> Nothing (opts, xs, ys) -> Just $ interpretResult config (catMaybes opts, xs, ys) parse :: [OptDescr (config -> Either InvalidArgument config)] -> config -> [String] -> Either String config parse options config = interpretResult config . getOpt Permute options interpretResult :: config -> ([config -> Either InvalidArgument config], [String], [String]) -> Either String config interpretResult config = interpretGetOptResult >=> foldResult config foldResult :: config -> [config -> Either InvalidArgument config] -> Either String config foldResult config opts = either (Left . renderInvalidArgument) return $ foldlM (flip id) config opts renderInvalidArgument :: InvalidArgument -> String renderInvalidArgument (InvalidArgument name value) = "invalid argument `" ++ value ++ "' for `--" ++ name ++ "'" interpretGetOptResult :: ([a], [String], [String]) -> Either String [a] interpretGetOptResult result = case result of (opts, [], []) -> Right opts (_, _, err:_) -> Left (init err) (_, arg:_, _) -> Left ("unexpected argument `" ++ arg ++ "'") interpretOptions :: [Option config] -> [OptDescr (config -> Either InvalidArgument config)] interpretOptions = concatMap interpretOption interpretOption :: Option config -> [OptDescr (config -> Either InvalidArgument config)] interpretOption (Option name shortcut argDesc help _) = case argDesc of NoArg setter -> [option $ GetOpt.NoArg (Right . setter)] Flag setter -> [ option (arg True) , GetOpt.Option [] ["no-" ++ name] (arg False) ("do not " ++ help) ] where arg v = GetOpt.NoArg (Right . setter v) OptArg argName setter -> [option $ GetOpt.OptArg arg argName] where arg mInput c = case setter mInput c of Just c_ -> Right c_ Nothing -> case mInput of Just input -> invalid input Nothing -> Right c Arg argName setter -> [option (GetOpt.ReqArg arg argName)] where arg input = maybe (invalid input) Right . setter input where invalid = Left . InvalidArgument name option arg = GetOpt.Option (maybeToList shortcut) [name] arg help