Skip to content

Commit

Permalink
add password support
Browse files Browse the repository at this point in the history
  • Loading branch information
chzyer committed Nov 20, 2015
1 parent ee4d466 commit 71e9536
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 17 deletions.
20 changes: 18 additions & 2 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"fmt"
"io"
"log"
"strconv"
Expand Down Expand Up @@ -30,6 +31,7 @@ var completer = readline.NewPrefixCompleter(
readline.PcItem("bye"),
),
readline.PcItem("setprompt"),
readline.PcItem("setpassword"),
readline.PcItem("bye"),
readline.PcItem("help"),
readline.PcItem("go",
Expand All @@ -45,18 +47,27 @@ var completer = readline.NewPrefixCompleter(
)

func main() {
l, err := readline.NewEx(&readline.Config{
cfg := &readline.Config{
Prompt: "\033[31m»\033[0m ",
HistoryFile: "/tmp/readline.tmp",
AutoComplete: completer,
InterruptPrompt: "\nInterrupt, Press Ctrl+D to exit",
EOFPrompt: "exit",
})
}

l, err := readline.NewEx(cfg)
if err != nil {
panic(err)
}
defer l.Close()

setPasswordCfg := l.GenPasswordConfig()
setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
l.SetPrompt(fmt.Sprintf("Enter password(%v): ", len(line)))
l.Refresh()
return nil, 0, false
})

log.SetOutput(l.Stderr())
for {
line, err := l.Readline()
Expand Down Expand Up @@ -88,6 +99,11 @@ func main() {
println("you enter:", strconv.Quote(string(pswd)))
case line == "help":
usage(l.Stderr())
case line == "setpassword":
pswd, err := l.ReadPasswordWithConfig(setPasswordCfg)
if err == nil {
println("you set:", strconv.Quote(string(pswd)))
}
case strings.HasPrefix(line, "setprompt"):
prompt := line[10:]
if prompt == "" {
Expand Down
24 changes: 22 additions & 2 deletions history.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,23 @@ func newOpHistory(cfg *Config) (o *opHistory) {
cfg: cfg,
history: list.New(),
}
return o
}

func (o *opHistory) IsHistoryClosed() bool {
return o.fd.Fd() == ^(uintptr(0))
}

func (o *opHistory) InitHistory() {
if o.IsHistoryClosed() {
o.initHistory()
}
}

func (o *opHistory) initHistory() {
if o.cfg.HistoryFile != "" {
o.historyUpdatePath(o.cfg.HistoryFile)
}
return
}

// only called by newOpHistory
Expand Down Expand Up @@ -65,7 +78,7 @@ func (o *opHistory) historyUpdatePath(path string) {
}

func (o *opHistory) CompactHistory() {
for o.history.Len() > o.cfg.HistoryLimit {
for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 {
o.history.Remove(o.history.Front())
}
}
Expand Down Expand Up @@ -225,9 +238,15 @@ func (o *opHistory) NewHistory(current []rune) {
o.PushHistory(nil)
}

func (o *opHistory) RevertHistory() {
o.historyVer++
o.current = o.history.Back()
}

func (o *opHistory) UpdateHistory(s []rune, commit bool) {
if o.current == nil {
o.PushHistory(s)
o.CompactHistory()
return
}
r := o.current.Value.(*hisItem)
Expand All @@ -242,6 +261,7 @@ func (o *opHistory) UpdateHistory(s []rune, commit bool) {
r.Tmp = append(r.Tmp[:0], s...)
}
o.current.Value = r
o.CompactHistory()
}

func (o *opHistory) PushHistory(s []rune) {
Expand Down
99 changes: 92 additions & 7 deletions operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Operation struct {
*opHistory
*opSearch
*opCompleter
*opPassword
*opVim
}

Expand Down Expand Up @@ -57,17 +58,16 @@ func (w *wrapWriter) Write(b []byte) (int, error) {

func NewOperation(t *Terminal, cfg *Config) *Operation {
op := &Operation{
cfg: cfg,
t: t,
buf: NewRuneBuffer(t, cfg.Prompt),
buf: NewRuneBuffer(t, cfg.Prompt, cfg.MaskRune),
outchan: make(chan []rune),
errchan: make(chan error),
}
op.SetHistoryPath(cfg.HistoryFile)
op.opVim = newVimMode(op)
op.w = op.buf.w
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
op.SetConfig(cfg)
op.opVim = newVimMode(op)
op.opCompleter = newOpCompleter(op.buf.w, op)
op.opPassword = newOpPassword(op)
go op.ioloop()
return op
}
Expand All @@ -76,11 +76,16 @@ func (o *Operation) SetPrompt(s string) {
o.buf.SetPrompt(s)
}

func (o *Operation) SetMaskRune(r rune) {
o.buf.SetMask(r)
}

func (o *Operation) ioloop() {
for {
keepInSearchMode := false
keepInCompleteMode := false
r := o.t.ReadRune()
isUpdateHistory := true

if o.IsInCompleteSelectMode() {
keepInCompleteMode = o.HandleCompleteSelect(r)
Expand Down Expand Up @@ -205,6 +210,8 @@ func (o *Operation) ioloop() {
// treat as EOF
o.buf.WriteString(o.cfg.EOFPrompt + "\n")
o.buf.Reset()
isUpdateHistory = false
o.RevertHistory()
o.errchan <- io.EOF
case CharInterrupt:
if o.IsSearchMode() {
Expand All @@ -222,6 +229,8 @@ func (o *Operation) ioloop() {
o.buf.Refresh(nil)
o.buf.WriteString(o.cfg.InterruptPrompt + "\n")
o.buf.Reset()
isUpdateHistory = false
o.RevertHistory()
o.errchan <- ErrInterrupt
default:
if o.IsSearchMode() {
Expand All @@ -236,19 +245,26 @@ func (o *Operation) ioloop() {
}
}

if o.cfg.Listener != nil {
newLine, newPos, ok := o.cfg.Listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
if ok {
o.buf.SetWithIdx(newPos, newLine)
}
}

if !keepInSearchMode && o.IsSearchMode() {
o.ExitSearchMode(false)
o.buf.Refresh(nil)
} else if o.IsInCompleteMode() {
if !keepInCompleteMode {
o.ExitCompleteMode(false)
o.buf.Refresh(nil)
o.Refresh()
} else {
o.buf.Refresh(nil)
o.CompleteRefresh()
}
}
if !o.IsSearchMode() {
if isUpdateHistory && !o.IsSearchMode() {
o.UpdateHistory(o.buf.Runes(), false)
}
}
Expand All @@ -274,6 +290,9 @@ func (o *Operation) Runes() ([]rune, error) {
o.t.EnterRawMode()
defer o.t.ExitRawMode()

if o.cfg.Listener != nil {
o.cfg.Listener.OnChange(nil, 0, 0)
}
o.buf.Refresh(nil) // print prompt
o.t.KickRead()
select {
Expand All @@ -284,6 +303,23 @@ func (o *Operation) Runes() ([]rune, error) {
}
}

func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) {
cfg := o.GenPasswordConfig()
cfg.Prompt = prompt
cfg.Listener = l
return o.PasswordWithConfig(cfg)
}

func (o *Operation) GenPasswordConfig() *Config {
return o.opPassword.PasswordConfig()
}

func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) {
o.opPassword.EnterPasswordMode(cfg)
defer o.opPassword.ExitPasswordMode()
return o.Slice()
}

func (o *Operation) Password(prompt string) ([]byte, error) {
w := o.Stdout()
if prompt != "" {
Expand Down Expand Up @@ -324,3 +360,52 @@ func (o *Operation) SetHistoryPath(path string) {
func (o *Operation) IsNormalMode() bool {
return !o.IsInCompleteMode() && !o.IsSearchMode()
}

func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
if op.cfg == cfg {
return op.cfg, nil
}
if err := cfg.Init(); err != nil {
return op.cfg, err
}
old := op.cfg
op.cfg = cfg
op.SetPrompt(cfg.Prompt)
op.SetMaskRune(cfg.MaskRune)

if cfg.opHistory == nil {
op.SetHistoryPath(cfg.HistoryFile)
cfg.opHistory = op.opHistory
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
}
op.opHistory = cfg.opHistory

// SetHistoryPath will close opHistory which already exists
// so if we use it next time, we need to reopen it by `InitHistory()`
op.opHistory.InitHistory()

op.opSearch = cfg.opSearch
return old, nil
}

func (o *Operation) Refresh() {
if o.t.IsReading() {
o.buf.Refresh(nil)
}
}

func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener {
return &DumpListener{f: f}
}

type DumpListener struct {
f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
}

func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
return d.f(line, pos, key)
}

type Listener interface {
OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
}
32 changes: 32 additions & 0 deletions password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package readline

type opPassword struct {
o *Operation
backupCfg *Config
}

func newOpPassword(o *Operation) *opPassword {
return &opPassword{o: o}
}

func (o *opPassword) ExitPasswordMode() {
o.o.SetConfig(o.backupCfg)
o.backupCfg = nil
}

func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) {
o.backupCfg, err = o.o.SetConfig(cfg)
return
}

func (o *opPassword) PasswordConfig() *Config {
return &Config{
MaskRune: '*',
InterruptPrompt: "\n",
EOFPrompt: "\n",
HistoryLimit: -1,

Stdout: o.o.cfg.Stdout,
Stderr: o.o.cfg.Stderr,
}
}
Loading

0 comments on commit 71e9536

Please sign in to comment.