packages feed

keysafe-0.20160922: UI/Readline.hs

{- Copyright 2016 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

module UI.Readline (readlineUI) where

import Types.UI
import Types
import System.Console.Readline
import System.Posix.Terminal
import System.Posix.IO
import Control.Exception
import System.IO
import Data.List
import Data.Char
import Text.Read
import Control.Monad
import qualified Data.ByteString.UTF8 as BU8
import qualified Data.Text as T

readlineUI :: UI
readlineUI = UI
	{ isAvailable = queryTerminal stdInput
	, showError = myShowError
	, showInfo = myShowInfo
	, promptQuestion = myPromptQuestion
	, promptName = myPromptName
	, promptPassword = myPromptPassword
	, promptKeyId = myPromptKeyId
	, withProgress = myWithProgress
	}

myShowError :: Desc -> IO ()
myShowError desc = do
	hPutStrLn stderr $ "Error: " ++ desc
	_ <- readline "[Press Enter]"
	putStrLn ""

myShowInfo :: Title -> Desc -> IO ()
myShowInfo title desc = do
	showTitle title
	putStrLn desc
	putStrLn ""

myPromptQuestion :: Title -> Desc -> Question -> IO Bool
myPromptQuestion title desc question = bracket_ setup cleanup go
  where
	setup = do
		showTitle title
		putStrLn desc
	cleanup = putStrLn ""
	go = do
		mresp <- readline $ question ++ " [y/n] "
		case mresp of
			Just s
				| "y" `isPrefixOf` (map toLower s) ->
					return True
				| "n" `isPrefixOf` (map toLower s) -> 
					return False
			_ -> do
				putStrLn "Please enter 'y' or 'n'"
				go

myPromptName :: Title -> Desc -> Maybe Name -> (Name -> Maybe Problem) -> IO (Maybe Name)
myPromptName title desc suggested checkproblem =
	bracket_ setup cleanup go
  where
	setup = do
		showTitle title
		putStrLn desc
	cleanup = putStrLn ""
	go = do
		case suggested of
			Nothing -> return ()
			Just (Name b) -> addHistory (BU8.toString b)
		mname <- readline "Name> "
		case mname of
			Just s -> do
				addHistory s
				let n = Name $ BU8.fromString s
				case checkproblem n of
					Nothing -> do
						putStrLn ""
						return $ Just n
					Just problem -> do
						putStrLn problem
						go
			Nothing -> return Nothing

myPromptPassword :: Bool -> Title -> Desc -> IO (Maybe Password)
myPromptPassword confirm title desc = bracket setup cleanup (const prompt)
  where
	setup = do
		showTitle title
		putStrLn desc
		origattr <- getTerminalAttributes stdInput
		let newattr = origattr `withoutMode` EnableEcho
		setTerminalAttributes stdInput newattr Immediately
		return origattr
	cleanup origattr = do
		setTerminalAttributes stdInput origattr Immediately
		putStrLn ""
	prompt = do
		putStr "Enter password> "
		hFlush stdout
		p1 <- getLine
		putStrLn ""
		if confirm
			then promptconfirm p1
			else return $ mkpassword p1
	promptconfirm p1 = do
		putStr "Confirm password> "
		hFlush stdout
		p2 <- getLine
		putStrLn ""
		if p1 /= p2
			then do
				putStrLn "Passwords didn't match, try again..."
				prompt
			else do
				putStrLn ""
				return $ mkpassword p1
	mkpassword = Just . Password . BU8.fromString

myPromptKeyId :: Title -> Desc -> [(Name, KeyId)] -> IO (Maybe KeyId)
myPromptKeyId _ _ [] = return Nothing
myPromptKeyId title desc l = do
	showTitle title
	putStrLn desc
	putStrLn ""
	forM_ nl $ \(n, ((Name name), (KeyId kid))) ->
		putStrLn $ show n ++ ".\t" ++ BU8.toString name ++ " (keyid " ++ T.unpack kid ++ ")"
	prompt
  where
	nl = zip [1 :: Integer ..] l
	prompt = do
		putStr "Enter number> "
		hFlush stdout
		r <- getLine
		putStrLn ""
		case readMaybe r of
			Just n
				| n > 0 && n <= length l -> do
					putStrLn ""
					return $ Just $ snd (l !! pred n)
			_ -> do
				putStrLn $ "Enter a number from 1 to " ++ show (length l)
				prompt

myWithProgress :: Title -> Desc -> ((Percent -> IO ()) -> IO a) -> IO a
myWithProgress title desc a = bracket_ setup cleanup (a sendpercent)
  where
	setup = do
		showTitle title
		putStrLn desc
	sendpercent p = do
		putStr (show p ++ "%  ")
		hFlush stdout
	cleanup = do
		putStrLn "done"
		putStrLn ""

showTitle :: Title -> IO ()
showTitle title = do
	putStrLn title
	putStrLn (replicate (length title) '-')
	putStrLn ""