packages feed

repl-toolkit 1.0.1.0 → 1.1.0.0

raw patch · 8 files changed

+138/−34 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

System/REPL.hs view
@@ -1,5 +1,5 @@ -- |Contents:
---  
+--
 --  [/Ask/]
 --     Asking the user for input in a principled way.
 --     Reading, parsing errors, predicate checks are all handled.
@@ -8,6 +8,14 @@ --     The main module of the package. Functions for creating
 --     commands, which can receive and ask for arguments.
 --     Commands are composable and can be built into a REPL.
+--
+--
+--     With commands, making a full-fledged CLI is as simple as writing:
+--
+--  @
+--  main :: IO ()
+--  main = makeREPLSimple [cmd1, cmd2, cmd3]
+--  @
 --
 --  [/Config/]
 --     Read configuration files in various formats.
System/REPL/Ask.hs view
@@ -1,8 +1,3 @@-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE TupleSections #-}
-
 -- |Asking the user for input on the console.
 --
 --  The main type is 'Asker', which takes care of parsing
System/REPL/Command.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE OverloadedStrings #-}
-
 -- |Provides Commands for REPLs. Commands are there to provide high-level
 --  handling of user input and to offer functionality in a standard, composable
 --  way.
@@ -149,6 +147,7 @@ import Data.ListLike.IO (ListLikeIO(..))
 import Data.Maybe (fromJust, isJust, fromMaybe)
 import Data.Ord
+import Data.Typeable (cast)
 import qualified Data.Text as T
 import System.REPL.Ask
 import System.REPL.Types
@@ -184,9 +183,11 @@ runSingleCommand :: (MonadThrow m) => Command m T.Text a -> T.Text -> m a
 runSingleCommand c t = fromJust <$> runSingleCommandIf (c{commandTest = const True}) t
 
--- |Runs the command with the input text as parameter. If the input doesn't
---  pass the command test, @Nothing@ is returned.
+-- |Runs the command with the input text as parameter.
 --
+--  The first parameter (or the empty string, if no input was given)
+--  is passed to the command test. If it fails the test, 'Nothing' is returned.
+--
 --  Can throw:
 --
 --  * 'MalformedParamsError'
@@ -194,15 +195,15 @@ runSingleCommandIf :: MonadThrow m => Command m T.Text a -> T.Text -> m (Maybe a)
 runSingleCommandIf c t = do
    t' <- readArgs t
-   if L.null t' || not (commandTest c $ LU.head t') then return Nothing
+   let t'' = if L.null t' then "" else LU.head t'
+   if not (commandTest c t'') then return Nothing
    else do
       (res, output) <- runPartialCommand c t'
-      let act = length t'
+      let act = max 0 (length t' - 1)
           mx  = act - length output
       when (not . L.null $ output) (throwM $ TooManyParamsError mx act)
       return $ Just res
 
-
 -- |Takes a list @xs@ and executes the first command in a list whose
 --  'commandTest' matches the input.
 --
@@ -577,15 +578,13 @@ -- |Throws a 'TooFewParamsError' if the length of the list is smaller than the second argument.
 checkParamNum :: MonadThrow m => [a] -> Int -> m ()
 checkParamNum xs need = if have < need then throwM $ TooFewParamsError need have else return ()
-   where have = length xs - 1
+   where have = max 0 (length xs - 1)
 
 -- |Wrapper for 'ask'.
 askC :: (MonadIO m, MonadCatch m)
      => Asker m a0 a -> [T.Text] -> Int -> m a
 askC f xs i = ask f (xs L.!! i)
 
---askC False f xs j i = maybe (throwM $ TooFewParamsError j (length xs - 1)) (ask f . Just) (xs L.!! i)
-
 -- |Runs a REPL based on a set of commands.
 --  For a line of input, the commands are tried in following order:
 --
@@ -624,6 +623,7 @@ -- |A variant of 'makeREPL' with some default settings:
 --
 --  * The "exit" command is 'defExitCmd'.
+--  * Commands consistining only of whitespace are ignored.
 --  * The "unknown" command prints "Unknown command: <user input>".
 --  * The prompt is "> ".
 --  * The error handler is 'defErrorHandler'.
@@ -632,9 +632,12 @@                -> m ()
 makeREPLSimple regular = makeREPL regular defExitCmd unknownCmd PR.prompt defErrorHandler
    where
-      unknownCmd = makeCommandN "" (const True) "" False [] (repeat lineAsker)
-                                (\t _ -> liftIO $ PR.putStrLn $ "Unknown command: " ++ t)
+      unknownCmd = makeCommandN "" (const True) "" False [] (repeat lineAsker) f
 
+      f t ts = if T.all isSpace t && L.all (T.all isSpace) ts
+               then return ()
+               else liftIO $ PR.putStrLn $ "Unknown command: " ++ t ++ "."
+
 -- Example commands
 -------------------------------------------------------------------------------
 
@@ -676,13 +679,90 @@ 
 -- |A default error handler that catches 'SomeREPLError' and prints it to stdout.
 --
---  Since all the sub-types of 'SomeREPLError' just wrap a 'SomeException', we
---  use the 'Show'-instance of that inner exception.
+--  For the following errors, we print a user-friendly error message:
 --
+-- * 'GenericTypeError' (when wrapped in an 'AskerTypeError'),
+-- * 'GenericPredicateError' (when wrapped in an 'AskerPredicateError'),
+-- * 'PathRootDoesNotExist' (when wrapped in an 'AskerPredicateError'),
+-- * 'PathIsNotWritable' (when wrapped in an 'AskerPredicateError'),
+-- * 'GenericPredicateError' (when wrapped in an 'AskerPredicateError'),
+-- * 'AskerInputAbortedError',
+-- * 'MalformedParamsError',
+-- * 'TooManyParamsError',
+-- * 'TooFewParamsError',
+-- * 'NoConfigFileParseError'.
+--
+--  For every other subtype of 'SomeREPLError', we just print the Show-instance.
+--
 --  Useful in combination with 'makeREPL'.
 defErrorHandler :: MonadIO m
                 => [Handler m ()]
-defErrorHandler = [Handler h]
+defErrorHandler =
+   [Handler h_askerGenericTypeError,
+    Handler h_askerGenericPredicateError,
+    Handler h_askerPathRootDoesNotExist,
+    Handler h_askerPathIsNotWritable,
+    Handler h_tooMalformedParamsError,
+    Handler h_tooManyParamsError,
+    Handler h_tooFewParamsError,
+    Handler h_noConfigFileParseError,
+    Handler h]
    where
+      put :: String -> IO ()
+      put = putStrLn
+
       h :: MonadIO m => SomeREPLError -> m ()
       h = liftIO . print
+
+      h_askerGenericTypeError :: MonadIO m => AskerTypeError -> m ()
+      h_askerGenericTypeError (AskerTypeError e) = case fromException e of
+         Just (GenericTypeError t) -> liftIO . put . T.unpack $ t
+         Nothing -> liftIO . print $ e
+
+
+      h_askerGenericPredicateError :: MonadIO m => AskerPredicateError -> m ()
+      h_askerGenericPredicateError (AskerPredicateError e) = case fromException e of
+         Just (GenericPredicateError t) -> liftIO . put . T.unpack $ t
+         Nothing -> liftIO . print $ e
+
+      h_askerPathRootDoesNotExist :: MonadIO m => AskerPredicateError -> m ()
+      h_askerPathRootDoesNotExist (AskerPredicateError e) = case fromException e of
+         Just (PathRootDoesNotExist fp) -> liftIO $ put $
+                                           "The root of the path '" ++ fp ++
+                                           "' does not exist."
+         Nothing -> liftIO . print $ e
+
+      h_askerPathIsNotWritable :: MonadIO m => AskerPredicateError -> m ()
+      h_askerPathIsNotWritable (AskerPredicateError e) = case fromException e of
+         Just (PathIsNotWritable fp) -> liftIO $ put $
+                                           "The path '" ++ fp ++
+                                           "' is not writable."
+         Nothing -> liftIO . print $ e
+
+      h_askerInputAbortedError :: MonadIO m => AskerTypeError -> m ()
+      h_askerInputAbortedError (AskerTypeError e) =
+         liftIO $ put "Input aborted."
+
+      h_tooMalformedParamsError :: MonadIO m => MalformedParamsError -> m ()
+      h_tooMalformedParamsError (MalformedParamsError t) = liftIO . put $
+         "Error parsing parameters: " ++ T.unpack t
+
+      h_tooManyParamsError :: MonadIO m => TooManyParamsError -> m ()
+      h_tooManyParamsError (TooManyParamsError m x) = liftIO . put $
+         "Expected " ++ exp ++ " parameters, got " ++ got
+         where
+            exp = if m > 0
+                  then "at most " ++ show m
+                  else "no"
+
+            got = if x <= 0 then "none." else show x ++ "."
+
+      h_tooFewParamsError :: MonadIO m => TooFewParamsError -> m ()
+      h_tooFewParamsError (TooFewParamsError m x) = liftIO . put $
+         "Expected at least " ++ show m ++ " parameters, got " ++ got
+         where
+            got = if x <= 0 then "none." else show x ++ "."
+
+      h_noConfigFileParseError :: MonadIO m => NoConfigFileParseError -> m ()
+      h_noConfigFileParseError (NoConfigFileParseError t) = liftIO . put $
+         "Error parsing configuration file: " ++ T.unpack t
System/REPL/Config.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-
 -- |Contains logic for reading configuration files.
 module System.REPL.Config (
    readConfigFile,
System/REPL/Prompt.hs view
@@ -1,9 +1,5 @@-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
 -- |Little helper functions for getting and putting lines.
---  
+--
 --  This module re-exports part of "Data.ListLike.IO", which contains names that clash with Prelude.
 module System.REPL.Prompt (
    -- *String-generic versions of Prelude Functions
System/REPL/Types.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE ExistentialQuantification #-}
-
 -- |Types used by other modules in the package.
 --
 --  The module contains the following exception hierarchy:
changelog.txt view
@@ -1,3 +1,7 @@+1.1.0.0 Expanded defErrorHandler with user-friendly messages.
+        Fixed the handling of empty input.
+        makeREPLSimple now ignores input consisting only of whitespace.
+
 1.0.1.0 Fixed name clashes with base, added new exports, fixed typos.
 
 1.0.0.1 Updated aeson dependency's upper bound.
repl-toolkit.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                repl-toolkit
-version:             1.0.1.0
+version:             1.1.0.0
 synopsis:            Toolkit for quickly whipping up config files and command-line interfaces.
 description:         A simple toolkit for quickly whipping up REPLs, input validation and sets of commands included.
 homepage:            https://github.com/ombocomp/repl-toolkit
@@ -27,7 +27,7 @@                        System.REPL.Prompt,
                        System.REPL.State,
                        System.REPL.Types
-  other-extensions:    OverloadedStrings,
+  default-extensions:  OverloadedStrings,
                        DeriveDataTypeable,
                        FlexibleContexts,
                        LambdaCase,
@@ -52,3 +52,30 @@                        text >=1.1
   default-language:    Haskell2010
 
+-- executable repl-toolkit-test
+--  main-is:             Test.hs
+--  build-depends:       base >=4.8 && <5,
+--                       aeson >=0.8.0.2 && <2,
+--                       bytestring >= 0.10 && <0.20,
+--                       data-default >= 0.5.3,
+--                       directory >= 1.2.1 && <2,
+--                       exceptions >=0.8 && <1,
+--                       filepath >= 1.3 && <2,
+--                       functor-monadic >=0.1,
+--                       ListLike >=4.1,
+--                       listsafe >= 0.1,
+--                       monad-loops >= 0.3 && <0.6,
+--                       mtl >= 2.2 && <3,
+--                       parsec >=3.1 && <4,
+--                       semigroupoids >= 4 && <6,
+--                       transformers >= 0.3 && <0.7,
+--                       text >=1.1,
+--                       repl-toolkit
+--  default-extensions:  OverloadedStrings,
+--                       DeriveDataTypeable,
+--                       FlexibleContexts,
+--                       LambdaCase,
+--                       ScopedTypeVariables,
+--                       ExistentialQuantification,
+--                       TupleSections
+--  default-language:    Haskell2010