1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
|
{-# LANGUAGE RecordWildCards, TypeSynonymInstances, FlexibleInstances #-}
-- | Module for implementing CmdArgs helpers. A CmdArgs helper is an external program,
-- that helps a user construct the command line arguments. To use a helper set the
-- environment variable @$CMDARGS_HELPER@ (or @$CMDARGS_HELPER_/YOURPROGRAM/@) to
-- one of:
--
-- * @echo /foo/@ will cause @/foo/@ to be used as the command arguments.
--
-- * @cmdargs-browser@ will cause a web browser to appear to help entering the arguments.
-- For this command to work, you will need to install the @cmdargs-browser@ package:
-- <http://hackage.haskell.org/package/cmdargs-browser>
module System.Console.CmdArgs.Helper(
-- * Called by the main program
execute,
-- * Called by the helper program
Unknown, receive, reply, comment
) where
-- Should really be under Explicit, but want to export it top-level as Helper
import System.Console.CmdArgs.Explicit.Type
import System.Console.CmdArgs.Explicit.SplitJoin
import System.Process
import Control.Exception
import Control.Monad
import Data.Char
import Data.IORef
import Data.List
import Data.Maybe
import System.Exit
import System.IO
import System.IO.Unsafe
hOut h x = do
hPutStrLn h x
hFlush h
-- | Run a remote command line entry.
execute
:: String -- ^ Name of the command to run, e.g. @echo argument@, @cmdargs-browser@
-> Mode a -- ^ Mode to run remotely
-> [String] -- ^ Initial set of command line flags (not supported by all helpers)
-> IO (Either String [String]) -- ^ Either an error message, or a list of flags to use
execute cmd mode args
| "echo" == takeWhile (not . isSpace) cmd = return $ Right $ splitArgs $ drop 4 cmd
| otherwise = withBuffering stdout NoBuffering $ do
(Just hin, Just hout, _, _) <- createProcess (shell cmd){std_in=CreatePipe, std_out=CreatePipe}
-- none of the buffering seems necessary in practice, but better safe than sorry
hSetBuffering hin LineBuffering
hSetBuffering hout LineBuffering
(m, ans) <- saveMode mode
hOut hin m
loop ans hin hout
where
loop ans hin hout = do
x <- hGetLine hout
if "Result " `isPrefixOf` x then
return $ read $ drop 7 x
else if "Send " `isPrefixOf` x then do
hOut hin =<< ans (drop 5 x)
loop ans hin hout
else if "#" `isPrefixOf` x then do
hOut stdout x
loop ans hin hout
else
return $ Left $ "Unexpected message from program: " ++ show x
withBuffering hndl mode act = bracket
(do old <- hGetBuffering hndl; hSetBuffering hndl mode; return old)
(hSetBuffering hndl)
(const act)
-- | Unknown value, representing the values stored within the 'Mode' structure. While the values
-- are not observable, they behave identically to the original values.
newtype Unknown = Unknown {fromUnknown :: Value} -- wrap Value so the Pack instance doesn't leak
-- | Receive information about the mode to display.
receive :: IO (Mode Unknown)
receive = do
m <- getLine
return $ remap2 Unknown fromUnknown $ loadMode m $ \msg -> unsafePerformIO $ do
hOut stdout $ "Send " ++ msg
getLine
-- | Send a reply with either an error, or a list of flags to use. This function exits the helper program.
reply :: Either String [String] -> IO ()
reply x = do
hOut stdout $ "Result " ++ show x
exitWith ExitSuccess
-- | Send a comment which will be displayed on the calling console, mainly useful for debugging.
comment :: String -> IO ()
comment x = hOut stdout $ "# " ++ x
---------------------------------------------------------------------
-- IO MAP
data IOMap a = IOMap (IORef (Int,[(Int,a)]))
newIOMap :: IO (IOMap a)
newIOMap = fmap IOMap $ newIORef (0, [])
addIOMap :: IOMap a -> a -> IO Int
addIOMap (IOMap ref) x = atomicModifyIORef ref $ \(i,xs) -> let j = i+1 in ((j,(j,x):xs), j)
getIOMap :: IOMap a -> Int -> IO a
getIOMap (IOMap ref) i = do (_,xs) <- readIORef ref; return $ fromJust $ lookup i xs
---------------------------------------------------------------------
-- SERIALISE A MODE
newtype Value = Value Int
{-# NOINLINE toValue #-}
toValue :: Mode a -> Mode Value
-- fairly safe, use of a table and pointers from one process to another, but referentially transparent
toValue x = unsafePerformIO $ do
-- the ref accumulates, so is a space leak
-- but it will all disappear after the helper goes, so not too much of an issue
mp <- newIOMap
let embed x = unsafePerformIO $ fmap Value $ addIOMap mp x
proj (Value x) = unsafePerformIO $ getIOMap mp x
return $ remap2 embed proj x
saveMode :: Mode a -> IO (String, String -> IO String) -- (value, ask questions from stdin)
saveMode m = do
mp <- newIOMap
res <- add mp $ pack $ toValue m
return $ (show res, fmap show . get mp . read)
where
add :: IOMap (Pack -> Pack) -> Pack -> IO Pack
add mp x = flip transformM x $ \x -> case x of
Func (NoShow f) -> do i <- addIOMap mp f; return $ FuncId i
x -> return x
get :: IOMap (Pack -> Pack) -> (Int,Pack) -> IO Pack
get mp (i,x) = do
f <- getIOMap mp i
add mp $ f x
loadMode :: String -> (String -> String) -> Mode Value -- given serialised, question asker, give me a value
loadMode x f = unpack $ rep $ read x
where
rep :: Pack -> Pack
rep x = flip transform x $ \x -> case x of
FuncId i -> Func $ NoShow $ \y -> rep $ read $ f $ show (i,y)
x -> x
-- Support data types
data Pack = Ctor String [(String, Pack)]
| List [Pack]
| Char Char
| Int Int
| Func (NoShow (Pack -> Pack))
| FuncId Int -- Never passed to pack/unpack, always transfromed away by saveMode/loadMode
| String String
| None -- ^ Never generated, only used for reading in bad cases
deriving (Show,Read)
newtype NoShow a = NoShow a
instance Show (NoShow a) where showsPrec = error "Cannot show value of type NoShow"
instance Read (NoShow a) where readsPrec = error "Cannot read value of type NoShow"
transformM, descendM :: Monad m => (Pack -> m Pack) -> Pack -> m Pack
transformM f x = f =<< descendM (transformM f) x
descendM f x = let (a,b) = uniplate x in liftM b $ mapM f a
transform, descend :: (Pack -> Pack) -> Pack -> Pack
transform f = f . descend (transform f)
descend f x = let (a,b) = uniplate x in b $ map f a
uniplate :: Pack -> ([Pack], [Pack] -> Pack)
uniplate (List xs) = (xs, List)
uniplate (Ctor x ys) = (map snd ys, Ctor x . zip (map fst ys))
uniplate x = ([], const x)
class Packer a where
pack :: a -> Pack
unpack :: Pack -> a
add a b = (a, pack b)
ctor x (Ctor y xs) | x == y = xs
ctor _ _ = []
get a b = unpack $ fromMaybe None $ lookup a b
-- General instances
instance Packer a => Packer [a] where
pack xs = if length ys == length zs && not (null ys) then String zs else List ys
where ys = map (pack) xs
zs = [x | Char x <- ys]
unpack (String xs) = unpack $ List $ map Char xs
unpack (List xs) = map (unpack) xs
unpack _ = []
instance (Packer a, Packer b) => Packer (a -> b) where
pack f = Func $ NoShow $ pack . f . unpack
unpack (Func (NoShow f)) = unpack . f . pack
instance Packer Value where
pack (Value x) = pack x
unpack x = Value $ unpack x
instance Packer Char where
pack = Char
unpack (Char x) = x
unpack _ = ' '
instance Packer Int where
pack = Int
unpack (Int x) = x
unpack _ = -1
instance (Packer a, Packer b) => Packer (a,b) where
pack (a,b) = Ctor "(,)" [add "fst" a, add "snd" b]
unpack x = (get "fst" y, get "snd" y)
where y = ctor "(,)" x
instance Packer a => Packer (Maybe a) where
pack Nothing = Ctor "Nothing" []
pack (Just x) = Ctor "Just" [add "fromJust" x]
unpack x@(Ctor "Just" _) = Just $ get "fromJust" $ ctor "Just" x
unpack _ = Nothing
instance (Packer a, Packer b) => Packer (Either a b) where
pack (Left x) = Ctor "Left" [add "fromLeft" x]
pack (Right x) = Ctor "Right" [add "fromRight" x]
unpack x@(Ctor "Left" _) = Left $ get "fromLeft" $ ctor "Left" x
unpack x@(Ctor "Right" _) = Right $ get "fromRight" $ ctor "Right" x
unpack _ = Left $ unpack None
instance Packer Bool where
pack True = Ctor "True" []
pack _ = Ctor "False" []
unpack (Ctor "True" _) = True
unpack _ = False
-- CmdArgs specific
instance Packer a => Packer (Group a) where
pack Group{..} = Ctor "Group"
[add "groupUnnamed" groupUnnamed
,add "groupHidden" groupHidden
,add "groupNamed" groupNamed]
unpack x = let y = ctor "Group" x in Group
{groupUnnamed = get "groupUnnamed" y
,groupHidden = get "groupHidden" y
,groupNamed = get "groupNamed" y}
instance Packer a => Packer (Mode a) where
pack Mode{..} = Ctor "Mode"
[add "modeGroupModes" modeGroupModes
,add "modeNames" modeNames
,add "modeHelp" modeHelp
,add "modeHelpSuffix" modeHelpSuffix
,add "modeArgs" modeArgs
,add "modeGroupFlags" modeGroupFlags
,add "modeValue" modeValue
,add "modeCheck" modeCheck
,add "modeReform" modeReform
,add "modeExpandAt" modeExpandAt]
unpack x = let y = ctor "Mode" x in Mode
{modeGroupModes = get "modeGroupModes" y
,modeNames = get "modeNames" y
,modeHelp = get "modeHelp" y
,modeHelpSuffix = get "modeHelpSuffix" y
,modeArgs = get "modeArgs" y
,modeGroupFlags = get "modeGroupFlags" y
,modeValue = get "modeValue" y
,modeCheck = get "modeCheck" y
,modeReform = get "modeReform" y
,modeExpandAt = get "modeExpandAt" y}
instance Packer a => Packer (Flag a) where
pack Flag{..} = Ctor "Flag"
[add "flagNames" flagNames
,add "flagInfo" flagInfo
,add "flagType" flagType
,add "flagHelp" flagHelp
,add "flagValue" flagValue]
unpack x = let y = ctor "Flag" x in Flag
{flagNames = get "flagNames" y
,flagInfo = get "flagInfo" y
,flagType = get "flagType" y
,flagHelp = get "flagHelp" y
,flagValue = get "flagValue" y}
instance Packer a => Packer (Arg a) where
pack Arg{..} = Ctor "Arg"
[add "argType" argType
,add "argRequire" argRequire
,add "argValue" argValue]
unpack x = let y = ctor "Arg" x in Arg
{argType = get "argType" y
,argRequire = get "argRequire" y
,argValue = get "argValue" y}
instance Packer FlagInfo where
pack FlagReq = Ctor "FlagReq" []
pack (FlagOpt x) = Ctor "FlagOpt" [add "fromFlagOpt" x]
pack (FlagOptRare x) = Ctor "FlagOptRare" [add "fromFlagOpt" x]
pack FlagNone = Ctor "FlagNone" []
unpack x@(Ctor name _) = case name of
"FlagReq" -> FlagReq
"FlagOpt" -> FlagOpt $ get "fromFlagOpt" $ ctor name x
"FlagOptRare" -> FlagOptRare $ get "fromFlagOpt" $ ctor name x
"FlagNone" -> FlagNone
unpack _ = FlagNone
|