multiarg 0.26.0.0 → 0.28.0.0
raw patch · 14 files changed
+2019/−1987 lines, 14 files
Files
- ChangeLog +7/−0
- lib/Multiarg.hs +167/−0
- lib/Multiarg/Combinator.hs +478/−0
- lib/Multiarg/CommandLine.hs +563/−0
- lib/Multiarg/Option.hs +45/−0
- lib/Multiarg/Prim.hs +681/−0
- lib/Multiarg/SampleParser.hs +70/−0
- lib/System/Console/MultiArg.hs +0/−167
- lib/System/Console/MultiArg/Combinator.hs +0/−478
- lib/System/Console/MultiArg/CommandLine.hs +0/−536
- lib/System/Console/MultiArg/Option.hs +0/−45
- lib/System/Console/MultiArg/Prim.hs +0/−681
- lib/System/Console/MultiArg/SampleParser.hs +0/−70
- multiarg.cabal +8/−10
ChangeLog view
@@ -1,3 +1,10 @@+Release 0.28.0.0++* Renamed everything from System.Console.MultiArg to Multiarg+ (shorter; note also case change from MultiArg to Multiarg)++* Removed existentials from Multiarg.CommandLine+ Release 0.24.0.4, February 24, 2014 * Changed lower bound on base down to 4.5.0.0
+ lib/Multiarg.hs view
@@ -0,0 +1,167 @@+-- | A combinator library for building command-line parsers.++module Multiarg (++ -- | To say this library is inspired by Parsec would probably insult the+ -- creators of Parsec, as this library could not possibly be as+ -- elegant or throughly considered as Parsec is. Nevertheless this+ -- library can be used in a similar style as Parsec, but is+ -- specialized for parsing command lines.+ --+ -- This parser was built because I could not find anything that would+ -- readily parse command lines where the options took more than one+ -- argument. For example, for the @tail@ command on GNU systems, the+ -- --lines option takes one argument to specify how many lines you+ -- want to see. Well, what if you want to build a program with an+ -- option that takes /two/ arguments, like @--foo bar baz@? I found no+ -- such library so I built this one. Nevertheless, using this library+ -- you can build parsers to parse a variety of command line+ -- vocabularies, from simple to complex.++ -- * Terminology++ -- | Some terms are used throughout multiarg:+ --+ -- [@word@] When you run your program from the Unix shell prompt,+ -- your shell is responsible for splitting the command line into+ -- words. Typically you separate words with spaces, although quoting+ -- can affect this. multiarg parses lists of words. Each word can+ -- consist of a single long option, a single long option and an+ -- accompanying option argument, a single short option, multiple+ -- short options, and even one or more multiple short options and an+ -- accompanying short option argument. Or, a word can be a+ -- positional argument or a stopper. All these are described below.+ --+ -- [@option@] Options allow a user to specify ways to tune the+ -- operation of a program. Typically options are indeed optional,+ -- although some programs do sport \"required options\" (a bit of an+ -- oxymoron). Options can be either short options or long+ -- options. Also, options can take arguments.+ --+ -- [@short option@] An option that is specified with a single hyphen+ -- and a single letter. For example, for the program @tail(1)@,+ -- possible short options include @n@ and @v@. With multiarg it is+ -- possible to easily parse short options that are specified in+ -- different words or in the same word. For example, if a user wants+ -- to run @tail@ with two options, he might type @tail -v -f@ or he+ -- might type @tail -vf@.+ --+ -- [@long option@] An option that is specified using two hyphens and+ -- what is usually a mnemonic word, though it could be as short as a+ -- single letter. For example, @tail(1)@ has long options including+ -- @follow@ and @verbose@. The user would specify these on the+ -- command line by typing @tail --follow --verbose@.+ --+ -- [@option argument@] Some options take additional arguments that+ -- are specific to the option and change what the option does. For+ -- instance, the @lines@ option to @tail(1)@ takes a single,+ -- optional argument, which is the number of lines to show. Option+ -- arguments can be optional or required, and a single option can+ -- take a mulitple, fixed number of arguments and it can take a+ -- variable number of arguments. Option arguments can be given in+ -- various ways. They can be specified in the same word as a long+ -- option by using an equals sign; they can also be specified in the+ -- same word as a short option simply by placing them in the same+ -- word, or they can be specified in the following word. For+ -- example, these different command lines all mean the same thing;+ -- @tail --verbose --lines=20@, @tail --verbose --lines 20@, @tail+ -- -vn 20@, @tail -v -n20@, @tail -vn20@, and @tail -v -n 20@, and+ -- numerous other combinations also have the same meaning.+ --+ -- [@GNU-style option argument@] A long option with an argument+ -- given with an equal sign, such as [@lines=20@].+ --+ -- [@positional argument@] A word on the command line that is not an+ -- option or an argument to an option. For instance, with @tail(1)@,+ -- you specify the files you want to see by using positional+ -- arguments. In the command @tail -n 10 myfile@, @myfile@ is a+ -- positional argument. For some programs, such as @git@ or @darcs@,+ -- a positional argument might be a \"command\" or a \"mode\", such+ -- as the @commit@ in @git commit@ or the @whatsnew@ in @darcs+ -- whatsnew@. multiarg has no primitive parsers that treat these+ -- positional arguments specially but it is trivial to build a+ -- parser for command lines such as this, too.+ --+ -- [@stopper@] A single word consisting solely of two hyphens,+ -- @--@. The user types this to indicate that all subsequent words+ -- on the command line are positional arguments, even if they begin+ -- with hyphens and therefore look like they might be options.+ --+ -- [@pending@] The user might specify more than one short option, or+ -- a short option and a short option argument, in a single word. For+ -- example, she might type @tail -vl20@. After parsing the @v@+ -- option, the Parser makes @l20@ into a \"pending\". The next+ -- parser can then treat @l20@ as an option argument to the @v@+ -- option (which is probably not what was wanted) or the next parser+ -- 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+ -- "Multiarg.CommandLine" module, which uses the+ -- underlying combinators to build a simple parser for you. That+ -- module is already exported from this module for easy usage.+ --+ -- "Multiarg.CommandLine" also has a parser that can+ -- handle multi-mode commands (examples include @git@, @darcs@, and+ -- @cvs@.)+ --+ -- For maximum flexibility you will want to start with the+ -- "Multiarg.Prim" module. Using those parsers you+ -- can easily build parsers that are quite complicated. The parsers+ -- can check for errors along the way, simplifying the sometimes+ -- complex task of ensuring that data a user supplied on the command+ -- line is good. You can easily build parsers for programs that take+ -- no options, take dozens of options, require that options be given+ -- in a particular order, require that some options be given, or bar+ -- some combinations of options. You might also require particular+ -- positional arguments. Other helpful functions are in+ -- "Multiarg.Combinator". You will also want to+ -- examine the source code for "Multiarg.Combinator"+ -- and "Multiarg.CommandLine" as these show some+ -- ways to use the primitive parsers and combinators.++ -- * Non-features and shortcomings+ --+ -- | multiarg isn't perfect; no software is. multiarg does not+ -- automatically make online help for your command line+ -- parsers. Getting this right would be tricky given the nature of+ -- the code and I don't even want to bother trying, as I just write+ -- my own online help in a text editor.+ --+ -- multiarg partially embraces \"The Tao of Option Parsing\" that+ -- Python's Optik (<http://optik.sourceforge.net/>) follows. Read+ -- \"The Tao of Option Parsing\" here:+ --+ -- <http://optik.sourceforge.net/doc/1.5/tao.html>+ --+ -- multiarg's philosophy is similar to that of Optik, which+ -- means you won't be able to use multiarg to (easily) build a clone+ -- to the UNIX @find(1)@ command. (You could do it, but multiarg won't+ -- help you very much.)+ --+ -- 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.+++ module Multiarg.Combinator+ , module Multiarg.CommandLine+ , module Multiarg.Option+ , module Multiarg.Prim+ , module System.Environment+ ) where++import Multiarg.Combinator+import Multiarg.CommandLine+import Multiarg.Option+import Multiarg.Prim+import System.Environment
+ lib/Multiarg/Combinator.hs view
@@ -0,0 +1,478 @@+-- | Combinators that are useful for building command-line+-- parsers. These build off the functions in+-- "Multiarg.Prim". Unlike those functions, these+-- functions have no access to the internals of the parser.+module Multiarg.Combinator (+ -- * Parser combinators+ notFollowedBy,++ -- * Combined long and short option parser+ OptSpec(OptSpec, longOpts, shortOpts, argSpec),+ InputError(..),+ reader,+ optReader,+ ArgSpec(..),+ parseOption,++ -- * Formatting errors+ formatError+ ) where++import Data.List (isPrefixOf, intersperse, nubBy)+import Data.Set ( Set )+import qualified Data.Set as Set+import Control.Applicative+ ((<*>), optional, (<$), (*>), (<|>), many)++import Multiarg.Prim+ ( Parser, try, approxLongOpt,+ nextWord, pendingShortOptArg, nonOptionPosArg,+ pendingShortOpt, nonPendingShortOpt, nextWord, (<?>),+ Error(..), Description(..))+import Multiarg.Option+ ( LongOpt, ShortOpt, unLongOpt,+ makeLongOpt, makeShortOpt, unShortOpt )+import qualified Data.Map as M+import Data.Map ((!))+import Data.Maybe (fromMaybe, mapMaybe)+import Data.Monoid ( mconcat )+++-- | @notFollowedBy p@ succeeds only if parser p fails. If p fails,+-- notFollowedBy succeeds without consuming any input. If p succeeds+-- and consumes input, notFollowedBy fails and consumes input. If p+-- succeeds and does not consume any input, notFollowedBy fails and+-- does not consume any input.+notFollowedBy :: Parser a -> Parser ()+notFollowedBy p =+ () <$ ((try p >> fail "notFollowedBy failed")+ <|> return ())+++unsafeShortOpt :: Char -> ShortOpt+unsafeShortOpt c =+ fromMaybe (error $ "invalid short option: " ++ [c])+ (makeShortOpt c)++unsafeLongOpt :: String -> LongOpt+unsafeLongOpt c =+ fromMaybe (error $ "invalid long option: " ++ c)+ (makeLongOpt c)+++-- |Specifies options for the 'parseOption' function. Each OptSpec+-- represents one command-line option.+data OptSpec a = OptSpec {+ longOpts :: [String]+ -- ^ Each String is a single long option, such as @version@. When+ -- the user specifies long options on the command line, she must+ -- type two dashes; however, do not include the dashes when you+ -- specify the long option here. Strings you specify as long options+ -- cannot include a dash as either the first or the second+ -- character, and they cannot include an equal sign anywhere. If+ -- 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)++-- | Reads in values that are members of Read. Provides a generic+-- error message if the read fails.+reader :: Read a => String -> Either InputError a+reader s = case reads s of+ (x, ""):[] -> return x+ _ -> Left . ErrorMsg $ "could not parse option argument"++-- | Reads in values that are members of Read, but the value does not+-- have to appear on the command line. Provides a generic error+-- message if the read fails. If the argument is Nothing, returns+-- Nothing.+optReader+ :: Read a+ => Maybe String+ -> Either InputError (Maybe a)+optReader ms = case ms of+ Nothing -> return Nothing+ Just s -> case reads s of+ (x, ""):[] -> return (Just x)+ _ -> Left . ErrorMsg $ "could not parse option argument"++-- | Indicates errors when parsing options to arguments.+data InputError+ = NoMsg+ -- ^ No error message accompanies this failure. multiarg will create+ -- a generic error message for you.++ | ErrorMsg String+ -- ^ Parsing the argument failed with this error message. An example+ -- might be @option argument is not an integer@ or @option argument+ -- is too large@. The text of the options the user provided is+ -- automatically prepended to the error message, so do not replicate+ -- this in your message.++ deriving (Eq, Show)++-- | Create an error message from an InputError.+errorMsg+ :: Either LongOpt ShortOpt+ -- ^ The option with the faulty argument++ -> [String]+ -- ^ The faulty command line arguments++ -> InputError+ -> String+errorMsg badOpt ss err = arg ++ opt ++ msg+ where+ arg = let aw = if length ss > 1 then "arguments " else "argument "+ ws = concat . intersperse " " . map quote $ ss+ quote s = "\"" ++ s ++ "\""+ in aw ++ ws+ opt = " to option " ++ optDesc+ optDesc = case badOpt of+ Left lo -> "--" ++ unLongOpt lo+ Right so -> "-" ++ [unShortOpt so]+ msg = " invalid" ++ detail+ detail = case err of+ NoMsg -> ""+ ErrorMsg s -> ": " ++ s++++-- | 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+-- different data constructors in an algebraic data type. Or you can+-- have each ArgSpec yield a function that transforms a record. For an+-- example that uses an algebraic data type, see+-- "Multiarg.SampleParser".+--+-- Most of these value constructors take as an argument a function+-- that returns an Either. The function should return a @Left+-- InputError@ if the parsing of the arguments failed--if, for+-- example, the user needs to enter an integer but she instead input a+-- letter. The functions should return a Right if parsing of the+-- arguments was successful.+data ArgSpec a =+ NoArg a+ -- ^ This option takes no arguments++ | OptionalArg (Maybe String -> Either InputError a)+ -- ^ This option takes an optional argument. As noted in \"The Tao+ -- of Option Parsing\", optional arguments can result in some+ -- ambiguity. (Read it here:+ -- <http://optik.sourceforge.net/doc/1.5/tao.html>) If option @a@+ -- takes an optional argument, and @b@ is also an option, what+ -- does @-ab@ mean? SimpleParser resolves this ambiguity by+ -- assuming that @b@ is an argument to @a@. If the user does not+ -- like this, she can specify @-a -b@ (in such an instance @-b@ is+ -- not parsed as an option to @-a@, because @-b@ begins with a+ -- hyphen and therefore \"looks like\" an option.) Certainly+ -- though, optional arguments lead to ambiguity, so if you don't+ -- like it, don't use them :)++ | OneArg (String -> Either InputError a)+ -- ^ This option takes one argument. Here, if option @a@ takes one+ -- 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 -> Either InputError a)+ -- ^ This option takes two arguments. Parsed similarly to+ -- 'OneArg'.++ | ThreeArg (String -> String -> String -> Either InputError a)+ -- ^ This option takes three arguments. Parsed similarly to+ -- 'OneArg'.++ | VariableArg ([String] -> Either InputError a)+ -- ^ This option takes a variable number of arguments--zero or+ -- more. Option arguments continue until the command line contains+ -- a word that begins with a hyphen. For example, if option @a@+ -- takes a variable number of arguments, then @-a one two three+ -- -b@ will be parsed as @a@ taking three arguments, and @-a -b@+ -- will be parsed as @a@ taking no arguments. If the user enters+ -- @-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+ ChoiceArg gs ->+ ChoiceArg . map (\(s, r) -> (s, f r)) $ gs++ OptionalArg g -> OptionalArg $ \ms -> fmap f (g ms)++ OneArg g ->+ OneArg $ \s1 -> fmap f (g s1)++ TwoArg g ->+ TwoArg $ \s1 s2 -> fmap f (g s1 s2)++ ThreeArg g ->+ ThreeArg $ \s1 s2 s3 -> fmap f (g s1 s2 s3)++ VariableArg g ->+ VariableArg $ \ls -> fmap f (g ls)+++-- | 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+-- next word on the command line is not a recognized option. Allows+-- the user to specify the shortest unambiguous match for long+-- 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+-- "Multiarg.SimpleParser".+parseOption :: [OptSpec a] -> Parser a+parseOption os =+ let longs = longOptParser os+ 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++longOptMap :: [OptSpec a] -> M.Map LongOpt (ArgSpec a)+longOptMap = M.fromList . concatMap toPairs where+ toPairs (OptSpec los _ as) = map (toPair as) los where+ toPair a s = (unsafeLongOpt s, a)++longOpt ::+ Set LongOpt+ -> M.Map LongOpt (ArgSpec a)+ -> Parser a+longOpt set mp = do+ (_, lo, maybeArg) <- approxLongOpt set+ let spec = mp ! lo+ maybeNextArg = maybe nextWord return maybeArg+ case spec of+ NoArg a -> case maybeArg of+ Nothing -> return a+ Just _ -> fail $ "option " ++ unLongOpt lo+ ++ " does not take argument"+ 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++ OptionalArg f -> case maybeArg of+ Nothing -> either (fail . errorMsg (Left lo) []) return+ $ f Nothing+ Just s -> either (fail . errorMsg (Left lo) [s]) return+ $ f (Just s)+++ OneArg f -> maybeNextArg >>= g+ where+ g a = either (fail . errorMsg (Left lo) [a]) return+ $ f a++ TwoArg f -> do+ a1 <- maybeNextArg+ a2 <- nextWord+ either (fail . errorMsg (Left lo) [a1, a2]) return+ $ f a1 a2++ ThreeArg f -> do+ a1 <- maybeNextArg+ a2 <- nextWord+ a3 <- nextWord+ either (fail . errorMsg (Left lo) [a1, a2, a3]) return+ $ f a1 a2 a3++ VariableArg f -> do+ as <- many nonOptionPosArg+ let args = case maybeArg of+ Nothing -> as+ Just a -> a:as+ either (fail . errorMsg (Left lo) args) return+ $ f args+++shortOpt :: OptSpec a -> Maybe (Parser a)+shortOpt o = mconcat parsers where+ parsers = map mkParser . shortOpts $ o+ mkParser c =+ let opt = unsafeShortOpt c+ in Just $ nextShort opt *> case argSpec o of+ NoArg a -> return a+ ChoiceArg ls -> shortChoiceArg opt ls+ OptionalArg f -> shortOptionalArg opt f+ OneArg f -> shortOneArg opt f+ TwoArg f -> shortTwoArg opt f+ ThreeArg f -> shortThreeArg opt f+ VariableArg f -> shortVariableArg opt f++-- | Parses a short option without an argument, either pending or+-- non-pending. Fails with a single error message rather than two.+nextShort :: ShortOpt -> Parser ()+nextShort o = p <?> ("short option: -" ++ [unShortOpt o])+ where+ p = do+ r1 <- optional $ pendingShortOpt o+ case r1 of+ Just () -> return ()+ Nothing -> nonPendingShortOpt o+++shortVariableArg+ :: ShortOpt+ -> ([String] -> Either InputError a)+ -> Parser a+shortVariableArg so f = do+ maybeSameWordArg <- optional pendingShortOptArg+ args <- many nonOptionPosArg+ let as = case maybeSameWordArg of+ Nothing -> args+ Just a -> a:args+ either (fail . errorMsg (Right so) as) return $ f as+++shortOneArg+ :: ShortOpt+ -> (String -> Either InputError a)+ -> Parser a+shortOneArg so f = do+ a <- firstShortArg+ either (fail . errorMsg (Right so) [a]) return $ f a++firstShortArg :: Parser String+firstShortArg =+ optional pendingShortOptArg >>= maybe nextWord 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+ :: ShortOpt+ -> (String -> String -> Either InputError a)+ -> Parser a+shortTwoArg so f = do+ a1 <- firstShortArg+ a2 <- nextWord+ either (fail . errorMsg (Right so) [a1, a2]) return+ $ f a1 a2+++shortThreeArg+ :: ShortOpt+ -> (String -> String -> String -> Either InputError a)+ -> Parser a+shortThreeArg so f = do+ a1 <- firstShortArg+ a2 <- nextWord+ a3 <- nextWord+ either (fail . errorMsg (Right so) [a1, a2, a3]) return+ $ f a1 a2 a3++shortOptionalArg+ :: ShortOpt+ -> (Maybe String -> Either InputError a)+ -> Parser a+shortOptionalArg so f = do+ maybeSameWordArg <- optional pendingShortOptArg+ case maybeSameWordArg of+ Nothing -> do+ maybeArg <- optional nonOptionPosArg+ case maybeArg of+ Nothing -> either (fail . errorMsg (Right so) []) return+ $ f Nothing+ Just a -> either (fail . errorMsg (Right so) [a]) return+ $ f (Just a)+ Just a -> either (fail . errorMsg (Right so) [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++-- | Formats error messages for nice display. Returns a multi-line+-- string (there is no need to append a newline to the end of the+-- string returned).+formatError+ :: String+ -- ^ Pass the name of your program here. Displayed at the beginning+ -- of the error message.++ -> Error+ -> String+formatError p (Error loc ls) =+ p ++ ": error: could not parse command line.\n"+ ++ "Error at: " ++ loc ++ "\n"+ ++ expError+ ++ genError+ ++ unk+ where+ toExp m = case m of { Expected s -> Just s; _ -> Nothing }+ expc = unlines . mapMaybe toExp $ ls+ expError = if null expc then "" else "Expecting:\n" ++ expc+ toGeneral m = case m of { General s -> Just s; _ -> Nothing }+ gen = unlines . mapMaybe toGeneral $ ls+ genError = if null gen+ then ""+ else let sep = if null expError+ then "" else "\n"+ in sep ++ gen+ unk = if any (== Unknown) ls then "Unknown error\n" else ""+
+ lib/Multiarg/CommandLine.hs view
@@ -0,0 +1,563 @@+-- | Some pre-built command line parsers. One is a simple command line+-- parser that can parse options that take an optional argument, one+-- or two arguments, or a variable number of arguments. For sample+-- code that uses this parser, see+-- "Multiarg.SampleParser".+--+-- Another parser is provided for multi-mode programs that are similar+-- to @git@ or @darcs@.+--+-- Previously there was a bug in System.Environment.getArgs that would+-- not properly encode Unicode command line arguments. multiarg used+-- to provide its own GetArgs module to deal with this. This bug was+-- in base 4.3.1.0, which was bundled with ghc 7.0.4. This bug was+-- fixed in base 4.4.0.0, which came with ghc 7.2. Since this bug has+-- been fixed for awhile, multiarg no longer has its own GetArgs+-- module.+module Multiarg.CommandLine (+ -- * Interspersion control+ Intersperse (Intersperse, StopOptions)++ -- * Types+ , ProgName+ , Opts(..)+ , OptsWithPosArgs(..)++ -- ** Modes+ , Mode+ , mode+ , mModeName+ , renameMode++ -- * Simple parsers+ , simplePure+ , simpleIO+ , simpleHelp+ , simpleHelpVersion++ -- * Mode parsers+ , modesPure+ , modesIO++ -- * Helpers to create various options and modes+ , optsHelp+ , optsHelpVersion+ , modeHelp++ ) where++import qualified Multiarg.Combinator as C+import qualified Multiarg.Prim as P+import System.Environment (getArgs, getProgName)+import System.Exit (exitFailure, exitSuccess)+import qualified System.IO as IO+import Control.Applicative ( many, (<|>), optional,+ (<$), (<*>), (<*), (<$>))+import Data.Bifunctor+import Data.List (find)+import Data.Maybe (catMaybes, fromJust)+import qualified Data.Set as Set+++-- | What to do after encountering the first non-option,+-- non-option-argument word on the command line? In either case, no+-- more options are parsed after a stopper.+data Intersperse =+ Intersperse+ -- ^ Additional options are allowed on the command line 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 an option. If @b@ is /not/ an option and the same+ -- 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.+++-- | Specifies a set of options.+data Opts s a = Opts+ { oShortcuts :: [C.OptSpec s]+ -- ^ Shortcut options are commonly options such as @--help@ or+ -- @--version@. Such options must be specified alone on the command+ -- line. The parser looks for one of these options first. If it+ -- finds one and it is the only option on the command line, only+ -- this option is processed and returned. If the option is not+ -- alone on the command line, an error occurs. If no shortcut+ -- option is found, the parser processes non-shortcut options+ -- instead.++ , oOptions :: [C.OptSpec a]+ -- ^ If the user does not specify any shortcut options, she may+ -- specify any number of these options.++ }++instance Bifunctor Opts where+ bimap fa fc o = Opts+ { oShortcuts = map (fmap fa) . oShortcuts $ o+ , oOptions = map (fmap fc) . oOptions $ o+ }++instance Functor (Opts a) where+ fmap f o = o { oOptions = fmap (fmap f) . oOptions $ o }++-- | Creates an Opts with a help shortcut option.+optsHelp+ :: h+ -- ^ Whatever type you wish to use for help+ -> [C.OptSpec a]+ -> Opts h a+optsHelp h = Opts [C.OptSpec ["help"] "h" (C.NoArg h)]++-- | Creates an Opts with help and version shortcut options.+optsHelpVersion+ :: h+ -- ^ What you wish to use for help++ -> h+ -- ^ What you wish to use for version++ -> [C.OptSpec a]+ -> Opts h a+optsHelpVersion h v = Opts [ C.OptSpec ["help"] "h" (C.NoArg h)+ , C.OptSpec ["version"] "v" (C.NoArg v) ]++-- | Specification for both options and positional arguments.+data OptsWithPosArgs s a = OptsWithPosArgs+ { opOpts :: Opts s a+ , opIntersperse :: Intersperse+ , opPosArg :: String -> Either C.InputError a+ }++instance Bifunctor OptsWithPosArgs where+ bimap fa fc o = OptsWithPosArgs+ { opOpts = bimap fa fc . opOpts $ o+ , opIntersperse = opIntersperse o+ , opPosArg = fmap (fmap fc) . opPosArg $ o+ }++instance Functor (OptsWithPosArgs s) where+ fmap f o = o+ { opOpts = fmap f . opOpts $ o+ , opPosArg = fmap (fmap f) . opPosArg $ o+ }++-- | Creates a mode.+mode+ :: String+ -- ^ How the user specifies the mode on the command line. For @git@+ -- for example this might be @commit@ or @log@.+++ -> OptsWithPosArgs s a+ -- ^ Options and positional arguments that are specific to this+ -- mode. For example, in the command line @git commit -a -m 'this+ -- is a log message'@, @commit@ is the mode name and everything+ -- after that is specified here as an option or positional argument+ -- that is specific to this mode.++ -> ([a] -> r)+ -- ^ This function is applied to a list of the results of parsing the+ -- options that are specific to this mode. The function returns a+ -- type of your choosing (though all modes in the same parser will+ -- have to return the same type.)++ -> Mode s r+mode n os f = Mode n (processMode f os)++++-- | Specifies a mode.+data Mode s r = Mode+ { mModeName :: String+ -- ^ How the user specifies the mode on the command line. For @git@+ -- for example this might be @commit@ or @log@.++ , mParser :: P.Parser (Either s r)+ }++instance Bifunctor Mode where+ bimap fa fc (Mode n p) = Mode n (fmap (bimap fa fc) p)++instance Functor (Mode s) where+ fmap f (Mode n p) = Mode n (fmap (fmap f) p)++-- | Changes the mode name that a user specifies on the command line.+renameMode+ :: (String -> String)+ -- ^ This function is applied to the existing name of the mode.+ -> Mode s r+ -> Mode s r++renameMode f (Mode n p) = Mode (f n) p++-- | Creates a Mode with a help option (help specific to the mode.)+modeHelp+ :: String+ -- ^ Mode name++ -> h+ -- ^ Whatever you want to use for the help (perhaps a string, or a+ -- function, or an IO action). Its type will have to match up with+ -- the type of the global shortcut options and with the shortcut+ -- type of the other modes.++ -> ([a] -> r)+ -- ^ When applied to the the mode options, returns the result.++ -> [C.OptSpec a]+ -- ^ Options for this mode++ -> Intersperse+ -- ^ Allow interspersion of mode options and positional arguments?++ -> (String -> Either C.InputError a)+ -- ^ Parses positional arguments++ -> Mode h r++modeHelp n h getR os i p = mode n (OptsWithPosArgs (Opts ss os) i p) getR+ where+ ss = [C.OptSpec ["help"] "h" (C.NoArg h)]++parseOpts :: Opts s a -> P.Parser (Either s [a])+parseOpts os = do+ let specials = oShortcuts os+ maySpecial <- optional (C.parseOption specials <* P.end)+ case maySpecial of+ Nothing -> fmap Right+ $ P.manyTill (C.parseOption (oOptions os)) endOrNonOpt+ Just spec -> return . Left $ spec++parseOptsWithPosArgs+ :: OptsWithPosArgs s a+ -> P.Parser (Either s [a])+parseOptsWithPosArgs os = do+ let specials = oShortcuts . opOpts $ os+ maySpecial <- optional (C.parseOption specials <* P.end)+ case maySpecial of+ Nothing ->+ let f = case opIntersperse os of+ Intersperse -> parseIntersperse+ StopOptions -> parseStopOpts+ parser = C.parseOption (oOptions . opOpts $ os)+ in fmap Right $ f parser (opPosArg os)+ Just spec -> return . Left $ spec++processMode+ :: ([a] -> r)+ -> OptsWithPosArgs s a+ -> P.Parser (Either s r)+processMode gr os = do+ eiOpts <- parseOptsWithPosArgs os+ return $ case eiOpts of+ Left x -> Left x+ Right opts -> Right (gr opts)+++parseModes+ :: [Mode s r]+ -> P.Parser (Either s r)+parseModes ms = do+ let modeWords = Set.fromList . map mModeName $ ms+ (_, w) <- P.matchApproxWord modeWords+ mParser . fromJust . find (\c -> mModeName c == w) $ ms+++-- | A pure (non-IO) parser for simple command lines--that is, command+-- lines that do not have modes.+simplePure+ :: OptsWithPosArgs s a+ -- ^ Specifies allowed regular options, allowed shortcut options,+ -- and how to parse positional arguments. Also specifies whether+ -- the user may intersperse options with positional arguments.++ -> [String]+ -- ^ The command line arguments to parse++ -> Either P.Error (Either s [a])+ -- ^ Returns an error if the command line arguments could not be+ -- parsed. If the parse was successful, returns an Either. A Left+ -- indicates that the user selected a shortcut option. A Right+ -- indicates that the user did not specify a shortcut option, and+ -- will contain a list of the options and positional arguments.+simplePure os ss = P.parse ss (parseOptsWithPosArgs os)++-- | A pure (non-IO) parser for command lines that contain modes.+modesPure+ :: Opts s g+ -- ^ Global options. These are specified before any mode. For+ -- instance, in the command @git --no-pager commit -a@, the option+ -- @--no-pager@ is a global option. Global options can contain+ -- shortcut options. For instance, @git --help@ contains a single+ -- shortcut option.++ -> ([g] -> Either String (Either r [Mode s r]))+ -- ^ This function processes the global options. If there are no+ -- shortcut options specified in the global options, it is applied+ -- to the result of processing the global options. This function+ -- may return a Left if there is something wrong with the+ -- global options (a nonsensical combination, perhaps.) Otherwise,+ -- it returns a @Right Either@. Return a Left if there is no need to+ -- process any modes at all after seeing the global options.+ -- Otherwise, return a Right with a list of modes.++ -> [String]+ -- ^ Command line arguments to parse++ -> Either P.Error (Either s r)+ -- ^ If the command line arguments fail to parse, this will be a+ -- Left with the error. If the parser is successful, this+ -- returns a @Right Either@. A Left indicates that the user entered a+ -- shortcut option, either in the global options or in one of the+ -- mode-specific options. A Right indicates that the user selected+ -- a mode.+modesPure os process ss = P.parse ss p+ where+ p = do+ eiGs <- parseOpts os+ case eiGs of+ Left spec -> return . Left $ spec+ Right gs -> case process gs of+ Left s -> fail s+ Right eiModes -> case eiModes of+ Left r -> return (Right r)+ Right modes -> parseModes modes++-- | A parser for simple command lines that do not contain modes.+-- Runs in the IO monad.+simpleIO+ :: [C.OptSpec a]+ -- ^ Options to parse++ -> Intersperse+ -- ^ Allow interspersion of options and arguments?++ -> (String -> Either C.InputError a)+ -- ^ How to parse positional arguments++ -> IO [a]+ -- ^ If there is an error parsing the command line, the program will+ -- exit with an error message. If successful the results are+ -- returned here.+simpleIO os i getArg = do+ let optsWithArgs = OptsWithPosArgs (Opts os []) i getArg+ ss <- getArgs+ case simplePure optsWithArgs ss of+ Left e -> errorAct e+ Right g -> case g of+ Left _ ->+ error "simpleIO: should never happen: no shortcut options"+ Right gs -> return gs++simpleIOCustomError+ :: (P.Error -> IO ())+ -> OptsWithPosArgs s a+ -> IO (Either s [a])+simpleIOCustomError showErr os = do+ ss <- getArgs+ case simplePure os ss of+ Left e -> showErr e >> exitFailure+ Right g -> return g+ ++-- | A command line parser for multi-mode command lines. Runs in the+-- IO monad.+modesIO+ :: Opts s g+ -- ^ Specifies global options and global shortcut options++ -> ([g] -> Either String (Either r [Mode s r]))+ -- ^ This function processes the global options. If there are no+ -- shortcut options specified in the global options, it is applied+ -- to the result of processing the global options. This function+ -- may return a Left if there is something wrong with the+ -- global options (a nonsensical combination, perhaps.) Otherwise,+ -- it returns a @Right Either@. Return a Left if there is no need to+ -- process any modes at all after seeing the global options.+ -- Otherwise, return a Right with a list of modes.++ -> IO (Either s r)+ -- ^ If parsing fails, the program will exit with a failure. If+ -- successful, the result is returned here. A Left indicates a+ -- shortcut option, either from the global options or from the+ -- mode-specific options; a Right indicates the mode a user+ -- selected.+modesIO os ms = do+ ss <- getArgs+ case modesPure os ms ss of+ Left e -> errorAct e+ Right g -> return g+++-- | The name of the program that was entered on the command line,+-- obtained from System.Environment.getProgName.+type ProgName = String++displayAct :: (ProgName -> String) -> IO a+displayAct getHelp = do+ pn <- getProgName+ putStr $ getHelp pn+ exitSuccess++errorAct :: P.Error -> IO a+errorAct e = do+ pn <- getProgName+ IO.hPutStr IO.stderr $ C.formatError pn e+ exitFailure++errorActDisplayHelp :: P.Error -> IO a+errorActDisplayHelp e = do+ pn <- getProgName+ IO.hPutStr IO.stderr $ C.formatError pn e+ IO.hPutStrLn IO.stderr $ "enter \"" ++ pn ++ " -h\" for help."+ exitFailure++-- | A parser for simple command lines. Adds a @--help@ option for+-- you.+simpleHelp+ :: (ProgName -> String)+ -- ^ Indicate the help here. This function, when applied to the name+ -- of the program, returns help. simpleHelp automatically adds+ -- options for @--help@ and @-h@ for you.++ -> [C.OptSpec a]+ -- ^ Options to parse++ -> Intersperse+ -- ^ Allow interspersion of options and positional arguments?++ -> (String -> Either C.InputError a)+ -- ^ How to parse positional arguments++ -> IO [a]+ -- ^ If the parser fails, the program will exit with an error. If+ -- the user requested help, it will be displayed and the program+ -- exits successfully. Otherwise, the options and positional+ -- arguments are returned here.+simpleHelp getHelp os ir getArg = do+ let shortcuts = [C.OptSpec ["help"] "h" (C.NoArg (displayAct getHelp))]+ opts = OptsWithPosArgs (Opts shortcuts os) ir getArg+ ei <- simpleIOCustomError errorActDisplayHelp opts+ case ei of+ Left act -> act+ Right as -> return as++-- | A parser for simple command lines without modes. Adds options+-- for @--help@ and @--version@ for you.+simpleHelpVersion+ :: (ProgName -> String)+ -- ^ Indicate the help here. This function, when applied to the name+ -- of the program, returns help. simpleHelpVersion automatically adds+ -- options for @--help@ and @-h@ for you.++ -> (ProgName -> String)+ -- ^ Indicate the version here. This function, when applied to the+ -- name of the program, returns a version string. simpleHelpVersion+ -- automatically adds an option for @--version@ for you.++ -> [C.OptSpec a]+ -- ^ Options to parse++ -> Intersperse+ -- ^ Allow interspersion of options and positional arguments?++ -> (String -> Either C.InputError a)+ -- ^ How to parse positional arguments++ -> IO [a]+ -- ^ If the parser fails, the program will exit with an error. If+ -- the user requested help or version information, it will be+ -- displayed and the program exits successfully. Otherwise, the+ -- options and positional arguments are returned here.++simpleHelpVersion getHelp getVer os ir getArg = do+ let shortcuts = [ C.OptSpec ["help"] "h"+ (C.NoArg (displayAct getHelp))+ , C.OptSpec ["version"] ""+ (C.NoArg (displayAct getVer)) ]+ opts = OptsWithPosArgs (Opts shortcuts os) ir getArg+ ei <- simpleIOCustomError errorActDisplayHelp opts+ case ei of+ Left act -> act+ Right as -> return as++-- # Helpers++-- | Handles positional arguments and errors with them. The parser for+-- the positional argument must be passed in (this way it can+-- be parsed with nonOptionPosArg or nextWord, as appropriate; when+-- parsing interpsersed command lines, you will want nonOptionPosArg;+-- when parsing non-interspersed command lines, you will need+-- nextWord.)+parsePosArg+ :: P.Parser String+ -- ^ Parser for Word for next positional argument+ -> (String -> Either C.InputError a)+ -- ^ Function to handle positional arguments+ -> P.Parser a+parsePosArg pa f = do+ a <- pa+ case f a of+ Left e ->+ let msg = "invalid positional argument: \"" ++ a ++ "\""+ in case e of+ C.NoMsg -> fail msg+ C.ErrorMsg s -> fail $ msg ++ ": " ++ s+ Right g -> return g++-- | Parses options only, where they are not interspersed with+-- positional arguments. Stops parsing only where it encouters a word+-- that does not begin with a dash. This way if the user enters a bad+-- option, it shows in the error message as a bad option rather than+-- simply not getting parsed.+parseOptsNoIntersperse :: P.Parser a -> P.Parser [a]+parseOptsNoIntersperse p = P.manyTill p e where+ e = P.end <|> nonOpt+ nonOpt = P.lookAhead next+ next = (() <$ P.nonOptionPosArg) <|> P.stopper++-- | Parses options and positional arguments where the two are not+-- interspersed. Stops parsing options when a stopper is encountered+-- or at the first word that does not look like an option.+parseStopOpts+ :: P.Parser a+ -> (String -> Either C.InputError a)+ -> P.Parser [a]+parseStopOpts optParser p =+ (++)+ <$> parseOptsNoIntersperse optParser+ <* optional P.stopper+ <*> many (parsePosArg P.nextWord p)+++-- | @parseIntersperse o p@ parses options and positional arguments,+-- where o is a parser that parses options, and p is a function that,+-- when applied to a string, returns the appropriate type.+--+-- If a stopper has not yet been seen, any word that begins with a+-- hyphen will not be parsed as a positional argument. Therefore, if+-- there is a word before a stopper and it begins with a hyphen, if it+-- is not a valid option then the parse will fail with an error.+parseIntersperse+ :: P.Parser a+ -> (String -> Either C.InputError a)+ -> P.Parser [a]+parseIntersperse optParser p =+ let pa = Just <$> parsePosArg P.nonOptionPosArg p+ po = Just <$> optParser+ ps = Nothing <$ P.stopper+ parser = po <|> ps <|> pa+ in catMaybes <$> P.manyTill parser P.end++-- | Looks at the next word. Succeeds if it is a non-option, or if we+-- are at the end of input. Fails otherwise.+endOrNonOpt :: P.Parser ()+endOrNonOpt = (P.lookAhead P.nonOptionPosArg >> return ())+ <|> P.end+
+ lib/Multiarg/Option.hs view
@@ -0,0 +1,45 @@+-- | These types represent options. Option names cannot have a dash as+-- their first or second character, and long option names cannot have+-- an equals sign anywhere in the name.+module Multiarg.Option (+ ShortOpt,+ unShortOpt,+ makeShortOpt,+ LongOpt,+ unLongOpt,+ makeLongOpt )+ where++-- | Short options. Options that are preceded with a single dash on+-- the command line and consist of a single letter. That single letter+-- cannot be a dash. Any other Unicode character is good (including+-- pathological ones like newlines).+newtype ShortOpt = ShortOpt { unShortOpt :: Char } deriving (Show, Eq, Ord)++-- | Creates a short option. Returns Nothing if the character is not+-- valid for a short option.+makeShortOpt :: Char -> Maybe ShortOpt+makeShortOpt c = case c of+ '-' -> Nothing+ x -> Just $ ShortOpt x++-- | Long options. Options that are preceded with two dashes on the+-- command line and typically consist of an entire mnemonic word, such+-- as @lines@. However, anything that is at least one letter long is+-- fine for a long option name. The name must be at least one+-- character long. It cannot have an equal sign anywhere in its+-- name. Otherwise any Unicode character is good (including+-- pathological ones like newlines).+data LongOpt = LongOpt { unLongOpt :: String } deriving (Show, Eq, Ord)++-- | Makes a long option. Returns Nothing if the string is not a valid+-- long option.+makeLongOpt :: String -> Maybe LongOpt+makeLongOpt t =+ if isValidLongOptText t then Just $ LongOpt t else Nothing+++isValidLongOptText :: String -> Bool+isValidLongOptText s = case s of+ [] -> False+ xs -> not $ '=' `elem` xs
+ lib/Multiarg/Prim.hs view
@@ -0,0 +1,681 @@+-- | Parser primitives. These are the only functions that have access+-- to the internals of the parser. Use these functions if you want to+-- build your own parser from scratch. If your needs are simpler, you+-- will want to look at "Multiarg.SimpleParser" or+-- "Multiarg.Combinator", which do a lot of grunt work+-- for you.+--+-- Internal design, especially the error handling, is based in large+-- part on Parsec, as described in the paper at+-- <http://legacy.cs.uu.nl/daan/pubs.html#parsec>.+module 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.+ parse,++ -- * Higher-level parser combinators+ good,+ choice,+ bind,+ lookAhead,++ -- ** Running parsers multiple times+ several,+ several1,+ manyTill,++ -- ** Failure and errors+ failString,+ genericThrow,+ (<?>),+ try,++ -- * Parsers+ -- ** Short options and arguments+ pendingShortOpt,+ nonPendingShortOpt,+ pendingShortOptArg,++ -- ** Long options and arguments+ exactLongOpt,+ approxLongOpt,++ -- ** Stoppers+ stopper,+ resetStopper,++ -- ** Positional (non-option) arguments+ nextWord,+ nextWordIs,+ nonOptionPosArg,+ matchApproxWord,++ -- ** Miscellaneous+ end,++ -- * Errors+ Description(..),+ Error(Error),+ InputDesc++ ) where+++import Multiarg.Option+ (ShortOpt,+ unShortOpt,+ LongOpt,+ unLongOpt,+ makeLongOpt )+import Control.Applicative ( Applicative, Alternative, optional )+import qualified Control.Applicative as A+import qualified Data.Set as Set+import Data.Set ( Set )+import qualified Control.Monad+import Control.Monad ( when, MonadPlus(mzero, mplus), guard, liftM )+import Data.Maybe (mapMaybe)+import Data.Monoid ( Monoid ( mempty, mappend ) )+import qualified Data.List as L+import Data.List (isPrefixOf)++-- | Parsers. Internally the parser tracks what input remains to be+-- parsed, whether there are any pending short options, and whether a+-- stopper has been seen. A parser can return a value of any type.+--+-- The parser also includes the notion of failure. Any parser can+-- fail; a failed parser affects the behavior of combinators such as+-- choice.+newtype Parser a = Parser { runParser :: State -> Consumed a }++instance Monad Parser where+ (>>=) = bind+ return = good+ fail = failString++instance Functor Parser where+ fmap = liftM++instance Applicative Parser where+ (<*>) = Control.Monad.ap+ pure = return++instance Alternative Parser where+ empty = genericThrow+ (<|>) = choice+ some = several1+ many = several++instance Monoid (Parser a) where+ mempty = genericThrow+ mappend = choice++instance MonadPlus Parser where+ mzero = genericThrow+ mplus = choice++type PendingShort = String+type Remaining = [String]+type SawStopper = Bool+data State = State PendingShort Remaining SawStopper++type InputDesc = String+data Description = Unknown | General String | Expected String+ deriving (Eq, Show, Ord)++-- | Error messages. To format error messages for nice display, see+-- 'Multiarg.Combinator.formatError'.+data Error = Error InputDesc [Description]+ deriving (Eq, Show, Ord)++data Reply a = Ok a State Error+ | Fail Error++data Consumed a = Consumed (Reply a)+ | Empty (Reply a)++-- | @good a@ always succeeds without consuming any input and has+-- result a. This provides the implementation for+-- 'Control.Monad.Monad.return' and+-- 'Control.Applicative.Applicative.pure'.+good :: a -> Parser a+good x = Parser $ \st -> Empty (Ok x st (Error (descLocation st) []))++-- | Combines two parsers into a single parser. The second parser can+-- optionally depend upon the result from the first parser.+--+-- This applies the first parser. If the first parser succeeds,+-- combine then takes the result from the first parser, applies the+-- function given to the result from the first parser, and then+-- applies the resulting parser.+--+-- If the first parser fails, combine will not apply the second+-- function but instead will bypass the second parser.+--+-- This provides the implementation for '>>=' in+-- 'Control.Monad.Monad'.+bind :: Parser a -> (a -> Parser b) -> Parser b+bind (Parser p) f = Parser $ \s ->+ case p s of+ Empty r1 -> case r1 of+ Ok x s' _ -> runParser (f x) s'+ Fail m -> Empty (Fail m)+ Consumed r1 -> Consumed $+ case r1 of+ Ok x s' _ -> case runParser (f x) s' of+ Consumed r -> r+ Empty r -> r+ Fail e -> Fail e++descLocation :: State -> InputDesc+descLocation (State ps rm st) = pending ++ next ++ stop+ where+ pending+ | null ps = ""+ | otherwise = "short option or short option argument: "+ ++ ps ++ " "+ next = case rm of+ [] -> "no words remaining"+ x:_ -> "next word: " ++ x+ stop = if st then " (stopper already seen)" else ""+++-- | @failString s@ always fails without consuming any input. The+-- failure contains a record of the string passed in by s. This+-- provides the implementation for 'Control.Monad.Monad.fail'.+failString :: String -> Parser a+failString str = Parser $ \s ->+ Empty (Fail (Error (descLocation s) [General str]))+++-- | Fail with an unhelpful error message. Usually 'throwString' is+-- more useful, but this is handy to implement some typeclass+-- instances.+genericThrow :: Parser a+genericThrow = Parser $ \s ->+ Empty (Fail (Error (descLocation s) [Unknown]))++-- | Runs the first parser. If it fails without consuming any input,+-- then runs the second parser. If the first parser succeeds, then+-- returns the result of the first parser. If the first parser fails+-- and consumes input, then returns the result of the first+-- parser. This provides the implementation for+-- '<|>' in 'Control.Applicative.Alternative'.+choice :: Parser a -> Parser a -> Parser a+choice p q = Parser $ \s ->+ case runParser p s of+ Empty (Fail msg1) ->+ case runParser q s of+ Empty (Fail msg2) -> mergeError msg1 msg2+ Empty (Ok x s' msg2) -> mergeOk x s' msg1 msg2+ c -> c+ Empty (Ok x s' msg1) ->+ case runParser q s of+ Empty (Fail msg2) -> mergeOk x s' msg1 msg2+ Empty (Ok _ _ msg2) -> mergeOk x s' msg1 msg2+ c -> c+ c -> c+ where+ mergeOk x s msg1 msg2 = Empty (Ok x s (merge msg1 msg2))+ mergeError msg1 msg2 = Empty (Fail (merge msg1 msg2))+ merge (Error loc exp1) (Error _ exp2) =+ Error loc (exp1 ++ exp2)++-- | Applies 'error' if a parser would succeed without consuming any+-- input. Useful for preventing infinite loops on parsers like+-- 'several1'.+crashOnEmptyOk+ :: String+ -- ^ Use this label when applying 'error'++ -> Parser a+ -> Parser a+crashOnEmptyOk str p = Parser $ \s ->+ case runParser p s of+ Empty r -> case r of+ Ok _ _ _ ->+ error $ "multiarg: error: " ++ str+ ++ " applied to parser that succeeds without "+ ++ "consuming any input. Aborted to prevent "+ ++ "an infinite loop."+ e -> Empty e+ o -> o+ ++-- | Runs a parser one or more times. Runs the parser once and then+-- applies 'several'.+several1 :: Parser a -> Parser [a]+several1 p = do+ r1 <- p+ rs <- several p+ return $ r1:rs+++-- | Runs a parser zero or more times. If the last run of the parser+-- fails without consuming any input, this parser succeeds without+-- consuming any input. If the last run of the parser fails while+-- consuming input, this parser fails while consuming input. This+-- provides the implementation for 'many' in Control.Applicative.+several :: Parser a -> Parser [a]+several unwrapped =+ let p = crashOnEmptyOk "several" unwrapped+ in do+ maybeA <- optional p+ case maybeA of+ Nothing -> return []+ Just a -> do+ rest <- several unwrapped+ return $ a:rest+ ++-- | Runs the parser given. If it fails without consuming any input,+-- replaces all Expected messages with the one given. Otherwise,+-- returns the result of the parser unchanged.+(<?>) :: Parser a -> String -> Parser a+p <?> str = Parser $ \s ->+ case runParser p s of+ Empty (Fail m) -> Empty (Fail (expect m str))+ Empty (Ok x s' m) -> Empty (Ok x s' (expect m str))+ x -> x+ where+ expect (Error pos ls) s =+ let ls' = mapMaybe notExpected ls+ notExpected d = case d of+ Expected _ -> Nothing+ x -> Just x+ in Error pos ((Expected s) : ls')++infix 0 <?>++-- | Runs a parser. This is the only way to change a value of type+-- @Parser a@ into a value of type @a@ (that is, it is the only way to+-- \"get out of the Parser monad\" or to \"escape the Parser monad\".)+parse+ :: [String]+ -- ^ Command line arguments to parse. Presumably you got these from+ -- 'getArgs'. If there is any chance that you will be parsing+ -- Unicode strings, see the documentation in+ -- "Multiarg.GetArgs" before you use+ -- 'System.Environment.getArgs'.++ -> Parser a+ -- ^ Parser to run++ -> Either 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+ -- 'choice' combinator can lead to a list of failures.++parse ss p =+ let s = State "" ss False+ procReply r = case r of+ Ok x _ _ -> Right x+ Fail m -> Left m+ in case runParser p s of+ Consumed r -> procReply r+ Empty r -> procReply r++-- | Parses only pending short options. Fails without consuming any+-- input if there has already been a stopper or if there are no+-- pending short options. Fails without consuming any input if there+-- is a pending short option, but it does not match the short option+-- given. Succeeds and consumes a pending short option if it matches+-- the short option given.+pendingShortOpt :: ShortOpt -> Parser ()+pendingShortOpt so = Parser $ \s@(State pends rm stop) ->+ let msg = Error (descLocation s)+ [Expected ("pending short option: -" ++ [unShortOpt so])]+ gd s' = Consumed (Ok () s' msg)+ err = Empty (Fail msg)+ in maybe err gd $ do+ guard $ not stop+ (first, rest) <- case pends of+ [] -> mzero+ x:xs -> return (x, xs)+ when (unShortOpt so /= first) mzero+ return $ State rest rm stop++-- | @lookAhead p@ runs parser p. If p succeeds, lookAhead p succeeds+-- without consuming any input. If p fails without consuming any+-- input, so does lookAhead. If p fails and consumes input, lookAhead+-- also fails and consumes input. If this is undesirable, combine with+-- "try".+lookAhead :: Parser a -> Parser a+lookAhead p = Parser $ \s ->+ case runParser p s of+ Consumed r -> case r of+ Ok x _ e -> Empty (Ok x s e)+ e -> Consumed e+ e -> e++nextW :: Remaining -> Maybe (String, Remaining)+nextW rm = case rm of+ [] -> Nothing+ x:xs -> Just (x, xs)++-- | Parses only non-pending short options. Fails without consuming+-- any input if:+--+-- * there are pending short options+--+-- * there has already been a stopper+--+-- * there are no arguments left to parse+--+-- * the next argument is an empty string+--+-- * the next argument does not begin with a dash+--+-- * the next argument is a single dash+--+-- * the next argument is a short option but it does not match+-- the one given+--+-- * the next argument is a stopper+--+-- Otherwise, consumes the next argument, puts any remaining letters+-- from the argument into a pending short, and removes the first word+-- from remaining arguments to be parsed.+nonPendingShortOpt :: ShortOpt -> Parser ()+nonPendingShortOpt so = Parser $ \s@(State ps rm stop) ->+ let dsc = [Expected+ $ "non pending short option: -" ++ [unShortOpt so]]+ err = Error (descLocation s) dsc+ errRet = Empty (Fail err)+ gd (ps'', rm'') = Consumed (Ok () (State ps'' rm'' stop) err)+ in maybe errRet gd $ do+ guard $ null ps+ guard $ not stop+ (a, rm') <- nextW rm+ (maybeDash, word) <- case a of+ [] -> mzero+ x:xs -> return (x, xs)+ guard (maybeDash == '-')+ (letter, arg) <- case word of+ [] -> mzero+ x:xs -> return (x, xs)+ guard (letter == unShortOpt so)+ return (arg, rm')+++-- | Parses an exact long option. That is, the text of the+-- command-line option must exactly match the text of the+-- option. Returns any argument that is attached to+-- the same word of the option with an equal sign (for example,+-- @--follow=\/dev\/random@ will return @Just \"\/dev\/random\"@ for the+-- argument.) If there is no equal sign, returns Nothing for the+-- argument. If there is an equal sign but there is nothing after it,+-- returns @Just \"\"@ for the argument.+--+-- If you do not want your long option to have equal signs and+-- GNU-style option arguments, wrap this parser in something that will+-- fail if there is an option argument.+--+-- Fails without consuming any input if:+--+-- * there are pending short options+--+-- * a stopper has been parsed+--+-- * there are no arguments left on the command line+--+-- * the next argument on the command line does not begin with+-- two dashes+--+-- * the next argument on the command line is @--@ (a stopper)+--+-- * the next argument on the command line does begin with two+-- dashes but its text does not match the argument we're looking for++exactLongOpt :: LongOpt -> Parser (Maybe String)+exactLongOpt lo = Parser $ \s@(State ps rm sp) ->+ let msg = Error (descLocation s)+ [Expected ("long option: --" ++ unLongOpt lo)]+ gd (arg, newRm) = Consumed (Ok arg (State ps newRm sp) msg)+ err = Empty (Fail msg)+ in maybe err gd $ do+ guard $ null ps+ guard $ not sp+ (x, rm') <- nextW rm+ (word, afterEq) <- getLongOption x+ guard (word == unLongOpt lo)+ return (afterEq, rm')+ ++getLongOption :: String -> Maybe (String, Maybe String)+getLongOption str = do+ guard (str /= "--")+ let (pre, word, afterEq) = splitLongWord str+ guard (pre == "--")+ return (word, afterEq)++-- | Takes a single String and returns a tuple, where the first element+-- is the first two letters, the second element is everything from the+-- third letter to the equal sign, and the third element is Nothing if+-- there is no equal sign, or Just String with everything after the+-- equal sign if there is one.+splitLongWord :: String -> (String, String, Maybe String)+splitLongWord t = (f, s, r) where+ (f, rest) = L.splitAt 2 t+ (s, withEq) = L.break (== '=') rest+ r = case withEq of+ [] -> Nothing+ _:xs -> Just xs++approxLongOptError :: [LongOpt] -> [Description]+approxLongOptError =+ map (Expected . ("long option: --" ++) . unLongOpt)+++assert :: e -> Bool -> Either e ()+assert e b = if b then Right () else Left e++fromMaybe :: e -> Maybe a -> Either e a+fromMaybe e = maybe (Left e) Right++-- | Examines the next word. If it matches a LongOpt in the set+-- unambiguously, returns a tuple of the word actually found and the+-- matching word in the set and the accompanying text after the equal+-- sign (if any). If the Set is empty, this parser will always fail.+approxLongOpt ::+ Set LongOpt+ -> Parser (String, LongOpt, Maybe String)+approxLongOpt ts = Parser $ \s@(State ps rm stop) ->+ let err ls = Error (descLocation s) (approxLongOptError ls)+ ert ls = Empty (Fail $ err ls)+ gd (found, opt, arg, rm'') =+ Consumed (Ok (found, opt, arg) (State ps rm'' stop)+ (err allOpts))+ allOpts = Set.toList ts+ in either ert gd $ do+ assert allOpts $ null ps+ assert allOpts $ not stop+ (x, rm') <- fromMaybe allOpts $ nextW rm+ (word, afterEq) <- fromMaybe allOpts $ getLongOption x+ opt <- fromMaybe allOpts $ makeLongOpt word+ if Set.member opt ts+ then return (word, opt, afterEq, rm')+ else do+ let p t = word `isPrefixOf` unLongOpt t+ matches = Set.filter p ts+ case Set.toList matches of+ [] -> Left allOpts+ (m:[]) -> return (word, m, afterEq, rm')+ ls -> Left ls+++-- | Parses only pending short option arguments. For example, for the+-- @tail@ command, if you enter the option @-c25@, then after parsing+-- the @-c@ option the @25@ becomes a pending short option argument+-- because it was in the same command line argument as the @-c@.+--+-- Fails without consuming any input if:+--+-- * a stopper has already been parsed+--+-- * there are no pending short option arguments+--+-- On success, returns the String of the pending short option argument+-- (this String will never be empty).+pendingShortOptArg :: Parser String+pendingShortOptArg = Parser $ \st@(State ps rm sp) ->+ let msg = [Expected "pending short option argument"]+ err = Error (descLocation st) msg+ ert = Empty (Fail err)+ gd str = Consumed (Ok str (State "" rm sp) err)+ in maybe ert gd $ do+ guard $ not sp+ case ps of+ [] -> mzero+ xs -> return xs+++-- | Parses a \"stopper\" - that is, a double dash. Changes the internal+-- state of the parser to reflect that a stopper has been seen.+stopper :: Parser ()+stopper = Parser $ \s@(State ps rm sp) ->+ let err = Error (descLocation s)+ [Expected "stopper, \"--\""]+ ert = Empty (Fail err)+ gd rm'' = Consumed (Ok () (State ps rm'' True) err)+ in maybe ert gd $ do+ guard $ not sp+ guard . null $ ps+ (x, rm') <- nextW rm+ guard $ x == "--"+ return rm'+++-- | If a stopper has already been seen, change the internal state+-- back to indicating that no stopper has been seen.+resetStopper :: Parser ()+resetStopper = Parser $ \s@(State ps rm _) ->+ Empty (Ok () (State ps rm False) (Error (descLocation s) []))+++-- | try p behaves just like p, but if p fails, try p will not consume+-- any input.+try :: Parser a -> Parser a+try a = Parser $ \s ->+ case runParser a s of+ Consumed r -> case r of+ Fail e -> Empty (Fail e)+ o -> Consumed o+ o -> o+++-- | Returns the next string on the command line as long as there are+-- no pendings. Succeeds even if a stopper is present. Be careful -+-- this will return the next string even if it looks like an option+-- (that is, it starts with a dash.) Consider whether you should be+-- using nonOptionPosArg instead. However this can be useful when+-- parsing command line options after a stopper.+nextWord :: Parser String+nextWord = Parser $ \s@(State ps rm sp) ->+ let err = Error (descLocation s) [dsc]+ dsc = Expected "next word"+ ert = Empty (Fail err)+ gd (str, rm'') = Consumed $ Ok str (State ps rm'' sp) err+ in maybe ert gd $ do+ guard $ null ps+ nextW rm+ ++-- | Parses the next word on the command line, but only if it exactly+-- matches the word given. Otherwise, fails without consuming any+-- input. Also fails without consuming any input if there are pending+-- short options or if a stopper has already been parsed. Does not pay+-- any attention to whether a stopper is present.+nextWordIs :: String -> Parser ()+nextWordIs str = Parser $ \s@(State ps rm sp) ->+ let err = Error (descLocation s) [dsc]+ dsc = Expected $ "next argument \"" ++ str ++ "\""+ ert = Empty $ Fail err+ gd rm'' = Consumed $ Ok () (State ps rm'' sp) err+ in maybe ert gd $ do+ guard $ null ps+ (a, rm') <- nextW rm+ guard (a == str)+ return rm'+++-- | If there are pending short options, fails without consuming any input.+--+-- Otherwise, if a stopper has NOT already been parsed, then returns+-- the next word if it is either a single dash or any other word that+-- does not begin with a dash. If the next word does not meet these+-- criteria, fails without consuming any input.+--+-- Otherwise, if a stopper has already been parsed, then returns the+-- next word, regardless of whether it begins with a dash or not.+nonOptionPosArg :: Parser String+nonOptionPosArg = Parser $ \s@(State ps rm sp) ->+ let err = Error (descLocation s) [dsc]+ dsc = Expected "non option positional argument"+ ert = Empty $ Fail err+ gd (str, rm'') = Consumed $ Ok str (State ps rm'' sp) err+ in maybe ert gd $ do+ guard $ null ps+ (x, rm') <- nextW rm+ result <- if sp+ then return x+ else case x of+ [] -> return x+ '-':[] -> return "-"+ f:_ -> if f == '-' then mzero else return x+ return (result, rm')+++-- | Succeeds if there is no more input left.+end :: Parser ()+end = Parser $ \s@(State ps rm _) ->+ let err = Error (descLocation s) [dsc]+ dsc = Expected "end of input"+ ert = Empty $ Fail err+ gd = Empty $ Ok () s err+ in if null ps && null rm then gd else ert+++-- | Examines the possible words in Set. If there are no pendings,+-- then get the next word and see if it matches one of the words in+-- Set. If so, returns the word actually parsed and the matching word+-- from Set. If there is no match, fails without consuming any+-- input. Pays no attention to whether a stopper has been seen.+matchApproxWord :: Set String -> Parser (String, String)+matchApproxWord set = Parser $ \s@(State ps rm sp) ->+ let err = Error (descLocation s) . lsDsc+ lsDsc = map (Expected . ("next word: " ++))+ ert = Empty . Fail . err+ gd (act, mtch, rm'') =+ Consumed $ Ok (act, mtch) (State ps rm'' sp) (err allWords)+ allWords = Set.toList set+ in either ert gd $ do+ assert allWords $ null ps+ (x, rm') <- fromMaybe allWords $ nextW rm+ let matches = Set.filter p set+ p t = x `isPrefixOf` t+ case Set.toList matches of+ [] -> Left allWords+ r:[] -> return (x, r, rm')+ xs -> Left xs+ +-- | @manyTill p end@ runs parser p zero or more times until parser+-- @end@ succeeds. If @end@ succeeds and consumes input, that input is+-- also consumed. in the result of @manyTill@. If that is a problem,+-- wrap it in @lookAhead@. Also, if @end@ fails and consumes input,+-- @manyTill@ fails and consumes input. If that is a problem, wrap+-- @end@ in @try@.+manyTill :: Parser a -> Parser end -> Parser [a]+manyTill p e = do+ maybeEnd <- optional e+ case maybeEnd of+ Just _ -> return []+ Nothing -> do+ a <- crashOnEmptyOk "manyTill" p+ rs <- manyTill p e+ return $ a:rs+
+ lib/Multiarg/SampleParser.hs view
@@ -0,0 +1,70 @@+-- | This is sample code using "Multiarg". This could+-- be a command-line parser for the version of the Unix command @tail@+-- that is included with GNU coreutils version 8.5. "main" simply gets+-- the command line arguments, parses them, and prints out what was+-- parsed. To test it out, simply compile an executable that looks+-- like this and then feed it different options:+--+-- > import Multiarg.SampleParser+-- > main = sampleMain Intersperse+--+-- or:+--+-- > import Multiarg.SampleParser+-- > main = sampleMain StopOptions+--+-- The code in the module is the sample code; the sample code is not+-- in the Haddock documentation! If you're reading this in Haddock,+-- you will want to also take a look at the actual source code.+module Multiarg.SampleParser where++import qualified Multiarg.Combinator as C+import qualified Multiarg.CommandLine as P++data Flag =+ Bytes String+ | Follow (Maybe String)+ | Retry+ | Lines String+ | Stats String+ | Pid String+ | Quiet+ | Sleep String+ | Verbose+ | Help+ | Version+ | Filename String+ deriving Show++specs :: [C.OptSpec Flag]++specs =+ [ C.OptSpec ["bytes"] ['c']+ (C.OneArg (return . Bytes))++ , C.OptSpec ["follow"] ['f']+ (C.OptionalArg (return . Follow))++ , C.OptSpec ["follow-retry"] ['F'] (C.NoArg Retry)++ , C.OptSpec ["lines"] ['n']+ (C.OneArg (return . Lines))++ , C.OptSpec ["max-unchanged-stats"] []+ (C.OneArg (return . Stats))++ , C.OptSpec ["pid"] []+ (C.OneArg (return . Pid))+ , C.OptSpec ["quiet"] ['q'] (C.NoArg Quiet)++ , C.OptSpec ["sleep-interval"] ['s']+ (C.OneArg (return . Sleep))+ , C.OptSpec ["verbose"] ['v'] (C.NoArg Verbose)+ , C.OptSpec ["help"] [] (C.NoArg Help)+ , C.OptSpec ["version"] [] (C.NoArg Version)+ ]++sampleMain :: P.Intersperse -> IO ()+sampleMain i = do+ r <- P.simpleIO specs i (return . Filename)+ print r
− lib/System/Console/MultiArg.hs
@@ -1,167 +0,0 @@--- | A combinator library for building command-line parsers.--module System.Console.MultiArg (-- -- | To say this library is inspired by Parsec would probably insult the- -- creators of Parsec, as this library could not possibly be as- -- elegant or throughly considered as Parsec is. Nevertheless this- -- library can be used in a similar style as Parsec, but is- -- specialized for parsing command lines.- --- -- This parser was built because I could not find anything that would- -- readily parse command lines where the options took more than one- -- argument. For example, for the @tail@ command on GNU systems, the- -- --lines option takes one argument to specify how many lines you- -- want to see. Well, what if you want to build a program with an- -- option that takes /two/ arguments, like @--foo bar baz@? I found no- -- such library so I built this one. Nevertheless, using this library- -- you can build parsers to parse a variety of command line- -- vocabularies, from simple to complex.-- -- * Terminology-- -- | Some terms are used throughout multiarg:- --- -- [@word@] When you run your program from the Unix shell prompt,- -- your shell is responsible for splitting the command line into- -- words. Typically you separate words with spaces, although quoting- -- can affect this. multiarg parses lists of words. Each word can- -- consist of a single long option, a single long option and an- -- accompanying option argument, a single short option, multiple- -- short options, and even one or more multiple short options and an- -- accompanying short option argument. Or, a word can be a- -- positional argument or a stopper. All these are described below.- --- -- [@option@] Options allow a user to specify ways to tune the- -- operation of a program. Typically options are indeed optional,- -- although some programs do sport \"required options\" (a bit of an- -- oxymoron). Options can be either short options or long- -- options. Also, options can take arguments.- --- -- [@short option@] An option that is specified with a single hyphen- -- and a single letter. For example, for the program @tail(1)@,- -- possible short options include @n@ and @v@. With multiarg it is- -- possible to easily parse short options that are specified in- -- different words or in the same word. For example, if a user wants- -- to run @tail@ with two options, he might type @tail -v -f@ or he- -- might type @tail -vf@.- --- -- [@long option@] An option that is specified using two hyphens and- -- what is usually a mnemonic word, though it could be as short as a- -- single letter. For example, @tail(1)@ has long options including- -- @follow@ and @verbose@. The user would specify these on the- -- command line by typing @tail --follow --verbose@.- --- -- [@option argument@] Some options take additional arguments that- -- are specific to the option and change what the option does. For- -- instance, the @lines@ option to @tail(1)@ takes a single,- -- optional argument, which is the number of lines to show. Option- -- arguments can be optional or required, and a single option can- -- take a mulitple, fixed number of arguments and it can take a- -- variable number of arguments. Option arguments can be given in- -- various ways. They can be specified in the same word as a long- -- option by using an equals sign; they can also be specified in the- -- same word as a short option simply by placing them in the same- -- word, or they can be specified in the following word. For- -- example, these different command lines all mean the same thing;- -- @tail --verbose --lines=20@, @tail --verbose --lines 20@, @tail- -- -vn 20@, @tail -v -n20@, @tail -vn20@, and @tail -v -n 20@, and- -- numerous other combinations also have the same meaning.- --- -- [@GNU-style option argument@] A long option with an argument- -- given with an equal sign, such as [@lines=20@].- --- -- [@positional argument@] A word on the command line that is not an- -- option or an argument to an option. For instance, with @tail(1)@,- -- you specify the files you want to see by using positional- -- arguments. In the command @tail -n 10 myfile@, @myfile@ is a- -- positional argument. For some programs, such as @git@ or @darcs@,- -- a positional argument might be a \"command\" or a \"mode\", such- -- as the @commit@ in @git commit@ or the @whatsnew@ in @darcs- -- whatsnew@. multiarg has no primitive parsers that treat these- -- positional arguments specially but it is trivial to build a- -- parser for command lines such as this, too.- --- -- [@stopper@] A single word consisting solely of two hyphens,- -- @--@. The user types this to indicate that all subsequent words- -- on the command line are positional arguments, even if they begin- -- with hyphens and therefore look like they might be options.- --- -- [@pending@] The user might specify more than one short option, or- -- a short option and a short option argument, in a single word. For- -- example, she might type @tail -vl20@. After parsing the @v@- -- option, the Parser makes @l20@ into a \"pending\". The next- -- parser can then treat @l20@ as an option argument to the @v@- -- option (which is probably not what was wanted) or the next parser- -- 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- -- "System.Console.MultiArg.CommandLine" module, which uses the- -- underlying combinators to build a simple parser for you. That- -- module is already exported from this module for easy usage.- --- -- "System.Console.MultiArg.CommandLine" also has a parser that can- -- handle multi-mode commands (examples include @git@, @darcs@, and- -- @cvs@.)- --- -- For maximum flexibility you will want to start with the- -- "System.Console.MultiArg.Prim" module. Using those parsers you- -- can easily build parsers that are quite complicated. The parsers- -- can check for errors along the way, simplifying the sometimes- -- complex task of ensuring that data a user supplied on the command- -- line is good. You can easily build parsers for programs that take- -- no options, take dozens of options, require that options be given- -- in a particular order, require that some options be given, or bar- -- some combinations of options. You might also require particular- -- positional arguments. Other helpful functions are in- -- "System.Console.MultiArg.Combinator". You will also want to- -- examine the source code for "System.Console.MultiArg.Combinator"- -- and "System.Console.MultiArg.CommandLine" as these show some- -- ways to use the primitive parsers and combinators.-- -- * Non-features and shortcomings- --- -- | multiarg isn't perfect; no software is. multiarg does not- -- automatically make online help for your command line- -- parsers. Getting this right would be tricky given the nature of- -- the code and I don't even want to bother trying, as I just write- -- my own online help in a text editor.- --- -- multiarg partially embraces \"The Tao of Option Parsing\" that- -- Python's Optik (<http://optik.sourceforge.net/>) follows. Read- -- \"The Tao of Option Parsing\" here:- --- -- <http://optik.sourceforge.net/doc/1.5/tao.html>- --- -- multiarg's philosophy is similar to that of Optik, which- -- means you won't be able to use multiarg to (easily) build a clone- -- to the UNIX @find(1)@ command. (You could do it, but multiarg won't- -- help you very much.)- --- -- 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.--- module System.Console.MultiArg.Combinator- , module System.Console.MultiArg.CommandLine- , module System.Console.MultiArg.Option- , module System.Console.MultiArg.Prim- , module System.Environment- ) where--import System.Console.MultiArg.Combinator-import System.Console.MultiArg.CommandLine-import System.Console.MultiArg.Option-import System.Console.MultiArg.Prim-import System.Environment
− lib/System/Console/MultiArg/Combinator.hs
@@ -1,478 +0,0 @@--- | Combinators that are useful for building command-line--- parsers. These build off the functions in--- "System.Console.MultiArg.Prim". Unlike those functions, these--- functions have no access to the internals of the parser.-module System.Console.MultiArg.Combinator (- -- * Parser combinators- notFollowedBy,-- -- * Combined long and short option parser- OptSpec(OptSpec, longOpts, shortOpts, argSpec),- InputError(..),- reader,- optReader,- ArgSpec(..),- parseOption,-- -- * Formatting errors- formatError- ) where--import Data.List (isPrefixOf, intersperse, nubBy)-import Data.Set ( Set )-import qualified Data.Set as Set-import Control.Applicative- ((<*>), optional, (<$), (*>), (<|>), many)--import System.Console.MultiArg.Prim- ( Parser, try, approxLongOpt,- nextWord, pendingShortOptArg, nonOptionPosArg,- pendingShortOpt, nonPendingShortOpt, nextWord, (<?>),- Error(..), Description(..))-import System.Console.MultiArg.Option- ( LongOpt, ShortOpt, unLongOpt,- makeLongOpt, makeShortOpt, unShortOpt )-import qualified Data.Map as M-import Data.Map ((!))-import Data.Maybe (fromMaybe, mapMaybe)-import Data.Monoid ( mconcat )----- | @notFollowedBy p@ succeeds only if parser p fails. If p fails,--- notFollowedBy succeeds without consuming any input. If p succeeds--- and consumes input, notFollowedBy fails and consumes input. If p--- succeeds and does not consume any input, notFollowedBy fails and--- does not consume any input.-notFollowedBy :: Parser a -> Parser ()-notFollowedBy p =- () <$ ((try p >> fail "notFollowedBy failed")- <|> return ())---unsafeShortOpt :: Char -> ShortOpt-unsafeShortOpt c =- fromMaybe (error $ "invalid short option: " ++ [c])- (makeShortOpt c)--unsafeLongOpt :: String -> LongOpt-unsafeLongOpt c =- fromMaybe (error $ "invalid long option: " ++ c)- (makeLongOpt c)----- |Specifies options for the 'parseOption' function. Each OptSpec--- represents one command-line option.-data OptSpec a = OptSpec {- longOpts :: [String]- -- ^ Each String is a single long option, such as @version@. When- -- the user specifies long options on the command line, she must- -- type two dashes; however, do not include the dashes when you- -- specify the long option here. Strings you specify as long options- -- cannot include a dash as either the first or the second- -- character, and they cannot include an equal sign anywhere. If- -- 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)---- | Reads in values that are members of Read. Provides a generic--- error message if the read fails.-reader :: Read a => String -> Either InputError a-reader s = case reads s of- (x, ""):[] -> return x- _ -> Left . ErrorMsg $ "could not parse option argument"---- | Reads in values that are members of Read, but the value does not--- have to appear on the command line. Provides a generic error--- message if the read fails. If the argument is Nothing, returns--- Nothing.-optReader- :: Read a- => Maybe String- -> Either InputError (Maybe a)-optReader ms = case ms of- Nothing -> return Nothing- Just s -> case reads s of- (x, ""):[] -> return (Just x)- _ -> Left . ErrorMsg $ "could not parse option argument"---- | Indicates errors when parsing options to arguments.-data InputError- = NoMsg- -- ^ No error message accompanies this failure. multiarg will create- -- a generic error message for you.-- | ErrorMsg String- -- ^ Parsing the argument failed with this error message. An example- -- might be @option argument is not an integer@ or @option argument- -- is too large@. The text of the options the user provided is- -- automatically prepended to the error message, so do not replicate- -- this in your message.-- deriving (Eq, Show)---- | Create an error message from an InputError.-errorMsg- :: Either LongOpt ShortOpt- -- ^ The option with the faulty argument-- -> [String]- -- ^ The faulty command line arguments-- -> InputError- -> String-errorMsg badOpt ss err = arg ++ opt ++ msg- where- arg = let aw = if length ss > 1 then "arguments " else "argument "- ws = concat . intersperse " " . map quote $ ss- quote s = "\"" ++ s ++ "\""- in aw ++ ws- opt = " to option " ++ optDesc- optDesc = case badOpt of- Left lo -> "--" ++ unLongOpt lo- Right so -> "-" ++ [unShortOpt so]- msg = " invalid" ++ detail- detail = case err of- NoMsg -> ""- ErrorMsg s -> ": " ++ s------ | 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--- different data constructors in an algebraic data type. Or you can--- have each ArgSpec yield a function that transforms a record. For an--- example that uses an algebraic data type, see--- "System.Console.MultiArg.SampleParser".------ Most of these value constructors take as an argument a function--- that returns an Either. The function should return a @Left--- InputError@ if the parsing of the arguments failed--if, for--- example, the user needs to enter an integer but she instead input a--- letter. The functions should return a Right if parsing of the--- arguments was successful.-data ArgSpec a =- NoArg a- -- ^ This option takes no arguments-- | OptionalArg (Maybe String -> Either InputError a)- -- ^ This option takes an optional argument. As noted in \"The Tao- -- of Option Parsing\", optional arguments can result in some- -- ambiguity. (Read it here:- -- <http://optik.sourceforge.net/doc/1.5/tao.html>) If option @a@- -- takes an optional argument, and @b@ is also an option, what- -- does @-ab@ mean? SimpleParser resolves this ambiguity by- -- assuming that @b@ is an argument to @a@. If the user does not- -- like this, she can specify @-a -b@ (in such an instance @-b@ is- -- not parsed as an option to @-a@, because @-b@ begins with a- -- hyphen and therefore \"looks like\" an option.) Certainly- -- though, optional arguments lead to ambiguity, so if you don't- -- like it, don't use them :)-- | OneArg (String -> Either InputError a)- -- ^ This option takes one argument. Here, if option @a@ takes one- -- 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 -> Either InputError a)- -- ^ This option takes two arguments. Parsed similarly to- -- 'OneArg'.-- | ThreeArg (String -> String -> String -> Either InputError a)- -- ^ This option takes three arguments. Parsed similarly to- -- 'OneArg'.-- | VariableArg ([String] -> Either InputError a)- -- ^ This option takes a variable number of arguments--zero or- -- more. Option arguments continue until the command line contains- -- a word that begins with a hyphen. For example, if option @a@- -- takes a variable number of arguments, then @-a one two three- -- -b@ will be parsed as @a@ taking three arguments, and @-a -b@- -- will be parsed as @a@ taking no arguments. If the user enters- -- @-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- ChoiceArg gs ->- ChoiceArg . map (\(s, r) -> (s, f r)) $ gs-- OptionalArg g -> OptionalArg $ \ms -> fmap f (g ms)-- OneArg g ->- OneArg $ \s1 -> fmap f (g s1)-- TwoArg g ->- TwoArg $ \s1 s2 -> fmap f (g s1 s2)-- ThreeArg g ->- ThreeArg $ \s1 s2 s3 -> fmap f (g s1 s2 s3)-- VariableArg g ->- VariableArg $ \ls -> fmap f (g ls)----- | 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--- next word on the command line is not a recognized option. Allows--- the user to specify the shortest unambiguous match for long--- 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-parseOption os =- let longs = longOptParser os- 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--longOptMap :: [OptSpec a] -> M.Map LongOpt (ArgSpec a)-longOptMap = M.fromList . concatMap toPairs where- toPairs (OptSpec los _ as) = map (toPair as) los where- toPair a s = (unsafeLongOpt s, a)--longOpt ::- Set LongOpt- -> M.Map LongOpt (ArgSpec a)- -> Parser a-longOpt set mp = do- (_, lo, maybeArg) <- approxLongOpt set- let spec = mp ! lo- maybeNextArg = maybe nextWord return maybeArg- case spec of- NoArg a -> case maybeArg of- Nothing -> return a- Just _ -> fail $ "option " ++ unLongOpt lo- ++ " does not take argument"- 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-- OptionalArg f -> case maybeArg of- Nothing -> either (fail . errorMsg (Left lo) []) return- $ f Nothing- Just s -> either (fail . errorMsg (Left lo) [s]) return- $ f (Just s)--- OneArg f -> maybeNextArg >>= g- where- g a = either (fail . errorMsg (Left lo) [a]) return- $ f a-- TwoArg f -> do- a1 <- maybeNextArg- a2 <- nextWord- either (fail . errorMsg (Left lo) [a1, a2]) return- $ f a1 a2-- ThreeArg f -> do- a1 <- maybeNextArg- a2 <- nextWord- a3 <- nextWord- either (fail . errorMsg (Left lo) [a1, a2, a3]) return- $ f a1 a2 a3-- VariableArg f -> do- as <- many nonOptionPosArg- let args = case maybeArg of- Nothing -> as- Just a -> a:as- either (fail . errorMsg (Left lo) args) return- $ f args---shortOpt :: OptSpec a -> Maybe (Parser a)-shortOpt o = mconcat parsers where- parsers = map mkParser . shortOpts $ o- mkParser c =- let opt = unsafeShortOpt c- in Just $ nextShort opt *> case argSpec o of- NoArg a -> return a- ChoiceArg ls -> shortChoiceArg opt ls- OptionalArg f -> shortOptionalArg opt f- OneArg f -> shortOneArg opt f- TwoArg f -> shortTwoArg opt f- ThreeArg f -> shortThreeArg opt f- VariableArg f -> shortVariableArg opt f---- | Parses a short option without an argument, either pending or--- non-pending. Fails with a single error message rather than two.-nextShort :: ShortOpt -> Parser ()-nextShort o = p <?> ("short option: -" ++ [unShortOpt o])- where- p = do- r1 <- optional $ pendingShortOpt o- case r1 of- Just () -> return ()- Nothing -> nonPendingShortOpt o---shortVariableArg- :: ShortOpt- -> ([String] -> Either InputError a)- -> Parser a-shortVariableArg so f = do- maybeSameWordArg <- optional pendingShortOptArg- args <- many nonOptionPosArg- let as = case maybeSameWordArg of- Nothing -> args- Just a -> a:args- either (fail . errorMsg (Right so) as) return $ f as---shortOneArg- :: ShortOpt- -> (String -> Either InputError a)- -> Parser a-shortOneArg so f = do- a <- firstShortArg- either (fail . errorMsg (Right so) [a]) return $ f a--firstShortArg :: Parser String-firstShortArg =- optional pendingShortOptArg >>= maybe nextWord 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- :: ShortOpt- -> (String -> String -> Either InputError a)- -> Parser a-shortTwoArg so f = do- a1 <- firstShortArg- a2 <- nextWord- either (fail . errorMsg (Right so) [a1, a2]) return- $ f a1 a2---shortThreeArg- :: ShortOpt- -> (String -> String -> String -> Either InputError a)- -> Parser a-shortThreeArg so f = do- a1 <- firstShortArg- a2 <- nextWord- a3 <- nextWord- either (fail . errorMsg (Right so) [a1, a2, a3]) return- $ f a1 a2 a3--shortOptionalArg- :: ShortOpt- -> (Maybe String -> Either InputError a)- -> Parser a-shortOptionalArg so f = do- maybeSameWordArg <- optional pendingShortOptArg- case maybeSameWordArg of- Nothing -> do- maybeArg <- optional nonOptionPosArg- case maybeArg of- Nothing -> either (fail . errorMsg (Right so) []) return- $ f Nothing- Just a -> either (fail . errorMsg (Right so) [a]) return- $ f (Just a)- Just a -> either (fail . errorMsg (Right so) [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---- | Formats error messages for nice display. Returns a multi-line--- string (there is no need to append a newline to the end of the--- string returned).-formatError- :: String- -- ^ Pass the name of your program here. Displayed at the beginning- -- of the error message.-- -> Error- -> String-formatError p (Error loc ls) =- p ++ ": error: could not parse command line.\n"- ++ "Error at: " ++ loc ++ "\n"- ++ expError- ++ genError- ++ unk- where- toExp m = case m of { Expected s -> Just s; _ -> Nothing }- expc = unlines . mapMaybe toExp $ ls- expError = if null expc then "" else "Expecting:\n" ++ expc- toGeneral m = case m of { General s -> Just s; _ -> Nothing }- gen = unlines . mapMaybe toGeneral $ ls- genError = if null gen- then ""- else let sep = if null expError- then "" else "\n"- in sep ++ gen- unk = if any (== Unknown) ls then "Unknown error\n" else ""-
− lib/System/Console/MultiArg/CommandLine.hs
@@ -1,536 +0,0 @@-{-# LANGUAGE ExistentialQuantification #-}--- | Some pre-built command line parsers. One is a simple command line--- parser that can parse options that take an optional argument, one--- or two arguments, or a variable number of arguments. For sample--- code that uses this parser, see--- "System.Console.MultiArg.SampleParser".------ Another parser is provided for multi-mode programs that are similar--- to @git@ or @darcs@.------ Previously there was a bug in System.Environment.getArgs that would--- not properly encode Unicode command line arguments. multiarg used--- to provide its own GetArgs module to deal with this. This bug was--- in base 4.3.1.0, which was bundled with ghc 7.0.4. This bug was--- fixed in base 4.4.0.0, which came with ghc 7.2. Since this bug has--- been fixed for awhile, multiarg no longer has its own GetArgs--- module.-module System.Console.MultiArg.CommandLine (- -- * Interspersion control- Intersperse (Intersperse, StopOptions)-- -- * Types- , ProgName- , Opts(..)- , OptsWithPosArgs(..)- , Mode(..)-- -- * Simple parsers- , simplePure- , simpleIO- , simpleHelp- , simpleHelpVersion-- -- * Mode parsers- , modesPure- , modesIO-- -- * Helpers to create various options and modes- , optsHelp- , optsHelpVersion- , modeHelp-- ) where--import qualified System.Console.MultiArg.Combinator as C-import qualified System.Console.MultiArg.Prim as P-import System.Environment (getArgs, getProgName)-import System.Exit (exitFailure, exitSuccess)-import qualified System.IO as IO-import Control.Applicative ( many, (<|>), optional,- (<$), (<*>), (<*), (<$>))-import Data.Bifunctor-import Data.List (find)-import Data.Maybe (catMaybes, fromJust)-import qualified Data.Set as Set----- | What to do after encountering the first non-option,--- non-option-argument word on the command line? In either case, no--- more options are parsed after a stopper.-data Intersperse =- Intersperse- -- ^ Additional options are allowed on the command line 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 an option. If @b@ is /not/ an option and the same- -- 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.----- | Specifies a set of options.-data Opts s a = Opts- { oShortcuts :: [C.OptSpec s]- -- ^ Shortcut options are commonly options such as @--help@ or- -- @--version@. Such options must be specified alone on the command- -- line. The parser looks for one of these options first. If it- -- finds one and it is the only option on the command line, only- -- this option is processed and returned. If the option is not- -- alone on the command line, an error occurs. If no shortcut- -- option is found, the parser processes non-shortcut options- -- instead.-- , oOptions :: [C.OptSpec a]- -- ^ If the user does not specify any shortcut options, she may- -- specify any number of these options.-- }--instance Bifunctor Opts where- bimap fa fc o = Opts- { oShortcuts = map (fmap fa) . oShortcuts $ o- , oOptions = map (fmap fc) . oOptions $ o- }--instance Functor (Opts a) where- fmap f o = o { oOptions = fmap (fmap f) . oOptions $ o }---- | Creates an Opts with a help shortcut option.-optsHelp- :: h- -- ^ Whatever type you wish to use for help- -> [C.OptSpec a]- -> Opts h a-optsHelp h = Opts [C.OptSpec ["help"] "h" (C.NoArg h)]---- | Creates an Opts with help and version shortcut options.-optsHelpVersion- :: h- -- ^ What you wish to use for help-- -> h- -- ^ What you wish to use for version-- -> [C.OptSpec a]- -> Opts h a-optsHelpVersion h v = Opts [ C.OptSpec ["help"] "h" (C.NoArg h)- , C.OptSpec ["version"] "v" (C.NoArg v) ]---- | Specification for both options and positional arguments.-data OptsWithPosArgs s a = OptsWithPosArgs- { opOpts :: Opts s a- , opIntersperse :: Intersperse- , opPosArg :: String -> Either C.InputError a- }--instance Bifunctor OptsWithPosArgs where- bimap fa fc o = OptsWithPosArgs- { opOpts = bimap fa fc . opOpts $ o- , opIntersperse = opIntersperse o- , opPosArg = fmap (fmap fc) . opPosArg $ o- }--instance Functor (OptsWithPosArgs s) where- fmap f o = o- { opOpts = fmap f . opOpts $ o- , opPosArg = fmap (fmap f) . opPosArg $ o- }---- | Specifies a mode.-data Mode s r = forall a. Mode- { mModeName :: String- -- ^ How the user specifies the mode on the command line. For @git@- -- for example this might be @commit@ or @log@.-- , mGetResult :: [a] -> r- -- ^ This function is applied to a list of the results of parsing the- -- options that are specific to this mode. The function returns a- -- type of your choosing (though all modes in the same parser will- -- have to return the same type.)-- , mOpts :: OptsWithPosArgs s a- -- ^ Options and positional arguments that are specific to this- -- mode. For example, in the command line @git commit -a -m 'this- -- is a log message'@, @commit@ is the mode name and everything- -- after that is specified here as an option or positional argument- -- that is specific to this mode.- }--instance Bifunctor Mode where- bimap fa fc (Mode n r o) = Mode- { mModeName = n- , mGetResult = fmap fc r- , mOpts = first fa o- }--instance Functor (Mode s) where- fmap f (Mode n gr os) = Mode n (fmap f gr) os---- | Creates a Mode with a help option (help specific to the mode.)-modeHelp- :: String- -- ^ Mode name-- -> h- -- ^ Whatever you want to use for the help (perhaps a string, or a- -- function, or an IO action). Its type will have to match up with- -- the type of the global shortcut options and with the shortcut- -- type of the other modes.-- -> ([a] -> r)- -- ^ When applied to the the mode options, returns the result.-- -> [C.OptSpec a]- -- ^ Options for this mode-- -> Intersperse- -- ^ Allow interspersion of mode options and positional arguments?-- -> (String -> Either C.InputError a)- -- ^ Parses positional arguments-- -> Mode h r--modeHelp n h getR os i p =- Mode n getR (OptsWithPosArgs (Opts ss os) i p)- where- ss = [C.OptSpec ["help"] "h" (C.NoArg h)]--parseOpts :: Opts s a -> P.Parser (Either s [a])-parseOpts os = do- let specials = oShortcuts os- maySpecial <- optional (C.parseOption specials <* P.end)- case maySpecial of- Nothing -> fmap Right- $ P.manyTill (C.parseOption (oOptions os)) endOrNonOpt- Just spec -> return . Left $ spec--parseOptsWithPosArgs- :: OptsWithPosArgs s a- -> P.Parser (Either s [a])-parseOptsWithPosArgs os = do- let specials = oShortcuts . opOpts $ os- maySpecial <- optional (C.parseOption specials <* P.end)- case maySpecial of- Nothing ->- let f = case opIntersperse os of- Intersperse -> parseIntersperse- StopOptions -> parseStopOpts- parser = C.parseOption (oOptions . opOpts $ os)- in fmap Right $ f parser (opPosArg os)- Just spec -> return . Left $ spec--parseModes- :: [Mode s r]- -> P.Parser (Either s r)-parseModes ms = do- let modeWords = Set.fromList . map mModeName $ ms- (_, w) <- P.matchApproxWord modeWords- processMode (fromJust . find (\c -> mModeName c == w) $ ms)- where- processMode (Mode _ gr os) = do- eiOpts <- parseOptsWithPosArgs os- return $ case eiOpts of- Left x -> Left x- Right opts -> Right (gr opts)----- | A pure (non-IO) parser for simple command lines--that is, command--- lines that do not have modes.-simplePure- :: OptsWithPosArgs s a- -- ^ Specifies allowed regular options, allowed shortcut options,- -- and how to parse positional arguments. Also specifies whether- -- the user may intersperse options with positional arguments.-- -> [String]- -- ^ The command line arguments to parse-- -> Either P.Error (Either s [a])- -- ^ Returns an error if the command line arguments could not be- -- parsed. If the parse was successful, returns an Either. A Left- -- indicates that the user selected a shortcut option. A Right- -- indicates that the user did not specify a shortcut option, and- -- will contain a list of the options and positional arguments.-simplePure os ss = P.parse ss (parseOptsWithPosArgs os)---- | A pure (non-IO) parser for command lines that contain modes.-modesPure- :: Opts s g- -- ^ Global options. These are specified before any mode. For- -- instance, in the command @git --no-pager commit -a@, the option- -- @--no-pager@ is a global option. Global options can contain- -- shortcut options. For instance, @git --help@ contains a single- -- shortcut option.-- -> ([g] -> Either String (Either r [Mode s r]))- -- ^ This function processes the global options. If there are no- -- shortcut options specified in the global options, it is applied- -- to the result of processing the global options. This function- -- may return a Left if there is something wrong with the- -- global options (a nonsensical combination, perhaps.) Otherwise,- -- it returns a @Right Either@. Return a Left if there is no need to- -- process any modes at all after seeing the global options.- -- Otherwise, return a Right with a list of modes.-- -> [String]- -- ^ Command line arguments to parse-- -> Either P.Error (Either s r)- -- ^ If the command line arguments fail to parse, this will be a- -- Left with the error. If the parser is successful, this- -- returns a @Right Either@. A Left indicates that the user entered a- -- shortcut option, either in the global options or in one of the- -- mode-specific options. A Right indicates that the user selected- -- a mode.-modesPure os process ss = P.parse ss p- where- p = do- eiGs <- parseOpts os- case eiGs of- Left spec -> return . Left $ spec- Right gs -> case process gs of- Left s -> fail s- Right eiModes -> case eiModes of- Left r -> return (Right r)- Right modes -> parseModes modes---- | A parser for simple command lines that do not contain modes.--- Runs in the IO monad.-simpleIO- :: [C.OptSpec a]- -- ^ Options to parse-- -> Intersperse- -- ^ Allow interspersion of options and arguments?-- -> (String -> Either C.InputError a)- -- ^ How to parse positional arguments-- -> IO [a]- -- ^ If there is an error parsing the command line, the program will- -- exit with an error message. If successful the results are- -- returned here.-simpleIO os i getArg = do- let optsWithArgs = OptsWithPosArgs (Opts os []) i getArg- ss <- getArgs- case simplePure optsWithArgs ss of- Left e -> errorAct e- Right g -> case g of- Left _ ->- error "simpleIO: should never happen: no shortcut options"- Right gs -> return gs--simpleIOCustomError- :: (P.Error -> IO ())- -> OptsWithPosArgs s a- -> IO (Either s [a])-simpleIOCustomError showErr os = do- ss <- getArgs- case simplePure os ss of- Left e -> showErr e >> exitFailure- Right g -> return g- ---- | A command line parser for multi-mode command lines. Runs in the--- IO monad.-modesIO- :: Opts s g- -- ^ Specifies global options and global shortcut options-- -> ([g] -> Either String (Either r [Mode s r]))- -- ^ This function processes the global options. If there are no- -- shortcut options specified in the global options, it is applied- -- to the result of processing the global options. This function- -- may return a Left if there is something wrong with the- -- global options (a nonsensical combination, perhaps.) Otherwise,- -- it returns a @Right Either@. Return a Left if there is no need to- -- process any modes at all after seeing the global options.- -- Otherwise, return a Right with a list of modes.-- -> IO (Either s r)- -- ^ If parsing fails, the program will exit with a failure. If- -- successful, the result is returned here. A Left indicates a- -- shortcut option, either from the global options or from the- -- mode-specific options; a Right indicates the mode a user- -- selected.-modesIO os ms = do- ss <- getArgs- case modesPure os ms ss of- Left e -> errorAct e- Right g -> return g----- | The name of the program that was entered on the command line,--- obtained from System.Environment.getProgName.-type ProgName = String--displayAct :: (ProgName -> String) -> IO a-displayAct getHelp = do- pn <- getProgName- putStr $ getHelp pn- exitSuccess--errorAct :: P.Error -> IO a-errorAct e = do- pn <- getProgName- IO.hPutStr IO.stderr $ C.formatError pn e- exitFailure--errorActDisplayHelp :: P.Error -> IO a-errorActDisplayHelp e = do- pn <- getProgName- IO.hPutStr IO.stderr $ C.formatError pn e- IO.hPutStrLn IO.stderr $ "enter \"" ++ pn ++ " -h\" for help."- exitFailure---- | A parser for simple command lines. Adds a @--help@ option for--- you.-simpleHelp- :: (ProgName -> String)- -- ^ Indicate the help here. This function, when applied to the name- -- of the program, returns help. simpleHelp automatically adds- -- options for @--help@ and @-h@ for you.-- -> [C.OptSpec a]- -- ^ Options to parse-- -> Intersperse- -- ^ Allow interspersion of options and positional arguments?-- -> (String -> Either C.InputError a)- -- ^ How to parse positional arguments-- -> IO [a]- -- ^ If the parser fails, the program will exit with an error. If- -- the user requested help, it will be displayed and the program- -- exits successfully. Otherwise, the options and positional- -- arguments are returned here.-simpleHelp getHelp os ir getArg = do- let shortcuts = [C.OptSpec ["help"] "h" (C.NoArg (displayAct getHelp))]- opts = OptsWithPosArgs (Opts shortcuts os) ir getArg- ei <- simpleIOCustomError errorActDisplayHelp opts- case ei of- Left act -> act- Right as -> return as---- | A parser for simple command lines without modes. Adds options--- for @--help@ and @--version@ for you.-simpleHelpVersion- :: (ProgName -> String)- -- ^ Indicate the help here. This function, when applied to the name- -- of the program, returns help. simpleHelpVersion automatically adds- -- options for @--help@ and @-h@ for you.-- -> (ProgName -> String)- -- ^ Indicate the version here. This function, when applied to the- -- name of the program, returns a version string. simpleHelpVersion- -- automatically adds an option for @--version@ for you.-- -> [C.OptSpec a]- -- ^ Options to parse-- -> Intersperse- -- ^ Allow interspersion of options and positional arguments?-- -> (String -> Either C.InputError a)- -- ^ How to parse positional arguments-- -> IO [a]- -- ^ If the parser fails, the program will exit with an error. If- -- the user requested help or version information, it will be- -- displayed and the program exits successfully. Otherwise, the- -- options and positional arguments are returned here.--simpleHelpVersion getHelp getVer os ir getArg = do- let shortcuts = [ C.OptSpec ["help"] "h"- (C.NoArg (displayAct getHelp))- , C.OptSpec ["version"] ""- (C.NoArg (displayAct getVer)) ]- opts = OptsWithPosArgs (Opts shortcuts os) ir getArg- ei <- simpleIOCustomError errorActDisplayHelp opts- case ei of- Left act -> act- Right as -> return as---- # Helpers---- | Handles positional arguments and errors with them. The parser for--- the positional argument must be passed in (this way it can--- be parsed with nonOptionPosArg or nextWord, as appropriate; when--- parsing interpsersed command lines, you will want nonOptionPosArg;--- when parsing non-interspersed command lines, you will need--- nextWord.)-parsePosArg- :: P.Parser String- -- ^ Parser for Word for next positional argument- -> (String -> Either C.InputError a)- -- ^ Function to handle positional arguments- -> P.Parser a-parsePosArg pa f = do- a <- pa- case f a of- Left e ->- let msg = "invalid positional argument: \"" ++ a ++ "\""- in case e of- C.NoMsg -> fail msg- C.ErrorMsg s -> fail $ msg ++ ": " ++ s- Right g -> return g---- | Parses options only, where they are not interspersed with--- positional arguments. Stops parsing only where it encouters a word--- that does not begin with a dash. This way if the user enters a bad--- option, it shows in the error message as a bad option rather than--- simply not getting parsed.-parseOptsNoIntersperse :: P.Parser a -> P.Parser [a]-parseOptsNoIntersperse p = P.manyTill p e where- e = P.end <|> nonOpt- nonOpt = P.lookAhead next- next = (() <$ P.nonOptionPosArg) <|> P.stopper---- | Parses options and positional arguments where the two are not--- interspersed. Stops parsing options when a stopper is encountered--- or at the first word that does not look like an option.-parseStopOpts- :: P.Parser a- -> (String -> Either C.InputError a)- -> P.Parser [a]-parseStopOpts optParser p =- (++)- <$> parseOptsNoIntersperse optParser- <* optional P.stopper- <*> many (parsePosArg P.nextWord p)----- | @parseIntersperse o p@ parses options and positional arguments,--- where o is a parser that parses options, and p is a function that,--- when applied to a string, returns the appropriate type.------ If a stopper has not yet been seen, any word that begins with a--- hyphen will not be parsed as a positional argument. Therefore, if--- there is a word before a stopper and it begins with a hyphen, if it--- is not a valid option then the parse will fail with an error.-parseIntersperse- :: P.Parser a- -> (String -> Either C.InputError a)- -> P.Parser [a]-parseIntersperse optParser p =- let pa = Just <$> parsePosArg P.nonOptionPosArg p- po = Just <$> optParser- ps = Nothing <$ P.stopper- parser = po <|> ps <|> pa- in catMaybes <$> P.manyTill parser P.end---- | Looks at the next word. Succeeds if it is a non-option, or if we--- are at the end of input. Fails otherwise.-endOrNonOpt :: P.Parser ()-endOrNonOpt = (P.lookAhead P.nonOptionPosArg >> return ())- <|> P.end-
− lib/System/Console/MultiArg/Option.hs
@@ -1,45 +0,0 @@--- | These types represent options. Option names cannot have a dash as--- their first or second character, and long option names cannot have--- an equals sign anywhere in the name.-module System.Console.MultiArg.Option (- ShortOpt,- unShortOpt,- makeShortOpt,- LongOpt,- unLongOpt,- makeLongOpt )- where---- | Short options. Options that are preceded with a single dash on--- the command line and consist of a single letter. That single letter--- cannot be a dash. Any other Unicode character is good (including--- pathological ones like newlines).-newtype ShortOpt = ShortOpt { unShortOpt :: Char } deriving (Show, Eq, Ord)---- | Creates a short option. Returns Nothing if the character is not--- valid for a short option.-makeShortOpt :: Char -> Maybe ShortOpt-makeShortOpt c = case c of- '-' -> Nothing- x -> Just $ ShortOpt x---- | Long options. Options that are preceded with two dashes on the--- command line and typically consist of an entire mnemonic word, such--- as @lines@. However, anything that is at least one letter long is--- fine for a long option name. The name must be at least one--- character long. It cannot have an equal sign anywhere in its--- name. Otherwise any Unicode character is good (including--- pathological ones like newlines).-data LongOpt = LongOpt { unLongOpt :: String } deriving (Show, Eq, Ord)---- | Makes a long option. Returns Nothing if the string is not a valid--- long option.-makeLongOpt :: String -> Maybe LongOpt-makeLongOpt t =- if isValidLongOptText t then Just $ LongOpt t else Nothing---isValidLongOptText :: String -> Bool-isValidLongOptText s = case s of- [] -> False- xs -> not $ '=' `elem` xs
− lib/System/Console/MultiArg/Prim.hs
@@ -1,681 +0,0 @@--- | Parser primitives. These are the only functions that have access--- to the internals of the parser. Use these functions if you want to--- build your own parser from scratch. If your needs are simpler, you--- will want to look at "System.Console.MultiArg.SimpleParser" or--- "System.Console.MultiArg.Combinator", which do a lot of grunt work--- for you.------ Internal design, especially the error handling, is based in large--- part on Parsec, as described in the paper at--- <http://legacy.cs.uu.nl/daan/pubs.html#parsec>.-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.- parse,-- -- * Higher-level parser combinators- good,- choice,- bind,- lookAhead,-- -- ** Running parsers multiple times- several,- several1,- manyTill,-- -- ** Failure and errors- failString,- genericThrow,- (<?>),- try,-- -- * Parsers- -- ** Short options and arguments- pendingShortOpt,- nonPendingShortOpt,- pendingShortOptArg,-- -- ** Long options and arguments- exactLongOpt,- approxLongOpt,-- -- ** Stoppers- stopper,- resetStopper,-- -- ** Positional (non-option) arguments- nextWord,- nextWordIs,- nonOptionPosArg,- matchApproxWord,-- -- ** Miscellaneous- end,-- -- * Errors- Description(..),- Error(Error),- InputDesc-- ) where---import System.Console.MultiArg.Option- (ShortOpt,- unShortOpt,- LongOpt,- unLongOpt,- makeLongOpt )-import Control.Applicative ( Applicative, Alternative, optional )-import qualified Control.Applicative as A-import qualified Data.Set as Set-import Data.Set ( Set )-import qualified Control.Monad-import Control.Monad ( when, MonadPlus(mzero, mplus), guard, liftM )-import Data.Maybe (mapMaybe)-import Data.Monoid ( Monoid ( mempty, mappend ) )-import qualified Data.List as L-import Data.List (isPrefixOf)---- | Parsers. Internally the parser tracks what input remains to be--- parsed, whether there are any pending short options, and whether a--- stopper has been seen. A parser can return a value of any type.------ The parser also includes the notion of failure. Any parser can--- fail; a failed parser affects the behavior of combinators such as--- choice.-newtype Parser a = Parser { runParser :: State -> Consumed a }--instance Monad Parser where- (>>=) = bind- return = good- fail = failString--instance Functor Parser where- fmap = liftM--instance Applicative Parser where- (<*>) = Control.Monad.ap- pure = return--instance Alternative Parser where- empty = genericThrow- (<|>) = choice- some = several1- many = several--instance Monoid (Parser a) where- mempty = genericThrow- mappend = choice--instance MonadPlus Parser where- mzero = genericThrow- mplus = choice--type PendingShort = String-type Remaining = [String]-type SawStopper = Bool-data State = State PendingShort Remaining SawStopper--type InputDesc = String-data Description = Unknown | General String | Expected String- deriving (Eq, Show, Ord)---- | Error messages. To format error messages for nice display, see--- 'System.Console.MultiArg.Combinator.formatError'.-data Error = Error InputDesc [Description]- deriving (Eq, Show, Ord)--data Reply a = Ok a State Error- | Fail Error--data Consumed a = Consumed (Reply a)- | Empty (Reply a)---- | @good a@ always succeeds without consuming any input and has--- result a. This provides the implementation for--- 'Control.Monad.Monad.return' and--- 'Control.Applicative.Applicative.pure'.-good :: a -> Parser a-good x = Parser $ \st -> Empty (Ok x st (Error (descLocation st) []))---- | Combines two parsers into a single parser. The second parser can--- optionally depend upon the result from the first parser.------ This applies the first parser. If the first parser succeeds,--- combine then takes the result from the first parser, applies the--- function given to the result from the first parser, and then--- applies the resulting parser.------ If the first parser fails, combine will not apply the second--- function but instead will bypass the second parser.------ This provides the implementation for '>>=' in--- 'Control.Monad.Monad'.-bind :: Parser a -> (a -> Parser b) -> Parser b-bind (Parser p) f = Parser $ \s ->- case p s of- Empty r1 -> case r1 of- Ok x s' _ -> runParser (f x) s'- Fail m -> Empty (Fail m)- Consumed r1 -> Consumed $- case r1 of- Ok x s' _ -> case runParser (f x) s' of- Consumed r -> r- Empty r -> r- Fail e -> Fail e--descLocation :: State -> InputDesc-descLocation (State ps rm st) = pending ++ next ++ stop- where- pending- | null ps = ""- | otherwise = "short option or short option argument: "- ++ ps ++ " "- next = case rm of- [] -> "no words remaining"- x:_ -> "next word: " ++ x- stop = if st then " (stopper already seen)" else ""----- | @failString s@ always fails without consuming any input. The--- failure contains a record of the string passed in by s. This--- provides the implementation for 'Control.Monad.Monad.fail'.-failString :: String -> Parser a-failString str = Parser $ \s ->- Empty (Fail (Error (descLocation s) [General str]))----- | Fail with an unhelpful error message. Usually 'throwString' is--- more useful, but this is handy to implement some typeclass--- instances.-genericThrow :: Parser a-genericThrow = Parser $ \s ->- Empty (Fail (Error (descLocation s) [Unknown]))---- | Runs the first parser. If it fails without consuming any input,--- then runs the second parser. If the first parser succeeds, then--- returns the result of the first parser. If the first parser fails--- and consumes input, then returns the result of the first--- parser. This provides the implementation for--- '<|>' in 'Control.Applicative.Alternative'.-choice :: Parser a -> Parser a -> Parser a-choice p q = Parser $ \s ->- case runParser p s of- Empty (Fail msg1) ->- case runParser q s of- Empty (Fail msg2) -> mergeError msg1 msg2- Empty (Ok x s' msg2) -> mergeOk x s' msg1 msg2- c -> c- Empty (Ok x s' msg1) ->- case runParser q s of- Empty (Fail msg2) -> mergeOk x s' msg1 msg2- Empty (Ok _ _ msg2) -> mergeOk x s' msg1 msg2- c -> c- c -> c- where- mergeOk x s msg1 msg2 = Empty (Ok x s (merge msg1 msg2))- mergeError msg1 msg2 = Empty (Fail (merge msg1 msg2))- merge (Error loc exp1) (Error _ exp2) =- Error loc (exp1 ++ exp2)---- | Applies 'error' if a parser would succeed without consuming any--- input. Useful for preventing infinite loops on parsers like--- 'several1'.-crashOnEmptyOk- :: String- -- ^ Use this label when applying 'error'-- -> Parser a- -> Parser a-crashOnEmptyOk str p = Parser $ \s ->- case runParser p s of- Empty r -> case r of- Ok _ _ _ ->- error $ "multiarg: error: " ++ str- ++ " applied to parser that succeeds without "- ++ "consuming any input. Aborted to prevent "- ++ "an infinite loop."- e -> Empty e- o -> o- ---- | Runs a parser one or more times. Runs the parser once and then--- applies 'several'.-several1 :: Parser a -> Parser [a]-several1 p = do- r1 <- p- rs <- several p- return $ r1:rs----- | Runs a parser zero or more times. If the last run of the parser--- fails without consuming any input, this parser succeeds without--- consuming any input. If the last run of the parser fails while--- consuming input, this parser fails while consuming input. This--- provides the implementation for 'many' in Control.Applicative.-several :: Parser a -> Parser [a]-several unwrapped =- let p = crashOnEmptyOk "several" unwrapped- in do- maybeA <- optional p- case maybeA of- Nothing -> return []- Just a -> do- rest <- several unwrapped- return $ a:rest- ---- | Runs the parser given. If it fails without consuming any input,--- replaces all Expected messages with the one given. Otherwise,--- returns the result of the parser unchanged.-(<?>) :: Parser a -> String -> Parser a-p <?> str = Parser $ \s ->- case runParser p s of- Empty (Fail m) -> Empty (Fail (expect m str))- Empty (Ok x s' m) -> Empty (Ok x s' (expect m str))- x -> x- where- expect (Error pos ls) s =- let ls' = mapMaybe notExpected ls- notExpected d = case d of- Expected _ -> Nothing- x -> Just x- in Error pos ((Expected s) : ls')--infix 0 <?>---- | Runs a parser. This is the only way to change a value of type--- @Parser a@ into a value of type @a@ (that is, it is the only way to--- \"get out of the Parser monad\" or to \"escape the Parser monad\".)-parse- :: [String]- -- ^ Command line arguments to parse. Presumably you got these from- -- 'getArgs'. If there is any chance that you will be parsing- -- Unicode strings, see the documentation in- -- "System.Console.MultiArg.GetArgs" before you use- -- 'System.Environment.getArgs'.-- -> Parser a- -- ^ Parser to run-- -> Either 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- -- 'choice' combinator can lead to a list of failures.--parse ss p =- let s = State "" ss False- procReply r = case r of- Ok x _ _ -> Right x- Fail m -> Left m- in case runParser p s of- Consumed r -> procReply r- Empty r -> procReply r---- | Parses only pending short options. Fails without consuming any--- input if there has already been a stopper or if there are no--- pending short options. Fails without consuming any input if there--- is a pending short option, but it does not match the short option--- given. Succeeds and consumes a pending short option if it matches--- the short option given.-pendingShortOpt :: ShortOpt -> Parser ()-pendingShortOpt so = Parser $ \s@(State pends rm stop) ->- let msg = Error (descLocation s)- [Expected ("pending short option: -" ++ [unShortOpt so])]- gd s' = Consumed (Ok () s' msg)- err = Empty (Fail msg)- in maybe err gd $ do- guard $ not stop- (first, rest) <- case pends of- [] -> mzero- x:xs -> return (x, xs)- when (unShortOpt so /= first) mzero- return $ State rest rm stop---- | @lookAhead p@ runs parser p. If p succeeds, lookAhead p succeeds--- without consuming any input. If p fails without consuming any--- input, so does lookAhead. If p fails and consumes input, lookAhead--- also fails and consumes input. If this is undesirable, combine with--- "try".-lookAhead :: Parser a -> Parser a-lookAhead p = Parser $ \s ->- case runParser p s of- Consumed r -> case r of- Ok x _ e -> Empty (Ok x s e)- e -> Consumed e- e -> e--nextW :: Remaining -> Maybe (String, Remaining)-nextW rm = case rm of- [] -> Nothing- x:xs -> Just (x, xs)---- | Parses only non-pending short options. Fails without consuming--- any input if:------ * there are pending short options------ * there has already been a stopper------ * there are no arguments left to parse------ * the next argument is an empty string------ * the next argument does not begin with a dash------ * the next argument is a single dash------ * the next argument is a short option but it does not match--- the one given------ * the next argument is a stopper------ Otherwise, consumes the next argument, puts any remaining letters--- from the argument into a pending short, and removes the first word--- from remaining arguments to be parsed.-nonPendingShortOpt :: ShortOpt -> Parser ()-nonPendingShortOpt so = Parser $ \s@(State ps rm stop) ->- let dsc = [Expected- $ "non pending short option: -" ++ [unShortOpt so]]- err = Error (descLocation s) dsc- errRet = Empty (Fail err)- gd (ps'', rm'') = Consumed (Ok () (State ps'' rm'' stop) err)- in maybe errRet gd $ do- guard $ null ps- guard $ not stop- (a, rm') <- nextW rm- (maybeDash, word) <- case a of- [] -> mzero- x:xs -> return (x, xs)- guard (maybeDash == '-')- (letter, arg) <- case word of- [] -> mzero- x:xs -> return (x, xs)- guard (letter == unShortOpt so)- return (arg, rm')----- | Parses an exact long option. That is, the text of the--- command-line option must exactly match the text of the--- option. Returns any argument that is attached to--- the same word of the option with an equal sign (for example,--- @--follow=\/dev\/random@ will return @Just \"\/dev\/random\"@ for the--- argument.) If there is no equal sign, returns Nothing for the--- argument. If there is an equal sign but there is nothing after it,--- returns @Just \"\"@ for the argument.------ If you do not want your long option to have equal signs and--- GNU-style option arguments, wrap this parser in something that will--- fail if there is an option argument.------ Fails without consuming any input if:------ * there are pending short options------ * a stopper has been parsed------ * there are no arguments left on the command line------ * the next argument on the command line does not begin with--- two dashes------ * the next argument on the command line is @--@ (a stopper)------ * the next argument on the command line does begin with two--- dashes but its text does not match the argument we're looking for--exactLongOpt :: LongOpt -> Parser (Maybe String)-exactLongOpt lo = Parser $ \s@(State ps rm sp) ->- let msg = Error (descLocation s)- [Expected ("long option: --" ++ unLongOpt lo)]- gd (arg, newRm) = Consumed (Ok arg (State ps newRm sp) msg)- err = Empty (Fail msg)- in maybe err gd $ do- guard $ null ps- guard $ not sp- (x, rm') <- nextW rm- (word, afterEq) <- getLongOption x- guard (word == unLongOpt lo)- return (afterEq, rm')- --getLongOption :: String -> Maybe (String, Maybe String)-getLongOption str = do- guard (str /= "--")- let (pre, word, afterEq) = splitLongWord str- guard (pre == "--")- return (word, afterEq)---- | Takes a single String and returns a tuple, where the first element--- is the first two letters, the second element is everything from the--- third letter to the equal sign, and the third element is Nothing if--- there is no equal sign, or Just String with everything after the--- equal sign if there is one.-splitLongWord :: String -> (String, String, Maybe String)-splitLongWord t = (f, s, r) where- (f, rest) = L.splitAt 2 t- (s, withEq) = L.break (== '=') rest- r = case withEq of- [] -> Nothing- _:xs -> Just xs--approxLongOptError :: [LongOpt] -> [Description]-approxLongOptError =- map (Expected . ("long option: --" ++) . unLongOpt)---assert :: e -> Bool -> Either e ()-assert e b = if b then Right () else Left e--fromMaybe :: e -> Maybe a -> Either e a-fromMaybe e = maybe (Left e) Right---- | Examines the next word. If it matches a LongOpt in the set--- unambiguously, returns a tuple of the word actually found and the--- matching word in the set and the accompanying text after the equal--- sign (if any). If the Set is empty, this parser will always fail.-approxLongOpt ::- Set LongOpt- -> Parser (String, LongOpt, Maybe String)-approxLongOpt ts = Parser $ \s@(State ps rm stop) ->- let err ls = Error (descLocation s) (approxLongOptError ls)- ert ls = Empty (Fail $ err ls)- gd (found, opt, arg, rm'') =- Consumed (Ok (found, opt, arg) (State ps rm'' stop)- (err allOpts))- allOpts = Set.toList ts- in either ert gd $ do- assert allOpts $ null ps- assert allOpts $ not stop- (x, rm') <- fromMaybe allOpts $ nextW rm- (word, afterEq) <- fromMaybe allOpts $ getLongOption x- opt <- fromMaybe allOpts $ makeLongOpt word- if Set.member opt ts- then return (word, opt, afterEq, rm')- else do- let p t = word `isPrefixOf` unLongOpt t- matches = Set.filter p ts- case Set.toList matches of- [] -> Left allOpts- (m:[]) -> return (word, m, afterEq, rm')- ls -> Left ls----- | Parses only pending short option arguments. For example, for the--- @tail@ command, if you enter the option @-c25@, then after parsing--- the @-c@ option the @25@ becomes a pending short option argument--- because it was in the same command line argument as the @-c@.------ Fails without consuming any input if:------ * a stopper has already been parsed------ * there are no pending short option arguments------ On success, returns the String of the pending short option argument--- (this String will never be empty).-pendingShortOptArg :: Parser String-pendingShortOptArg = Parser $ \st@(State ps rm sp) ->- let msg = [Expected "pending short option argument"]- err = Error (descLocation st) msg- ert = Empty (Fail err)- gd str = Consumed (Ok str (State "" rm sp) err)- in maybe ert gd $ do- guard $ not sp- case ps of- [] -> mzero- xs -> return xs----- | Parses a \"stopper\" - that is, a double dash. Changes the internal--- state of the parser to reflect that a stopper has been seen.-stopper :: Parser ()-stopper = Parser $ \s@(State ps rm sp) ->- let err = Error (descLocation s)- [Expected "stopper, \"--\""]- ert = Empty (Fail err)- gd rm'' = Consumed (Ok () (State ps rm'' True) err)- in maybe ert gd $ do- guard $ not sp- guard . null $ ps- (x, rm') <- nextW rm- guard $ x == "--"- return rm'----- | If a stopper has already been seen, change the internal state--- back to indicating that no stopper has been seen.-resetStopper :: Parser ()-resetStopper = Parser $ \s@(State ps rm _) ->- Empty (Ok () (State ps rm False) (Error (descLocation s) []))----- | try p behaves just like p, but if p fails, try p will not consume--- any input.-try :: Parser a -> Parser a-try a = Parser $ \s ->- case runParser a s of- Consumed r -> case r of- Fail e -> Empty (Fail e)- o -> Consumed o- o -> o----- | Returns the next string on the command line as long as there are--- no pendings. Succeeds even if a stopper is present. Be careful ---- this will return the next string even if it looks like an option--- (that is, it starts with a dash.) Consider whether you should be--- using nonOptionPosArg instead. However this can be useful when--- parsing command line options after a stopper.-nextWord :: Parser String-nextWord = Parser $ \s@(State ps rm sp) ->- let err = Error (descLocation s) [dsc]- dsc = Expected "next word"- ert = Empty (Fail err)- gd (str, rm'') = Consumed $ Ok str (State ps rm'' sp) err- in maybe ert gd $ do- guard $ null ps- nextW rm- ---- | Parses the next word on the command line, but only if it exactly--- matches the word given. Otherwise, fails without consuming any--- input. Also fails without consuming any input if there are pending--- short options or if a stopper has already been parsed. Does not pay--- any attention to whether a stopper is present.-nextWordIs :: String -> Parser ()-nextWordIs str = Parser $ \s@(State ps rm sp) ->- let err = Error (descLocation s) [dsc]- dsc = Expected $ "next argument \"" ++ str ++ "\""- ert = Empty $ Fail err- gd rm'' = Consumed $ Ok () (State ps rm'' sp) err- in maybe ert gd $ do- guard $ null ps- (a, rm') <- nextW rm- guard (a == str)- return rm'----- | If there are pending short options, fails without consuming any input.------ Otherwise, if a stopper has NOT already been parsed, then returns--- the next word if it is either a single dash or any other word that--- does not begin with a dash. If the next word does not meet these--- criteria, fails without consuming any input.------ Otherwise, if a stopper has already been parsed, then returns the--- next word, regardless of whether it begins with a dash or not.-nonOptionPosArg :: Parser String-nonOptionPosArg = Parser $ \s@(State ps rm sp) ->- let err = Error (descLocation s) [dsc]- dsc = Expected "non option positional argument"- ert = Empty $ Fail err- gd (str, rm'') = Consumed $ Ok str (State ps rm'' sp) err- in maybe ert gd $ do- guard $ null ps- (x, rm') <- nextW rm- result <- if sp- then return x- else case x of- [] -> return x- '-':[] -> return "-"- f:_ -> if f == '-' then mzero else return x- return (result, rm')----- | Succeeds if there is no more input left.-end :: Parser ()-end = Parser $ \s@(State ps rm _) ->- let err = Error (descLocation s) [dsc]- dsc = Expected "end of input"- ert = Empty $ Fail err- gd = Empty $ Ok () s err- in if null ps && null rm then gd else ert----- | Examines the possible words in Set. If there are no pendings,--- then get the next word and see if it matches one of the words in--- Set. If so, returns the word actually parsed and the matching word--- from Set. If there is no match, fails without consuming any--- input. Pays no attention to whether a stopper has been seen.-matchApproxWord :: Set String -> Parser (String, String)-matchApproxWord set = Parser $ \s@(State ps rm sp) ->- let err = Error (descLocation s) . lsDsc- lsDsc = map (Expected . ("next word: " ++))- ert = Empty . Fail . err- gd (act, mtch, rm'') =- Consumed $ Ok (act, mtch) (State ps rm'' sp) (err allWords)- allWords = Set.toList set- in either ert gd $ do- assert allWords $ null ps- (x, rm') <- fromMaybe allWords $ nextW rm- let matches = Set.filter p set- p t = x `isPrefixOf` t- case Set.toList matches of- [] -> Left allWords- r:[] -> return (x, r, rm')- xs -> Left xs- --- | @manyTill p end@ runs parser p zero or more times until parser--- @end@ succeeds. If @end@ succeeds and consumes input, that input is--- also consumed. in the result of @manyTill@. If that is a problem,--- wrap it in @lookAhead@. Also, if @end@ fails and consumes input,--- @manyTill@ fails and consumes input. If that is a problem, wrap--- @end@ in @try@.-manyTill :: Parser a -> Parser end -> Parser [a]-manyTill p e = do- maybeEnd <- optional e- case maybeEnd of- Just _ -> return []- Nothing -> do- a <- crashOnEmptyOk "manyTill" p- rs <- manyTill p e- return $ a:rs-
− lib/System/Console/MultiArg/SampleParser.hs
@@ -1,70 +0,0 @@--- | This is sample code using "System.Console.MultiArg". This could--- be a command-line parser for the version of the Unix command @tail@--- that is included with GNU coreutils version 8.5. "main" simply gets--- the command line arguments, parses them, and prints out what was--- parsed. To test it out, simply compile an executable that looks--- like this and then feed it different options:------ > import System.Console.MultiArg.SampleParser--- > main = sampleMain Intersperse------ or:------ > import System.Console.MultiArg.SampleParser--- > main = sampleMain StopOptions------ The code in the module is the sample code; the sample code is not--- in the Haddock documentation! If you're reading this in Haddock,--- you will want to also take a look at the actual source code.-module System.Console.MultiArg.SampleParser where--import qualified System.Console.MultiArg.Combinator as C-import qualified System.Console.MultiArg.CommandLine as P--data Flag =- Bytes String- | Follow (Maybe String)- | Retry- | Lines String- | Stats String- | Pid String- | Quiet- | Sleep String- | Verbose- | Help- | Version- | Filename String- deriving Show--specs :: [C.OptSpec Flag]--specs =- [ C.OptSpec ["bytes"] ['c']- (C.OneArg (return . Bytes))-- , C.OptSpec ["follow"] ['f']- (C.OptionalArg (return . Follow))-- , C.OptSpec ["follow-retry"] ['F'] (C.NoArg Retry)-- , C.OptSpec ["lines"] ['n']- (C.OneArg (return . Lines))-- , C.OptSpec ["max-unchanged-stats"] []- (C.OneArg (return . Stats))-- , C.OptSpec ["pid"] []- (C.OneArg (return . Pid))- , C.OptSpec ["quiet"] ['q'] (C.NoArg Quiet)-- , C.OptSpec ["sleep-interval"] ['s']- (C.OneArg (return . Sleep))- , C.OptSpec ["verbose"] ['v'] (C.NoArg Verbose)- , C.OptSpec ["help"] [] (C.NoArg Help)- , C.OptSpec ["version"] [] (C.NoArg Version)- ]--sampleMain :: P.Intersperse -> IO ()-sampleMain i = do- r <- P.simpleIO specs i (return . Filename)- print r
multiarg.cabal view
@@ -1,5 +1,5 @@ Name: multiarg-Version: 0.26.0.0+Version: 0.28.0.0 Cabal-version: >=1.8 Build-Type: Simple License: BSD3@@ -16,8 +16,6 @@ minimum-versions.txt, current-versions.txt, sunlight-test.hs -tested-with: GHC==7.4.1, GHC==7.6.3- description: multiarg is a parser combinator library to build command line parsers. With it you can easily create parsers with options that take more than one option argument--for example, I created@@ -30,7 +28,7 @@ for many situtations and shields you from the underlying complexity if you don't need it. .- See the documentation in the System.Console.MultiArg module for+ See the documentation in the Multiarg module for details. source-repository head@@ -46,12 +44,12 @@ hs-source-dirs: lib Exposed-modules:- System.Console.MultiArg- , System.Console.MultiArg.Combinator- , System.Console.MultiArg.CommandLine- , System.Console.MultiArg.Option- , System.Console.MultiArg.Prim- , System.Console.MultiArg.SampleParser+ Multiarg+ , Multiarg.Combinator+ , Multiarg.CommandLine+ , Multiarg.Option+ , Multiarg.Prim+ , Multiarg.SampleParser ghc-options: -Wall