packages feed

wizards (empty) → 0.1

raw patch · 7 files changed

+433/−0 lines, 7 filesdep +MonadPromptdep +basedep +haskelinesetup-changed

Dependencies added: MonadPrompt, base, haskeline, mtl, transformers

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2012, Liam O'Connor-Davis++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Liam O'Connor-Davis nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/Console/Wizard.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE GeneralizedNewtypeDeriving, Trustworthy, MultiParamTypeClasses, FlexibleInstances #-}+module System.Console.Wizard +    ( -- * Wizards+      -- $intro+      Wizard (..)   +    , PromptString (..)+      -- * Primitives+      -- $primitives+    , line+    , linePrewritten+    , password+    , character +    , output+    , outputLn+      -- * 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.Reader+import Control.Monad.Prompt+import Data.Maybe++-- | A @Wizard a@ is a conversation with the user that will result in a data type @a@, or may fail.+--   A 'Wizard' is made up of one or more \"primitives\" (see below), composed using the 'Applicative',+--  'Monad' and 'Alternative' instances. The 'Alternative' instance is, as you might expect, a maybe-style cascade. +--   If the first wizard fails, the next one is tried.+--  +--  The 'Wizard' constructor is exported here for use when developing backends,  but it is better for end-users to +--  simply pretend that 'Wizard' is an opaque data type. Don't depend on this unless you have no other choice.+-- +--  'Wizard's are, internally, just a maybe transformer over a prompt monad for each primitive action.+newtype Wizard backend a = Wizard (MaybeT (RecPrompt (WizardAction backend)) a)+      deriving (Monad, Functor, Applicative, Alternative, MonadPlus)+instance MonadPrompt (WizardAction s (RecPrompt (WizardAction s))) (Wizard s) where    +    prompt = Wizard . lift . prompt++++-- $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.++-- | Read one line of input from the user.+line :: PromptString -> Wizard b String+line str = prompt $ Line str++-- | Read one line of input, with some default text already present, before and/or after the editing cursor.+--   Backends are not required to display this default text, or position the cursor anywhere, it is merely+--   a suggestion.+linePrewritten :: PromptString+               -> String  -- ^ Text to the left of the cursor+               -> String  -- ^ Text to the right of the cursor+               -> Wizard b String+linePrewritten p s1 s2 = prompt $ LinePreset p s1 s2++-- | Read one line of password input, with an optional mask character.+--   The exact masking behavior of the password may vary from backend to backend. The masking character+--   does not have to be honoured.+password :: PromptString+         -> Maybe Char -- ^ Mask character, if any.+         -> Wizard b String+password str m = prompt $ Password str m +                 +-- | Read a single character only from input.+character :: PromptString -> Wizard b Char+character = prompt . Character ++-- | Output a string, if the backend used supports output.+output :: String -> Wizard b ()+output = prompt . Output++-- | Output a string followed by a newline, if the backend used supports such output.+outputLn :: String -> Wizard b ()+outputLn = prompt . OutputLn++-- $modifiers+-- /Modifiers/ change the behaviour of existing wizards.++-- | Retry produces a wizard that will retry the entire conversation again if it fails.+-- Conceptually, it could thought of as @retry x = x \<|\> retry x@, however it also prints+-- a user-friendly error message in the event of failure.+retry :: Wizard b a -> Wizard b a+retry = retryMsg "Invalid input. Please try again."++-- | Same as 'retry', except the error message can be specified.+retryMsg :: 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 :: 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 :: (a -> Maybe c) -> Wizard b a -> Wizard b c+parser f a = a >>= liftMaybe . f++-- | @validator p w@ causes a wizard to fail if the output value does not satisfy the predicate @p@.+validator :: (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 :: 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) => (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) => Wizard b String -> Wizard b a+parseRead = parser (readP)++-- | Translate a maybe value into wizard success/failure.	+liftMaybe :: 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
+ System/Console/Wizard/BasicIO.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, GADTs, KindSignatures #-}+module System.Console.Wizard.BasicIO+        ( BasicIO+        , runBasicIO+        ) where+import System.Console.Wizard+import System.Console.Wizard.Internal +import Control.Monad.Trans+import Control.Monad.Prompt+import Control.Monad.Trans.Maybe+import Control.Applicative((<$>))++import Data.Typeable++-- | A very simple standard IO backend for @wizards@, supporting input and output.+--   Default text and password masks are ignored.+--   A more full-featured back-end is based on Haskeline.+--   Arbitrary IO actions can be performed in wizards via a 'MonadIO' instance.+data BasicIO (m :: * -> *) r = ArbitraryIO (IO r)++-- | Runs a Wizard action in the BasicIO backend.+runBasicIO :: Wizard BasicIO a -> IO (Maybe a)+runBasicIO (Wizard (MaybeT c)) = runRecPromptM f c+  where f :: WizardAction BasicIO (RecPrompt (WizardAction BasicIO) ) c -> IO c+        f (Line s) = getLine+        f (Character s) = getChar+        f (Password s m) = getLine+        f (LinePreset s f b) = getLine+        f (Output s) = putStr s+        f (OutputLn s) = putStrLn s+        f (Backend (ArbitraryIO a)) = a++instance MonadIO (Wizard BasicIO) where+    liftIO = prompt . Backend . ArbitraryIO
+ System/Console/Wizard/Haskeline.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, GADTs #-}+module System.Console.Wizard.Haskeline +        ( Haskeline+        , UnexpectedEOF (..)+        , runHaskeline+        , withSettings+        ) where+import System.Console.Wizard+import System.Console.Wizard.Internal+import System.Console.Haskeline    +import Control.Monad.Trans+import Control.Monad.Prompt+import Control.Monad.Trans.Maybe+import Control.Exception+import Data.Typeable++-- | A Haskeline backend for @wizards@, supporting input, output, default text, and password input.+--   In addition, Haskeline settings can be modified for a single wizard, and arbitrary IO can be+--   performed using the 'MonadIO' instance.+data Haskeline m r = SetSettings (Settings IO) (m r)+                   | ArbitraryIO (IO r)++-- | The Haskeline back-end will throw this exception if EOF is encountered+--   when it is not expected. Specifically, when actions such as 'getInputLine' return 'Nothing'.+data UnexpectedEOF = UnexpectedEOF deriving (Show, Typeable)+instance Exception UnexpectedEOF++-- | Runs a Wizard action in the Haskeline backend.+runHaskeline :: Wizard Haskeline a -> InputT IO (Maybe a)+runHaskeline (Wizard c) = runRecPromptM f $ runMaybeT c+  where f :: WizardAction Haskeline (RecPrompt (WizardAction Haskeline) )  a -> InputT IO a+        f (Line s) = getInputLine s >>= maybeToException UnexpectedEOF+        f (Character s) = getInputChar s >>= maybeToException UnexpectedEOF+        f (Password s m) = getPassword m s >>= maybeToException UnexpectedEOF+        f (LinePreset s f b) = getInputLineWithInitial s (f,b) >>= maybeToException UnexpectedEOF +        f (Output s) = outputStr s+        f (OutputLn s) = outputStrLn s+        f (Backend (SetSettings s v)) = liftIO $ runInputT s (runRecPromptM f v)+        f (Backend (ArbitraryIO a)) = liftIO $ a++-- | Modifies a wizard so that it will run with different Haskeline 'Settings' to the top level input monad.+withSettings :: Settings IO -> Wizard Haskeline a -> Wizard Haskeline a+withSettings sets (Wizard (MaybeT v)) = Wizard $ MaybeT $ prompt $ Backend $ SetSettings sets $ v++instance MonadIO (Wizard Haskeline) where+    liftIO = prompt . Backend . ArbitraryIO++maybeToException :: (Monad m, Exception e) => e -> Maybe a -> m a+maybeToException e (Just v) = return v+maybeToException e (Nothing) = throw e
+ System/Console/Wizard/Internal.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE GADTs, KindSignatures #-}+module System.Console.Wizard.Internal ( WizardAction (..)+                                      , PromptString (..)+                                      -- $backend+                                      ) where++type PromptString = String ++-- | Internally, a 'Wizard' is essentially a prompt monad with a 'WizardAction'. A constructor exists for each primitive action, as well+--   as a special \"escape hatch\" constructor ('Backend') used for writing backend-specific primitives and modifiers.+--   Each back-end has a corresponding data type, used as a type parameter for 'Wizard'. This data type is usually opaque, but internally+--   specifies additional primitive actions that are specific to the back-end.+--   'WizardAction' is parameterised by this data type (for use in the 'Backend' constructor), the prompt monad itself (so that modifiers+--   can be made as well as primitives) and the return type of the action.+data WizardAction :: ((* -> *) -> * -> *) -> (* -> *) -> * -> * where+    Line       :: PromptString -> WizardAction b m String+    LinePreset :: PromptString -> String -> String -> WizardAction b m String+    Password   :: PromptString -> Maybe Char -> WizardAction b m String+    Character  :: PromptString -> WizardAction b m Char+    Output     :: String       -> WizardAction b m ()+    OutputLn   :: String       -> WizardAction b m ()    +    Backend    :: b m a        -> WizardAction b m a+-- $backend+--   A short tutorial on writing backends.+--+--   Backends consist of two main components:+--   +--      1. A back-end data type (the type parameter to 'Wizard'), which includes constructors+--         for any primitive actions or modifiers that are specific to the back-end.+--+--      2. An interpreter function, of type @Wizard DataType a -> B (Maybe a)@ for some type @B@ (depending on the backend).+--         Typically this function will provide semantics for each 'WizardAction' using 'runRecPromptM' or similar.+--   +--   The 'Backend' constructor can be used to add back-end specific primitives and modifiers.+-- +--   As an example, suppose I am writing a back-end to @IO@, like "System.Console.Wizard.BasicIO".+--   One additional primitive action that I might want to include is the ability to run arbitrary @IO@ actions while a wizard is running.+--   So, my backend data type will be:+--+-- @+-- data MyBackend (m :: * -> *) r = ArbitraryIO (IO r) -- kind signature to avoid defaulting to *+-- @+-- +--   And my interpreter function will be:+--+-- @+--   runWizardMyBackend :: Wizard MyBackend a -> IO a+--   runWizardMyBackend (Wizard (MaybeT c)) = runRecPromptM f c+--         where f :: WizardAction MyBackend (RecPrompt (WizardAction MyBackend)) a -> IO a  +--               f (Output s) = putStr s+--               f (...     ) = ...+--               f (Backend (ArbitraryIO io)) = io+-- @+-- +-- And then the action can be easily defined:+--+-- @+--   runIO :: IO a -> Wizard MyBackend a+--   runIO = prompt . Backend . ArbitraryIO +-- @+--+-- I might also want to include a /modifier/, which say, colours any output text green. Assuming I have a function+-- @+--    withGreenText :: IO a -> IO a+-- @+-- which causes any output produced by the input action to be coloured green, we can use the 'Backend' constructor to transform+-- this into a wizard modifier.+-- +-- @+--data MyBackend m r = ArbitraryIO (IO r)+--                   | GreenText (m r)+--+--runWizardMyBackend :: Wizard MyBackend+--runWizardMyBackend (Wizard (MaybeT c)) = runRecPromptM f c+--      where f :: WizardAction MyBackend (RecPrompt (WizardAction MyBackend)) a -> IO a  +--            f (Output s) = putStr s+--            f (...     ) = ...+--            f (Backend (ArbitraryIO io)) = io+--            f (Backend (GreenText a)) = withGreenText $ runRecPromptM f a+--+--greenText :: Wizard MyBackend a -> Wizard MyBackend a+--greenText (Wizard (MaybeT a)) = prompt (Backend (GreenText a))+-- @+--+-- 
+ wizards.cabal view
@@ -0,0 +1,91 @@+-- wizards.cabal auto-generated by cabal init. For additional options,+-- see+-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.+-- The name of the package.+Name:                wizards++-- The package version. See the Haskell package versioning policy+-- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for+-- standards guiding when and how versions should be incremented.+Version:             0.1++-- A short (one-line) description of the package.+Synopsis:            High level, generic library for interrogative user interfaces++-- A longer description of the package.+-- Description:         ++-- The license under which the package is released.+License:             BSD3++-- The file containing the license text.+License-file:        LICENSE++-- The package author(s).+Author:              Liam O'Connor-Davis++-- An email address to which users can send suggestions, bug reports,+-- and patches.+Maintainer:          liamoc@cse.unsw.edu.au+++Description: @wizards@ is a package designed for the quick and painless development of /interrogative/ programs, which+  revolve around a \"dialogue\" with the user, who is asked a series of questions in a sequence much like an +  installation wizard.+  .+  Everything from interactive system scripts, to installation wizards, to full-blown shells can be implemented with+  the support of @wizards@.+  .+  It is developed transparently on top of a Prompt monad, which separates out the semantics of the program from any+  particular interface. A variety of backends exist, including "System.Console.Wizard.Haskeline" and +  "System.Console.Wizard.BasicIO". It is also possible to write your own backends. While both built-in backends +  operate on a console, there is no reason why @wizards@ cannot also be used for making GUI wizard interfaces.+  .+  .+  See the github page for examples on usage:+  .+  <http://www.github.com/liamoc/wizards>+  .+  For creating backends, the module "System.Console.Wizard.Internal" has a brief tutorial. ++-- A copyright notice.+-- Copyright:           ++Category:            User Interfaces++Build-type:          Simple++-- Extra files to be distributed with the package, such as examples or+-- a README.+-- Extra-source-files:  ++-- Constraint on the version of Cabal needed to build this package.+Cabal-version:       >=1.6++source-repository head+   type:     git   +   location: git://github.com/liamoc/wizards.git+   +source-repository this+   type:     git   +   location: git://github.com/liamoc/wizards.git+   tag:      0.1+++Library+  -- Modules exported by the library.+  Exposed-modules: System.Console.Wizard+                   System.Console.Wizard.Internal+                   System.Console.Wizard.Haskeline+                   System.Console.Wizard.BasicIO+                   +  +  -- Packages needed in order to build this package.+  Build-depends: base == 4.*, haskeline == 0.6.*, mtl == 2.0.*, transformers == 0.2.*, MonadPrompt == 1.0.*+  +  -- Modules not exported by this package.+  -- Other-modules:       +  +  -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.+  -- Build-tools:         +