OptParse.curry 14.7 KB
Newer Older
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
module OptParse 
  ( Arg
  , Parser
  , ParseSpec
  , ArgProps
  , OptProps
  , Mod
  , long
  , short
  , optional
  , metavar
  , help
  , (<>)
  , (<.>)
  , (<|>)
  , option
  , arg
  , rest
  , flag
  , command
  , commands
  , optParser
  , printUsage
  , renderUsage
  , parse
  ) where
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
27 28 29 30 31 32 33

import Debug
import Char (isAscii)
import List (intercalate)
import qualified DetParse as P
import qualified Boxes as B

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
34 35 36 37 38
--- A command line argument. Used to represent a parsed command line.
--- 
--- @cons Flag - a flag, e.g. `-d` or `--enable-debug`
--- @cons Val - a simple value
--- @cons FlagWithValue - an option, e.g. `-v debug` or `--verbosity=debug`
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
39 40 41 42
data Arg = Flag String
         | Val  String
         | FlagWithValue String String

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
43 44 45 46 47 48 49
--- A partial command line parser. 
---
--- @cons OptP - parses an option with a value
--- @cons FlagP - parses a flag
--- @cons ArgP - parses a positional argument
--- @cons RestP - a parses that consumes the rest
--- @cons CmdP - a parser that branches out to command sub-parsers
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
50 51 52
data Parser a = OptP ArgProps OptProps (String -> a)
              | FlagP ArgProps OptProps a
              | ArgP ArgProps (String -> a)
53
              | RestP ArgProps (String -> a)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
54 55
              | CmdP ArgProps [(String, ArgProps, a, ParseSpec a)]

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
56
--- A parser specification. A collection of parsers.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
57 58
data ParseSpec a = ParseSpec [Parser a]

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
59
--- Properties that for all parser types.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
60 61 62 63 64
data ArgProps = ArgProps
  { metavarName :: String
  , helpText :: Maybe String 
  , argOptional :: Bool }

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
65
--- Properties for option/flag parsers.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
66 67 68 69
data OptProps = OptProps
  { longName :: String
  , shortName :: String }

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
70
--- Modifiers for argument and option properties.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
71 72 73 74
data Mod = Mod 
  { optMod :: OptProps -> OptProps
  , argMod :: ArgProps -> ArgProps }

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
75
--- Get argument properties from a parser.
76 77 78 79 80 81 82
argProps :: Parser a -> ArgProps
argProps (OptP a _ _) = a
argProps (FlagP a _ _) = a
argProps (ArgP a _) = a
argProps (RestP a _) = a
argProps (CmdP a _) = a

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
83
--- Default argument properties.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
84 85 86
defaultArgProps :: ArgProps
defaultArgProps = ArgProps "" Nothing False

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
87
--- Default option properties.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
88 89 90
defaultOptProps :: OptProps
defaultOptProps = OptProps "" ""

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
91 92 93 94 95
--- Identity modifiers.
idm :: Mod
idm = Mod id id

--- Set the long name of an option.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
96 97 98
long :: String -> Mod
long s = Mod (\o -> o { longName = s }) id

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
99
--- Set the short name of an option.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
100 101 102
short :: String -> Mod
short s = Mod (\o -> o { shortName = s }) id

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
103
--- Set the optional flag of an argument.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
104 105 106
optional :: Mod
optional = Mod id (\a -> a { argOptional = True })

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
107 108
--- Set the metavar of an argument. The metavar is used to print usage 
--- information.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
109 110 111
metavar :: String -> Mod
metavar s = Mod id (\a -> a { metavarName = s })

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
112
--- Set the help text of an argument. Used to print usage information.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
113 114 115
help :: String -> Mod
help s = Mod id (\a -> a { helpText = Just s })

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
116
--- Combine two modifiers.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
117 118 119
(<>) :: Mod -> Mod -> Mod
(Mod o1 a1) <> (Mod o2 a2) = Mod (o2 . o1) (a2 . a1)

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
120 121 122 123 124 125 126 127 128 129 130
--- Create an option.
---
--- @param f function that converts the parsed value into a parse result
--- @param m modifiers for this argument
option :: (String -> a) -> Mod -> [Parser a]
option f (Mod o a) = [OptP (a defaultArgProps) (o defaultOptProps) f]

--- Create a positional argument.
---
--- @param f function that converts the parsed value into a parse result
--- @param m modifiers for this argument
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
131 132 133
arg :: (String -> a) -> Mod -> [Parser a]
arg f (Mod _ a) = [ArgP (a defaultArgProps) f]

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
134 135 136 137
--- Create an argument that consumes the rest of the command line.
  ---
--- @param f function that converts the parsed value into a parse result
--- @param m modifiers for this argument
138 139 140
rest :: (String -> a) -> Mod -> [Parser a]
rest f (Mod _ a) = [RestP (a defaultArgProps) f]

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
141 142 143 144
--- Create a flag.
---
--- @param a result of the parser
--- @param m modifiers for this argument
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
145 146 147
flag :: a -> Mod -> [Parser a]
flag f (Mod o a) = [FlagP (a defaultArgProps) (o defaultOptProps) f]

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
148 149 150 151
infixl 4 <.>
infixl 5 <|>

--- Combine two arguments.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
152 153 154
(<.>) :: [a] -> [a] -> [a]
(<.>) = (++)

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
155
--- Combine command sub parsers.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
156 157 158
(<|>) :: [a] -> [a] -> [a]
(<|>) = (++)

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
159 160 161 162 163 164
--- Create a sub-parser for a command. Must be used with `commands`.
--- 
--- @param n the name of the command
--- @param m modifiers for this command
--- @param a the result of this parse
--- @param ps parsers for the rest of the command line for this command
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
165 166 167
command :: String -> Mod -> a -> [Parser a] -> [(String, ArgProps, a, ParseSpec a)]
command n (Mod _ a) d ps = [(n, a defaultArgProps, d, ParseSpec ps)]

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
168 169 170
--- Create a parse spec from a list of parsers.
optParser :: [Parser a] -> ParseSpec a
optParser = ParseSpec
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
171

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
172 173 174 175
--- Create a command parser.
---
--- @param m modifiers for this command
--- @param cs command sub-parsers, created by `command`
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
176 177 178
commands :: Mod -> [(String, ArgProps, a, ParseSpec a)] -> [Parser a]
commands (Mod _ a) cmds = [CmdP (a defaultArgProps) cmds]

179 180 181
margin :: Int
margin = 5

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
182 183 184 185 186
--- Print usage information for a command line parser specification.
--- 
--- @param p the name of the current program
--- @param c the maximum number of columns to use
--- @param p the parser specification
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
187 188 189
printUsage :: String -> Int -> ParseSpec a -> IO ()
printUsage prog w spec = B.printBox $ usageBox prog w spec

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
190 191 192 193 194
--- Render usage information to a string.
---
--- @param p the name of the current program
--- @param c the maximum number of columns to use
--- @param p the parser specification
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
195 196 197
renderUsage :: String -> Int -> ParseSpec a -> String
renderUsage prog w spec = B.render $ usageBox prog w spec

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
198 199 200 201 202
--- Create a box for usage information.
---
--- @param p the name of the current program
--- @param c the maximum number of columns to use
--- @param p the parser specification
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
203 204 205 206 207 208
usageBox :: String -> Int -> ParseSpec a -> B.Box
usageBox prog w (ParseSpec ps) = usageLine B./+/ optBox B./+/ argBox B./+/ cmdsBox
 where
  opts = filter isOpt ps
  args = filter isArg ps
  cmds = filter isCmd ps
209 210
  formattedArgs = map formatArgForUsage $ filter (not . isOpt) ps
  usageLine = B.text prog B.<+> (B.text $ intercalate " " $ formattedArgs)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
211
  maxOptLen = foldl max 0 $ map optLen opts
212 213
  optBox = B.table (map optRow opts) [
    maxOptLen + margin, w - maxOptLen - margin]
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
214
  maxArgLen = foldl max 0 $ map posnLen args
215 216
  argBox = B.table (map argRow args) [
    maxArgLen + margin, w - maxArgLen - margin]
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
217 218 219
  maxCmdsLen = foldl max 0 $ map cmdsLen cmds
  cmdsBox = B.vcat B.left $ (map (cmdsRows maxCmdsLen w) cmds) 

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
220
--- Render an argument for the usage line.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
221
formatArgForUsage :: Parser a -> String
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
222
formatArgForUsage p = wrap $ argMetavar p
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
223 224 225
 where
  wrap s = if isOptional p then "[" ++ s ++ "]" else s

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
226
--- Render detailed help for an option/flag.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
227
optRow :: Parser a -> [String]
228
optRow (OptP a o _) = [sh ++ " " ++ lo ++ " " ++ metavarName a, hlp]
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
229
 where 
230 231 232
  sh = "-" ++ (shortName o) ++ (if longName o /= "" then "," else "")
  lo = "--" ++ (longName o)
  hlp = case helpText a of 
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
233 234
    Nothing -> ""
    Just h  -> h
235
optRow (FlagP a o _) = [sh ++ " " ++ lo ++ " " ++ metavarName a, hlp]
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
236
 where 
237 238 239
  sh = "-" ++ (shortName o) ++ (if longName o /= "" then "," else "")
  lo = "--" ++ (longName o)
  hlp = case helpText a of 
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
240 241
    Nothing -> ""
    Just h  -> h
242 243 244
optRow (ArgP _ _)  = error "OptParse.optRow: called on ArgP"
optRow (RestP _ _) = error "OptParse.optRow: called on RestP"
optRow (CmdP _ _)  = error "OptParse.optRow: called on CmdP"
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
245

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
246
--- Render detailed help for a positional argument.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
247
argRow :: Parser a -> [String]
248
argRow (ArgP a _) = [metavarName a, hlp]
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
249
 where
250
  hlp = case helpText a of
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
251 252
    Nothing -> ""
    Just  h -> h 
253
argRow (RestP a _) = [metavarName a, hlp]
254
 where
255
  hlp = case helpText a of
256 257
    Nothing -> ""
    Just  h -> h 
258 259 260
argRow (OptP _ _ _)  = error "OptParse.argRow: called on OptP"
argRow (FlagP _ _ _) = error "OptParse.argRow: called on FlagP"
argRow (CmdP _ _)    = error "OptParse.argRow: called on CmdP"
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
261

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
262
--- Render detailed help for a command parser.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
263
cmdsRows :: Int -> Int -> Parser a -> B.Box
264
cmdsRows max w (CmdP a cmds) = hdr B.// tbl
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
265
 where
266 267 268 269
  hdr = B.text $ "Options for " ++ (metavarName a)
  tbl = B.table (map cmdRow cmds) [max + margin, w - max - margin]
  cmdRow (n, x, _, _) = [n, getHelp x]
  getHelp x = case helpText x of
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
270 271
    Nothing -> ""
    Just  h -> h
272 273 274 275
cmdsRows _ _ (OptP _ _ _)  = error "OptParse.cmdsRows: called on OptP"
cmdsRows _ _ (FlagP _ _ _) = error "OptParse.cmdsRows: called on FlagP"
cmdsRows _ _ (ArgP _ _)    = error "OptParse.cmdsRows: called on ArgP"
cmdsRows _ _ (RestP _ _)   = error "OptParse.cmdsRows: called on RestP"
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
276

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
277
--- Calculate maximum command length for a command parser.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
278
cmdsLen :: Parser a -> Int
279
cmdsLen (CmdP _ cmds) = foldl max 0 $ map cmdsLen' cmds
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
280 281
 where
  cmdsLen' (n, _, _, _) = length n
282 283
cmdsLen (ArgP _ _)    = 0
cmdsLen (OptP _ _ _)  = 0
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
284
cmdsLen (FlagP _ _ _) = 0
285
cmdsLen (RestP _ _)   = 0
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
286

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
287
--- Length of a positional argument name.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
288
posnLen :: Parser a -> Int
289 290 291
posnLen (ArgP a _)    = length (metavarName a)
posnLen (CmdP _ _)    = 0
posnLen (OptP _ _ _)  = 0
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
292
posnLen (FlagP _ _ _) = 0
293
posnLen (RestP _ _)   = 0
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
294 295

optLen' :: ArgProps -> OptProps -> Int
296 297
optLen' a o = length (shortName o) + 2 + length (longName o) + 3 + 
  length (metavarName a) + 2
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
298

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
299
--- Length needed to represent a option/flag detailed help.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
300
optLen :: Parser a -> Int
301
optLen (OptP a o _)  = optLen' a o
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
302
optLen (FlagP a o _) = optLen' a o
303 304 305
optLen (ArgP _ _)    = 0
optLen (CmdP _ _)    = 0
optLen (RestP _ _)   = 0 
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
306

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
307 308 309 310 311
--- Parses a command line via a parser spec.
--- 
--- @param l the command line
--- @param s the parser spec
--- @param p the name of the current program
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
312 313 314 315 316
parse :: String -> ParseSpec a -> String -> Either String [a]
parse argv spec prog = case P.parse pArgs argv of
  Nothing -> Left $ parseError prog spec "Couldn't parse command line!"
  Just as -> parseArgs as spec prog

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
317 318 319 320 321 322
--- Renders parsed parts of a command line back to a string.
renderCommandLine :: [Arg] -> String
renderCommandLine [] = []
renderCommandLine ((Val s):as) = s ++ " " ++ (renderCommandLine as)
renderCommandLine ((FlagWithValue n v):as) = 
  "--" ++ n ++ "=" ++ v ++ " " ++ (renderCommandLine as)
323
renderCommandLine ((Flag n):as) = "--" ++ n ++ " " ++ (renderCommandLine as)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
324 325 326 327 328 329

--- Further parses a parsed command line using a parser spec.
--- 
--- @param as parsed command line
--- @param s parser spec
--- @param p name of the current program
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
330
parseArgs :: [Arg] -> ParseSpec a -> String -> Either String [a]
331
parseArgs args sp@(ParseSpec specs) prog = parse' args rst []
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
332 333
 where
  opts = filter isOpt specs
334
  rst = filter (not . isOpt) specs
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
335
  parse' ((Val s):as) (p:ps) xs = case p of
336
    ArgP _ f    -> parse' as ps ((f s):xs)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
337
    RestP _ f   -> parse' [] (p:ps) ((f $ renderCommandLine ((Val s):as)):xs)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
338 339 340 341 342
    CmdP _ cmds -> case findCommand s cmds of
      Nothing -> Left $ parseError prog sp $ "Unknown command '" ++ s ++ "'."
      Just (cmd, _, d, spec) -> case parseArgs as spec (prog ++ " " ++ cmd) of
        Left e -> Left e
        Right xs' -> Right $ xs ++ [d] ++ xs'
343
    OptP _ _ _  -> error "OptP in list of positional candidates"
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
344 345 346
    FlagP _ _ _ -> error "FlagP in list of positional candidates"
  parse' ((FlagWithValue n v):as) ps xs = case filter (optMatches n) opts of
    [] -> Left $ parseError prog sp $ "Unknown option '" ++ n ++ "'."
347 348 349 350 351
    ((OptP _ _ f):_)  -> parse' as ps ((f v):xs)
    ((ArgP _ _):_)    -> error $ "OptParse.parseArgs: ArgP matches opt " ++ n
    ((FlagP _ _ _):_) -> error $ "OptParse.parseArgs: FlagP matches opt " ++ n
    ((RestP _ _):_)   -> error $ "OptParse.parseArgs: RestP matches opt " ++ n
    ((CmdP _ _):_)    -> error $ "OptParse.parseArgs: CmdP matches opt " ++ n
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
352 353 354 355
  parse' ((Flag n):as) ps xs = if n == "h" || n == "help"
    then Left $ parseError prog sp ""
    else case filter (optMatches n) opts of
      [] -> Left $ parseError prog sp $ "Unknown option '" ++ n ++ "'."
356 357
      ((OptP _ _ f):_)  -> case as of
        ((Val v):as')   -> parse' as' ps ((f v):xs)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
358 359
        _           -> Left $ parseError prog sp $ "Option '" ++ n ++ "' expects a value."
      ((FlagP _ _ f):_) -> parse' as ps (f:xs)
360 361 362 363
      ((ArgP _ _):_)    -> error $ "OptParse.parseArgs: ArgP matches flag " ++ n
      ((RestP _ _):_)   -> error $ "OptParse.parseArgs: RestP matches flag " ++ n
      ((CmdP _ _):_)    -> error $ "OptParse.parseArgs: CmdP matches flag " ++ n
  parse' ((Val _):as) []    xs = parse' as [] xs
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
364 365
  parse' []           (p:ps) xs = if isOptional p
    then parse' [] ps xs
366 367
    else Left $ parseError prog sp $ 
      "Expected " ++ (argMetavar p) ++ ", but there are no arguments left."
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
368 369
  parse' []           []    xs = Right xs

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
370 371 372 373 374
--- Renders a parse error.
--- 
--- @param p name of the current program
--- @param s parser spec
--- @param e error that occured
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
375 376 377
parseError :: String -> ParseSpec a -> String -> String
parseError prog spec err = renderUsage prog 80 spec ++ "\n" ++ err

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
378
--- Gets the metavar from a parser.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
379
argMetavar :: Parser a -> String
380
argMetavar = metavarName . argProps
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
381

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
382
--- Does an option/flag match a name?
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
383
optMatches :: String -> Parser a -> Bool
384 385 386 387
optMatches _ (ArgP _ _)    = False
optMatches _ (CmdP _ _)    = False
optMatches _ (RestP _ _)   = False
optMatches n (OptP _ o _)  = n == (longName o) || n == (shortName o)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
388 389
optMatches n (FlagP _ o _) = n == (longName o) || n == (shortName o)

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
390
--- Is the argument optional?
391 392
isOptional :: Parser a -> Bool
isOptional = argOptional . argProps
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
393

Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
394
--- Finds a command in a list of command specs.
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
395 396 397 398 399 400 401 402 403 404 405
findCommand :: String -> [(String, ArgProps, a, ParseSpec a)] -> Maybe (String, ArgProps, a, ParseSpec a)
findCommand s cmds = case cmd of
  [] -> Nothing
  (c:_) -> Just c
 where
  cmd = filter ((== s) . fst3) cmds
  fst3 (a, _, _, _) = a

isOpt :: Parser a -> Bool
isOpt (OptP _ _ _) = True
isOpt (ArgP _ _) = False
406
isOpt (RestP _ _) = False
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
407 408 409 410 411 412
isOpt (CmdP _ _) = False
isOpt (FlagP _ _ _) = True

isArg :: Parser a -> Bool
isArg (OptP _ _ _) = False
isArg (ArgP _ _) = True
413
isArg (RestP _ _) = True
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
414 415 416 417 418 419
isArg (CmdP _ _) = False
isArg (FlagP _ _ _) = False

isCmd :: Parser a -> Bool
isCmd (OptP _ _ _) = False
isCmd (ArgP _ _) = False
420
isCmd (RestP _ _) = False
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
isCmd (CmdP _ _) = True
isCmd (FlagP _ _ _) = False

pArgs :: P.Parser [Arg]
pArgs = (:) P.<$> pArg P.<*> (pWhitespace P.*> pArgs P.<|> P.yield [])

pWhitespace :: P.Parser Char
pWhitespace = P.check (== ' ') P.anyChar

pArg :: P.Parser Arg
pArg = Flag P.<$> pFlagNoValue
  P.<|> pFlagValue
  P.<|> Val P.<$> P.some pNonWhitespace

pFlagValue :: P.Parser Arg
436 437 438
pFlagValue = FlagWithValue 
  P.<$> (P.char '-' P.*> P.char '-' P.*> P.some pNonWhitespace) 
  P.<*> (P.char '=' P.*> P.some pNonWhitespace)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
439 440

pFlagNoValue :: P.Parser String
441 442
pFlagNoValue = P.char '-' 
  P.*> ((P.char '-' P.*> P.some pNonWhiteEqual) P.<|> pAsciiNonWhitespace)
Jonas Oberschweiber's avatar
Jonas Oberschweiber committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458

pNonWhiteEqual :: P.Parser Char
pNonWhiteEqual = P.check f P.anyChar
 where
  f c = c /= ' ' && c /= '='

pNonWhitespace :: P.Parser Char
pNonWhitespace = P.check (/= ' ') P.anyChar

pAsciiNonWhitespace :: P.Parser String
pAsciiNonWhitespace = (:[]) P.<$> P.check f P.anyChar
 where
  f c = isAscii c && c /= ' '

pAscii :: P.Parser Char
pAscii = P.check isAscii P.anyChar