-- | 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