multiarg 0.18.0.0 → 0.30.0.10
raw patch · 32 files changed
Files
- ChangeLog +173/−0
- NEWS +0/−123
- README.md +78/−0
- lib/Multiarg.hs +37/−0
- lib/Multiarg/Examples.hs +31/−0
- lib/Multiarg/Examples/Grover.hs +110/−0
- lib/Multiarg/Examples/Telly.hs +88/−0
- lib/Multiarg/Internal.hs +176/−0
- lib/Multiarg/Limeline.hs +44/−0
- lib/Multiarg/Maddash.hs +319/−0
- lib/Multiarg/Mode.hs +15/−0
- lib/Multiarg/Mode/Internal.hs +254/−0
- lib/Multiarg/Types.hs +185/−0
- lib/Multiarg/Util.hs +34/−0
- lib/Multiarg/Vocabulary.hs +94/−0
- lib/System/Console/MultiArg.hs +0/−169
- lib/System/Console/MultiArg/Combinator.hs +0/−551
- lib/System/Console/MultiArg/CommandLine.hs +0/−515
- lib/System/Console/MultiArg/Option.hs +0/−45
- lib/System/Console/MultiArg/Prim.hs +0/−676
- lib/System/Console/MultiArg/SampleParser.hs +0/−58
- multiarg.cabal +171/−42
- tests/Ernie.hs +122/−0
- tests/Grover/Tests.hs +136/−0
- tests/Makeopt.hs +62/−0
- tests/Multiarg/Maddash/Instances.hs +31/−0
- tests/Multiarg/Maddash/Tests.hs +138/−0
- tests/Multiarg/Types/Instances.hs +42/−0
- tests/Telly/Tests.hs +81/−0
- tests/grover-main.hs +9/−0
- tests/multiarg-tests.hs +13/−0
- tests/telly-main.hs +6/−0
+ ChangeLog view
@@ -0,0 +1,173 @@+Release 0.30.0.4++* Version bump for QuickCheck++Release 0.30.0.0++* Completely changed the API. This version is much better tested than+ previous versions. Instead of using parser combinators as a model,+ this version is built on a Mealy finite state machine.++* The tests are particularly exhaustive; there is a module that+ produces all possible command-line words that can parse to a+ particular option (for instance, the user might enter "-a -b foo" or+ "-abfoo" if -b is an option that takes a single option argument.)+ Different combinations are tested randomly.++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++Release 0.24.0.2, February 18, 2014+Changes since release 0.24.0.0:++* Removed upper bounds from build dependencies, and changed the+ lower bounds.++Release 0.24.0.0, September 12, 2013+Changes since release 0.20.0.0:++* Remove OneArgE, TwoArgE, etc. from Combinator module. These data+ constructors were renamed OneArg, TwoArg, etc. Having both OneArg+ and OneArgE was redundant.++* Improved error messages when the user enters an unrecognized option.+ Previously unrecognized options were passed through to client code+ as a positional argument; now they will be recognized as bad+ options.++Release 0.20.0.0, August 30, 2013+Changes since release 0.18.0.0:++* Remove explicit-exception; use Either instead.++Release 0.18.0.0, August 14, 2013+Changes since release 0.16.0.0:++* Rename SimpleParser module to CommandLine++* Made a number of changes to CommandLine to reorganize the data types+ and functions responsible for parsing simple and multi-mode command+ lines++* Remove GetArgs, as this was for an old library bug which has been+ fixed for some time now++Release 0.16.0.0, May 21, 2013+Changes since release 0.14.0.0:++* Rename OptArgError to InputError++* Change functions in SimpleParser to allow for positional arguments+ that might fail to be parsed++Release 0.14.0.0, April 8, 2013+Changes since release 0.12.0.2:++* Change long options to remove restrictions on what characters can+ make up a long option name. Now the only restriction is that long+ options cannot be zero length and they may not contain an equal+ sign.++Release 0.12.0.2, March 31, 2013+Changes since release 0.12.0.0:++* Reorder NEWS so recent versions are on top++* Fix bug in Prim.stopper that prevented most stoppers from being+ recognized++Release 0.12.0.0, March 27, 2013+Changes since release 0.10.0.0:++* Added value constructors to the Combinator module to allow for+ easier parsing of option arguments that can fail to parse++* Added automatic "Enter -h for help" to simpleWithHelp and+ modesWithHelp functions in SimpleParser++Release 0.10.0.0, March 7, 2013+Changes since release 0.8.0.0:++* Added the simpleWithHelp and modesWithHelp pre-built parsers to+ the SimpleParser module++* Added the mHelp field to the Mode record to allow for the+ modesWithHelp pre-built parser++* Changed the modes function in the SimpleParser module. The new+ function has a simpler type. It will however break old code.++Release 0.8.0.0, January 8, 2013+Changes since release 0.6.0.0:++* Added a multi-mode parser to SimpleParser++* All modules (except SampleParser) are now re-exported from+ System.Console.MultiArg. Therefore one import will bring in all of+ MultiArg. However, this introduced a naming conflict, because+ SimpleParser and Prim both had functions named "parse". Therefore I+ renamed SimpleParser.parse to "simple".++* Removed the <??> combinator from Prim and replaced it with+ <?>. Removed <?> from Combinator. The new <?> behaves as one would+ expect from Parsec.++* Simplified errors. There is now just one type of error message, and+ that is a string. Reversed error messages when Prim.parse returns+ (this way they are in the order the user would expect.)++* Client code written for 0.6.0.0 will probably break with this+ release, mostly because of changes to the export lists. Combinator+ no longer re-exports from other modules, because now the expectation+ is that if you want to import everything you just import+ System.Console.MultiArg. So fixing that is just a matter of changing+ imports in client code. Other breakage will be limited to error+ handling code.++Release 0.6.0.0, September 28, 2012+Changes since release 0.4.0.0:++* Add Functor instances for OptSpec, ArgSpec++* Add ThreeArg and ChoiceArg ArgSpecs++Release 0.4.0.0, June 30, 2012+Changes since release 0.2.0.0:++* Code written for version 0.2.* will not work at all with this+ version.++* Removed dependency on text library. multiarg now only deals with+ plain Strings. The memory usage concerns that originally led me to+ use Texts were unfounded.++* Dramatically simplified code for primitive parsers. No more custom+ error types, monad transformers, or user states. This sort of+ functionality is easily implemented in the parsers that you can+ build; baking it into the primitive parsers makes things needlessly+ complicated.++* Reworked included combinators in Combinator module.++Release 0.2.0.0, April 2, 2012+Changes since release 0.1.0.0:++* Henceforth all public releases will have only even numbers in their+ release numbers (e.g. 0.2.0.0, 0.2.0.2, etc.) If a release has any+ odd numbers in its version, it is a development release.++* QuickCheck tests are removed. Eventually I will reimplement these in+ a separate library package.++* Added Feed function to System.Console.MultiArg.Prim; this allows you+ to carry out stateful parses without using a user state. Sometimes+ this aids in composability--for instance, I use it when combining+ parsers from different parts of the same program.
− NEWS
@@ -1,123 +0,0 @@-Release 0.18.0.0, August 14, 2013-Changes since release 0.16.0.0:--* Rename SimpleParser module to CommandLine--* Made a number of changes to CommandLine to reorganize the data types- and functions responsible for parsing simple and multi-mode command- lines--* Remove GetArgs, as this was for an old library bug which has been- fixed for some time now--Release 0.16.0.0, May 21, 2013-Changes since release 0.14.0.0:--* Rename OptArgError to InputError--* Change functions in SimpleParser to allow for positional arguments- that might fail to be parsed--Release 0.14.0.0, April 8, 2013-Changes since release 0.12.0.2:--* Change long options to remove restrictions on what characters can- make up a long option name. Now the only restriction is that long- options cannot be zero length and they may not contain an equal- sign.--Release 0.12.0.2, March 31, 2013-Changes since release 0.12.0.0:--* Reorder NEWS so recent versions are on top--* Fix bug in Prim.stopper that prevented most stoppers from being- recognized--Release 0.12.0.0, March 27, 2013-Changes since release 0.10.0.0:--* Added value constructors to the Combinator module to allow for- easier parsing of option arguments that can fail to parse--* Added automatic "Enter -h for help" to simpleWithHelp and- modesWithHelp functions in SimpleParser--Release 0.10.0.0, March 7, 2013-Changes since release 0.8.0.0:--* Added the simpleWithHelp and modesWithHelp pre-built parsers to- the SimpleParser module--* Added the mHelp field to the Mode record to allow for the- modesWithHelp pre-built parser--* Changed the modes function in the SimpleParser module. The new- function has a simpler type. It will however break old code.--Release 0.8.0.0, January 8, 2013-Changes since release 0.6.0.0:--* Added a multi-mode parser to SimpleParser--* All modules (except SampleParser) are now re-exported from- System.Console.MultiArg. Therefore one import will bring in all of- MultiArg. However, this introduced a naming conflict, because- SimpleParser and Prim both had functions named "parse". Therefore I- renamed SimpleParser.parse to "simple".--* Removed the <??> combinator from Prim and replaced it with- <?>. Removed <?> from Combinator. The new <?> behaves as one would- expect from Parsec.--* Simplified errors. There is now just one type of error message, and- that is a string. Reversed error messages when Prim.parse returns- (this way they are in the order the user would expect.)--* Client code written for 0.6.0.0 will probably break with this- release, mostly because of changes to the export lists. Combinator- no longer re-exports from other modules, because now the expectation- is that if you want to import everything you just import- System.Console.MultiArg. So fixing that is just a matter of changing- imports in client code. Other breakage will be limited to error- handling code.--Release 0.6.0.0, September 28, 2012-Changes since release 0.4.0.0:--* Add Functor instances for OptSpec, ArgSpec--* Add ThreeArg and ChoiceArg ArgSpecs--Release 0.4.0.0, June 30, 2012-Changes since release 0.2.0.0:--* Code written for version 0.2.* will not work at all with this- version.--* Removed dependency on text library. multiarg now only deals with- plain Strings. The memory usage concerns that originally led me to- use Texts were unfounded.--* Dramatically simplified code for primitive parsers. No more custom- error types, monad transformers, or user states. This sort of- functionality is easily implemented in the parsers that you can- build; baking it into the primitive parsers makes things needlessly- complicated.--* Reworked included combinators in Combinator module.--Release 0.2.0.0, April 2, 2012-Changes since release 0.1.0.0:--* Henceforth all public releases will have only even numbers in their- release numbers (e.g. 0.2.0.0, 0.2.0.2, etc.) If a release has any- odd numbers in its version, it is a development release.--* QuickCheck tests are removed. Eventually I will reimplement these in- a separate library package.--* Added Feed function to System.Console.MultiArg.Prim; this allows you- to carry out stateful parses without using a user state. Sometimes- this aids in composability--for instance, I use it when combining- parsers from different parts of the same program.
+ README.md view
@@ -0,0 +1,78 @@+# Multiarg++This is multiarg, a library of combinators to parse command lines.++For "released" code see Hackage:++http://hackage.haskell.org/package/multiarg++multiarg is on Github:++http://www.github.com/massysett/multiarg++## Building++If you obtained this code through Hackage, just build it using the+ordinary Cabal command:++cabal install++If you obtain this code on Github, you will first need to generate the+Cabal file and generate the tests. This will require that you install+two libraries:++cabal install cartel quickpull++Then run this script to generate the Cabal file and the tests:++sh generate++## Versioning++multiarg releases are numbered in accordance with the Haskell+Package Versioning Policy.++Currently the multiarg library depends only on the "base" package, so+multiarg should have wide compatibility with different compilers and+sets of libraries. The tests have some additional dependencies.++## Build history++If you're having trouble building multiarg, try looking at the+travis-ci build history at:++https://travis-ci.org/massysett/multiarg++It shows successful builds and the versions of any package+dependencies that were installed when that build succeeded, so it+might help you diagnose any dependency issues.++[](https://travis-ci.org/massysett/multiarg)++## Similar libraries++Of course there are many command-line parsing modules and libraries+out there; here are some comparisons.++[optparse-applicative](https://hackage.haskell.org/package/optparse-applicative):+very featureful with a well thought-out interface. Builds help for+you. I often use this if it meets my needs. From what I can tell,+though, it strips out information about the relative ordering of the+words from the command line; for instance, if the user typed "hello+--opt1 --opt2", you cannot tell whether she entered "--opt1" before+she entered "--opt2". Also, from what I can tell it cannot easily+parse options that take more than one argument.++[GetOpt](https://hackage.haskell.org/package/base-4.7.0.2/docs/System-Console-GetOpt.html):+comes with the base libraries, so you don't have to install anything+extra, which gives it a huge advantage. Keeps information about the+relative ordering of the words from the command line. Cannot easily+parse options that take more than one argument.++[cmdargs](https://hackage.haskell.org/package/cmdargs): after multiple+passes through the Haddocks I could never make any sense of this+library at all, which must be a reflection of my level of Haskell+ignorance.++More comparisons are at the [Haskell+Wiki](https://www.haskell.org/haskellwiki/Command_line_option_parsers).
+ lib/Multiarg.hs view
@@ -0,0 +1,37 @@+-- | Parse command lines with /options/ that might take multiple+-- /option arguments/.+--+-- I built this library could not find anything that would readily+-- parse command lines where the /options/ took more than one+-- /option argument/. For example, for the @tail@ command on GNU systems, the+-- @--lines@ /option/ takes one /option 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 /option arguments/, like @--foo bar+-- baz@? I found no such library so I built this one.+--+-- Please consult the "Multiarg.Vocabulary" module to learn common+-- vocabulary used throughout Multiarg and its documentation. Words+-- that appear in /italics/ are defined in "Multiarg.Vocabulary".+--+-- Use this module to build parsers for simple commands. The+-- 'parseCommandLine' function runs in the IO monad and will cause+-- your program to exit unsuccessfully if there are any errors in the+-- command line, printing an error message in the process. If you+-- want more control over error handling, use the "Multiarg.Internal"+-- module.+--+-- To write parsers for commands with multiple modes (for instance,+-- @ghc-pkg@ has multiple modes, such as @ghc-pkg list@ and @ghc-pkg+-- check@) use the "Multiarg.Mode" module.+--+-- You will find examples in "Multiarg.Examples.Telly" for non-mode+-- parsers, and in "Multiarg.Examples.Grover" for mode parsers.+module Multiarg+ ( ArgSpec(..)+ , OptSpec+ , optSpec+ , parseCommandLine+ ) where++import Multiarg.Internal+import Multiarg.Types
+ lib/Multiarg/Examples.hs view
@@ -0,0 +1,31 @@+-- | These modules provide examples. Since they are live code, cabal+-- compiles them, which ensures that the examples actually compile.+-- In addition, the examples are used as fodder for the test cases;+-- this provides assurance not only that the library is tested but+-- also that the examples work as they should.+--+-- "Multiarg.Examples.Telly" provides an example parser for a command+-- that does not have modes; this is the sort of parser you build with+-- "Multiarg". "Multiarg.Examples.Grover" provides an example of a+-- parser for multiple modes; you build this sort of parser using+-- "Multiarg.Mode".+--+-- To see these examples in action, compile the library using the+-- "programs" flag, like so:+--+-- > cabal configure -fprograms+-- > cabal build+--+-- This will create two programs, @telly@ and @grover@. You simply+-- pass /words/ to these programs just like an ordinary user would,+-- and the programs will print the results of what they parse. If you+-- entered /words/ that parse correctly, you will see this result; if+-- there are any errors, you will see that instead. For example:+--+-- >>> dist/build/telly/telly --uno testarg filename+-- [Uno "testarg",PosArg "filename"]+--+-- >>> dist/build/grover/grover --verbose 2 int --double 5 2+-- Right (ModeResult [Right (Verbose 2)] (Right (Ints [Right (Double 5 2)])))++module Multiarg.Examples where
+ lib/Multiarg/Examples/Grover.hs view
@@ -0,0 +1,110 @@+-- | Grover is a simple example program that shows how to write a+-- parser for commands with multiple modes. You build such parsers+-- using "Multiarg.Mode". It provides an example for the+-- documentation, and it also provides fodder for the QuickCheck+-- tests. You will want to look at the source code.+--+-- Grover has three modes: @int@, @string@, and @maybe@. Each of+-- these modes has three options: @-z@ or @--zero@, which takes no+-- arguments; @-s@ or @--single@, which takes one argument; @-d@ or+-- @--double@, which takes two arguments; and @-t@ or @--triple@,+-- which takes three arguments. The type of the argument depends on+-- the mode. For @int@, the argument or arguments must be an integer;+-- for @string@ the arguments can be any string; and for @maybe@ the+-- arguments must be a Maybe Int, such as @Nothing@ or @Just 5@.+--+-- Each mode also accepts any number of positional arguments, which+-- can be any string.+--+-- Grover handles simple errors right inside the parser by using the+-- @Either@ type as a return value.++module Multiarg.Examples.Grover where++import Control.Applicative+import Multiarg.Mode+import Text.Read (readMaybe)++-- | Grover's global options.+data Global+ = Help+ | Verbose Int+ -- ^ The Int would indicate, for example, the desired level of+ -- verbosity.+ | Version+ deriving (Eq, Ord, Show)++-- | Handles all options and positional arguments for any Grover mode.+data GroverOpt a+ = Zero+ | Single a+ | Double a a+ | Triple a a a+ | PosArg String+ deriving (Eq, Ord, Show)++instance Functor GroverOpt where+ fmap f g = case g of+ Zero -> Zero+ Single a -> Single (f a)+ Double a b -> Double (f a) (f b)+ Triple a b c -> Triple (f a) (f b) (f c)+ PosArg a -> PosArg a++-- | All of Grover's global options. The 'OptSpec' is parameterized+-- on an 'Either' to allow for error handling. If the user enters a+-- non-integer argument for the @--verbose@ option, a @Left@ with an+-- error message is returned.+globalOptSpecs :: [OptSpec (Either String Global)]+globalOptSpecs =+ [ optSpec "h" ["help"] . ZeroArg . return $ Help+ , optSpec "v" ["verbose"] . OneArg $ \s ->+ Verbose <$> readErr s+ , optSpec "" ["version"] . ZeroArg . return $ Version+ ]++-- | A list of 'OptSpec' that works for any 'Mode'.+modeOptSpecs :: Read a => [OptSpec (Either String (GroverOpt a))]+modeOptSpecs =+ [ optSpec "z" ["zero"] . ZeroArg . Right $ Zero+ , optSpec "s" ["single"] . OneArg $ \s -> Single <$> readErr s++ , optSpec "d" ["double"] . TwoArg $ \s1 s2 ->+ Double <$> readErr s1 <*> readErr s2++ , optSpec "t" ["triple"] . ThreeArg $ \s1 s2 s3 ->+ Triple <$> readErr s1 <*> readErr s2 <*> readErr s3+ ]++-- | Holds the results of parsing Grover's modes.+data Result+ = Ints [Either String (GroverOpt Int)]+ | Strings [Either String (GroverOpt String)]+ | Maybes [Either String (GroverOpt (Maybe Int))]+ deriving (Eq, Ord, Show)++-- | All Grover modes.+modes :: [Mode Result]+modes =+ [ mode "int" modeOptSpecs (return . PosArg) Ints+ , mode "string" modeOptSpecs (return . PosArg) Strings+ , mode "maybe" modeOptSpecs (return . PosArg) Maybes+ ]++-- | Reads a value. If it cannot be read, returns an error message.+readErr :: Read a => String -> Either String a+readErr s = case readMaybe s of+ Nothing -> Left $ "could not read value: " ++ s+ Just a -> Right a++-- | Parses all of Grover's options and modes.+parseGrover+ :: [String]+ -- ^ Command line arguments, presumably from 'getArgs'+ -> Either (String, [String])+ (ModeResult (Either String Global) Result)+ -- ^ Returns a 'Left' if there are errors, or a 'Right' if there are+ -- no errors. (In an actual application, further processing of a+ -- 'Right' would be necessary to determine whether all entered+ -- arguments were valid.)+parseGrover = parseModeLine globalOptSpecs modes
+ lib/Multiarg/Examples/Telly.hs view
@@ -0,0 +1,88 @@+-- | Telly is a simple command-line program to test command-line+-- parsers that do not have multiple modes. This includes most+-- command-line programs; you build parsers like this using+-- "Multiarg". This module provides an example for documentation+-- purposes; it also provides fodder for the QuickCheck test cases.+-- You will want to look at the source code.++module Multiarg.Examples.Telly where++import Multiarg++-- | A data type to hold the result of command line parsing.+data Telly+ = PosArg String+ -- ^ Positional argument++ | Empty+ -- ^ @--empty@, @-e@+ | Single String+ -- ^ @--single@, @-s@+ | Double String String+ -- ^ @--double@, @-d@+ | Triple String String String+ -- ^ @--triple@, @-t@++ | Zero+ -- ^ @-0@+ | One String+ -- ^ @-1@+ | Two String String+ -- ^ @-2@+ | Three String String String+ -- ^ @-3@++ | Cero+ -- ^ @--cero@+ | Uno String+ -- ^ @--uno@+ | Dos String String+ -- ^ @--dos@+ | Tres String String String+ -- ^ @--tres@+ deriving (Eq, Ord, Show)++optSpecs :: [OptSpec Telly]+optSpecs =+ [ optSpec "e" ["empty"] (ZeroArg Empty)+ , optSpec "s" ["single"] (OneArg Single)+ , optSpec "d" ["double"] (TwoArg Double)+ , optSpec "t" ["triple"] (ThreeArg Triple)++ , optSpec "0" [] (ZeroArg Zero)+ , optSpec "1" [] (OneArg One)+ , optSpec "2" [] (TwoArg Two)+ , optSpec "3" [] (ThreeArg Three)++ , optSpec "" ["cero"] (ZeroArg Cero)+ , optSpec "" ["uno"] (OneArg Uno)+ , optSpec "" ["dos"] (TwoArg Dos)+ , optSpec "" ["tres"] (ThreeArg Tres)+ ]++help :: String -> String+help progName = unlines+ [ progName ++ " - simple program to test Multiarg."+ , "Parses command line and prints the results to standard output."+ , "Usage:"+ , progName ++ " [options] ARGUMENTS..."+ , ""+ , "Options:"+ , ""+ , "--empty, -e - option that takes no arguments"+ , "--single ARG, -s ARG - option that takes one argument"+ , "--double ARG1 ARG2, -d ARG1 ARG2 - option that takes two arguments"+ , "--triple ARG1 ARG2 ARG3, -t ARG1 ARG2 ARG3"+ , " - option that takes three arguments"+ , ""+ , "--cero - same as --empty"+ , "--uno - same as --single"+ , "--dos - same as --double"+ , "--tres - same as --triple"+ , ""+ , "--help, -h - show help and exit"+ ]++parse :: IO [Telly]+parse = parseCommandLine help optSpecs PosArg+
+ lib/Multiarg/Internal.hs view
@@ -0,0 +1,176 @@+-- | Functions and types used by the "Multiarg" module. You don't+-- have to worry about \"breaking\" anything by using this module.+-- This module is separate from "Multiarg" only because it makes the+-- documentation in that module cleaner, as that module should satisfy+-- most use cases. Use this module if you want more control over+-- error handling, or if you want to process arguments using pure+-- functions rather than IO functions.+module Multiarg.Internal where++import Multiarg.Maddash+import Multiarg.Limeline+import Multiarg.Types+import Multiarg.Util+import Data.Either (partitionEithers)+import System.Environment+import System.Exit+import qualified System.IO as IO++limelineOutputToParsedCommandLine+ :: ([Either [Output a] (PosArg a)], Maybe OptName)+ -> ParsedCommandLine a+limelineOutputToParsedCommandLine (ls, mayOpt) =+ ParsedCommandLine (concatMap f ls) mayOpt+ where+ f ei = case ei of+ Left os -> map g os+ where+ g o = case o of+ Good a -> Right a+ OptionError oe -> Left oe+ Right (PosArg pa) -> [Right pa]+++-- | Indicates the result of parsing a command line.+data ParsedCommandLine a+ = ParsedCommandLine [Either OptionError a] (Maybe OptName)+ -- ^ @ParsedCommandLine a b@, where:+ --+ -- @a@ is a list of errors and results, in the original order in+ -- which they appeared on the command line.+ --+ -- @b@ is @Just p@ if the user included an /option/ at the end of+ -- the command line and there were not enough following /words/ to+ -- provide the /option/ with its necessary /option arguments/, where+ -- @p@ is the /name/ of the /option/ with insufficient+ -- /option arguments/;+ -- otherwise 'Nothing'.+ deriving (Eq, Ord, Show)++instance Functor ParsedCommandLine where+ fmap f (ParsedCommandLine ls m) = ParsedCommandLine+ (map (fmap f) ls) m++-- | Gets the results from a parsed command line. If there were+-- errors, returns a 'Left' with an error message; otherwise, returns+-- a 'Right' with a list of the results.+parsedResults+ :: ParsedCommandLine a+ -> Either (String, [String]) [a]+parsedResults (ParsedCommandLine ls mayOpt) =+ let (ers, gds) = partitionEithers ls+ in case (ers, mayOpt) of+ ([], Nothing) -> Right gds+ ([], Just opt) -> Left (insufficientOptArgs opt, [])+ (x:xs, Just opt) -> Left+ (optError x, map optError xs ++ [insufficientOptArgs opt])+ (x:xs, Nothing) -> Left+ (optError x, map optError xs)++insufficientOptArgs :: OptName -> String+insufficientOptArgs n = "not enough arguments given for option: "+ ++ optNameToString n++optError :: OptionError -> String+optError oe = case oe of+ BadOption opt ->+ "unrecognized option: " ++ optNameToString opt+ LongArgumentForZeroArgumentOption lng arg ->+ "argument given for option that takes no arguments. "+ ++ "option: --" ++ longNameToString lng+ ++ " argument: " ++ optArgToString arg+++-- | Parses a command line; a pure function (unlike+-- 'parseCommandLineIO').+parseCommandLinePure++ :: [OptSpec a]+ -- ^ All program options++ -> (String -> a)+ -- ^ Processes non-option positional arguments++ -> [String]+ -- ^ Input tokens from the command line, probably obtained from+ -- 'getArgs'++ -> ParsedCommandLine a++parseCommandLinePure os fPos inp =+ limelineOutputToParsedCommandLine limeOut+ where+ limeOut = interspersed shrts lngs fPos (map Word inp)+ (shrts, lngs) = splitOptSpecs os++-- | Parses a command line. Runs in the IO monad so that it can do+-- some tedious things for you:+--+-- * fetches the /words/ on command line using 'getArgs' and the name+-- of the program with 'getProgName'+--+-- * prints help, if the user requested help, and exits+-- successfully+--+-- * prints an error message and exits unsuccessfully, if the user+-- entered a bad command line (such as an unknown option)+--+-- If you don't want this degree of automation or if you want a pure+-- function, see the 'parseCommandLinePure' function in the+-- "Multiarg.Internal" module.+parseCommandLine+ :: (String -> String)+ -- ^ Returns help for your command. This function is applied to the+ -- name of the program being run, which is obtained from+ -- 'getProgName'. The function should return a string that gives+ -- help for how to use your command; this string is printed as-is.++ -> [OptSpec a]+ -- ^ All program /options/. An /option/ for @-h@ and for @--help@ is+ -- added for you, using the help function given above. If the user+ -- asks for help, then it is printed and the program exits+ -- successfully. If the user gives a command line with one or more+ -- errors in it, an error message is printed, along with something+ -- like @Enter program-name --help for help@.++ -> (String -> a)+ -- ^ Processes /positional arguments/.++ -> IO [a]+ -- ^ Fetches the /words/ from the command line arguments using+ -- 'getArgs' and parses them. If there is an error, prints an error+ -- message and exits unsuccessfully. Otherwise, returns the parsed+ -- result, where each item in the list corresponds to a parsed+ -- /option/ or /positional argument/ in the order in which it+ -- appeared on the command line.+parseCommandLine fHelp os fPos = do+ progName <- getProgName+ args <- getArgs+ case parsedResults $ parseCommandLineHelp os fPos args of+ Left (e1, es) -> do+ IO.hPutStrLn IO.stderr $ progName ++ ": error"+ _ <- mapM (IO.hPutStrLn IO.stderr) $ e1 : es+ IO.hPutStrLn IO.stderr $ "enter \"" ++ progName ++ " --help\" "+ ++ "for help."+ exitFailure+ Right mayResults -> case sequence mayResults of+ Nothing -> do+ putStr (fHelp progName)+ exitSuccess+ Just ls -> return ls+++-- | Automatically adds a /short option/, @-h@, and a /long option/,+-- @--help@. Intended primarily for use by the 'parseCommandLineIO'+-- function.+parseCommandLineHelp+ :: [OptSpec a]+ -> (String -> a)+ -> [String]+ -> ParsedCommandLine (Maybe a)+parseCommandLineHelp os fPos inp =+ limelineOutputToParsedCommandLine+ $ interspersed shrts lngs (fmap Just fPos) (map Word inp)+ where+ (shrts, lngs) = addHelpOption os+
+ lib/Multiarg/Limeline.hs view
@@ -0,0 +1,44 @@+-- | Processes both /options/ and /positional arguments/. Functions+-- here return both any successful results and any errors. Ordinarily+-- you will not need this module; instead, see "Multiarg" for most+-- uses or "Multiarg.Mode" for commands that have more than one mode.+module Multiarg.Limeline where++import Multiarg.Types+import Multiarg.Maddash+-- GHC 7.10 exports Word from the Prelude+import Prelude hiding (Word)++data PosArg a = PosArg a+ deriving (Eq, Ord, Show)++instance Functor PosArg where+ fmap f (PosArg a) = PosArg (f a)++-- | Processes a command line where /options/ are interspersed with+-- /positional arguments/. A /stopper/ is not returned; all+-- /words/ after a /stopper/ are treated as+-- /positional arguments/.+interspersed+ :: [(ShortName, ArgSpec a)]+ -> [(LongName, ArgSpec a)]+ -> (String -> a)+ -> [Word]+ -> ([Either [Output a] (PosArg a)], Maybe OptName)+interspersed shorts longs fTok = go+ where+ go toks = (map Left outs ++ outsRest, err)+ where+ (outs, ei) = processWords shorts longs toks+ (outsRest, err) = case ei of+ Left (opt, _) -> ([], Just opt)+ Right [] -> ([], Nothing)+ Right ((Word x):xs)+ | x == "--" ->+ ( map (\(Word t) -> Right . PosArg . fTok $ t) xs+ , Nothing )+ | otherwise -> ( (Right . PosArg . fTok $ x) : rest+ , mayErrRest )+ where+ (rest, mayErrRest) = go xs+
+ lib/Multiarg/Maddash.hs view
@@ -0,0 +1,319 @@+-- | Maddash is a Mealy finite state machine that processes /options/.+-- Ordinarily you will not need this module; instead, see "Multiarg"+-- for most uses or "Multiarg.Mode" for commands that have more than+-- one mode.+--+-- The machine consists of the following parts:+--+-- * The set of states, in 'State'+--+-- * The start state, which is 'Ready'+--+-- * The input alphabet, which is all 'Word's. A 'Word' is an+-- input /word/ from the command line.+--+-- * The output alphabet, which is 'Pallet'. A 'Pallet' indicates+-- whether its input is not an option at all with 'NotAnOption'. This+-- indicates that the input 'Word' was not a short option and was not+-- a long option; that is, it was not a single dash followed by a+-- non-dash character and it was not a double dash followed by another+-- character. (Neither a single dash alone nor a double dash alone is+-- an option.) Anything else is an option and will return 'Full',+-- which is a list of 'Output'. Each 'Output' indicates either an+-- error or a good result.+--+-- * The transition function and the output function are combined into+-- a single function, 'processWord'.++module Multiarg.Maddash+ ( -- * /Options/ and /option arguments/+ OptName(..)+ , optSpec+ , ArgSpec(..)+ , ShortName+ , LongName+ , shortName+ , longName+ , shortNameToChar+ , longNameToString++ -- * Machine components+ , Output(..)+ , Pallet(..)+ , State(..)+ , isReady+ , isPending+ , processWord++ -- * Multi-word processor+ , processWords++ -- * Errors+ , OptArg(..)+ , OptionError(..)+ ) where++import Control.Applicative+import Multiarg.Types+-- GHC 7.10 exports Word from the Prelude+import Prelude hiding (Word)++-- * Machine components++data Output a+ = Good a+ | OptionError OptionError+ deriving (Eq, Ord, Show)++instance Functor Output where+ fmap f (Good a) = Good (f a)+ fmap _ (OptionError e) = OptionError e++data Pallet a+ = NotAnOption+ | Full [Output a]+ deriving (Eq, Ord, Show)++instance Functor Pallet where+ fmap _ NotAnOption = NotAnOption+ fmap f (Full os) = Full (map (fmap f) os)++data State a+ = Ready+ -- ^ Accepting new words++ | Pending OptName (Word -> ([Output a], State a))+ -- ^ In the middle of processing an /option/; this function will be+ -- applied to the next word to get a result+++instance Functor State where+ fmap _ Ready = Ready+ fmap f (Pending o g)+ = Pending o (\t -> let (os, st') = g t+ in (map (fmap f) os, fmap f st'))++instance Show (State a) where+ show Ready = "Ready"+ show (Pending o _) = "Pending - " ++ show o++isReady :: State a -> Bool+isReady Ready = True+isReady _ = False++isPending :: State a -> Bool+isPending (Pending _ _) = True+isPending _ = False++-- | Process a single word in the machine.+processWord+ :: [(ShortName, ArgSpec a)]+ -> [(LongName, ArgSpec a)]+ -> State a+ -> Word+ -> (Pallet a, State a)+processWord shorts longs st inp = case st of+ Pending _ f -> (Full os, st')+ where+ (os, st') = f inp+ Ready -> case procOpt of+ Just (os, st') -> (Full os, st')+ Nothing -> (NotAnOption, Ready)+ where+ procOpt = procShort shorts inp <|> procLong longs inp++-- * Multi-word processor++-- | Processes multiple /words/ in the machine. Processing ends with+-- the first /word/ that is 'NotAnOption'. This first /word/ that is+-- 'NotAnOption', and all remaining /words/, are returned in the+-- result. A list of all lists of 'Output' are also returned, with+-- one list for each input 'Word' that was processed. Each of these+-- lists may be of any length. For instance, if the input /word/ is+-- the /flag/ for a /long option/ that takes two /option arguments/,+-- the corresponding list will be empty. If the input /word/ is a+-- /flag/ for a /short option/, this list may have more than one+-- element.+processWords+ :: [(ShortName, ArgSpec a)]+ -> [(LongName, ArgSpec a)]+ -> [Word]+ -> ([[Output a]], Either (OptName, Word -> ([Output a], State a)) [Word])+processWords shorts longs = go Ready+ where+ go Ready [] = ([], Right [])+ go (Pending opt f) [] = ([], Left (opt, f))+ go st (t:ts) = (rs, eiToksPend)+ where+ (pallet, st') = processWord shorts longs st t+ (rs, eiToksPend) = case pallet of+ NotAnOption -> ([], Right (t:ts))+ Full out -> (out : outRest, ei)+ where+ (outRest, ei) = go st' ts++-- * Errors++data OptionError+ = BadOption OptName+ | LongArgumentForZeroArgumentOption LongName OptArg+ -- ^ The user gave an argument for a long option that does not take+ -- an argument.+ deriving (Eq, Ord, Show)++-- * All exported types and functions above this line++-- * Internal functions - not exported++-- | Examines a word to determine if it is a short option. If so,+-- processes it; otherwise, returns Nothing.+procShort+ :: [(ShortName, ArgSpec a)]+ -> Word+ -> Maybe ([Output a], State a)+procShort shorts inp = fmap (getShortOpt shorts) (isShort inp)++getShortOpt+ :: [(ShortName, ArgSpec a)]+ -> (ShortName, ShortTail)+ -> ([Output a], State a)+getShortOpt shorts (short, rest) = case lookup short shorts of+ Nothing -> ( [OptionError (BadOption (OptName (Left short))) ], Ready)+ Just arg -> procShortOpt shorts short arg rest++procShortOpt+ :: [(ShortName, ArgSpec a)]+ -> ShortName+ -> ArgSpec a+ -> ShortTail+ -> ([Output a], State a)+procShortOpt opts _ (ZeroArg a) inp = (this : rest, st)+ where+ this = Good a+ (rest, st) = case splitShortTail inp of+ Nothing -> ([], Ready)+ Just (opt,arg) -> getShortOpt opts (opt, arg)++procShortOpt _ shrt (OneArg f) (ShortTail inp) = case inp of+ [] -> ([], Pending opt g)+ where+ g tok = ([res], Ready)+ where+ res = Good . f . optArgToString $ arg+ arg = wordToOptArg tok+ xs -> ([res], Ready)+ where+ res = Good . f . optArgToString $ optArg+ optArg = OptArg xs+ where+ opt = OptName (Left shrt)++procShortOpt _ shrt (TwoArg f) (ShortTail inp) = ([], Pending opt g)+ where+ g tok1 = case inp of+ [] -> ([], Pending opt h)+ where+ h tok2 = ([res], Ready)+ where+ oa2 = wordToOptArg tok2+ res = Good $ f (optArgToString oa1) (optArgToString oa2)++ xs -> ([res], Ready)+ where+ res = Good $ f (optArgToString tokArg) (optArgToString oa1)+ tokArg = OptArg xs+ where+ oa1 = wordToOptArg tok1+ opt = OptName (Left shrt)++procShortOpt _ shrt (ThreeArg f) (ShortTail inp) = ([], Pending opt g)+ where+ opt = OptName (Left shrt)+ g tok1 = ([], Pending opt h)+ where+ oa1 = wordToOptArg tok1+ h tok2 = case inp of+ [] -> ([], Pending opt i)+ where+ i tok3 = ([res], Ready)+ where+ oa3 = wordToOptArg tok3+ res = Good $ f (optArgToString oa1) (optArgToString oa2)+ (optArgToString oa3)+ tokInp -> ([res], Ready)+ where+ tokArg = wordToOptArg (Word tokInp)+ res = Good $ f (optArgToString tokArg) (optArgToString oa1)+ (optArgToString oa2)+ where+ oa2 = wordToOptArg tok2++procLong+ :: [(LongName, ArgSpec a)]+ -> Word+ -> Maybe ([Output a], State a)+procLong longs inp = fmap (procLongOpt longs) (isLong inp)++procLongOpt+ :: [(LongName, ArgSpec a)]+ -> (LongName, Maybe OptArg)+ -> ([Output a], State a)+procLongOpt longs (inp, mayArg) = case lookup inp longs of+ Nothing -> ( [OptionError (BadOption . OptName . Right $ inp)], Ready)+ Just (ZeroArg r) -> ([result], Ready)+ where+ result = case mayArg of+ Nothing -> Good r+ Just arg -> OptionError (LongArgumentForZeroArgumentOption inp arg)++ Just (OneArg f) -> case mayArg of+ Nothing -> ([], Pending opt run)+ where+ run tok = ([out], Ready)+ where+ out = Good $ f (optArgToString arg1)+ arg1 = wordToOptArg tok+ Just arg -> ([out], Ready)+ where+ out = Good $ f (optArgToString arg)++ Just (TwoArg f) -> ([], Pending opt g)+ where+ g gTok = case mayArg of+ Just arg1 -> ([out], Ready)+ where+ out = Good $ f (optArgToString arg1) (optArgToString gArg)+ Nothing -> ([], Pending opt h)+ where+ h hTok = ([out], Ready)+ where+ out = Good $ f (optArgToString gArg)+ (optArgToString hArg)+ hArg = wordToOptArg hTok+ where+ gArg = wordToOptArg gTok++ Just (ThreeArg f) -> ([], Pending opt g)+ where+ g gTok = ([], Pending opt h)+ where+ gArg = wordToOptArg gTok+ h hTok = case mayArg of+ Just arg1 -> ([out], Ready)+ where+ out = Good $ f (optArgToString arg1) (optArgToString gArg)+ (optArgToString hArg)+ Nothing -> ([], Pending opt i)+ where+ i iTok = ([out], Ready)+ where+ iArg = wordToOptArg iTok+ out = Good $ f (optArgToString gArg)+ (optArgToString hArg)+ (optArgToString iArg)+ where+ hArg = wordToOptArg hTok+ where+ opt = OptName (Right inp)++-- * end
+ lib/Multiarg/Mode.hs view
@@ -0,0 +1,15 @@+-- | Helps you build command-line parsers for programs that have more+-- than one so-called /mode/; examples of such programs include @git@,+-- @darcs@, and @ghc-pkg@.+module Multiarg.Mode+ ( ArgSpec(..)+ , OptSpec+ , optSpec+ , Mode+ , mode+ , ModeResult(..)+ , parseModeLine+ ) where++import Multiarg.Mode.Internal+import Multiarg.Types
+ lib/Multiarg/Mode/Internal.hs view
@@ -0,0 +1,254 @@+-- | Internal functions used by "Multiarg.Mode". You don't have to+-- worry about \"breaking\" anything by using this module; it is+-- separate from "Multiarg.Mode" primarily to tidy up the+-- documentation in that module. The functions in "Multiarg.Mode"+-- should satisfy most use cases. However, if you want more control+-- over error handling, you can use this module.+module Multiarg.Mode.Internal where++import Data.Either (partitionEithers)+import Multiarg.Maddash+import Multiarg.Internal+import Multiarg.Util+import Multiarg.Types+-- GHC 7.10 exports Word from the Prelude+import Prelude hiding (Word)++newtype ModeName = ModeName String+ deriving (Eq, Ord, Show)++data ParsedMode a+ = ModeGood a+ | ModeError [OptionError] (Either OptionError OptName)+ -- ^ There was an error. There may be zero or more initial+ -- OptionError. There must be at least one error, which is either+ -- an OptionError or the name of an option, if the error is that+ -- there were not enough words following the option to provide it+ -- with its necessary arguments.+ deriving (Eq, Ord, Show)++instance Functor ParsedMode where+ fmap f (ModeGood a) = ModeGood (f a)+ fmap _ (ModeError ls ei) = ModeError ls ei++-- | A 'Mode' represents a single command line mode, such as @check@+-- for @ghc-pkg check@. It contains the name of the mode, as well as+-- a parser that handles all /options/ and /positional arguments/ for+-- the mode. Ordinarily you will create a 'Mode' using the 'mode'+-- function rather than by using the constructor directly.+data Mode r = Mode ModeName ([Word] -> ParsedMode r)++instance Functor Mode where+ fmap f (Mode s p) = Mode s (fmap (fmap f) p)++parsedCommandLineToParsedMode+ :: ([a] -> r)+ -> ParsedCommandLine a+ -> ParsedMode r+parsedCommandLineToParsedMode fMode (ParsedCommandLine ls mayOpt)+ = case mayOpt of+ Nothing -> case mayLast errs of+ Nothing -> ModeGood (fMode goods)+ Just (errs1st, errsLst) -> ModeError errs1st (Left errsLst)+ Just opt -> ModeError errs (Right opt)+ where+ (errs, goods) = partitionEithers ls++-- | Creates a new 'Mode'.+mode+ :: String+ -- ^ Mode name. For instance, for the @check@ mode of @ghc-pkg@,+ -- this would be @check@.+ -> [OptSpec a]+ -- ^ Mode /options/+ -> (String -> a)+ -- ^ Parses /positional arguments/+ -> ([a] -> r)+ -- ^ Processes the result of all mode /options/+ -> Mode r+mode name opts fPos fMode+ = Mode (ModeName name)+ $ parsedCommandLineToParsedMode fMode+ . parseCommandLinePure opts fPos+ . map (\(Word s) -> s)++data GlobalLocalEnd a+ = GlobalInsufficientOptArgs OptName+ | ModeNotFound String [String]+ | NoMode+ | ModeFound (ParsedMode a)+ deriving (Eq, Ord, Show)++data GlobalLocal g r+ = GlobalLocal [Either OptionError g] (GlobalLocalEnd r)+ deriving (Eq, Ord, Show)++-- | The result of parsing a mode command line.+data ModeResult g r+ = ModeResult [g] (Either [String] r)+ -- ^ @ModeResult a b@ is a successfully parsed mode command line,+ -- where:+ --+ -- @a@ is a list of all global options parsed; and+ --+ -- @b@ indicates the result of parsing the mode. It is @Either c+ -- d@, where @Left c@ indicates that no mode was parsed. This+ -- arises under two circumstances. If the user did not include any+ -- /words/ after the global /options/, then @c@ will be the empty+ -- list, @[]@. If the user did include /words/ after the global+ -- options, but the first /word/ was not recognized as a mode, then+ -- this list will contain the first /word/ and any subsequent /words/.+ -- Therefore, note that if the user attempted to use a mode that+ -- does not exist (e.g. she misspelled it), this is not treated as+ -- an error. It's up to the client code to deal with this issue+ -- (for instance, your program might not view this situation as+ -- being an error.)+ --+ -- If @b@ is @Right d@, this indicates that the user entered a+ -- recognized mode, and the result is @d@.++ deriving (Eq, Ord, Show)++getModeResult+ :: GlobalLocal g r+ -> Either (String, [String]) (ModeResult g r)+getModeResult (GlobalLocal eis end)+ = combine global (endToModeResult end)+ where+ (glblErrs, glblGoods) = partitionEithers eis+ global = case glblErrs of+ [] -> Right glblGoods+ x:xs -> Left (x, xs)++combine+ :: Either (OptionError, [OptionError]) [g]+ -- ^ Global result. Contains either one or more errors, or global+ -- /option/ results.++ -> Either (String, [String]) (Either [String] r)+ -- ^ Result of parsing mode /word/, and the mode /options/ and+ -- /positional arguments/. May be @Left a@, where @a@ is one or+ -- more errors, or @Right b@, where @b@ is a good result. A good+ -- result @b@ may be either @Left c@, where @c@ is a list of+ -- /positional arguments/, or @Right d@, where @d@ is the mode+ -- result. @c@ indicates that no mode was recognized and may be+ -- either @[]@, which indicates that the user passed no /words/ at+ -- all after the global /options/, or @x:xs@, indicating that the+ -- user did pass /words/ after the global /options/, but the first+ -- /word/ was not recognized as a mode.++ -> Either (String, [String]) (ModeResult g r)+combine (Left (oe1, oes)) (Left (me1, mes)) =+ Left ( globalOptErrorToString oe1+ , map globalOptErrorToString oes ++ (me1 : mes) )+combine (Left (oe1, oes)) (Right _) =+ Left (globalOptErrorToString oe1, map globalOptErrorToString oes)+combine (Right _) (Left (me1, mes)) = Left (me1, mes)+combine (Right glbls) (Right r) =+ Right (ModeResult glbls r)++endToModeResult+ :: GlobalLocalEnd a+ -> Either (String, [String]) (Either [String] a)+endToModeResult end = case end of+ GlobalInsufficientOptArgs on -> Left+ (labeledInsufficientOptArgs "global" on, [])+ ModeNotFound s ss -> Right (Left $ s:ss)+ NoMode -> Right (Left [])+ ModeFound pm -> extractParsedMode pm++extractParsedMode+ :: ParsedMode a+ -> Either (String, [String]) (Either b a)+extractParsedMode (ModeGood g) = Right . Right $ g+extractParsedMode (ModeError es lst) = Left $ case es of+ [] -> (eiToError lst, [])+ (x:xs) ->+ ( modeOptErrorToString x+ , (map modeOptErrorToString xs) ++ [eiToError lst] )++globalOptErrorToString :: OptionError -> String+globalOptErrorToString = optErrorToString "global"++modeOptErrorToString :: OptionError -> String+modeOptErrorToString = optErrorToString "mode"++optErrorToString :: String -> OptionError -> String+optErrorToString lbl oe = case oe of+ BadOption opt ->+ "unrecognized " ++ lbl ++ " option: " ++ optNameToString opt+ LongArgumentForZeroArgumentOption lng arg ->+ "argument given for " ++ lbl ++ " option that takes no arguments. "+ ++ "option: --" ++ longNameToString lng+ ++ " argument: " ++ optArgToString arg+++eiToError :: Either OptionError OptName -> String+eiToError ei = case ei of+ Left oe -> modeOptErrorToString oe+ Right on -> labeledInsufficientOptArgs "mode" on+++labeledInsufficientOptArgs :: String -> OptName -> String+labeledInsufficientOptArgs lbl on = "insufficient option arguments "+ ++ "given for " ++ lbl ++ " option: " ++ optNameToString on+++-- | Parses a command line that may contain modes.+parseModeLine+ :: [OptSpec g]+ -- ^ Global /options/. This might, for example, include a @--help@+ -- /option/.+ -> [Mode r]+ -- ^ All modes+ -> [String]+ -- ^ All command line /words/+ -> Either (String, [String]) (ModeResult g r)+ -- ^ Returns @Either a b@. @Left a@ represents an error. Each+ -- String represents a single error (this is returned as a pair+ -- because there must be at least one error; a simple list would not+ -- reflect this requirement.)+ --+ -- @Right b@ indicates that parsing proceeded successfully; consult+ -- 'ModeResult' to see what is returned.+parseModeLine glbl mds =+ getModeResult+ . parseModeLineWithErrors glbl mds++parseModeLineWithErrors+ :: [OptSpec g]+ -- ^ Global options+ -> [Mode r]+ -- ^ All modes+ -> [String]+ -- ^ All command line tokens+ -> GlobalLocal g r+parseModeLineWithErrors glbl mds tokStrings = GlobalLocal lsErrsGoods end+ where+ toks = map Word tokStrings+ (shorts, longs) = splitOptSpecs glbl+ (outs, eiOptTok) = processWords shorts longs toks+ lsErrsGoods = map f . concat $ outs+ where+ f (Good a) = Right a+ f (OptionError e) = Left e+ end = case eiOptTok of+ Left (opt, _) -> GlobalInsufficientOptArgs opt+ Right [] -> NoMode+ Right (x:xs) -> case findExactMode x mds of+ Nothing -> ModeNotFound (unWord x) (map unWord xs)+ where+ unWord (Word t) = t+ Just (Mode _ f) -> ModeFound (f xs)++-- | Takes a token and a list of all modes; returns the matching mode+-- if there is one, or Nothing if there is no match.+findExactMode+ :: Word+ -> [Mode a]+ -> Maybe (Mode a)+findExactMode _ [] = Nothing+findExactMode tok@(Word s) (m@(Mode (ModeName n) _) : ms)+ | s == n = Just m+ | otherwise = findExactMode tok ms+
+ lib/Multiarg/Types.hs view
@@ -0,0 +1,185 @@+-- | Types used throughout Multiarg, and associated functions.+-- Ordinarily you should not need this module; "Multiarg" and+-- "Multiarg.Mode" export all the types and constructors you should+-- ordinarily need. However, if you want more control than those+-- modules afford, you can import this one.+module Multiarg.Types+ ( ArgSpec(..)+ , OptSpec(..)+ , optSpec+ , ShortName+ , shortNameToChar+ , shortName+ , LongName+ , longNameToString+ , longName+ , Word(..)+ , OptName(..)+ , optNameToString+ , OptArg(..)+ , ShortTail(..)+ , isLong+ , isShort+ , wordToOptArg+ , splitShortTail+ ) where++-- GHC 7.10 incorporates 'Data.Word' into the Prelude, which clashes+-- with a binding below.+import Prelude hiding (Word)++-- | Specifies how many /option arguments/ an /option/ takes.+data ArgSpec a+ = ZeroArg a+ -- ^ This /option/ takes no /option arguments/+ | OneArg (String -> a)+ -- ^ This /option/ takes one /option argument/+ | TwoArg (String -> String -> a)+ -- ^ This /option/ takes two /option arguments/+ | ThreeArg (String -> String -> String -> a)+ -- ^ This /option/ takes three /option arguments/++instance Functor ArgSpec where+ fmap f (ZeroArg a) = ZeroArg (f a)+ fmap f (OneArg g) = OneArg $ \a -> f (g a)+ fmap f (TwoArg g) = TwoArg $ \a b -> f (g a b)+ fmap f (ThreeArg g) = ThreeArg $ \a b c -> f (g a b c)++instance Show (ArgSpec a) where+ show (ZeroArg _) = "ZeroArg"+ show (OneArg _) = "OneArg"+ show (TwoArg _) = "TwoArg"+ show (ThreeArg _) = "ThreeArg"++-- | Specifies an /option/. Typically you will use 'optSpec' to+-- create an 'OptSpec' rather than using the constructor directly.+-- Each 'OptSpec' may contain mulitple /short option names/ and+-- /long option names/; but each 'OptSpec' contains only one 'ArgSpec'.+-- Therefore, all /short option names/ and /long option names/+-- specified in a single 'OptSpec' are synonymous.+data OptSpec a = OptSpec [ShortName] [LongName] (ArgSpec a)+ deriving Show++instance Functor OptSpec where+ fmap f (OptSpec s l p) = OptSpec s l (fmap f p)++-- | Creates an 'OptSpec'.+optSpec+ :: [Char]+ -- ^ There is one character for each desired /short option name/.+ -- Each of these characters may not be a hyphen; otherwise,+ -- 'optSpec' will apply 'error'.++ -> [String]+ -- ^ There is one string for each desired /long option name/. Each+ -- string:+ --+ -- * cannot be empty;+ --+ -- * must not begin with a hyphen; and+ --+ -- * must not contain an equal sign.+ --+ -- Otherwise, 'optSpec' will apply 'error'.++ -> ArgSpec a+ -- ^ How many /option arguments/ this /option/ takes. This also+ -- specifies what is returned when the /option/ is parsed on the+ -- command line.++ -> OptSpec a+optSpec ss ls = OptSpec (map mkShort ss) (map mkLong ls)+ where+ mkShort s = case shortName s of+ Nothing -> error $ "invalid short option name: " ++ [s]+ Just n -> n+ mkLong s = case longName s of+ Nothing -> error $ "invalid long option name: " ++ s+ Just n -> n+++-- | A /short option name/.+newtype ShortName = ShortName { shortNameToChar :: Char }+ deriving (Eq, Ord, Show)++-- | A /long option name/.+newtype LongName = LongName { longNameToString :: String }+ deriving (Eq, Ord, Show)++-- | Creates a /short option name/. Any character other than a single+-- hyphen will succeed.+shortName :: Char -> Maybe ShortName+shortName '-' = Nothing+shortName x = Just $ ShortName x++-- | Creates a /long option name/. The string may not be empty, and the+-- first character may not be a hyphen. In addition, no character may+-- be an equal sign.+longName :: String -> Maybe LongName+longName s = case s of+ [] -> Nothing+ '-':_ -> Nothing+ xs | '=' `elem` xs -> Nothing+ | otherwise -> Just $ LongName xs++-- | The /name/ of an /option/ (either a /short option name/+-- or a /long option name/).+newtype OptName = OptName (Either ShortName LongName)+ deriving (Eq, Ord, Show)++optNameToString :: OptName -> String+optNameToString (OptName ei) = case ei of+ Left shrt -> '-' : shortNameToChar shrt : []+ Right lng -> "--" ++ longNameToString lng++-- | A /word/ supplied by the user on the command line.+newtype Word = Word String+ deriving (Eq, Ord, Show)++-- | An /option argument/.+newtype OptArg = OptArg { optArgToString :: String }+ deriving (Eq, Ord, Show)++-- | Is this /word/ an input for a /long option/?+isLong+ :: Word+ -> Maybe (LongName, Maybe OptArg)+ -- ^ Nothing if the option does not begin with a double dash and is+ -- not at least three characters long. Otherwise, returns the+ -- characters following the double dash to the left of any equal+ -- sign. The Maybe in the tuple is Nothing if there is no equal+ -- sign, or Just followed by characters following the equal sign if+ -- there is one.+isLong (Word ('-':'-':[])) = Nothing+isLong (Word ('-':'-':xs)) = Just (LongName optName, arg)+ where+ (optName, end) = span (/= '=') xs+ arg = case end of+ [] -> Nothing+ _:rs -> Just . OptArg $ rs+isLong _ = Nothing++-- | Characters after the first /short option name/ in a /flag/ that+-- specifies a /short option/ instance, if the user supplies+-- @-afoobar@, then this will be @foobar@.+newtype ShortTail = ShortTail String+ deriving (Eq, Ord, Show)++-- | Is this an input /word/ for a /short argument/?+isShort+ :: Word+ -> Maybe (ShortName, ShortTail)+isShort (Word ('-':'-':_)) = Nothing+isShort (Word ('-':[])) = Nothing+isShort (Word ('-':x:xs)) = Just (ShortName x, ShortTail xs)+isShort _ = Nothing++wordToOptArg :: Word -> OptArg+wordToOptArg (Word t) = OptArg t++-- | If possible, splits a ShortTail into a /short option name/ and a+-- remaining tail.+splitShortTail :: ShortTail -> Maybe (ShortName, ShortTail)+splitShortTail (ShortTail s) = case s of+ [] -> Nothing+ x:xs -> Just (ShortName x, ShortTail xs)
+ lib/Multiarg/Util.hs view
@@ -0,0 +1,34 @@+-- | Grab bag of miscellaneous functions.+module Multiarg.Util where++import Multiarg.Types++-- | Returns a list of the first items in a list and the last item, or+-- Nothing if the list is empty.+mayLast :: [a] -> Maybe ([a], a)+mayLast [] = Nothing+mayLast xs = Just (init xs, last xs)++-- | Partitions a list of 'OptSpec' into the short flags and long+-- flags.+splitOptSpecs+ :: [OptSpec a]+ -> ([(ShortName, ArgSpec a)], [(LongName, ArgSpec a)])+splitOptSpecs = foldr f ([], [])+ where+ f (OptSpec so lo sp) (ss, ls) = (so' ++ ss, lo' ++ ls)+ where+ so' = map (\o -> (o, sp)) so+ lo' = map (\o -> (o, sp)) lo++-- | Adds an option for @h@ and @help@. The resulting 'ArgSpec'+-- return 'Nothing' if help was requested, or 'Just' with the original+-- argument for any other option.+addHelpOption+ :: [OptSpec a]+ -> ( [(ShortName, ArgSpec (Maybe a))]+ , [(LongName, ArgSpec (Maybe a))] )+addHelpOption os = splitOptSpecs os'+ where+ os' = optSpec "h" ["help"] (ZeroArg Nothing) : map (fmap Just) os+
+ lib/Multiarg/Vocabulary.hs view
@@ -0,0 +1,94 @@+-- | Vocabulary used throughout Multiarg.+--+-- Each time one of these words is used in the documentation, it is+-- /italicized/ (or, if you are viewing the source code directly+-- rather than through Haddock, it is /surrounded by slashes/).+--+-- [/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 /short options/+-- with the last /short option/ being accompanied by an+-- /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 /option arguments/.+-- The option is specified on the command line with both the /flag/+-- that specifies the option and of any /option arguments/ that are+-- included with the /option/. Therefore the /option/ might be+-- specified on the command line using one /word/ or multiple /words/,+-- and in the case of short /options/, multiple /options/ might be in+-- one /word/.+--+-- [/short option/] An /option/ that is specified on the command line+-- using a /flag/ whose /word/ begins with a hyphen, and with a single+-- letter. For example, for the program @tail(1)@, possible short+-- options include @n@ and @v@. Multiarg will parse /words/ that+-- contain mulitple /short options/. For example, if a user wants to+-- run @tail@ with two options, he might type @tail -v -f@ or he might+-- type @tail -vf@.+--+-- [/flag/] A /flag/ uniquely specifies an /option/. To specify an+-- /option/ on the command line, the user must present both a /flag/+-- and any /option arguments/. In the case of a /long option/, the+-- /flag/ consists of one or more characters (typically a mnemonic+-- word), preceded by two hyphens. In the case of a /short option/,+-- the /flag/ consists of a single character, in a /word/ that begins+-- with a single hyphen; the /word/ might contain more than one /flag/+-- for multiple /short options/.+--+-- [/short option name/] A short option is specified on the command+-- line using a /flag/ and any /option arguments/. The /flag/+-- contains the /short option name/, which is a single character. A+-- /short option name/ is never a single hyphen.+--+-- [/long option name/] A long option is specified on the command line+-- using a /flag/ and any /option arguments/. The /flag/ begins with+-- two hyphens, followed by the /long option name/, which must be at+-- least one letter but typically is a mnemonic word.+--+-- [/name/] Either a /short option name/ or /long option name/, as+-- appropriate.+--+-- [/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@. A long option is+-- specified on the command line with a /flag/ and any /option arguments/.+--+-- [/option argument/] An /option/ may take anywhere from zero to+-- three /option arguments/. When using a /short option/, the first+-- /option argument/ and the /flag/ may be contained in the same+-- /word/ by appending the /option argument/ immediately after the+-- /flag/ without an intervening space. When using a /long option/,+-- the first /option argument/ and the /flag/ may be contained in the+-- same word by separating the /flag/ and the /option argument with an+-- equal sign. In any case in which an /option argument/ and a /flag/+-- are in the same /word/, the /option argument/ must be the last+-- thing to appear in the /word/. When using either /short options/+-- or /long options/, the first /option argument/ may appear in the+-- same /word/ as the /flag/ or in the /word/ following the /flag/;+-- the second and third /option arguments/ (if applicable) must each+-- appear in its own /word/.+--+-- [/positional argument/] A /word/ on the command line that does not+-- contain a /flag/, is not a /stopper/, and is not an /option argument/.+-- 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/.+--+-- [/stopper/] A /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/.++module Multiarg.Vocabulary where+
− lib/System/Console/MultiArg.hs
@@ -1,169 +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 Control.Monad.Exception.Synchronous- , 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 Control.Monad.Exception.Synchronous-import System.Environment
− lib/System/Console/MultiArg/Combinator.hs
@@ -1,551 +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 qualified Control.Monad.Exception.Synchronous as Ex-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 -> Ex.Exceptional InputError a-reader s = case reads s of- (a, ""):[] -> return a- _ -> Ex.throw . 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- -> Ex.Exceptional InputError (Maybe a)-optReader ms = case ms of- Nothing -> return Nothing- Just s -> case reads s of- (a, ""):[] -> return (Just a)- _ -> Ex.throw . 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".------ Some of these value constructors have names ending in @E@. Use--- these constructors when you want to parse option arguments that may--- fail to parse--for example, you want to parse an Int. The function--- passed as an argument to the value constructor indicates failure by--- returing an 'Exception'.-data ArgSpec a =- NoArg a- -- ^ This option takes no arguments-- | OptionalArg (Maybe String -> 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 -> 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 -> a)- -- ^ This option takes two arguments. Parsed similarly to- -- 'OneArg'.-- | ThreeArg (String -> String -> String -> a)- -- ^ This option takes three arguments. Parsed similarly to- -- 'OneArg'.-- | VariableArg ([String] -> a)- -- ^ This option takes a variable number of arguments--zero or- -- more. Option arguments continue until the command line contains- -- 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@.-- | OptionalArgE (Maybe String -> Ex.Exceptional InputError a)- -- ^ This option takes an optional argument, like- -- 'OptionalArg'. Parsing of the optional argument might fail.-- | OneArgE (String -> Ex.Exceptional InputError a)- -- ^ This option takes a single argument, like 'OneArg'. Parsing- -- of the argument might fail.-- | TwoArgE (String -> String -> Ex.Exceptional InputError a)- -- ^ This option takes two arguments, like 'TwoArg'. Parsing of- -- the arguments might fail.-- | ThreeArgE (String -> String -> String -> Ex.Exceptional InputError a)- -- ^ This option takes three arguments, like 'ThreeArg'. Parsing- -- of the arguments might fail.-- | VariableArgE ([String] -> Ex.Exceptional InputError a)- -- ^ This option takes a variable number of arguments, like- -- 'VariableArg'. Parsing of the arguments might fail.---instance Functor ArgSpec where- fmap f a = case a of- NoArg i -> NoArg $ f i- OptionalArg g ->- OptionalArg $ \ms -> f (g ms)- OneArg g ->- OneArg $ \s1 -> f (g s1)- TwoArg g ->- TwoArg $ \s1 s2 -> f (g s1 s2)- ThreeArg g ->- ThreeArg $ \s1 s2 s3 -> f (g s1 s2 s3)- VariableArg g ->- VariableArg $ \ls -> f (g ls)- ChoiceArg gs ->- ChoiceArg . map (\(s, r) -> (s, f r)) $ gs-- OptionalArgE g -> OptionalArgE $ \ms -> fmap f (g ms)-- OneArgE g ->- OneArgE $ \s1 -> fmap f (g s1)-- TwoArgE g ->- TwoArgE $ \s1 s2 -> fmap f (g s1 s2)-- ThreeArgE g ->- ThreeArgE $ \s1 s2 s3 -> fmap f (g s1 s2 s3)-- VariableArgE g ->- VariableArgE $ \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"- OptionalArg f -> return (f maybeArg)- OneArg f -> f <$> maybeNextArg- TwoArg f -> f <$> maybeNextArg <*> nextWord- ThreeArg f -> f <$> maybeNextArg <*> nextWord <*> nextWord- VariableArg f -> do- as <- many nonOptionPosArg- return . f $ case maybeArg of- Nothing -> as- Just a1 -> a1 : as- ChoiceArg ls -> do- s <- maybeNextArg- case matchAbbrev ls s of- Nothing -> fail $ "option " ++ unLongOpt lo- ++ " requires an argument: "- ++ (concat . intersperse ", " . map fst $ ls)- Just g -> return g-- OptionalArgE f -> case maybeArg of- Nothing -> Ex.switch (fail . errorMsg (Left lo) []) return- $ f Nothing- Just s -> Ex.switch (fail . errorMsg (Left lo) [s]) return- $ f (Just s)--- OneArgE f -> maybeNextArg >>= g- where- g a = Ex.switch (fail . errorMsg (Left lo) [a]) return- $ f a-- TwoArgE f -> do- a1 <- maybeNextArg- a2 <- nextWord- Ex.switch (fail . errorMsg (Left lo) [a1, a2]) return- $ f a1 a2-- ThreeArgE f -> do- a1 <- maybeNextArg- a2 <- nextWord- a3 <- nextWord- Ex.switch (fail . errorMsg (Left lo) [a1, a2, a3]) return- $ f a1 a2 a3-- VariableArgE f -> do- as <- many nonOptionPosArg- let args = case maybeArg of- Nothing -> as- Just a -> a:as- Ex.switch (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- OptionalArg f -> shortOptionalArg f- OneArg f -> shortOneArg f- TwoArg f -> shortTwoArg f- ThreeArg f -> shortThreeArg f- VariableArg f -> shortVariableArg f- ChoiceArg ls -> shortChoiceArg opt ls- OptionalArgE f -> shortOptionalArgE opt f- OneArgE f -> shortOneArgE opt f- TwoArgE f -> shortTwoArgE opt f- ThreeArgE f -> shortThreeArgE opt f- VariableArgE f -> shortVariableArgE 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 :: ([String] -> a) -> Parser a-shortVariableArg f = do- maybeSameWordArg <- optional pendingShortOptArg- args <- many nonOptionPosArg- case maybeSameWordArg of- Nothing -> return (f args)- Just arg1 -> return (f (arg1:args))---shortVariableArgE- :: ShortOpt- -> ([String] -> Ex.Exceptional InputError a)- -> Parser a-shortVariableArgE so f = do- maybeSameWordArg <- optional pendingShortOptArg- args <- many nonOptionPosArg- let as = case maybeSameWordArg of- Nothing -> args- Just a -> a:args- Ex.switch (fail . errorMsg (Right so) as) return $ f as---shortOneArg :: (String -> a) -> Parser a-shortOneArg f = f <$> firstShortArg--shortOneArgE- :: ShortOpt- -> (String -> Ex.Exceptional InputError a)- -> Parser a-shortOneArgE so f = do- a <- firstShortArg- Ex.switch (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 :: (String -> String -> a) -> Parser a-shortTwoArg f = f <$> firstShortArg <*> nextWord--shortTwoArgE- :: ShortOpt- -> (String -> String -> Ex.Exceptional InputError a)- -> Parser a-shortTwoArgE so f = do- a1 <- firstShortArg- a2 <- nextWord- Ex.switch (fail . errorMsg (Right so) [a1, a2]) return- $ f a1 a2--shortThreeArg :: (String -> String -> String -> a) -> Parser a-shortThreeArg f = f <$> firstShortArg <*> nextWord <*> nextWord--shortThreeArgE- :: ShortOpt- -> (String -> String -> String -> Ex.Exceptional InputError a)- -> Parser a-shortThreeArgE so f = do- a1 <- firstShortArg- a2 <- nextWord- a3 <- nextWord- Ex.switch (fail . errorMsg (Right so) [a1, a2, a3]) return- $ f a1 a2 a3--shortOptionalArg :: (Maybe String -> a) -> Parser a-shortOptionalArg f = do- maybeSameWordArg <- optional pendingShortOptArg- case maybeSameWordArg of- Nothing -> do- maybeArg <- optional nonOptionPosArg- case maybeArg of- Nothing -> return (f Nothing)- Just a -> return (f (Just a))- Just a -> return (f (Just a))--shortOptionalArgE- :: ShortOpt- -> (Maybe String -> Ex.Exceptional InputError a)- -> Parser a-shortOptionalArgE so f = do- maybeSameWordArg <- optional pendingShortOptArg- case maybeSameWordArg of- Nothing -> do- maybeArg <- optional nonOptionPosArg- case maybeArg of- Nothing -> Ex.switch (fail . errorMsg (Right so) []) return- $ f Nothing- Just a -> Ex.switch (fail . errorMsg (Right so) [a]) return- $ f (Just a)- Just a -> Ex.switch (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,515 +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(..)- , MapShortcuts(..)- , 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 qualified Control.Monad.Exception.Synchronous as Ex-import System.Environment (getArgs, getProgName)-import System.Exit (exitFailure, exitSuccess)-import qualified System.IO as IO-import Control.Applicative ( many, (<|>), optional,- (<$), (<*>), (<*), (<$>))-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- { oOptions :: [C.OptSpec a]- -- ^ If the user does not specify any shortcut options, she may- -- specify any number of these options.-- , 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.-- }---- | 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 os = Opts os [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 os = Opts os [ C.OptSpec ["help"] "h" (C.NoArg h)- , C.OptSpec ["version"] "v" (C.NoArg v) ]--instance Functor (Opts s) where- fmap f (Opts os ss) = Opts (map (fmap f) os) ss---- | Things that contain shortcut options that can be changed.-class MapShortcuts f where- smap :: (a -> b) -> f a o -> f b o--instance MapShortcuts Opts where- smap f (Opts os ss) = Opts os (map (fmap f) ss)---- | Specification for both options and positional arguments.-data OptsWithPosArgs s a = OptsWithPosArgs- { opOpts :: Opts s a- , opIntersperse :: Intersperse- , opPosArg :: String -> Ex.Exceptional C.InputError a- }--instance MapShortcuts OptsWithPosArgs where- smap f (OptsWithPosArgs os i p) = OptsWithPosArgs (smap f os) i p--instance Functor (OptsWithPosArgs s) where- fmap f (OptsWithPosArgs os i p) =- OptsWithPosArgs (fmap f os) i (fmap (fmap f) p)---- | 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.- }---- | 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 -> Ex.Exceptional C.InputError a)- -- ^ Parses positional arguments-- -> Mode h r--modeHelp n h getR os i p =- Mode n getR (OptsWithPosArgs (Opts os ss) i p)- where- ss = [C.OptSpec ["help"] "h" (C.NoArg h)]--instance MapShortcuts Mode where- smap f (Mode n g o) = Mode n g (smap f o)--instance Functor (Mode s) where- fmap f (Mode n gr os) = Mode n (fmap f gr) os--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-- -> Ex.Exceptional 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] -> Ex.Exceptional 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 an Exception if there is something wrong with the- -- global options (a nonsensical combination, perhaps.) Otherwise,- -- it returns an 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-- -> Ex.Exceptional P.Error (Either s r)- -- ^ If the command line arguments fail to parse, this will be an- -- Exception with the error. If the parser is successful, this- -- returns an 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- Ex.Exception s -> fail s- Ex.Success 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 -> Ex.Exceptional 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- Ex.Exception e -> errorAct e- Ex.Success 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- Ex.Exception e -> showErr e >> exitFailure- Ex.Success 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] -> Ex.Exceptional 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 an Exception if there is something wrong with the- -- global options (a nonsensical combination, perhaps.) Otherwise,- -- it returns an 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- Ex.Exception e -> errorAct e- Ex.Success 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 -> Ex.Exceptional 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 os shortcuts) 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 -> Ex.Exceptional 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 os shortcuts) ir getArg- ei <- simpleIOCustomError errorActDisplayHelp opts- case ei of- Left act -> act- Right as -> return as---- # Helpers---- | Parses positional arguments and handles errors with them.-parsePosArg- :: (String -> Ex.Exceptional C.InputError a)- -> P.Parser a-parsePosArg p = do- a <- P.nextWord- case p a of- Ex.Exception e ->- let msg = "invalid positional argument: \"" ++ a ++ "\""- in case e of- C.NoMsg -> fail msg- C.ErrorMsg s -> fail $ msg ++ ": " ++ s- Ex.Success 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 -> Ex.Exceptional C.InputError a)- -> P.Parser [a]-parseStopOpts optParser p =- (++)- <$> parseOptsNoIntersperse optParser- <* optional P.stopper- <*> many (parsePosArg 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.-parseIntersperse- :: P.Parser a- -> (String -> Ex.Exceptional C.InputError a)- -> P.Parser [a]-parseIntersperse optParser p =- let pa = Just <$> parsePosArg 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,676 +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 Control.Monad.Exception.Synchronous as Ex-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-- -> Ex.Exceptional Error a- -- ^ Success or failure. Any parser might fail; for example, the- -- command line might not have any values left to parse. Use of the- -- 'choice' combinator can lead to a list of failures.--parse ss p =- let s = State "" ss False- procReply r = case r of- Ok x _ _ -> Ex.Success x- Fail m -> Ex.Exception 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)----- | 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 Ex.switch ert gd $ do- Ex.assert allOpts $ null ps- Ex.assert allOpts $ not stop- (x, rm') <- Ex.fromMaybe allOpts $ nextW rm- (word, afterEq) <- Ex.fromMaybe allOpts $ getLongOption x- opt <- Ex.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- [] -> Ex.throw allOpts- (m:[]) -> return (word, m, afterEq, rm')- ls -> Ex.throw 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 Ex.switch ert gd $ do- Ex.assert allWords $ null ps- (x, rm') <- Ex.fromMaybe allWords $ nextW rm- let matches = Set.filter p set- p t = x `isPrefixOf` t- case Set.toList matches of- [] -> Ex.throw allWords- r:[] -> return (x, r, rm')- xs -> Ex.throw 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,58 +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 Bytes)- , C.OptSpec ["follow"] ['f'] (C.OptionalArg Follow)- , C.OptSpec ["follow-retry"] ['F'] (C.NoArg Retry)- , C.OptSpec ["lines"] ['n'] (C.OneArg Lines)- , C.OptSpec ["max-unchanged-stats"] [] (C.OneArg Stats)- , C.OptSpec ["pid"] [] (C.OneArg Pid)- , C.OptSpec ["quiet"] ['q'] (C.NoArg Quiet)- , C.OptSpec ["sleep-interval"] ['s'] (C.OneArg 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,53 +1,182 @@-Name: multiarg-Version: 0.18.0.0-Cabal-version: >=1.8-Build-Type: Simple-License: BSD3-Copyright: 2011-2013 Omari Norman.+-- This Cabal file generated using the Cartel library.+-- Cartel is available at:+-- http://www.github.com/massysett/cartel+--+-- Script name used to generate: genCabal.hs+-- Generated on: 2015-09-09 21:55:24.296803 EDT+-- Cartel library version: 0.14.2.6++name: multiarg+version: 0.30.0.10+cabal-version: >= 1.18+license: BSD3+license-file: LICENSE+build-type: Simple+copyright: Copyright 2011-2015 Omari Norman author: Omari Norman maintainer: omari@smileystation.com stability: Experimental homepage: https://github.com/massysett/multiarg-bug-reports: omari@smileystation.com-Category: Console, Parsing-License-File: LICENSE-synopsis: Combinators to build command line parsers-extra-source-files: NEWS+bug-reports: https://github.com/massysett/multiarg/issues+synopsis: Command lines for options that take multiple arguments+description:+ multiarg helps you build command-line parsers for+ programs with options that take more than one argument.+ See the documentation in the Multiarg module for details.+category: Console, Parsing+extra-source-files:+ ChangeLog+ README.md -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- multiarg due to the apparent lack of such ability amongst other- parsers. Its basic design is loosely inspired by Parsec.- .- Provides Parser, a monad you use to build parsers. This monad exposes- multiarg's full functionality. The library also has a simple,- pre-built parser built with the underlying combinators, which works- 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- details.+Library+ hs-source-dirs:+ lib+ ghc-options:+ -Wall+ default-language: Haskell2010+ build-depends:+ base >= 4.7.0.0 && < 5+ exposed-modules:+ Multiarg+ Multiarg.Examples+ Multiarg.Examples.Grover+ Multiarg.Examples.Telly+ Multiarg.Internal+ Multiarg.Limeline+ Multiarg.Maddash+ Multiarg.Mode+ Multiarg.Mode.Internal+ Multiarg.Types+ Multiarg.Util+ Multiarg.Vocabulary source-repository head- type: git- location: git://github.com/massysett/multiarg.git--Library- Build-depends:- base ==4.6.*- , containers ==0.5.*- , explicit-exception ==0.1.*+ type: git+ location: https://github.com/massysett/multiarg.git - hs-source-dirs: lib+Executable grover+ main-is: grover-main.hs+ if flag(programs)+ buildable: True+ hs-source-dirs:+ lib+ ghc-options:+ -Wall+ default-language: Haskell2010+ build-depends:+ base >= 4.7.0.0 && < 5+ build-depends:+ QuickCheck >= 2.7+ , tasty >= 0.10+ , tasty-quickcheck >= 0.8+ , tasty-th >= 0.1+ other-modules:+ Multiarg+ Multiarg.Examples+ Multiarg.Examples.Grover+ Multiarg.Examples.Telly+ Multiarg.Internal+ Multiarg.Limeline+ Multiarg.Maddash+ Multiarg.Mode+ Multiarg.Mode.Internal+ Multiarg.Types+ Multiarg.Util+ Multiarg.Vocabulary+ Ernie+ Grover.Tests+ Makeopt+ Multiarg.Maddash.Instances+ Multiarg.Maddash.Tests+ Multiarg.Types.Instances+ Telly.Tests+ hs-source-dirs:+ tests+ else+ buildable: False - 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+Executable telly+ main-is: telly-main.hs+ if flag(programs)+ buildable: True+ hs-source-dirs:+ lib+ ghc-options:+ -Wall+ default-language: Haskell2010+ build-depends:+ base >= 4.7.0.0 && < 5+ build-depends:+ QuickCheck >= 2.7+ , tasty >= 0.10+ , tasty-quickcheck >= 0.8+ , tasty-th >= 0.1+ other-modules:+ Multiarg+ Multiarg.Examples+ Multiarg.Examples.Grover+ Multiarg.Examples.Telly+ Multiarg.Internal+ Multiarg.Limeline+ Multiarg.Maddash+ Multiarg.Mode+ Multiarg.Mode.Internal+ Multiarg.Types+ Multiarg.Util+ Multiarg.Vocabulary+ Ernie+ Grover.Tests+ Makeopt+ Multiarg.Maddash.Instances+ Multiarg.Maddash.Tests+ Multiarg.Types.Instances+ Telly.Tests+ hs-source-dirs:+ tests+ else+ buildable: False - ghc-options: -Wall+Test-Suite multiarg-tests+ hs-source-dirs:+ lib+ ghc-options:+ -Wall+ default-language: Haskell2010+ build-depends:+ base >= 4.7.0.0 && < 5+ type: exitcode-stdio-1.0+ main-is: multiarg-tests.hs+ other-modules:+ Multiarg+ Multiarg.Examples+ Multiarg.Examples.Grover+ Multiarg.Examples.Telly+ Multiarg.Internal+ Multiarg.Limeline+ Multiarg.Maddash+ Multiarg.Mode+ Multiarg.Mode.Internal+ Multiarg.Types+ Multiarg.Util+ Multiarg.Vocabulary+ Ernie+ Grover.Tests+ Makeopt+ Multiarg.Maddash.Instances+ Multiarg.Maddash.Tests+ Multiarg.Types.Instances+ Telly.Tests+ hs-source-dirs:+ tests+ other-extensions:+ TemplateHaskell+ build-depends:+ QuickCheck >= 2.7+ , tasty >= 0.10+ , tasty-quickcheck >= 0.8+ , tasty-th >= 0.1 +Flag programs+ description: Build sample programs+ default: False+ manual: True
+ tests/Ernie.hs view
@@ -0,0 +1,122 @@+-- | Functions to assist with testing.+module Ernie where++import Control.Applicative+import Makeopt+import Multiarg.Types+import Test.QuickCheck++-- | Generates words that start with a single hyphen.+startsWithOneHyphen :: Gen String+startsWithOneHyphen = fmap ('-':) (listOf1 arbitrary)++-- | Generates words that start with two hyphens.+startsWithTwoHyphens :: Gen String+startsWithTwoHyphens = fmap ("--" ++) arbitrary++-- | Generates words that do not start with a hyphen.+startsWithNonHyphen :: Gen String+startsWithNonHyphen = (:) <$> (arbitrary `suchThat` (/= '-'))+ <*> arbitrary++-- | Generates words for option arguments. Ensures that some start+-- with hyphens (these are valid option arguments.)+optArg :: Gen String+optArg = oneof [ startsWithOneHyphen, startsWithNonHyphen ]++short :: Char -> [String] -> [[String]]+short c os = case shortName c of+ Nothing -> error "Ernie.hs: error: could not create short name"+ Just o -> processShortOptions [] (o, os)++long :: String -> [String] -> [[String]]+long s os = case longName s of+ Nothing -> error "Ernie.hs: error: could not create long name"+ Just o -> processLongOption o os++pickItem :: [a] -> Gen a+pickItem a+ | null a = fail "pickItem: empty list"+ | otherwise = fmap (a !!) (choose (0, length a - 1))++-- | Generates non-option positional arguments that appear to the+-- right of the stopper. This can be any word at all.+posArgRight :: Gen String+posArgRight = oneof+ [ arbitrary, startsWithOneHyphen, startsWithTwoHyphens ]++-- | Generates non-option positional arguments that appear to the left+-- of the stopper. Cannot be preceded by a dash; can, however, be a+-- single hyphen only.+posArgLeft :: Gen String+posArgLeft =+ frequency [ (5, startsWithNonHyphen)+ , (1, return "-") ]++-- | Generates options, non-option positional arguments that are a+-- single hyphen only, and non-option positional arguments that do not+-- start with a hyphen; these may appear to the left of a stopper.+preStopper+ :: Gen a+ -- ^ Generates options+ -> (String -> a)+ -- ^ Creates non-option positional arguments+ -> Gen [a]+preStopper genOpt fPos =+ listOf (oneof [ genOpt, fmap fPos posArgLeft ])++-- | Generates any word at all, with a healthy mix of empty lists+-- (stoppers are unusual.)+postStopper+ :: (String -> a)+ -- ^ Creates non-option positional arguments+ -> Gen [a]+postStopper fPos =+ oneof [ return [], listOf (fmap fPos posArgRight) ]++-- | Generates a valid list of interspersed command-line options; that+-- is, a list that the user could have entered in the command line.+-- This list may be transformed into strings, which can then be parsed+-- and compared against this original value.+--+-- Returns a pair @(a, b)@, where @a@ is everything to the left of the+-- stopper, and @b@ (if non-empty) is everything to the right of the+-- stopper.++interspersedLine+ :: Gen a+ -- ^ Generates options+ -> (String -> a)+ -- ^ Creates non-option positional arguments+ -> Gen ([a], [a])+interspersedLine genOpt fPos =+ (,)+ <$> preStopper genOpt fPos+ <*> postStopper fPos+++-- | Takes an interspersed line and creates a set of strings that+-- would, when parsed, yield the interspersed line.+interspersedLineToStrings++ :: ([a], [a])+ -- ^ @(a, b)@, where+ --+ -- @a@ is everything to the left of the stopper, and+ --+ -- @b@ is everything to the right of the stopper++ -> (a -> [[String]])+ -- ^ Converts a single item to a nested list of String. Each list+ -- of String is a possible way to render this item. This list must+ -- not be empty.++ -> Gen [String]++interspersedLineToStrings (left, right) fConv = do+ l <- fmap concat . mapM pickItem . map fConv $ left+ r <- fmap concat . mapM pickItem . map fConv $ right+ alwaysStopper <- arbitrary+ let stop | not (null r) || alwaysStopper = ["--"]+ | otherwise = []+ return $ l ++ stop ++ r
+ tests/Grover/Tests.hs view
@@ -0,0 +1,136 @@+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# LANGUAGE TemplateHaskell #-}+module Grover.Tests where++import Ernie+import Multiarg.Examples.Grover+import Control.Applicative+import Test.QuickCheck hiding (Result)+import Multiarg.Mode+import Test.Tasty+import Test.Tasty.TH+import Test.Tasty.QuickCheck++tests :: TestTree+tests = $(testGroupGenerator)++genGlobal :: Gen Global+genGlobal = oneof+ [ return Help+ , Verbose <$> arbitrary+ , return Version+ ]++data GroverOpts+ = GOInts [GroverOpt Int]+ | GOStrings [GroverOpt String]+ | GOMaybes [GroverOpt (Maybe Int)]+ deriving (Eq, Ord, Show)++instance Arbitrary GroverOpts where+ arbitrary = oneof+ [ fmap GOInts . listOf . genGroverOpt $ arbitrary+ , fmap GOStrings . listOf . genGroverOpt $ arbitrary+ , fmap GOMaybes . listOf . genGroverOpt $ arbitrary+ ]++-- | Generates a mode option. Does not generate positional arguments.+genGroverOpt+ :: Gen a+ -- ^ Generates arguments+ -> Gen (GroverOpt a)+genGroverOpt g = oneof+ [ return Zero+ , Single <$> g+ , Double <$> g <*> g+ , Triple <$> g <*> g <*> g+ ]++globalToNestedList :: Global -> [[String]]+globalToNestedList glbl = case glbl of+ Help -> long "help" [] ++ short 'h' []+ Verbose i -> long "verbose" [show i] ++ short 'v' [show i]+ Version -> long "version" []++groverOptToNestedList :: Show a => GroverOpt a -> [[String]]+groverOptToNestedList gvr = case gvr of+ Zero -> long "zero" [] ++ short 'z' []+ Single a -> long "single" ls ++ short 's' ls+ where+ ls = [show a]+ Double a b -> long "double" ls ++ short 'd' ls+ where+ ls = [show a, show b]+ Triple a b c -> long "triple" ls ++ short 't' ls+ where+ ls = [show a, show b, show c]+ PosArg s -> [[s]]++-- | A valid Grover AST, combined with a set of strings that, when+-- parsed, should yield that AST.+data ValidGrover+ = ValidGrover [Global] (Either [String] Result) [String]+ -- ^ @ValidGrover a b c@, where+ --+ -- @a@ is the list of global options+ --+ -- @b@ is either a list of strings (indicates that the user entered+ -- no mode), or the mode, and its associated options+ --+ -- @c@ is a list of strings that, when parsed, should return @a@ and @b@.+ deriving (Eq, Ord, Show)++instance Arbitrary ValidGrover where+ arbitrary = do+ globals <- listOf genGlobal+ glblStrings <- fmap concat . mapM pickItem+ . map globalToNestedList $ globals+ (ei, endStrings) <- oneof+ [ resultAndStrings Ints "int"+ , resultAndStrings Strings "string"+ , resultAndStrings Maybes "maybe"+ ]+ return $ ValidGrover globals ei (glblStrings ++ endStrings)++-- | Generates a list of String, where the first String will not be+-- interpreted as a mode.++genNonModePosArg :: Gen [String]+genNonModePosArg = frequency ([ (1, return []), (3, nonEmpty)])+ where+ nonEmpty = (:) <$> firstWord <*> listOf arbitrary+ where+ firstWord = arbitrary `suchThat`+ (\s -> not (s `elem` ["int", "string", "maybe"])+ && not (startsWithHyphen s))+ startsWithHyphen s = case s of+ '-':_ -> True+ _ -> False+++resultAndStrings+ :: (Arbitrary a, Show a)++ => ([Either String (GroverOpt a)] -> Result)+ -- ^ Function that creates a Result++ -> String+ -- ^ Name of mode++ -> Gen (Either [String] Result, [String])+resultAndStrings fRes modeName = frequency [(1, nonMode), (4, withMode)]+ where+ nonMode = fmap (\ls -> (Left ls, ls)) genNonModePosArg+ withMode = do+ ispLine <- interspersedLine (genGroverOpt arbitrary) PosArg+ strings <- interspersedLineToStrings ispLine groverOptToNestedList+ return ( Right . fRes . map Right $ fst ispLine ++ snd ispLine+ , modeName : strings )++prop_ValidGrover (ValidGrover globals ei strings) = result === expected+ where+ result = parseModeLine globalOptSpecs modes strings+ expected = Right (ModeResult (map Right globals) ei)+++prop_alwaysTrue = True
+ tests/Makeopt.hs view
@@ -0,0 +1,62 @@+-- | Makeopt produces all possible partitions for a given set of+-- command line options.++module Makeopt where++import Multiarg.Maddash++processShortOptions+ :: [ShortName]+ -> (ShortName, [String])+ -> [[String]]+processShortOptions firstNames (inLast, args)+ = shortPartitions firstName restNames args+ where+ (firstName, restNames) = case firstNames of+ [] -> (shortNameToChar inLast, [])+ (x:xs) -> (shortNameToChar x, map shortNameToChar $ xs ++ [inLast])++processLongOption+ :: LongName+ -> [String]+ -> [[String]]+processLongOption lngName ss = lists ss+ where+ lng = "--" ++ longNameToString lngName+ lists [] = [[lng]]+ lists (x:xs) = [ (lng ++ "=" ++ x) : xs+ , lng : x : xs+ ]++partitions :: [a] -> [[[a]]]+partitions [] = [[]]+partitions (x:xs) = [[x]:p | p <- partitions xs]+ ++ [(x:ys):yss | (ys:yss) <- partitions xs]+++shortPartitions+ :: Char+ -- ^ First flag+ -> String+ -- ^ Remaining flags+ -> [String] + -- ^ Arguments+ -> [[String]]+shortPartitions c1 cs args = case args of+ [] -> flags+ x:xs+ | null x -> separate+ | otherwise -> together ++ separate+ where+ separate = [ list ++ (x:xs) | list <- flags ]+ together = do+ list <- flags+ case addToEnd list x of+ Nothing -> error "shortPartitions: error"+ Just r -> return $ r ++ xs+ where+ flags = map (map ('-':)) $ partitions (c1 : cs)+ +addToEnd :: [[a]] -> [a] -> Maybe [[a]]+addToEnd [] _ = Nothing+addToEnd xs toAdd = Just (init xs ++ [last xs ++ toAdd])
+ tests/Multiarg/Maddash/Instances.hs view
@@ -0,0 +1,31 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Multiarg.Maddash.Instances where++import Control.Applicative+import Test.QuickCheck+import Multiarg.Maddash+import Multiarg.Types.Instances ()++instance Arbitrary OptionError where+ arbitrary = oneof+ [ BadOption <$> arbitrary+ , LongArgumentForZeroArgumentOption <$> arbitrary <*> arbitrary+ ]++instance Arbitrary a => Arbitrary (Output a) where+ arbitrary = oneof+ [ Good <$> arbitrary+ , OptionError <$> arbitrary+ ]++instance Arbitrary a => Arbitrary (Pallet a) where+ arbitrary = oneof+ [ return NotAnOption+ , Full <$> arbitrary+ ]++instance Arbitrary a => Arbitrary (State a) where+ arbitrary = oneof+ [ return Ready+ , Pending <$> arbitrary <*> arbitrary+ ]
+ tests/Multiarg/Maddash/Tests.hs view
@@ -0,0 +1,138 @@+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# LANGUAGE TemplateHaskell #-}+module Multiarg.Maddash.Tests where++import Control.Applicative+import Multiarg.Types+import Multiarg.Maddash+import Makeopt+import Test.QuickCheck+import Test.Tasty+import Test.Tasty.TH+import Test.Tasty.QuickCheck+import Multiarg.Types.Instances ()+import Multiarg.Maddash.Instances ()+import Multiarg.Types.Instances ()++tests :: TestTree+tests = $(testGroupGenerator)++genInt :: Gen Int+genInt = arbitrary++singleDash :: Multiarg.Types.Word+singleDash = Multiarg.Types.Word "-"++stopper :: Multiarg.Types.Word+stopper = Multiarg.Types.Word "--"++genNonOptWord :: Gen Multiarg.Types.Word+genNonOptWord = oneof+ [ return singleDash+ , return stopper+ , do+ c1 <- arbitrary `suchThat` (/= '-')+ cs <- listOf arbitrary+ return $ Multiarg.Types.Word (c1 : cs)+ ]++genPending :: Arbitrary a => Gen (State a)+genPending = Pending <$> arbitrary <*> arbitrary++-- * Properties++-- | Non-option token always returns NotAnOption if State is Ready+prop_nonOptWordNotAnOptionIfStateIsReady :: Property+prop_nonOptWordNotAnOptionIfStateIsReady =+ forAll arbitrary $ \shorts ->+ forAll arbitrary $ \longs ->+ forAll genNonOptWord $ \token ->+ let (pallet, _) = processWord shorts longs Ready token+ _types = shorts :: [(ShortName, ArgSpec Int)]+ in pallet == NotAnOption++-- | Stopper always returns NotAnOption if State is Ready+prop_stopperNotAnOptionIfStateIsReady :: Property+prop_stopperNotAnOptionIfStateIsReady =+ forAll arbitrary $ \shorts ->+ forAll arbitrary $ \longs ->+ let (pallet, _) = processWord shorts longs Ready stopper+ _types = shorts :: [(ShortName, ArgSpec Int)]+ in pallet == NotAnOption++-- | Single dash always returns NotAnOption if State is Ready+prop_singleDashNotAnOptionIfStateIsReady :: Property+prop_singleDashNotAnOptionIfStateIsReady =+ forAll arbitrary $ \shorts ->+ forAll arbitrary $ \longs ->+ let (pallet, _) = processWord shorts longs Ready singleDash+ _types = shorts :: [(ShortName, ArgSpec Int)]+ in pallet == NotAnOption++-- | processWord never returns NotAnOption when input is Pending+prop_processWordNeverReturnsNotAnOptionOnPending =+ forAll arbitrary $ \shorts ->+ forAll arbitrary $ \longs ->+ forAll genPending $ \state ->+ forAll arbitrary $ \token ->+ let (pallet, _) = processWord shorts longs state token+ _types = shorts :: [(ShortName, ArgSpec Int)]+ in pallet /= NotAnOption++-- | NotAnOption is always returned with Ready+prop_processWordNotAnOptionWithReady =+ forAll arbitrary $ \shorts ->+ forAll arbitrary $ \longs ->+ forAll arbitrary $ \state ->+ forAll arbitrary $ \token ->+ let (pallet, state') = processWord shorts longs state token+ _types = shorts :: [(ShortName, ArgSpec Int)]+ in pallet == NotAnOption ==> isReady state'++pickOne :: [a] -> Gen a+pickOne ls+ | null ls = error "pickOne: null list"+ | otherwise = fmap (\ix -> ls !! ix) (choose (0, length ls - 1))++data OptionWithToks = OptionWithToks+ { owtOptName :: OptName+ , owtArgSpec :: ArgSpec Int+ , owtArgs :: [String]+ , owtWords :: [Multiarg.Types.Word]+ , owtResultOuts :: [[Output Int]]+ , owtResultToks :: Maybe [Multiarg.Types.Word]+ , owtExpected :: Int+ } deriving Show++instance Arbitrary OptionWithToks where+ arbitrary = do+ OptName on <- arbitrary+ as <- arbitrary+ (args, expected) <- case as of+ ZeroArg a -> return ([], a)+ OneArg f -> do+ s <- arbitrary+ return ([s], f s)+ TwoArg f -> do+ s1:s2:[] <- vectorOf 2 arbitrary+ return ([s1,s2], f s1 s2)+ ThreeArg f -> do+ s1:s2:s3:[] <- vectorOf 3 arbitrary+ return ([s1,s2,s3], f s1 s2 s3)+ let strings = case on of+ Left shrt -> processShortOptions [] (shrt, args)+ Right lng -> processLongOption lng args+ toks <- fmap (map Multiarg.Types.Word) $ pickOne strings+ let (shrts, lngs) = case on of+ Left shrt -> ([(shrt, as)], [])+ Right lng -> ([], [(lng, as)])+ (procRslts, procEi) = processWords shrts lngs toks+ mayToks = either (const Nothing) Just procEi+ return $ OptionWithToks (OptName on) as args toks procRslts+ mayToks expected++prop_optionWithToksResultToksEmpty = (== Just []) . owtResultToks++prop_optionWithToksResultIsExpected owt+ = concat (owtResultOuts owt) == [Good . owtExpected $ owt]+
+ tests/Multiarg/Types/Instances.hs view
@@ -0,0 +1,42 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Multiarg.Types.Instances where++import Control.Applicative+import Test.QuickCheck+import Multiarg.Types++instance Arbitrary a => Arbitrary (ArgSpec a) where+ arbitrary = oneof+ [ ZeroArg <$> arbitrary+ , OneArg <$> arbitrary+ , TwoArg <$> arbitrary+ , ThreeArg <$> arbitrary+ ]++instance Arbitrary ShortName where+ arbitrary = do+ c <- arbitrary+ case shortName c of+ Nothing -> arbitrary+ Just n -> return n++instance Arbitrary LongName where+ arbitrary = do+ c1 <- arbitrary `suchThat` (\c -> c /= '-' && c /= '=')+ cs <- listOf (arbitrary `suchThat` (/= '='))+ case longName (c1 : cs) of+ Nothing -> error $ "could not generate long name: " ++ (c1:cs)+ Just n -> return n++instance Arbitrary OptName where+ arbitrary = fmap OptName arbitrary++instance Arbitrary Multiarg.Types.Word where+ arbitrary = Multiarg.Types.Word <$> arbitrary++instance CoArbitrary Multiarg.Types.Word where+ coarbitrary (Multiarg.Types.Word s) = coarbitrary s++instance Arbitrary OptArg where+ arbitrary = OptArg <$> arbitrary+
+ tests/Telly/Tests.hs view
@@ -0,0 +1,81 @@+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# LANGUAGE TemplateHaskell #-}+module Telly.Tests where++import Multiarg.Examples.Telly+import Test.QuickCheck+import Control.Applicative+import Ernie+import Multiarg.Internal+import Test.Tasty+import Test.Tasty.TH+import Test.Tasty.QuickCheck++tests :: TestTree+tests = $(testGroupGenerator)++-- | Generates any option.+option :: Gen Telly+option = oneof+ [ return Empty+ , Single <$> optArg+ , Double <$> optArg <*> optArg+ , Triple <$> optArg <*> optArg <*> optArg++ , return Zero+ , One <$> optArg+ , Two <$> optArg <*> optArg+ , Three <$> optArg <*> optArg <*> optArg++ , return Cero+ , Uno <$> optArg+ , Dos <$> optArg <*> optArg+ , Tres <$> optArg <*> optArg <*> optArg+ ]++tellyToNestedList :: Telly -> [[String]]+tellyToNestedList telly = case telly of+ PosArg s -> [[s]]+ Empty -> long "empty" [] ++ short 'e' []+ Single s -> long "single" [s] ++ short 's' [s]+ Double s1 s2 -> long "double" [s1, s2] ++ short 'd' [s1, s2]+ Triple s1 s2 s3 -> long "triple" [s1, s2, s3]+ ++ short 't' [s1, s2, s3]++ Zero -> short '0' []+ One s -> short '1' [s]+ Two s1 s2 -> short '2' [s1, s2]+ Three s1 s2 s3 -> short '3' [s1, s2, s3]++ Cero -> long "cero" []+ Uno s -> long "uno" [s]+ Dos s1 s2 -> long "dos" [s1, s2]+ Tres s1 s2 s3 -> long "tres" [s1, s2, s3]++tellyToStrings :: Telly -> Gen [String]+tellyToStrings = pickItem . tellyToNestedList++validTellyStrings :: Gen ([Telly], [String])+validTellyStrings = do+ unneededStopper <- arbitrary+ (start, end) <- interspersedLine option PosArg+ let startStrings = map tellyToNestedList start+ endStrings = map tellyToNestedList end+ startList <- fmap concat $ mapM pickItem startStrings+ endList <- fmap concat $ mapM pickItem endStrings+ let endList'+ | null end && not unneededStopper = endList+ | otherwise = "--" : endList+ return (start ++ end, startList ++ endList')++prop_parseStringsYieldsTellies+ = forAll validTellyStrings $ \(tellies, strings) ->+ let ParsedCommandLine ls _+ = parseCommandLinePure optSpecs PosArg strings+ in map Right tellies === ls++prop_parseStringsYieldsNoEndError+ = forAll validTellyStrings $ \(_, strings) ->+ let ParsedCommandLine _ mayOpt+ = parseCommandLinePure optSpecs PosArg strings+ in mayOpt === Nothing
+ tests/grover-main.hs view
@@ -0,0 +1,9 @@+module Main where++import Multiarg.Examples.Grover+import System.Environment++main :: IO ()+main = do+ as <- getArgs+ putStrLn . show $ parseGrover as
+ tests/multiarg-tests.hs view
@@ -0,0 +1,13 @@+module Main where++import qualified Multiarg.Maddash.Tests+import qualified Grover.Tests+import qualified Telly.Tests+import Test.Tasty++main :: IO ()+main = defaultMain $ testGroup "all properties"+ [ Multiarg.Maddash.Tests.tests+ , Grover.Tests.tests+ , Telly.Tests.tests+ ]
+ tests/telly-main.hs view
@@ -0,0 +1,6 @@+module Main where++import Multiarg.Examples.Telly++main :: IO ()+main = parse >>= print