% % @(#) $Docid: Aug. 15th 2001 14:55 Sigbjorn Finne $ % @(#) $Contactid: sof@galconn.com $ % A command-line options library - sof 1/97 [Updated to use more Haskell 1.4 features -- sof 1/98] \begin{code} module GetOpt ( Opt(..) -- instance Functor, Monad, MonadZero, MonadPlus {- The Opt monad primitives: -} -- add another item (to the front) , pushArg -- :: String -> Opt a () -- transform the threaded state , updState -- :: (a -> a) -> Opt a () -- aka zero , failed -- :: Opt a b -- Opt try , catchOpt -- :: Opt a b -> Opt a b -> Opt a b {- some useful Opt matchers: -} -- match if string is prefix of current element. , prefixMatch -- :: String -> Opt a String , prefixed -- :: String -> Opt a b -> Opt a b -- if current option matches pred, try Opt argument. , matches -- :: (String -> Bool) -> (String -> Opt a b) -> Opt a b -- test if flag is set , flag -- :: String -> (a -> a) -> Opt a () , flags -- :: [(String,a->a)] -> Opt a () -- n-way disjunction , opts -- :: [Opt a b] -> Opt a b , orOpt -- :: Opt a b -> Opt a b -> Opt a b , thenOpt -- :: Opt a b -> Opt a b -> Opt a b -- try matching --{disable,enable}-foo , toggle -- :: String -- -> String -- -> String -- -> (Bool -> a -> a) -- -> Opt a () , toggles -- :: String -- -> String -- -> [(String,Bool -> a->a)] -- -> Opt a () -- try matching -ifoo (where -i is the prefix) , prefixArg -- :: String -> (String -> a -> a) -> Opt a () -- try matching -o foo , optionArg -- :: String -> (String -> Opt a b) -> Opt a b , optionWithOptArg -- :: String -> Opt a b -> Opt a b -- exact string match , string -- :: String -> Opt a () -- useful combinators for when using attribute-lists -- to gather options , (-=) -- :: String -> a -> Opt [a] () , (-==) -- :: String -> (String -> a) -> Opt [a] () , (-===) -- :: String -> (String -> a) -> Opt [a] () , (-====) -- :: String -> (Maybe String -> a) -> Opt [a] () , (-?) -- :: (String -> Bool) -> (String -> a) -> Opt [a] () -- Do the actual matching. , getOpts -- :: Opt a b -> a -> [String] -> ([String],a) ) where import Utils ( prefix ) import Monad infixr 1 `bindOpt`, `seqOpt` -- needed for older Hugsen. infixl 9 `orOpt` \end{code} Use a monad to encode the matching operations we want to do on the command line contents, threading a value that will record what we've seen so far plus the remainder of the command-line. \begin{code} data Opt a b = Opt ([String] -> a -> Maybe ([String],a,b)) -- bind & return over Opt bindOpt :: Opt a b -> (b -> Opt a c) -> Opt a c bindOpt (Opt opt_a) fopt = Opt (\ args st -> case opt_a args st of Nothing -> Nothing Just (args',st',v) -> case fopt v of Opt opt_b -> opt_b args' st') seqOpt :: Opt a b -> Opt a c -> Opt a c seqOpt a b = a `bindOpt` (\ _ -> b) returnOpt :: b -> Opt a b returnOpt v = Opt (\ args st -> Just (args,st,v)) {- The Opt primitives for pop and push of cmd line options, plus primitive for updating the threaded state. -} pushArg :: String -> Opt a () pushArg str = Opt (\ args st -> Just (str:args,st,())) popArg :: Opt a String popArg = Opt (\ args st -> case args of [] -> Nothing (x:xs) -> Just (xs,st,x) ) updState :: (a -> a) -> Opt a () updState f = Opt (\ args st -> Just (args, f st, ())) {- result :: a -> Opt a () result v = updState (\ _ -> v) -} -- a not-that-useful operation on Opt. mapOpt :: (b -> c) -> Opt a b -> Opt a c mapOpt f (Opt opt) = Opt (\ args st -> case opt args st of Nothing -> Nothing Just (args',st',v) -> Just (args',st',f v)) -- Let's overload! instance Monad (Opt s) where a >>= b = bindOpt a b return = returnOpt instance Functor (Opt s) where fmap = mapOpt instance MonadPlus (Opt s) where mplus = thenOpt mzero = failed -- no match. failed :: Opt a b failed = Opt (\ _ _ -> Nothing) -- try left, if not successful, give right a spin. catchOpt :: Opt a b -> Opt a b -> Opt a b catchOpt (Opt opt_a) (Opt opt_b) = Opt (\ args st -> case opt_a args st of Nothing -> opt_b args st Just x -> Just x) \end{code} Scanning a list of command-line options using an Opt action that encodes what's interesting and worth noting. ToDo: add error support (in the monad?) \begin{code} getOpts :: Opt a b -> a -> [String] -> ([String],a) getOpts _ st [] = ([],st) getOpts o@(Opt opt) st args@(x:xs) = case opt args st of Nothing -> let (args',st') = getOpts o st xs in (x:args',st') Just (args',st',_) -> getOpts o st' args' \end{code} A number of useful matching combinators for command-line options follow: \begin{code} prefixMatch :: String -> Opt a String prefixMatch str = do arg <- popArg case prefix str arg of Nothing -> failed Just arg' -> return arg' prefixed :: String -> Opt a b -> Opt a b prefixed pre n_opt = do arg <- prefixMatch pre -- push back what's left of the option, and continue. pushArg arg n_opt matches :: (String -> Bool) -> (String -> Opt a b) -> Opt a b matches matcher opt = do arg <- popArg if matcher arg then opt arg else failed flag :: String -> (a -> a) -> Opt a () flag str f = do arg <- popArg case prefix str arg of Nothing -> failed Just{} -> updState f opts :: [Opt a b] -> Opt a b opts ls = foldl1 (orOpt) ls orOpt :: Opt a b -> Opt a b -> Opt a b orOpt = catchOpt thenOpt :: Opt a b -> Opt a b -> Opt a b thenOpt opt_a opt_b = opt_a `seqOpt` opt_b \end{code} \begin{code} flags :: [(String,a->a)] -> Opt a () flags ls = opts (map (\ (str,f) -> flag str f) ls) toggle :: String -> String -> String -> (Bool -> a -> a) -> Opt a () toggle on off str f = ((prefixed on (returnOpt True)) `orOpt` (prefixed off (returnOpt False))) >>= \ flg -> prefixed str (popArg >> updState (f flg)) toggles :: String -> String -> [(String,Bool -> a->a)] -> Opt a () toggles on off ls = opts (map (\ (str,f) -> toggle on off str f) ls) prefixArg :: String -> (String -> a -> a) -> Opt a () prefixArg str f = do arg <- popArg case prefix str arg of Nothing -> failed Just arg' -> updState (f arg') optionArg :: String -> (String -> Opt a b) -> Opt a b optionArg str f = do -- get current option arg <- popArg case prefix str arg of Nothing -> failed Just{} -> do -- get option value arg' <- popArg f arg' optionWithOptArg :: String -> Opt a b -> Opt a b optionWithOptArg str f = do arg <- popArg case prefix str arg of Nothing -> failed Just _ -> f string :: String -> Opt a () string str = do rest <- prefixMatch str case rest of [] -> returnOpt () _ -> failed (-=) :: String -> a -> Opt [a] () (-=) str v = flag str (v:) (-==) :: String -> (String -> a) -> Opt [a] () (-==) str f = prefixArg str (\ ls -> ((f ls):)) (-===) :: String -> (String -> a) -> Opt [a] () (-===) str f = optionArg str (\ val -> updState ((f val):)) (-====) :: String -> (Maybe String -> a) -> Opt [a] () (-====) str f = optionWithOptArg str (popArg >>= \ val -> updState ((f (Just val)):)) (-?) :: (String -> Bool) -> (String -> a) -> Opt [a] () (-?) matcher f = matches matcher (\ ls -> updState ((f ls):)) \end{code}