Skip to content

Commit

Permalink
Extract readline specific code to a separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
nineinchnick committed Jun 12, 2023
1 parent c725d05 commit 4d2ae4b
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 118 deletions.
133 changes: 133 additions & 0 deletions rline/readline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//go:build !new_readline

package rline

import (
"io"
"os"

"github.com/gohxs/readline"
"github.com/mattn/go-isatty"
)

var (
// ErrInterrupt is the interrupt error.
ErrInterrupt = readline.ErrInterrupt
)

// baseRline should be embedded in a struct implementing the IO interface,
// as it keeps implementation specific state.
type baseRline struct {
instance *readline.Instance
}

// SetOutput sets the output format func.
func (l *rline) SetOutput(f func(string) string) {
l.instance.Config.Output = f
}

// New readline input/output handler.
func New(forceNonInteractive bool, out, histfile string) (IO, error) {
// determine if interactive
interactive := isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stdin.Fd())
cygwin := isatty.IsCygwinTerminal(os.Stdout.Fd()) && isatty.IsCygwinTerminal(os.Stdin.Fd())
var closers []func() error
// configure stdin
var stdin io.ReadCloser
switch {
case forceNonInteractive:
interactive, cygwin = false, false
case cygwin:
stdin = os.Stdin
default:
stdin = readline.Stdin
}
// configure stdout
var stdout io.WriteCloser
switch {
case out != "":
var err error
stdout, err = os.OpenFile(out, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
closers = append(closers, stdout.Close)
interactive = false
case cygwin:
stdout = os.Stdout
default:
stdout = readline.Stdout
}
// configure stderr
var stderr io.Writer = os.Stderr
if !cygwin {
stderr = readline.Stderr
}
if interactive {
// wrap it with cancelable stdin
stdin = readline.NewCancelableStdin(stdin)
}
// create readline instance
l, err := readline.NewEx(&readline.Config{
HistoryFile: histfile,
DisableAutoSaveHistory: true,
InterruptPrompt: "^C",
HistorySearchFold: true,
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
FuncIsTerminal: func() bool {
return interactive || cygwin
},
FuncFilterInputRune: func(r rune) (rune, bool) {
if r == readline.CharCtrlZ {
return r, false
}
return r, true
},
})
if err != nil {
return nil, err
}
closers = append(closers, l.Close)
n := l.Operation.Runes
pw := func(prompt string) (string, error) {
buf, err := l.ReadPassword(prompt)
if err != nil {
return "", err
}
return string(buf), nil
}
if forceNonInteractive {
n, pw = nil, nil
}
return &rline{
instance: l,
nextLine: n,
close: func() error {
for _, f := range closers {
_ = f()
}
return nil
},
stdout: stdout,
stderr: stderr,
isInteractive: interactive || cygwin,
prompt: l.SetPrompt,
completer: func(a Completer) {
cfg := l.Config.Clone()
cfg.AutoComplete = readlineCompleter{c: a}
l.SetConfig(cfg)
},
saveHistory: l.SaveHistory,
passwordPrompt: pw,
}, nil
}

type readlineCompleter struct {
c Completer
}

func (r readlineCompleter) Do(line []rune, pos int) (newLine [][]rune, length int) {
return r.c.Complete(line, pos)
}
119 changes: 1 addition & 118 deletions rline/rline.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ import (
"bytes"
"errors"
"io"
"os"

"github.com/gohxs/readline"
isatty "github.com/mattn/go-isatty"
)

var (
// ErrInterrupt is the interrupt error.
ErrInterrupt = readline.ErrInterrupt
// ErrPasswordNotAvailable is the password not available error.
ErrPasswordNotAvailable = errors.New("password not available")
)
Expand Down Expand Up @@ -51,7 +45,7 @@ type Completer interface {

// rline provides a type compatible with the IO interface.
type rline struct {
instance *readline.Instance
baseRline
nextLine func() ([]rune, error)
close func() error
stdout io.Writer
Expand Down Expand Up @@ -126,117 +120,6 @@ func (l *rline) Password(prompt string) (string, error) {
return "", ErrPasswordNotAvailable
}

// SetOutput sets the output format func.
func (l *rline) SetOutput(f func(string) string) {
l.instance.Config.Output = f
}

// New creates a new readline input/output handler.
func New(forceNonInteractive bool, out, histfile string) (IO, error) {
// determine if interactive
interactive := isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stdin.Fd())
cygwin := isatty.IsCygwinTerminal(os.Stdout.Fd()) && isatty.IsCygwinTerminal(os.Stdin.Fd())
var closers []func() error
// configure stdin
var stdin io.ReadCloser
switch {
case forceNonInteractive:
interactive, cygwin = false, false
case cygwin:
stdin = os.Stdin
default:
stdin = readline.Stdin
}
// configure stdout
var stdout io.WriteCloser
switch {
case out != "":
var err error
stdout, err = os.OpenFile(out, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
closers = append(closers, stdout.Close)
interactive = false
case cygwin:
stdout = os.Stdout
default:
stdout = readline.Stdout
}
// configure stderr
var stderr io.Writer = os.Stderr
if !cygwin {
stderr = readline.Stderr
}
if interactive {
// wrap it with cancelable stdin
stdin = readline.NewCancelableStdin(stdin)
}
// create readline instance
l, err := readline.NewEx(&readline.Config{
HistoryFile: histfile,
DisableAutoSaveHistory: true,
InterruptPrompt: "^C",
HistorySearchFold: true,
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
FuncIsTerminal: func() bool {
return interactive || cygwin
},
FuncFilterInputRune: func(r rune) (rune, bool) {
if r == readline.CharCtrlZ {
return r, false
}
return r, true
},
})
if err != nil {
return nil, err
}
closers = append(closers, l.Close)
n := l.Operation.Runes
pw := func(prompt string) (string, error) {
buf, err := l.ReadPassword(prompt)
if err != nil {
return "", err
}
return string(buf), nil
}
if forceNonInteractive {
n, pw = nil, nil
}
return &rline{
instance: l,
nextLine: n,
close: func() error {
for _, f := range closers {
_ = f()
}
return nil
},
stdout: stdout,
stderr: stderr,
isInteractive: interactive || cygwin,
prompt: l.SetPrompt,
completer: func(a Completer) {
cfg := l.Config.Clone()
cfg.AutoComplete = readlineCompleter{c: a}
l.SetConfig(cfg)
},
saveHistory: l.SaveHistory,
passwordPrompt: pw,
}, nil
}

type readlineCompleter struct {
c Completer
}

func (r readlineCompleter) Do(line []rune, pos int) (newLine [][]rune, length int) {
return r.c.Complete(line, pos)
}

func NewFromReader(reader *bufio.Reader, out, err io.Writer, pw passwordPrompt) IO {
return &rline{
nextLine: func() ([]rune, error) {
Expand Down

0 comments on commit 4d2ae4b

Please sign in to comment.