packages feed

repline (empty) → 0.1.0.0

raw patch · 5 files changed

+416/−0 lines, 5 filesdep +basedep +containersdep +haskelinesetup-changed

Dependencies added: base, containers, haskeline, mtl

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2014 Stephen Diehl++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,88 @@+Repline+-------++Slightly higher level wrapper for creating GHCi-like REPL monads that are composable with normal MTL+transformers. Mostly exists because I got tired of implementing the same interface for simple shells over and+over, and because vanilla Haskeline has a kind of quirky API.++[![Build Status](https://travis-ci.org/sdiehl/repline.svg)](https://travis-ci.org/sdiehl/repline)++Usage+-----++```haskell+type Repl a = HaskelineT IO a++-- Evaluation : handle each line user inputs+cmd :: String -> Repl ()+cmd input = liftIO $ print input+++-- Tab Completion: return a completion for partial words entered+completer :: Monad m => WordCompleter m+completer n = do+  let names = ["kirk", "spock", "mccoy"]+  let matches = filter (isPrefixOf n) names+  return $ map simpleCompletion matches++-- Commands+help :: [String] -> Repl ()+help args = liftIO $ print $ "Help: " ++ show args++say :: [String] -> Repl ()+say args = do+  _ <- liftIO $ system $ "cowsay" ++ " " ++ (unwords args)+  return ()+++options :: [(String, [String] -> Repl ())]+options = [+    ("help", help)  -- :help+  , ("say", say)    -- :say+  ]+++ini :: Repl ()+ini = liftIO $ putStrLn "Welcome!"++main :: IO ()+main = evalRepl ">>> " cmd options (Word completer) ini+```++Trying it out:++```haskell+$ runhaskell Main.hs+Welcome!+>>> <TAB>+kirk spock mccoy++>>> k<TAB>+kirk++>>> spam+"spam"++>>> :say Hello Haskell+ _______________ +< Hello Haskell >+ --------------- +        \   ^__^+         \  (oo)\_______+            (__)\       )\/\+                ||----w |+                ||     ||+```++Installation+------------++```bash+$ cabal install repline+```++License+-------++Copyright (c) 2014, Stephen Diehl+Released under the MIT License
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ repline.cabal view
@@ -0,0 +1,37 @@+name:                repline+version:             0.1.0.0+license:             MIT+license-file:        LICENSE+author:              Stephen Diehl+maintainer:          stephen.m.diehl@gmail.com+copyright:           2014 Stephen Diehl+category:            User Interfaces+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10+tested-with:         GHC == 7.6.1, GHC == 7.6.3, GHC == 7.8.3+Bug-Reports:         https://github.com/sdiehl/repline/issues++Description:+  Haskeline wrapper for GHCi-like REPL interfaces. Composable with normal mtl transformers.+Source-Repository head+    Type: git+    Location: git@github.com:sdiehl/repline.git++library+  hs-source-dirs:      src+  exposed-modules:     System.Console.Repline+  -- other-modules:       +  build-depends:+    base       >= 4.6 && <4.7,+    containers >= 0.5 && <0.6,+    mtl        >= 2.2 && <2.3,+    haskeline  >= 0.7 && <0.8+  other-extensions:+    FlexibleInstances,+    FlexibleContexts,+    TypeSynonymInstances,+    UndecidableInstances,+    MultiParamTypeClasses,+    GeneralizedNewtypeDeriving+  default-language:    Haskell2010
+ src/System/Console/Repline.hs view
@@ -0,0 +1,269 @@+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |++Repline exposes an additional monad transformer on top of Haskeline called 'HaskelineT'. It simplifies several+aspects of composing Haskeline with State and Exception monads in modern versions of mtl.++> type Repl a = HaskelineT IO a++The evaluator 'evalRepl' evaluates a 'HaskelineT' monad transformer by constructing a shell with several+custom functions and evaluating it inside of IO:++  * Commands: Handled on ordinary input.++  * Completions: Handled when tab key is pressed.++  * Options: Handled when a command prefixed by a colon is entered.++  * Banner: Text Displayed at initialization.++  * Initializer: Run at initialization.++A simple evaluation function might simply echo the output back to the screen.++> -- Evaluation : handle each line user inputs+> cmd :: String -> Repl ()+> cmd input = liftIO $ print input++Several tab completion options are available, the most common is the 'WordCompleter' which completes on single+words separated by spaces from a list of matches. The internal logic can be whatever is required and can also+access a StateT instance to query application state.++> -- Tab Completion: return a completion for partial words entered+> completer :: Monad m => WordCompleter m+> completer n = do+>   let names = ["kirk", "spock", "mccoy"]+>   let matches = filter (isPrefixOf n) names+>   return $ map simpleCompletion matches++Input which is prefixed by a colon (commands like \":type\" and \":help\") queries an association list of+functions which map to custom logic. The function takes a space-separated list of augments in it's first+argument. If the entire line is desired then the 'unwords' function can be used to concatenate.++> -- Commands+> help :: [String] -> Repl ()+> help args = liftIO $ print $ "Help: " ++ show args+>+> say :: [String] -> Repl ()+> say args = do+>   _ <- liftIO $ system $ "cowsay" ++ " " ++ (unwords args)+>   return ()++Now we need only map these functions to their commands.++> options :: [(String, [String] -> Repl ())]+> options = [+>     ("help", help)  -- :help+>   , ("say", say)    -- :say+>   ]++The banner function is simply an IO action that is called at the start of the shell.++> ini :: Repl ()+> ini = liftIO $ putStrLn "Welcome!"++Putting it all together we have a little shell.++> main :: IO ()+> main = evalRepl ">>> " cmd options (Word completer) ini++Putting this in a file we can test out our cow-trek shell.++> $ runhaskell Main.hs+> Welcome!+> >>> <TAB>+> kirk spock mccoy+>+> >>> k<TAB>+> kirk+>+> >>> spam+> "spam"+>+> >>> :say Hello Haskell+>  _______________+> < Hello Haskell >+>  ---------------+>         \   ^__^+>          \  (oo)\_______+>             (__)\       )\/\+>                 ||----w |+>                 ||     ||++See <https://github.com/sdiehl/repline> for more examples.++-}++module System.Console.Repline (+  HaskelineT,+  runHaskelineT,++  Cmd,+  Options,+  WordCompleter,+  LineCompleter,+  CompleterStyle(..),+  Command,++  --ReplSettings(ReplSettings),+  --emptySettings,++  evalRepl,+  --evalReplWith,++  listFiles, -- re-export+  trimComplete,+) where++import System.Console.Haskeline.Completion+import System.Console.Haskeline.MonadException+import qualified System.Console.Haskeline as H++import Data.List (isPrefixOf)+import Control.Applicative+import Control.Monad.State.Strict++-------------------------------------------------------------------------------+-- Haskeline Transformer+-------------------------------------------------------------------------------++newtype HaskelineT (m :: * -> *) a = HaskelineT { unHaskeline :: H.InputT m a }+ deriving (Monad, Functor, Applicative, MonadIO, MonadException, MonadTrans, MonadHaskeline)++runHaskelineT :: MonadException m => H.Settings m -> HaskelineT m a -> m a+runHaskelineT s m = H.runInputT s (H.withInterrupt (unHaskeline m))++class MonadException m => MonadHaskeline m where+  getInputLine :: String -> m (Maybe String)+  getInputChar :: String -> m (Maybe Char)+  outputStr    :: String -> m ()+  outputStrLn  :: String -> m ()++instance MonadException m => MonadHaskeline (H.InputT m) where+  getInputLine = H.getInputLine+  getInputChar = H.getInputChar+  outputStr    = H.outputStr+  outputStrLn  = H.outputStrLn++instance MonadState s m => MonadState s (HaskelineT m) where+  get = lift get+  put = lift . put++instance (MonadHaskeline m) => MonadHaskeline (StateT s m) where+  getInputLine = lift . getInputLine+  getInputChar = lift . getInputChar+  outputStr    = lift . outputStr+  outputStrLn  = lift . outputStrLn++-------------------------------------------------------------------------------+-- Repl+-------------------------------------------------------------------------------++type Cmd m = [String] -> m ()+type Options m = [(String, Cmd m)]+type Command m = String -> m ()++type WordCompleter m = (String -> m [Completion])+type LineCompleter m = (String -> String -> m [Completion])++data ReplSettings m = ReplSettings+  { _cmd       :: Command (HaskelineT m) -- ^ Command function+  , _opts      :: Options (HaskelineT m) -- ^ Options map+  , _compstyle :: CompleterStyle m       -- ^ Internal completer function+  , _init      :: m ()                   -- ^ Initializer function.+  , _banner    :: String                 -- ^ Banner string+  }++emptySettings :: MonadHaskeline m => ReplSettings m+emptySettings = ReplSettings (const $ return ()) [] File (return ()) ""++-- | Wrap a HasklineT action so that if an interrupt is thrown the shell continues as normal.+tryAction :: MonadException m => HaskelineT m a -> HaskelineT m a+tryAction (HaskelineT f) = HaskelineT (H.withInterrupt loop)+    where loop = handle (\H.Interrupt -> loop) f++-- | Completion loop.+replLoop :: MonadException m+         => String+         -> Command (HaskelineT m)+         -> Options (HaskelineT m)+         -> HaskelineT m ()+replLoop banner cmdM opts = loop+  where+    loop = do+      minput <- H.handleInterrupt (return (Just "")) $ getInputLine banner+      case minput of+        Nothing -> outputStrLn "Goodbye."++        Just "" -> loop+        Just ":" -> loop++        Just (':' : cmds) -> do+          let (cmd:args) = words cmds+          optMatcher cmd opts args+          loop++        Just input -> do+          H.handleInterrupt (liftIO $ putStrLn "Ctrl-C") $ cmdM input+          loop++-- | Match the options.+optMatcher :: MonadHaskeline m => String -> Options m -> [String] -> m ()+optMatcher s [] _ = outputStrLn $ "No such command :" ++ s+optMatcher s ((x, m):xs) args+  | s `isPrefixOf` x = m args+  | otherwise = optMatcher s xs args++-- | Evaluate the REPL logic into a MonadException context.+evalRepl :: MonadException m             -- Terminal monad ( often IO ).+         => String                       -- ^ Banner+         -> Command (HaskelineT m)       -- ^ Command function+         -> Options (HaskelineT m)       -- ^ Options list and commands+         -> CompleterStyle m             -- ^ Tab completion function+         -> HaskelineT m a               -- ^ Initializer+         -> m ()+evalRepl banner cmd opts comp initz = runHaskelineT _readline (initz >> monad)+  where+    monad = replLoop banner cmd opts+    _readline = H.Settings+      { H.complete       = mkCompleter comp+      , H.historyFile    = Just ".history"+      , H.autoAddHistory = True+      }++-- | Evaluate the REPL logic with the settings record.+evalReplWith :: MonadException m => ReplSettings m -> m ()+evalReplWith settings = runHaskelineT _readline (tryAction monad)+  where+    monad = replLoop (_banner settings) (_cmd settings) (_opts settings)+    _readline = H.Settings+      { H.complete       = mkCompleter (_compstyle settings)+      , H.historyFile    = Just ".history"+      , H.autoAddHistory = True+      }++-------------------------------------------------------------------------------+-- Completions+-------------------------------------------------------------------------------++data CompleterStyle m+  = Word (WordCompleter m)       -- ^ Completion function takes single word.+  | Cursor (LineCompleter m)     -- ^ Completion function takes tuple of full line.+  | File                         -- ^ Completion function completes files in CWD.+  | Mixed (CompletionFunc m)     -- ^ Function manually manipulates readline result.++mkCompleter :: MonadIO m => CompleterStyle m -> CompletionFunc m+mkCompleter (Word f)   = completeWord (Just '\\') " \t()[]" f+mkCompleter (Cursor f) = completeWordWithPrev (Just '\\') " \t()[]" f+mkCompleter File       = completeFilename+mkCompleter (Mixed f)  = f++trimComplete :: String -> Completion -> Completion+trimComplete prefix (Completion a b c) = Completion (drop (length prefix) a) b c