{-# LANGUAGE PatternGuards #-} {-| This module describes the attributes that can be specified on flags and modes. Many attributes have examples specified on the following data type: > data Sample = Sample > {str :: String > ,strs :: [String]} -} module System.Console.CmdArgs.UI( -- ** Attribute mechanism mode, Mode, (&=), (&), Attrib, -- ** Flag attributes text, typ, typFile, typDir, empty, flag, explicit, enum, args, argPos, unknownFlags, -- ** Mode attributes prog, helpSuffix, defMode ) where import System.Console.CmdArgs.Type import System.IO.Unsafe import Data.Dynamic import Data.Data import Data.List import Data.Maybe import Data.IORef import Control.Exception import Data.Char import Control.Monad.State import Data.Function infix 1 &= infixl 2 & --------------------------------------------------------------------- -- STATE MANAGEMENT {-# NOINLINE info #-} info :: IORef Attrib info = unsafePerformIO $ newIORef $ Attrib [] -- | Add attributes to a value. Always returns the first argument, but -- has a non-pure effect on the environment. Take care when performing -- program transformations. -- -- > value &= attrib1 & attrib2 (&=) :: a -> Attrib -> a (&=) x is = unsafePerformIO $ do writeIORef info is return x -- | Combine two attributes. (&) :: Attrib -> Attrib -> Attrib (&) (Attrib x) (Attrib y) = Attrib $ x ++ y collect :: a -> IO [Info] collect x = do evaluate x Attrib x <- readIORef info writeIORef info $ Attrib [] -- don't leak the info's return x -- | Construct a 'Mode' from a value annotated with attributes. mode :: Data a => a -> Mode a mode val = unsafePerformIO $ do info <- collect val let con = toConstr val name = map toLower $ showConstr con ref <- newIORef $ constrFields con flags <- liftM concat $ sequence $ flip gmapQ val $ \i -> do info <- collect i n:ns <- readIORef ref writeIORef ref ns case toFlagType $ typeOf i of _ | [FldEnum xs] <- info -> return [x{flagName=n} | x <- xs] Nothing -> error $ "Can't handle a type of " ++ show (typeOf i) Just x -> return [flagInfo flagDefault{flagName=n,flagKey=n,flagVal=toDyn i,flagType=x} info] return $ modeInfo modeDefault{modeVal=val,modeName=name,modeFlags=flags} info --------------------------------------------------------------------- -- INFO ITEMS -- | Attributes to modify the behaviour. newtype Attrib = Attrib [Info] data Info = FldEmpty String | FldArgs | FldArgPos Int | FldTyp String | Text String | FldFlag String | FldExplicit | HelpSuffix [String] | FldUnknown | FldEnum [Flag] | ModDefault | ModProg String deriving Show modeInfo :: Mode a -> [Info] -> Mode a modeInfo = foldl $ \m x -> case x of Text x -> m{modeText=x} HelpSuffix x -> m{modeHelpSuffix=x} ModDefault -> m{modeDef=True} ModProg x -> m{modeProg=Just x} x -> error $ "Invalid attribute at mode level: " ++ show x flagInfo :: Flag -> [Info] -> Flag flagInfo = foldl $ \m x -> case x of Text x -> m{flagText=x} FldExplicit -> m{flagExplicit=True} FldTyp x -> m{flagTyp=x} FldEmpty x -> m{flagOpt=Just x} FldFlag x -> m{flagFlag=x:flagFlag m} FldArgs -> m{flagArgs=Just Nothing} FldArgPos i -> m{flagArgs=Just (Just i)} FldUnknown -> m{flagUnknown=True} x -> error $ "Invalid attribute at argument level: " ++ show x --------------------------------------------------------------------- -- USER INTERFACE -- | Flag: Make the value of a flag optional, using the supplied -- value if none is given. -- -- > {str = def &= empty "foo"} -- > -s --str[=VALUE] (default=foo) empty :: (Show a, Typeable a) => a -> Attrib empty x = Attrib $ return $ case cast x of Just y -> FldEmpty y _ -> FldEmpty $ show x -- | Flag: The the type of a flag's value, usually upper case. Only -- used for the help message. -- -- > {str = def &= typ "FOO"} -- > -s --str=FOO typ :: String -> Attrib typ = Attrib . return . FldTyp -- | Flag/Mode: Descriptive text used in the help output. -- -- > {str = def &= text "Help message"} -- > -s --str=VALUE Help message text :: String -> Attrib text = Attrib . return . Text -- | Flag: Add flags which trigger this option. -- -- > {str = def &= flag "foo"} -- > -s --str --foo=VALUE flag :: String -> Attrib flag = Attrib . return . FldFlag -- | Flag: This field should be used to store the non-flag arguments. Can -- only be applied to fields of type @[String]@. -- -- > {strs = def &= args} args :: Attrib args = Attrib [FldArgs] -- | Flag: This field should be used to store a particular argument position -- (0-based). Can only be applied to fields of type @String@. -- -- > {str = def &= argPos 0} argPos :: Int -> Attrib argPos = Attrib . return . FldArgPos -- | Flag: Alias for @'typ' \"FILE\"@. typFile :: Attrib typFile = typ "FILE" -- | Flag: Alias for @'typ' \"DIR\"@. typDir :: Attrib typDir = typ "DIR" -- | Mode: Suffix to be added to the help message. helpSuffix :: [String] -> Attrib helpSuffix = Attrib . return . HelpSuffix -- | Flag: This field should be used to store all unknown flag arguments. -- If no @unknownFlags@ field is set, unknown flags raise errors. -- Can only be applied to fields of type @[String]@. -- -- > {strs = def &= unknownFlags} unknownFlags :: Attrib unknownFlags = Attrib [FldUnknown] -- | Mode: This mode is the default. If no mode is specified and a mode has this -- attribute then that mode is selected, otherwise an error is raised. defMode :: Attrib defMode = Attrib [ModDefault] -- | Mode: This is the name of the program running, used to override the result -- from @getProgName@. Only used in the help message. prog :: String -> Attrib prog = Attrib . return . ModProg -- | Flag: A field is an enumeration of possible values. -- -- > data Choice = Yes | No deriving (Data,Typeable,Show,Eq) -- > data Sample = Sample {choice :: Choice} -- > {choice = Yes & enum [Yes &= "say yes", No &= "say no"]} -- -- > -y --yes say yes (default) -- > -n --no say no enum :: (Typeable a, Eq a, Show a) => a -> [a] -> a enum def xs = unsafePerformIO $ do ys <- forM xs $ \x -> do y <- collect x return $ flagInfo flagDefault{flagKey=map toLower (show x), flagType=FlagBool (toDyn x), flagVal = toDyn False} y return $ def &= Attrib [FldEnum ys] -- | Flag: A field should not have any flag names guessed for it. -- All flag names must be specified by 'flag'. -- -- > {str = def &= explicit & flag "foo"} -- > --foo=VALUE explicit :: Attrib explicit = Attrib [FldExplicit]