cmdargs-0.1: System/Console/CmdArgs/UI.hs
{-# 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]