{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NoImplicitPrelude #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Init.Prompt -- Copyright : (c) Brent Yorgey 2009 -- License : BSD-like -- -- Maintainer : cabal-devel@haskell.org -- Stability : provisional -- Portability : portable -- -- User prompt utility functions for use by the 'cabal init' command. -- ----------------------------------------------------------------------------- module Distribution.Client.Init.Prompt ( prompt , promptYesNo , promptStr , promptList ) where import Prelude hiding (break, putStrLn, getLine, putStr) import Distribution.Client.Compat.Prelude hiding (break, empty, getLine, putStr, putStrLn) import Distribution.Client.Init.Types import qualified System.IO -- | Create a prompt with optional default value that returns a -- String. promptStr :: Interactive m => String -> DefaultPrompt String -> m String promptStr = promptDefault Right id -- | Create a yes/no prompt with optional default value. promptYesNo :: Interactive m => String -- ^ prompt message -> DefaultPrompt Bool -- ^ optional default value -> m Bool promptYesNo = promptDefault recogniseYesNo showYesNo where recogniseYesNo s | (toLower <$> s) == "y" = Right True | (toLower <$> s) == "n" || s == "N" = Right False | otherwise = Left $ "Cannot parse input: " ++ s showYesNo True = "y" showYesNo False = "n" -- | Create a prompt with optional default value that returns a value -- of some Text instance. prompt :: (Interactive m, Parsec t, Pretty t) => String -> DefaultPrompt t -> m t prompt = promptDefault eitherParsec prettyShow -- | Create a prompt from a prompt string and a String representation -- of an optional default value. mkDefPrompt :: String -> DefaultPrompt String -> String mkDefPrompt msg def = msg ++ "?" ++ format def where format MandatoryPrompt = " " format OptionalPrompt = " [optional] " format (DefaultPrompt s) = " [default: " ++ s ++ "] " -- | Create a prompt from a list of strings promptList :: Interactive m => String -- ^ prompt -> [String] -- ^ choices -> DefaultPrompt String -- ^ optional default value -> Maybe (String -> String) -- ^ modify the default value to present in-prompt -- e.g. empty string maps to "(none)", but only in the -- prompt. -> Bool -- ^ whether to allow an 'other' option -> m String promptList msg choices def modDef hasOther = do putStrLn $ msg ++ ":" -- Output nicely formatted list of options for_ prettyChoices $ \(i,c) -> do let star = if DefaultPrompt c == def then "*" else " " let output = concat $ if i < 10 then [" ", star, " ", show i, ") ", c] else [" ", star, show i, ") ", c] putStrLn output go where prettyChoices = let cs = if hasOther then choices ++ ["Other (specify)"] else choices in zip [1::Int .. length choices + 1] cs numChoices = length choices invalidChoice input = do let msg' = if null input then "Empty input is not a valid choice." else concat [ input , " is not a valid choice. Please choose a number from 1 to " , show (length prettyChoices) , "." ] putStrLn msg' breakOrContinue ("promptList: " ++ input) go go = do putStr $ mkDefPrompt "Your choice" $ maybe def (<$> def) modDef input <- getLine case def of DefaultPrompt d | null input -> return d _ -> case readMaybe input of Nothing -> invalidChoice input Just n | n > 0, n <= numChoices -> return $ choices !! (n-1) | n == numChoices + 1, hasOther -> promptStr "Please specify" OptionalPrompt | otherwise -> invalidChoice (show n) -- | Create a prompt with an optional default value. promptDefault :: Interactive m => (String -> Either String t) -- ^ parser -> (t -> String) -- ^ pretty-printer -> String -- ^ prompt message -> (DefaultPrompt t) -- ^ optional default value -> m t promptDefault parse pprint msg def = do putStr $ mkDefPrompt msg (pprint <$> def) hFlush System.IO.stdout input <- getLine case def of DefaultPrompt d | null input -> return d _ -> case parse input of Right t -> return t Left err -> do putStrLn $ "Couldn't parse " ++ input ++ ", please try again!" breakOrContinue ("promptDefault: " ++ err ++ " on input: " ++ input) (promptDefault parse pprint msg def) -- | Prompt utility for breaking out of an interactive loop -- in the pure case -- breakOrContinue :: Interactive m => String -> m a -> m a breakOrContinue msg act = break >>= \case True -> throwPrompt $ BreakException msg False -> act