File: Process.hs

package info (click to toggle)
haskell-cmdargs 0.10.14-3
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 348 kB
  • ctags: 1
  • sloc: haskell: 2,972; makefile: 3
file content (150 lines) | stat: -rw-r--r-- 6,230 bytes parent folder | download | duplicates (5)
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
{-# LANGUAGE RecordWildCards #-}
module System.Console.CmdArgs.Explicit.Process(process) where

import System.Console.CmdArgs.Explicit.Type
import Control.Arrow
import Data.List
import Data.Maybe


-- | Process a list of flags (usually obtained from @getArgs@/@expandArgsAt@) with a mode. Returns
--   @Left@ and an error message if the command line fails to parse, or @Right@ and
--   the associated value.
process :: Mode a -> [String] -> Either String a
process = processMode


processMode :: Mode a -> [String] -> Either String a
processMode m args =
    case find of
        Ambiguous xs -> Left $ ambiguous "mode" a xs
        Found x -> processMode x as
        NotFound
            | null (fst $ modeArgs m) && isNothing (snd $ modeArgs m) && args /= [] &&
              not (null $ modeModes m) && not ("-" `isPrefixOf` concat args)
                -> Left $ missing "mode" $ concatMap modeNames $ modeModes m
            | otherwise -> either Left (modeCheck m) $ processFlags m (modeValue m) args
    where
        (find,a,as) = case args of
            [] -> (NotFound,"",[])
            x:xs -> (lookupName (map (modeNames &&& id) $ modeModes m) x, x, xs)


data S a = S
    {val :: a -- The value you are accumulating
    ,args :: [String] -- The arguments you are processing through
    ,argsCount :: Int -- The number of unnamed arguments you have seen
    ,errs :: [String] -- The errors you have seen
    }

stop :: Mode a -> S a -> Maybe (Either String a)
stop mode S{..}
    | not $ null errs = Just $ Left $ last errs
    | null args = Just $ if argsCount >= mn then Right val else
        Left $ "Expected " ++ (if Just mn == mx then "exactly" else "at least") ++ show mn ++ " unnamed arguments, but got only " ++ show argsCount
    | otherwise = Nothing
    where (mn, mx) = argsRange mode

err :: S a -> String -> S a
err s x = s{errs=x:errs s}

upd :: S a -> (a -> Either String a) -> S a
upd s f = case f $ val s of
    Left x -> err s x
    Right x -> s{val=x}


processFlags :: Mode a -> a -> [String] -> Either String a
processFlags mode val_ args_ = f $ S val_ args_ 0 []
    where f s = fromMaybe (f $ processFlag mode s) $ stop mode s


pickFlags long mode = [(filter (\x -> (length x > 1) == long) $ flagNames flag,(flagInfo flag,flag)) | flag <- modeFlags mode]


processFlag :: Mode a -> S a -> S a
processFlag mode s_@S{args=('-':'-':xs):ys} | xs /= "" =
    case lookupName (pickFlags True mode) a of
        Ambiguous poss -> err s $ ambiguous "flag" ("--" ++ a) poss
        NotFound -> err s $ "Unknown flag: --" ++ a
        Found (arg,flag) -> case arg of
            FlagNone | null b -> upd s $ flagValue flag ""
                     | otherwise -> err s $ "Unhandled argument to flag, none expected: --" ++ xs
            FlagReq | null b && null ys -> err s $ "Flag requires argument: --" ++ xs
                    | null b -> upd s{args=tail ys} $ flagValue flag $ head ys
                    | otherwise -> upd s $ flagValue flag $ tail b
            _ | null b -> upd s $ flagValue flag $ fromFlagOpt arg
              | otherwise -> upd s $ flagValue flag $ tail b
    where
        s = s_{args=ys}
        (a,b) = break (== '=') xs


processFlag mode s_@S{args=('-':x:xs):ys} | x /= '-' =
    case lookupName (pickFlags False mode) [x] of
        Ambiguous poss -> err s $ ambiguous "flag" ['-',x] poss
        NotFound -> err s $ "Unknown flag: -" ++ [x]
        Found (arg,flag) -> case arg of
            FlagNone | "=" `isPrefixOf` xs -> err s $ "Unhandled argument to flag, none expected: -" ++ [x]
                     | otherwise -> upd s_{args=['-':xs|xs/=""] ++ ys} $ flagValue flag ""
            FlagReq | null xs && null ys -> err s $ "Flag requires argument: -" ++ [x]
                    | null xs -> upd s_{args=tail ys} $ flagValue flag $ head ys
                    | otherwise -> upd s_{args=ys} $ flagValue flag $ if "=" `isPrefixOf` xs then tail xs else xs
            FlagOpt x | null xs -> upd s_{args=ys} $ flagValue flag x
                      | otherwise -> upd s_{args=ys} $ flagValue flag $ if "=" `isPrefixOf` xs then tail xs else xs
            FlagOptRare x | "=" `isPrefixOf` xs -> upd s_{args=ys} $ flagValue flag $ tail xs
                          | otherwise -> upd s_{args=['-':xs|xs/=""] ++ ys} $ flagValue flag x
    where
        s = s_{args=ys}


processFlag mode s_@S{args="--":ys} = f s_{args=ys}
    where f s | isJust $ stop mode s = s
              | otherwise = f $ processArg mode s

processFlag mode s = processArg mode s

processArg mode s_@S{args=x:ys, argsCount=count} = case argsPick mode count of
    Nothing -> err s $ "Unhandled argument, " ++ str ++ " expected: " ++ x
        where str = if count == 0 then "none" else "at most " ++ show count
    Just arg -> case argValue arg x (val s) of
            Left e -> err s $ "Unhandled argument, " ++ e ++ ": " ++ x
            Right v -> s{val=v}
    where
        s = s_{args=ys, argsCount=count+1}


-- find the minimum and maximum allowed number of arguments (Nothing=infinite)
argsRange :: Mode a -> (Int, Maybe Int)
argsRange Mode{modeArgs=(lst,end)} = (mn,mx)
    where mn = length $ dropWhile (not . argRequire) $ reverse $ lst ++ maybeToList end
          mx = if isJust end then Nothing else Just $ length lst


argsPick :: Mode a -> Int -> Maybe (Arg a)
argsPick Mode{modeArgs=(lst,end)} i = if i < length lst then Just $ lst !! i else end


---------------------------------------------------------------------
-- UTILITIES

ambiguous typ got xs = "Ambiguous " ++ typ ++ " '" ++ got ++ "', could be any of: " ++ unwords xs
missing typ xs = "Missing " ++ typ ++ ", wanted any of: " ++ unwords xs


data LookupName a = NotFound
                  | Ambiguous [Name]
                  | Found a

-- different order to lookup so can potentially partially-apply it
lookupName :: [([Name],a)] -> Name -> LookupName a
lookupName names value =
    case (match (==), match isPrefixOf) of
        ([],[]) -> NotFound
        ([],[x]) -> Found $ snd x
        ([],xs) -> Ambiguous $ map fst xs
        ([x],_) -> Found $ snd x
        (xs,_) -> Ambiguous $ map fst xs
    where
        match op = [(head ys,v) | (xs,v) <- names, let ys = filter (op value) xs, ys /= []]