multiarg 0.4.0.0 → 0.6.0.0
raw patch · 7 files changed
+201/−124 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ System.Console.MultiArg.Combinator: ChoiceArg :: [(String, a)] -> ArgSpec a
+ System.Console.MultiArg.Combinator: ThreeArg :: (String -> String -> String -> a) -> ArgSpec a
+ System.Console.MultiArg.Combinator: instance Functor ArgSpec
+ System.Console.MultiArg.Combinator: instance Functor OptSpec
Files
- NEWS +7/−0
- System/Console/MultiArg.hs +13/−2
- System/Console/MultiArg/Combinator.hs +129/−71
- System/Console/MultiArg/Option.hs +7/−6
- System/Console/MultiArg/Prim.hs +23/−23
- System/Console/MultiArg/SimpleParser.hs +20/−20
- multiarg.cabal +2/−2
NEWS view
@@ -30,3 +30,10 @@ complicated. * Reworked included combinators in Combinator module.++Release 0.6.0.0, September 28, 2012+Changes since release 0.4.0.0:++* Add Functor instances for OptSpec, ArgSpec++* Add ThreeArg and ChoiceArg ArgSpecs
System/Console/MultiArg.hs view
@@ -19,7 +19,7 @@ -- vocabularies, from simple to complex. -- * Terminology- + -- | Some terms are used throughout multiarg: -- -- [@word@] When you run your program from the Unix shell prompt,@@ -96,7 +96,7 @@ -- can parse @l@ as a short option. This would result in a -- \"pending\" of @20@. Then, the next parser can treat @20@ as an -- option argument. After that parse there will be no pendings.- + -- * Getting started -- |If your needs are simple to moderately complicated just look at the@@ -145,6 +145,17 @@ -- -- multiarg can be complicated, although I'd like to believe this is -- because it addresses a complicated problem in a flexible way.++ -- * Projects usings multiarg++ -- | * Penny, an extensible double-entry accounting+ -- system. <http://hackage.haskell.org/package/penny-lib> The code+ -- using multiarg is woven throughout the system; for example, see+ -- the Penny.Liberty module.+ --+ -- * multiarg was originally written for Pantry, which I have yet to+ -- polish for release, but which is available on Github at+ -- <https://github.com/massysett/Pantry>. module System.Console.MultiArg.SimpleParser ) where
System/Console/MultiArg/Combinator.hs view
@@ -6,19 +6,21 @@ -- * Parser combinators notFollowedBy, (<?>),- + -- * Combined long and short option parser OptSpec(OptSpec, longOpts, shortOpts, argSpec),- ArgSpec(NoArg, OptionalArg, OneArg, TwoArg, VariableArg),+ ArgSpec(NoArg, OptionalArg, OneArg, TwoArg,+ ThreeArg, VariableArg, ChoiceArg), parseOption,- + -- * Other words matchApproxWord ) where- -import Data.List (isPrefixOf, intersperse)++import Data.List (isPrefixOf, intersperse, nubBy, intercalate) import Data.Set ( Set ) import qualified Data.Set as Set-import Control.Applicative ((<*>), optional, (<$))+import Control.Applicative+ ((<$>), (<*>), optional, (<$), (*>), (<|>), many) import System.Console.MultiArg.Prim ( Parser, throw, try, approxLongOpt,@@ -28,9 +30,9 @@ import System.Console.MultiArg.Option ( LongOpt, ShortOpt, unLongOpt, makeLongOpt, makeShortOpt, unShortOpt )-import Control.Applicative ((<|>), many) import qualified Data.Map as M import Data.Map ((!))+import Data.Maybe (fromMaybe) import Data.Monoid ( mconcat ) @@ -51,7 +53,7 @@ -- all previous errors, replacing them with a single error of type -- Replaced containing the string given. (<?>) :: Parser a -> String -> Parser a-(<?>) l e = l <??> (const [Replaced e])+(<?>) l e = l <??> const [Replaced e] infix 0 <?> @@ -66,7 +68,7 @@ matches = Set.filter p s err = throw $ Expected ("word matching one of: "- ++ (concat . intersperse ", " $ Set.toList s))+ ++ (intercalate ", " $ Set.toList s)) case Set.toList matches of [] -> err (x:[]) -> return (a, x)@@ -74,14 +76,14 @@ unsafeShortOpt :: Char -> ShortOpt-unsafeShortOpt c = case makeShortOpt c of- Nothing -> error $ "invalid short option: " ++ [c]- Just o -> o+unsafeShortOpt c =+ fromMaybe (error $ "invalid short option: " ++ [c])+ (makeShortOpt c) unsafeLongOpt :: String -> LongOpt-unsafeLongOpt c = case makeLongOpt c of- Nothing -> error $ "invalid long option: " ++ c- Just o -> o+unsafeLongOpt c =+ fromMaybe (error $ "invalid long option: " ++ c)+ (makeLongOpt c) -- |Specifies options for the 'parseOption' function. Each OptSpec@@ -97,16 +99,19 @@ -- your long option does not meet these conditions, a runtime error -- will occur. - + , shortOpts :: [Char] -- ^ Each Char is a single short option, such as @v@. The -- character cannot be a dash; if it is, a runtime error will occur.- + , argSpec :: ArgSpec a -- ^ What to do each time one of the given long options or -- short options appears on the command line. } +instance Functor OptSpec where+ fmap f (OptSpec ls ss as) = OptSpec ls ss (fmap f as)+ -- | Specifies how many arguments each option takes. As with -- 'System.Console.GetOpt.ArgDescr', there are (at least) two ways to -- use this type. You can simply represent each possible option using@@ -137,10 +142,15 @@ -- argument, @-a -b@ will be parsed with @-b@ being an argument to -- option @a@, even though @-b@ starts with a hyphen and therefore -- \"looks like\" an option.- + | TwoArg (String -> String -> a)- -- ^ This option takes two arguments. Parsed similarly to 'OneArg'.+ -- ^ This option takes two arguments. Parsed similarly to+ -- 'OneArg'. + | ThreeArg (String -> String -> String -> a)+ -- ^ This option takes three arguments. Parsed similarly to+ -- 'OneArg'.+ | VariableArg ([String] -> a) -- ^ This option takes a variable number of arguments--zero or -- more. Option arguments continue until the command line contains@@ -151,8 +161,34 @@ -- @-a@ as the last option on the command line, then the only way -- to indicate the end of arguments for @a@ and the beginning of -- positional argments is with a stopper.- + | ChoiceArg [(String, a)]+ -- ^ This option takes a single argument, which must match one of+ -- the strings given in the list. The user may supply the shortest+ -- unambiguous string. If the argument list to ChoiceArg has+ -- duplicate strings, only the first string is used. For instance,+ -- ChoiceArg could be useful if you were parsing the @--color@+ -- option to GNU grep, which requires the user to supply one of+ -- three arguments: @always@, @never@, or @auto@.+++instance Functor ArgSpec where+ fmap f a = case a of+ NoArg i -> NoArg $ f i+ OptionalArg g ->+ OptionalArg $ \ms -> f (g ms)+ OneArg g ->+ OneArg $ \s1 -> f (g s1)+ TwoArg g ->+ TwoArg $ \s1 s2 -> f (g s1 s2)+ ThreeArg g ->+ ThreeArg $ \s1 s2 s3 -> f (g s1 s2 s3)+ VariableArg g ->+ VariableArg $ \ls -> f (g ls)+ ChoiceArg gs ->+ ChoiceArg . map (\(s, r) -> (s, f r)) $ gs++ -- | Parses a single command line option. Examines all the options -- specified using multiple OptSpec and parses one option on the -- command line accordingly. Fails without consuming any input if the@@ -161,6 +197,14 @@ -- options; for example, the user could type @--verb@ for @--verbose@ -- and @--vers@ for @--version@. --+-- This function is applied to a list of OptSpec, rather than to a+-- single OptSpec, because in order to correctly handle the parsing of+-- shortened long options (e.g. @--verb@ rather than @--verbose@) it+-- is necessary for one function to have access to all of the+-- OptSpec. Applying this function multiple times to different lists+-- of OptSpec and then using the @<|>@ function to combine them will+-- break the proper parsing of shortened long options.+-- -- For an example that uses this function, see -- "System.Console.MultiArg.SimpleParser". parseOption :: [OptSpec a] -> Parser a@@ -169,11 +213,11 @@ in case mconcat ([shortOpt] <*> os) of Nothing -> longs Just shorts -> longs <|> shorts- + longOptParser :: [OptSpec a] -> Parser a longOptParser os = longOpt (longOptSet os) (longOptMap os)- + longOptSet :: [OptSpec a] -> Set LongOpt longOptSet = Set.fromList . concatMap toOpts where toOpts = map unsafeLongOpt . longOpts@@ -190,43 +234,42 @@ longOpt set mp = do (_, lo, maybeArg) <- approxLongOpt set let spec = mp ! lo+ maybeNextArg = maybe nextArg return maybeArg case spec of NoArg a -> case maybeArg of Nothing -> return a Just _ -> fail $ "option " ++ unLongOpt lo ++ " does not take argument" OptionalArg f -> return (f maybeArg)- OneArg f -> case maybeArg of- Nothing -> do- a1 <- nextArg- return $ f a1- Just a -> return $ f a- TwoArg f -> case maybeArg of- Nothing -> do- a1 <- nextArg- a2 <- nextArg- return $ f a1 a2- Just a1 -> do- a2 <- nextArg- return $ f a1 a2+ OneArg f -> f <$> maybeNextArg+ TwoArg f -> f <$> maybeNextArg <*> nextArg+ ThreeArg f -> f <$> maybeNextArg <*> nextArg <*> nextArg VariableArg f -> do as <- many nonOptionPosArg return . f $ case maybeArg of Nothing -> as Just a1 -> a1 : as-+ ChoiceArg ls -> do+ s <- maybeNextArg+ case matchAbbrev ls s of+ Nothing -> fail $ "option " ++ unLongOpt lo+ ++ " requires an argument: "+ ++ (concat . intersperse ", " . map fst $ ls)+ Just g -> return g shortOpt :: OptSpec a -> Maybe (Parser a) shortOpt o = mconcat parsers where parsers = map mkParser . shortOpts $ o mkParser c = let opt = unsafeShortOpt c- in Just $ case argSpec o of- NoArg a -> a <$ nextShort opt- OptionalArg f -> shortOptionalArg opt f- OneArg f -> shortOneArg opt f- TwoArg f -> shortTwoArg opt f- VariableArg f -> shortVariableArg opt f+ in Just $ nextShort opt *> case argSpec o of+ NoArg a -> return a+ OptionalArg f -> shortOptionalArg f+ OneArg f -> shortOneArg f+ TwoArg f -> shortTwoArg f+ ThreeArg f -> shortThreeArg f+ VariableArg f -> shortVariableArg f+ ChoiceArg ls -> shortChoiceArg opt ls -- | Parses a short option without an argument, either pending or -- non-pending. Fails with a single error message rather than two.@@ -236,44 +279,43 @@ err = Expected ("short option: " ++ [unShortOpt o]) e ls = err : (drop 2 ls) -shortVariableArg :: ShortOpt -> ([String] -> a) -> Parser a-shortVariableArg opt f = do- nextShort opt+shortVariableArg :: ([String] -> a) -> Parser a+shortVariableArg f = do maybeSameWordArg <- optional pendingShortOptArg args <- many nonOptionPosArg case maybeSameWordArg of Nothing -> return (f args) Just arg1 -> return (f (arg1:args))- -shortTwoArg :: ShortOpt -> (String -> String -> a) -> Parser a-shortTwoArg opt f = do- nextShort opt- maybeSameWordArg <- optional pendingShortOptArg- case maybeSameWordArg of- Nothing -> do- arg1 <- nextArg- arg2 <- nextArg- return (f arg1 arg2)- Just arg1 -> do - arg2 <- nextArg- return (f arg1 arg2)- -shortOneArg :: ShortOpt -> (String -> a) -> Parser a-shortOneArg opt f = do- nextShort opt- maybeSameWordArg <- optional pendingShortOptArg- case maybeSameWordArg of- Nothing -> do- arg <- nextArg- return (f arg)- Just a -> return (f a)+shortOneArg :: (String -> a) -> Parser a+shortOneArg f = f <$> firstShortArg -shortOptionalArg :: ShortOpt -> (Maybe String -> a) -> Parser a-shortOptionalArg opt f = do- nextShort opt+firstShortArg :: Parser String+firstShortArg =+ optional pendingShortOptArg >>= maybe nextArg return+++shortChoiceArg :: ShortOpt -> [(String, a)] -> Parser a+shortChoiceArg opt ls =+ firstShortArg+ >>= maybe err return . matchAbbrev ls+ where+ err = fail $ "option " ++ [unShortOpt opt] ++ " requires "+ ++ "one argument: "+ ++ (concat . intersperse " " . map fst $ ls)++++shortTwoArg :: (String -> String -> a) -> Parser a+shortTwoArg f = f <$> firstShortArg <*> nextArg++shortThreeArg :: (String -> String -> String -> a) -> Parser a+shortThreeArg f = f <$> firstShortArg <*> nextArg <*> nextArg++shortOptionalArg :: (Maybe String -> a) -> Parser a+shortOptionalArg f = do maybeSameWordArg <- optional pendingShortOptArg case maybeSameWordArg of Nothing -> do@@ -282,3 +324,19 @@ Nothing -> return (f Nothing) Just a -> return (f (Just a)) Just a -> return (f (Just a))++-- | Finds the unambiguous short match for a string, if there is+-- one. Returns a string describing the error condition if there is+-- one, or the matching result if successful.+matchAbbrev :: [(String, a)] -> String -> Maybe a+matchAbbrev ls s =+ let ls' = nubBy (\x y -> fst x == fst y) ls+ in case lookup s ls' of+ Just a -> return a+ Nothing ->+ let pdct (t, _) = s `isPrefixOf` t+ in case filter pdct ls of+ (_, a):[] -> return a+ _ -> Nothing++
System/Console/MultiArg/Option.hs view
@@ -11,6 +11,7 @@ where import Data.List (find)+import Data.Maybe (isNothing) -- | Short options. Options that are preceded with a single dash on -- the command line and consist of a single letter. That single letter@@ -38,19 +39,19 @@ -- | Makes a long option. Returns Nothing if the string is not a valid -- long option. makeLongOpt :: String -> Maybe LongOpt-makeLongOpt t = case isValidLongOptText t of- True -> Just $ LongOpt t- False -> Nothing+makeLongOpt t =+ if isValidLongOptText t then Just $ LongOpt t else Nothing + isValidLongOptText :: String -> Bool isValidLongOptText s = case s of [] -> False x:xs ->- if x == '-' || x == '='+ if x `elem` ['-', '='] then False else case xs of [] -> True y:_ ->- if y == '-' || y == '='+ if y `elem` ['-', '='] then False- else maybe True (const False) (find (== '=') xs)+ else isNothing . find (== '=') $ xs
System/Console/MultiArg/Prim.hs view
@@ -7,13 +7,13 @@ module System.Console.MultiArg.Prim ( -- * Parser types Parser,- + -- * Running a parser- + -- | Each parser runner is applied to a list of Strings, which are the- -- command line arguments to parse. + -- command line arguments to parse. parse,- + -- * Higher-level parser combinators parserMap, good,@@ -21,7 +21,7 @@ choice, combine, lookAhead,- + -- ** Running parsers multiple times several, manyTill,@@ -32,13 +32,13 @@ genericThrow, (<??>), try,- + -- * Parsers -- ** Short options and arguments pendingShortOpt, nonPendingShortOpt,- pendingShortOptArg, - + pendingShortOptArg,+ -- ** Long options and arguments exactLongOpt, approxLongOpt,@@ -46,14 +46,14 @@ -- ** Stoppers stopper, resetStopper,- + -- ** Positional (non-option) arguments nextArg, nonOptionPosArg,- + -- ** Miscellaneous end,- + -- * Errors Message(Expected, StrMsg, Replaced, UnknownError), Error(Error),@@ -66,7 +66,7 @@ (ShortOpt, unShortOpt, LongOpt,- unLongOpt, + unLongOpt, makeLongOpt ) import Control.Applicative ( Applicative, Alternative ) import qualified Control.Applicative@@ -77,7 +77,7 @@ import Control.Monad ( when, MonadPlus(mzero, mplus), guard ) import Data.Monoid ( Monoid ( mempty, mappend ) ) import qualified Data.List as L-import Data.List (isPrefixOf)+import Data.List (isPrefixOf, intercalate) type Location = String @@ -104,10 +104,10 @@ -- something else. The string indicates what was expected. | StrMsg String -- ^ The 'fromString' function was applied.- + | Replaced String -- ^ A previous list of error messages was replaced with this error message.- + | UnknownError -- ^ Any other error; used by 'genericThrow'. @@ -182,10 +182,10 @@ -- Unicode strings, see the documentation in -- "System.Console.MultiArg.GetArgs" before you use -- 'System.Environment.getArgs'.- + -> Parser a -- ^ Parser to run- + -> Exceptional Error a -- ^ Success or failure. Any parser might fail; for example, the -- command line might not have any values left to parse. Use of the@@ -355,7 +355,7 @@ pendingShortOpt :: ShortOpt -> Parser () pendingShortOpt so = Parser $ \s ->- let err = Expected ("pending short option: " ++ [(unShortOpt so)])+ let err = Expected ("pending short option: " ++ [unShortOpt so]) es = (Bad, s { errors = err : errors s }) gd newSt = (Good (), newSt) in maybe es gd $ do@@ -480,8 +480,8 @@ let (pre, word, afterEq) = splitLongWord str guard (pre == "--") return (word, afterEq)- + nextWord :: ParseSt -> Maybe (String, ParseSt) nextWord st = case remaining st of [] -> Nothing@@ -496,7 +496,7 @@ approxLongOptError set st = st { errors = newE : errors st } where newE = Expected ex ex = "a long option: " ++ longs- longs = concat . L.intersperse ", " $ opts+ longs = intercalate ", " opts opts = fmap unLongOpt . Set.toList $ set -- | Examines the next word. If it matches a Text in the set@@ -517,7 +517,7 @@ if Set.member opt ts then return ((word, opt, afterEq), s') else do- let p t = word `isPrefixOf` (unLongOpt t)+ let p t = word `isPrefixOf` unLongOpt t matches = Set.filter p ts case Set.toList matches of [] -> Nothing@@ -584,8 +584,8 @@ Good g -> (Good g, s') Bad -> (Bad, s'') where s'' = s { errors = errors s' }- + -- | Returns the next string on the command line as long as there are -- no pendings. Be careful - this will return the next string even if -- it looks like an option (that is, it starts with a dash.) Consider@@ -726,7 +726,7 @@ let (ls, finalGoodSt, finalBadSt) = parseRepeat st' f in (a : ls, finalGoodSt, finalBadSt) (Bad, st') -> ([], st1, st')- + -- | Succeeds if there is no more input left. end :: Parser ()
System/Console/MultiArg/SimpleParser.hs view
@@ -5,19 +5,19 @@ module System.Console.MultiArg.SimpleParser ( -- * Interspersion control Intersperse (Intersperse, StopOptions)- + -- * Option specifications , C.OptSpec (OptSpec, longOpts, shortOpts, argSpec) , C.ArgSpec (NoArg, OptionalArg, OneArg, TwoArg, VariableArg)- + -- * Exceptions , Ex.Exceptional (Exception, Success) , P.Error (Error) , P.Message (Expected, StrMsg, Replaced, UnknownError)- + -- * Get command line arguments , G.getArgs- + -- * The parser , parse ) where@@ -26,8 +26,8 @@ import qualified System.Console.MultiArg.GetArgs as G import qualified System.Console.MultiArg.Combinator as C import qualified Control.Monad.Exception.Synchronous as Ex-import Control.Applicative ( many, (<|>), optional, (*>),- (<$))+import Control.Applicative ( many, (<|>), optional,+ (<$), (<*>), (<*), (<$>)) import Data.Maybe (catMaybes) -- | What to do after encountering the first non-option,@@ -42,21 +42,21 @@ -- command line is entered, then @-b@ will result in an error -- because @-b@ starts with a hyphen and therefore \"looks like\" an -- option.- + | StopOptions -- ^ No additional options will be parsed after encountering the -- first positional argument. For example, if @a@ and @b@ are -- options, in the command line @-a posarg -b@, @b@ will be parsed -- as a positional argument rather than as an option. --- | Parse a command line. +-- | Parse a command line. parse :: Intersperse -- ^ What to do after encountering the first positional argument- + -> [C.OptSpec a] -- ^ All possible options- + -> (String -> a) -- ^ How to handle positional arguments. This function is applied to -- the appropriate string every time the parser encounters a@@ -82,15 +82,15 @@ parseOptsNoIntersperse p = P.manyTill p e where e = P.end <|> nonOpt nonOpt = P.lookAhead next- next = ((() <$ P.nonOptionPosArg) <|> P.stopper)+ next = (() <$ P.nonOptionPosArg) <|> P.stopper parseStopOpts :: P.Parser a -> (String -> a) -> P.Parser [a]-parseStopOpts optParser p = do- opts <- parseOptsNoIntersperse optParser- _ <- optional P.stopper- args <- many P.nextArg- return $ opts ++ (map p args)+parseStopOpts optParser p =+ (\opts args -> opts ++ map p args)+ <$> parseOptsNoIntersperse optParser+ <* optional P.stopper+ <*> many P.nextArg -- | @parseIntersperse o p@ parses options and positional arguments,@@ -98,8 +98,8 @@ -- when applied to a string, returns the appropriate type. parseIntersperse :: P.Parser a -> (String -> a) -> P.Parser [a] parseIntersperse optParser p =- let pa = P.nonOptionPosArg >>= return . Just . p- po = optParser >>= return . Just- ps = P.stopper *> return Nothing+ let pa = (Just . p) <$> P.nonOptionPosArg+ po = Just <$> optParser+ ps = Nothing <$ P.stopper parser = po <|> ps <|> pa- in P.manyTill parser P.end >>= return . catMaybes+ in catMaybes <$> P.manyTill parser P.end
multiarg.cabal view
@@ -1,5 +1,5 @@ Name: multiarg-Version: 0.4.0.0+Version: 0.6.0.0 Cabal-version: >=1.8 Build-Type: Simple License: MIT@@ -41,7 +41,7 @@ base ==4.*, explicit-exception ==0.1.*, containers ==0.4.*- + -- See documentation in System.Console.MultiArg.GetArgs for details if flag(newbase) Build-depends: