Giter Club home page Giter Club logo

repline's Introduction

Repline

Build Status Hackage

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 decided to canonize the giant pile of hacks that I use to make Haskeline work.

See Documentation for more detailed usage.

Examples

Migration from 0.3.x

This release adds two parameters to the ReplOpts constructor and evalRepl function.

  • finaliser
  • multilineCommand

The finaliser function is a function run when the Repl monad is is exited.

-- | Decide whether to exit the REPL or not
data ExitDecision
  = Continue -- | Keep the REPL open
  | Exit     -- | Close the REPL and exit

For example:

final :: Repl ExitDecision
final = do
  liftIO $ putStrLn "Goodbye!"
  return Exit

The multilineCommand argument takes a command which invokes a multiline edit mode in which the user can paste/enter text across multiple lines terminating with a Ctrl-D / EOF. This can be used in conjunction with a customBanner function to indicate the entry mode.

customBanner :: MultiLine -> Repl String
customBanner SingleLine = pure ">>> "
customBanner MultiLine = pure "| "

See Multiline for a complete example.

Migration from 0.2.x

The underlying haskeline library that provides readline support had a breaking API change in 0.8.0.0 which removed the bespoke System.Console.Haskeline.MonadException module in favour of using the exceptions package. This is a much better design and I strongly encourage upgrading. To migrate simply add the following bounds to your Cabal file.

build-depends:
  repline   >= 0.3.0.0
  haskeline >= 0.8.0.0

You may also need to add the following to your stack.yaml file if using Stack.

resolver: lts-15.0
packages:
  - .
extra-deps:
  - haskeline-0.8.0.0
  - repline-0.3.0.0

Usage

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"]
  return $ filter (isPrefixOf n) names

-- 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!"

repl :: IO ()
repl = evalRepl (pure ">>> ") cmd options Nothing (Word completer) ini

Trying it out:

$ stack repl Simple.hs
Prelude> main

Welcome!
>>> <TAB>
kirk spock mccoy

>>> k<TAB>
kirk

>>> spam
"spam"

>>> :say Hello Haskell
 _______________
< Hello Haskell >
 ---------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Stateful Tab Completion

Quite often tab completion is dependent on the internal state of the Repl so we'd like to query state of the interpreter for tab completions based on actions performed themselves within the Repl, this is modeleted naturally as a monad transformer stack with StateT on top of HaskelineT.

type IState = Set.Set String
type Repl a = HaskelineT (StateT IState IO) a

-- Evaluation
cmd :: String -> Repl ()
cmd input = modify $ \s -> Set.insert input s

-- Completion
comp :: (Monad m, MonadState IState m) => WordCompleter m
comp n = do
  ns <- get
  return  $ filter (isPrefixOf n) (Set.toList ns)

-- Commands
help :: [String] -> Repl ()
help args = liftIO $ print $ "Help!" ++ show args

puts :: [String] -> Repl ()
puts args = modify $ \s -> Set.union s (Set.fromList args)

opts :: [(String, [String] -> Repl ())]
opts = [
    ("help", help) -- :help
  , ("puts", puts) -- :puts
  ]

ini :: Repl ()
ini = return ()

-- Tab completion inside of StateT
repl :: IO ()
repl = flip evalStateT Set.empty
     $ evalRepl (pure ">>> ") cmd opts Nothing (Word comp) ini

Prefix Completion

Just as GHCi will provide different tab completion for kind-level vs type-level symbols based on which prefix the user has entered, we can also set up a provide this as a first-level construct using a Prefix tab completer which takes care of the string matching behind the API.

type Repl a = HaskelineT IO a

-- Evaluation
cmd :: String -> Repl ()
cmd input = liftIO $ print input

-- Prefix tab completeter
defaultMatcher :: MonadIO m => [(String, CompletionFunc m)]
defaultMatcher = [
    (":file"    , fileCompleter)
  , (":holiday" , listCompleter ["christmas", "thanksgiving", "festivus"])
  ]

-- Default tab completer
byWord :: Monad m => WordCompleter m
byWord n = do
  let names = ["picard", "riker", "data", ":file", ":holiday"]
  return $ filter (isPrefixOf n) names

files :: [String] -> Repl ()
files args = liftIO $ do
  contents <- readFile (unwords args)
  putStrLn contents

holidays :: [String] -> Repl ()
holidays [] = liftIO $ putStrLn "Enter a holiday."
holidays xs = liftIO $ do
  putStrLn $ "Happy " ++ unwords xs ++ "!"

opts :: [(String, [String] -> Repl ())]
opts = [
    ("file", files)
  , ("holiday", holidays)
  ]

init :: Repl ()
init = return ()

repl :: IO ()
repl = evalRepl (pure ">>> ") cmd opts Nothing (Prefix (wordCompleter byWord) defaultMatcher) init

Trying it out:

$ stack repl examples/Prefix.hs
Prelude> main

>>> :file <TAB>
sample1.txt sample2.txt

>>> :file sample1.txt

>>> :holiday <TAB>
christmas thanksgiving festivus

License

Copyright (c) 2014-2020, Stephen Diehl Released under the MIT License

repline's People

Contributors

adamwespiser avatar basile-henry avatar clinty avatar faineance avatar fredefox avatar imalsogreg avatar juhp avatar luc-tielen avatar quasicomputational avatar rschmukler avatar sdiehl avatar sjakobi avatar sorki avatar typedrat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

repline's Issues

GHC 8.8 support

GHC 8.8 comes with a new and notably breaking version of haskeline that switches from using a bespoke MonadException to using the exceptions type class troika.

Exit from repl by Command

Can we provide a way to exit from repl by Command like :quit in GHCi?
I'm ready to implement this

Make it possible to execute an rc file in the initialiser

I am looking for a way to execute an rc file during initialisation phase.

Right now, all I can do is call "command" manually, but there is no
way to invoke the options parser manually. I'd have to reimplement options
parsing myself, which seems wrong.

The goal is to have something like .ghci, where :commands would also work as expected.

Basic Repline example, commands not working

I was trying to get Repline working in a project of mine and wasn't able to get command options to work. So I made a test stack project where I copy pasted in the exact Simple example code and still the command options did not work:

Welcome!
>>> 
kirk   spock  mccoy
>>> kirk 
"kirk "
>>> spam
"spam"
>>> :say Hello Haskell
":say Hello Haskell"
>>> :help arg1 arg2 arg3
":help arg1 arg2 arg3"

I'm using thelts-14.13 resolver. Here is a link to my sample project:
https://github.com/ssbothwell/replinetest

Incorrect Haddock Documentation

In line 89 of System.Console.Repline, not enough arguments have been applied to evalRepl:

main = evalRepl (pure ">>> ") cmd options (Just ':') (Word completer) ini

REPL does not continue when the `abort` function is used

Thank you for creating a great library.

In docs, the abort function is said to abort and continue the REPL loop.

It works normally in the Command, but REPL does not restart in Options.

Is it intended?

Example code:
import           Control.Monad.IO.Class (liftIO)
import           Data.List              (isPrefixOf)
import           System.Console.Repline

main :: IO ()
main = mainLoop

type Repl a = HaskelineT IO a

mainLoop :: IO ()
mainLoop = evalReplOpts replopts
  where
    replopts = ReplOpts
      { banner           = const (pure ">>> ")
      , command          = cmds
      , options          = opts
      , prefix           = Just ':'
      , multilineCommand = Nothing
      , tabComplete      = Word0 completer
      , initialiser      = ini
      , finaliser        = final
      }

final :: Repl ExitDecision
final = do
  liftIO $ putStrLn "GoodBye!\n"
  return Exit

ini :: Repl ()
ini = liftIO $ putStrLn "Hello, REPL"

cmds :: String -> Repl ()
cmds str = do
  liftIO $ print str
  abort

opts :: Options (HaskelineT IO)
opts =
  [ ("foo", foo)
  ]

completer :: Monad m => WordCompleter m
completer n = do
  let opts = [":foo"]
  return $ filter (isPrefixOf n) opts

foo :: String -> Repl ()
foo args = do
  liftIO $ print args
  abort

expected:

>>> :foo hello
"hello"
>>>

actual:

>>> :foo hello
"hello"
ghci> 

Irrefutable pattern when giving a whitespace option

I get the following error when I give one (or multiple) space as an option: ": "

>>> : 
src/System/Console/Repline.hs:207:15-37: Irrefutable pattern failed for pattern cmd : args

I can have a go at fixing it, I just thought I would report the issue first in case I don't get a fix for it.

Export MonadHaskeline

I have a small project I'd like to transition from using raw haskeline to this library. However, I make a lot of use of getInputChar directly to do things like ask the user a yes/no follow-up question after a command, and the lifted version defined in the MonadHaskeline class isn't exported. In fact, as far as I can tell, it isn't ever used in this library apart from defining other instances of MonadHaskeline. Is there a good reason not to use getInputChar directly with repline, or perhaps a more idiomatic solution to the problem of getting follow-up answers?

Support for haskeline 0.8

Since haskeline < 0.8 doesn't support GHC 8.10, that should also unblock compatibility with GHC 8.10.

autocomplete optparse-applicative parsers ?

I would like to rewrite one of my program from python to haskell. My program relies on https://github.com/python-cmd2/cmd2 and makes extensive use of cmd2 autocompletion based on python's argparse parsers.
I was wondering if there was a similar possibility (out of the box) with repline to accept an optparse-applicative parser and autocompletes that Parser ?
If not do you know any similar library that would do it ?

Blockchain FOREVER!!11

Stephen-man, all of your arguments against blockchain come from

a) envy
b) hence, bitterness

don't they?

Are they driven by the fact that you missed bitcoin for $0.1 in the past? As such, the only thing left for you is to critisize cryptocurrencies.


And don't recognize the crypto. Fight.

Add the ability to control the history file behavior

Sometimes I wish to store the history somewhere like ~/.local/, this will be more convenient for a shell-like application.

However, I've noticed that currently the history-related configurations seem to be hardcoded:

H.historyFile = Just ".history",
H.autoAddHistory = True

I think it would be nice if I could control these options while conserving all the niceties that come with this wrapper :)

Anyway, thanks a lot for making this project! 🙏

Add the ability to manually toggle multi-line mode

Currently, there's a way to toggle multiline mode based on matching a specific commmand:

>>> :multi
... continuation

However, it would be nice to provide a way to toggle this mode based on parsing information, to make something like the following work

>>> incomplete expression (
... so REPL enters multiline mode )

A possible API for this would be:

beginMultiline :: HaskelineT m ()
endMultiline :: HaskelineT m ()
 -- returns a list of lines since the enclosing beginMultiline
 -- alternatively, this could be managed by the caller
multilineContext :: HaskelineT m [String]

allowing something like

cmd :: Command (HaskelineT IO)
cmd s = do
  context <- multilineContext
  if Parser.isIncomplete (context ++ [s])
    then beginMultiline
    else do
      endMultiline
      -- process (context ++ [s]) as a multi-line chunk

Tighten bounds on haskeline on Hackage

Right now, repline-0.2.2.0 imports System.Console.Haskeline.MonadException, which only exists in haskeline < 0.8.0.0

Thanks!

vanessa@vanessa-desktop ~/git-builds/junk/repline-0.2.2.0 🌸 cabal build --constraint='haskeline >= 0.8.0.0'
Resolving dependencies...
Build profile: -w ghc-8.8.2 -O1
In order, the following will be built (use -v for more details):
 - repline-0.2.2.0 (lib) (configuration changed)
Configuring library for repline-0.2.2.0..
Preprocessing library for repline-0.2.2.0..
Building library for repline-0.2.2.0..
[1 of 1] Compiling System.Console.Repline ( src/System/Console/Repline.hs, /home/vanessa/git-builds/junk/repline-0.2.2.0/dist-newstyle/build/x86_64-linux/ghc-8.8.2/repline-0.2.2.0/build/System/Console/Repline.o ) [System.Console.Haskeline changed]

src/System/Console/Repline.hs:142:1: error:
    Could not load module ‘System.Console.Haskeline.MonadException’
    It is a member of the hidden package ‘haskeline-0.7.5.0’.
    Perhaps you need to add ‘haskeline’ to the build-depends in your .cabal file.
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
    |
142 | import System.Console.Haskeline.MonadException
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.