|
| 1 | +{-# LANGUAGE CPP #-} |
1 | 2 | module Main where |
2 | 3 |
|
3 | 4 | import System.Console.Haskeline |
4 | | -import System.Environment |
| 5 | +import Options.Applicative |
| 6 | +import Data.Monoid ((<>)) |
| 7 | +#ifndef MINGW |
| 8 | +import System.IO (openFile, hClose, IOMode(..)) |
| 9 | +#endif |
5 | 10 |
|
6 | 11 | {-- |
7 | 12 | Testing the line-input functions and their interaction with ctrl-c signals. |
8 | 13 |
|
9 | 14 | Usage: |
10 | | -./Test (line input) |
11 | | -./Test chars (character input) |
12 | | -./Test password (no masking characters) |
13 | | -./Test password \* |
14 | | -./Test initial (use initial text in the prompt) |
| 15 | + ./Test (line input, default Behavior) |
| 16 | + ./Test chars (character input) |
| 17 | + ./Test password (no masking) |
| 18 | + ./Test password \* (masking with '*') |
| 19 | + ./Test initial (initial text in the prompt) |
| 20 | +
|
| 21 | + ./Test --mode useTermHandles (POSIX only) |
| 22 | + ./Test --mode useTermHandlesWith --term-type vt100 |
| 23 | + (POSIX only) |
| 24 | +
|
| 25 | +The --mode flag selects the haskeline Behavior; positional INPUT_ARG |
| 26 | +selects which prompt function to use, and works with any --mode. |
15 | 27 | --} |
16 | 28 |
|
| 29 | +data Mode |
| 30 | + = UseTerm -- ^ defaultBehavior |
| 31 | + | UseTermHandles -- ^ useTermHandles on /dev/tty |
| 32 | + | UseTermHandlesWith String -- ^ useTermHandlesWith TERM on /dev/tty |
| 33 | + |
| 34 | +data InputMode |
| 35 | + = LineInput |
| 36 | + | CharInput |
| 37 | + | PasswordInput (Maybe Char) |
| 38 | + | InitialInput |
| 39 | + |
| 40 | +data Opts = Opts { optMode :: Mode, optInput :: InputMode } |
| 41 | + |
17 | 42 | mySettings :: Settings IO |
18 | 43 | mySettings = defaultSettings {historyFile = Just "myhist"} |
19 | 44 |
|
20 | 45 | main :: IO () |
21 | 46 | main = do |
22 | | - args <- getArgs |
23 | | - let inputFunc = case args of |
24 | | - ["chars"] -> fmap (fmap (\c -> [c])) . getInputChar |
25 | | - ["password"] -> getPassword Nothing |
26 | | - ["password", [c]] -> getPassword (Just c) |
27 | | - ["initial"] -> flip getInputLineWithInitial ("left ", "right") |
28 | | - _ -> getInputLine |
29 | | - runInputT mySettings $ withInterrupt $ loop inputFunc 0 |
30 | | - where |
31 | | - loop :: (String -> InputT IO (Maybe String)) -> Int -> InputT IO () |
32 | | - loop inputFunc n = do |
33 | | - minput <- handleInterrupt (return (Just "Caught interrupted")) |
34 | | - $ inputFunc (show n ++ ":") |
35 | | - case minput of |
36 | | - Nothing -> return () |
37 | | - Just "quit" -> return () |
38 | | - Just "q" -> return () |
39 | | - Just s -> do |
40 | | - outputStrLn ("line " ++ show n ++ ":" ++ s) |
41 | | - loop inputFunc (n+1) |
| 47 | + Opts {optMode = m, optInput = im} <- execParser optsInfo |
| 48 | + case m of |
| 49 | + UseTerm -> |
| 50 | + runInputT mySettings (runAction im) |
| 51 | +#ifndef MINGW |
| 52 | + UseTermHandles -> |
| 53 | + runWithHandles Nothing im |
| 54 | + UseTermHandlesWith t -> |
| 55 | + runWithHandles (Just t) im |
| 56 | +#else |
| 57 | + _ -> error "useTermHandles[With] is not available on Windows" |
| 58 | +#endif |
| 59 | + |
| 60 | +runAction :: InputMode -> InputT IO () |
| 61 | +runAction im = withInterrupt $ loop (inputFunc im) 0 |
| 62 | + |
| 63 | +inputFunc :: InputMode -> String -> InputT IO (Maybe String) |
| 64 | +inputFunc LineInput = getInputLine |
| 65 | +inputFunc CharInput = fmap (fmap (\c -> [c])) . getInputChar |
| 66 | +inputFunc (PasswordInput mc) = getPassword mc |
| 67 | +inputFunc InitialInput = flip getInputLineWithInitial ("left ", "right") |
| 68 | + |
| 69 | +loop :: (String -> InputT IO (Maybe String)) -> Int -> InputT IO () |
| 70 | +loop f n = do |
| 71 | + minput <- handleInterrupt (return (Just "Caught interrupted")) |
| 72 | + $ f (show n ++ ":") |
| 73 | + case minput of |
| 74 | + Nothing -> return () |
| 75 | + Just "quit" -> return () |
| 76 | + Just "q" -> return () |
| 77 | + Just s -> do |
| 78 | + outputStrLn ("line " ++ show n ++ ":" ++ s) |
| 79 | + loop f (n+1) |
| 80 | + |
| 81 | +#ifndef MINGW |
| 82 | +-- Drive Haskeline against /dev/tty (the controlling terminal) via the |
| 83 | +-- useTermHandles[With] Behaviors. This still happens to be the controlling |
| 84 | +-- tty in our test setup, but it goes through the new code path. |
| 85 | +runWithHandles :: Maybe String -> InputMode -> IO () |
| 86 | +runWithHandles mTerm im = do |
| 87 | + input <- openFile "/dev/tty" ReadMode |
| 88 | + output <- openFile "/dev/tty" WriteMode |
| 89 | + let beh = case mTerm of |
| 90 | + Nothing -> useTermHandles input output |
| 91 | + Just t -> useTermHandlesWith t input output |
| 92 | + runInputTBehavior beh mySettings (runAction im) |
| 93 | + hClose input |
| 94 | + hClose output |
| 95 | +#endif |
| 96 | + |
| 97 | +---------------------------------------------------------------- |
| 98 | +-- Command-line parsing |
| 99 | + |
| 100 | +optsInfo :: ParserInfo Opts |
| 101 | +optsInfo = info (optsP <**> helper) |
| 102 | + (fullDesc <> progDesc "Haskeline test program") |
| 103 | + |
| 104 | +optsP :: Parser Opts |
| 105 | +optsP = Opts <$> modeP <*> inputModeP |
| 106 | + |
| 107 | +modeP :: Parser Mode |
| 108 | +modeP = mkMode |
| 109 | + <$> strOption |
| 110 | + ( long "mode" |
| 111 | + <> value "useTerm" |
| 112 | + <> showDefault |
| 113 | + <> metavar "MODE" |
| 114 | + <> help "useTerm | useTermHandles | useTermHandlesWith" ) |
| 115 | + <*> optional |
| 116 | + (strOption |
| 117 | + ( long "term-type" |
| 118 | + <> metavar "TERM" |
| 119 | + <> help "term type for --mode useTermHandlesWith" )) |
| 120 | + where |
| 121 | + mkMode "useTerm" _ = UseTerm |
| 122 | + mkMode "useTermHandles" _ = UseTermHandles |
| 123 | + mkMode "useTermHandlesWith" (Just t) = UseTermHandlesWith t |
| 124 | + mkMode "useTermHandlesWith" Nothing = |
| 125 | + error "--mode useTermHandlesWith requires --term-type" |
| 126 | + mkMode other _ = |
| 127 | + error ("unknown --mode: " ++ other) |
| 128 | + |
| 129 | +inputModeP :: Parser InputMode |
| 130 | +inputModeP = mkInput <$> many (argument str (metavar "INPUT_ARG")) |
| 131 | + where |
| 132 | + mkInput [] = LineInput |
| 133 | + mkInput ["chars"] = CharInput |
| 134 | + mkInput ["password"] = PasswordInput Nothing |
| 135 | + mkInput ["password", [c]] = PasswordInput (Just c) |
| 136 | + mkInput ["initial"] = InitialInput |
| 137 | + mkInput xs = |
| 138 | + error ("unrecognized positional args: " ++ show xs) |
0 commit comments