wizards-1.0: System/Console/Wizard.hs
{-# LANGUAGE FlexibleContexts, TypeOperators, Trustworthy #-}
-- Necessary for MonadIO instance.
{-# LANGUAGE UndecidableInstances #-}
module System.Console.Wizard
( -- * Wizards
-- $intro
Wizard (..)
, PromptString (..)
, run
, (:<:)
, (:+:)
-- * Primitives
-- $primitives
, Line
, line
, LinePrewritten
, linePrewritten
, Password
, password
, Character
, character
, Output
, output
, OutputLn
, outputLn
, ArbitraryIO
-- * Modifiers
-- $modifiers
, retry
, retryMsg
, defaultTo
, parser
, validator
-- * Convenience
, nonEmpty
, inRange
, parseRead
-- * Utility
, liftMaybe
, ensure
, readP
) where
import System.Console.Wizard.Internal
import Control.Applicative
import Control.Monad.Trans.Maybe
import Control.Monad.Trans
import Control.Monad.Free
import Control.Monad.Reader
import Data.Maybe
import Data.Monoid
-- $primitives
-- /Primitives/ are the basic building blocks for @wizards@. Use these functions to produce wizards that
-- ask for input from the user, or output information.
-- | Output a string. Does not fail.
output :: (Output :<: b) => String -> Wizard b ()
output s = Wizard $ lift $ inject (Output s (Pure ()))
-- | Output a string followed by a newline. Does not fail.
outputLn :: (OutputLn :<: b) => String -> Wizard b ()
outputLn s = Wizard $ lift $ inject (OutputLn s (Pure ()))
-- | Read one line of input from the user. Cannot fail (but may throw exceptions, depending on the backend).
line :: (Line :<: b) => PromptString -> Wizard b String
line s = Wizard $ lift $ inject (Line s Pure)
-- | Read a single character only from input. Cannot fail (but may throw exceptions, depending on the backend).
character :: (Character :<: b)
=> PromptString
-> Wizard b Char
character p = Wizard $ lift $ inject (Character p Pure)
instance (ArbitraryIO :<: b) => MonadIO (Wizard b) where
liftIO v = Wizard $ lift $ inject (ArbitraryIO v Pure)
-- | Read one line of input, with some default text already present, before and/or after the editing cursor.
--- Cannot fail (but may throw exceptions, depending on the backend).
linePrewritten :: (LinePrewritten :<: b)
=> PromptString
-> String -- ^ Text to the left of the cursor
-> String -- ^ Text to the right of the cursor
-> Wizard b String
linePrewritten p s1 s2 = Wizard $ lift $ inject (LinePrewritten p s1 s2 Pure)
-- | Read one line of password input, with an optional mask character.
--- Cannot fail (but may throw exceptions, depending on the backend).
password :: (Password :<: b)
=> PromptString
-> Maybe Char -- ^ Mask character, if any.
-> Wizard b String
password p mc = Wizard $ lift $ inject (Password p mc Pure)
-- $modifiers
-- /Modifiers/ change the behaviour of existing wizards.
-- | Retry produces a wizard that will retry the entire conversation again if it fails.
-- It is simply @retry x = x \<|\> retry x@.
retry :: Functor b => Wizard b a -> Wizard b a
retry x = x <|> retry x
-- | Same as 'retry', except an error message can be specified.
retryMsg :: (OutputLn :<: b) => String -> Wizard b a -> Wizard b a
retryMsg msg x = x <|> (outputLn msg >> retryMsg msg x)
-- | @x \`defaultTo\` y@ will return @y@ if @x@ fails, e.g @parseRead line \`defaultTo\` 0@.
defaultTo :: Functor b => Wizard b a -> a -> Wizard b a
defaultTo wz d = wz <|> pure d
-- | Like 'fmap', except the function may be partial ('Nothing' causes the wizard to fail).
parser :: Functor b => (a -> Maybe c) -> Wizard b a -> Wizard b c
parser f a = a >>= liftMaybe . f
-- | @validator p@ causes a wizard to fail if the output value does not satisfy the predicate @p@.
validator :: Functor b => (a -> Bool) -> Wizard b a -> Wizard b a
validator = parser . ensure
-- | Simply @validator (not . null)@, makes a wizard fail if it gets an empty string.
nonEmpty :: Functor b => Wizard b [a] -> Wizard b [a]
nonEmpty = validator (not . null)
-- | Makes a wizard fail if it gets an ordered quantity outside of the given range.
inRange :: (Ord a, Functor b) => (a,a) -> Wizard b a -> Wizard b a
inRange (b,t) = validator (\x -> b <= x && x <= t)
-- | Simply @parser readP@. Attaches a simple @read@ parser to a 'Wizard'.
parseRead :: (Read a, Functor b) => Wizard b String -> Wizard b a
parseRead = parser (readP)
-- | Translate a maybe value into wizard success/failure.
liftMaybe :: Functor b => Maybe a -> Wizard b a
liftMaybe (Just v) = pure v
liftMaybe (Nothing) = mzero
-- | Ensures that a maybe value satisfies a given predicate.
ensure :: (a -> Bool) -> a -> Maybe a
ensure p v | p v = Just v
| otherwise = Nothing
-- | A read-based parser for the 'parser' modifier.
readP :: Read a => String -> Maybe a
readP = fmap fst . listToMaybe . reads