Skip to content

Commit bee8cf8

Browse files
committed
Added useful utility methods and default "exit" and "help" commands..
1 parent f0144ce commit bee8cf8

File tree

6 files changed

+114
-49
lines changed

6 files changed

+114
-49
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1+
*~
2+
.DS_Store
13
example/example
4+
.idea/
5+
*.iml

CHANGES.txt

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# For now, dates (DD/MM/YYYY) are used until ishell gets stable enough to warrant tags.
2+
# Attempts will be made to ensure non breaking updates as much as possible.
3+
4+
12/07/2015:
5+
Added PrintCommands, Commands, ShowPrompt methods to Shell.
6+
Added default "exit" and "help" commands to Shell returned by NewShell().
7+
8+
11/07/2015:
9+
Initial version.

README.md

+16-14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import "github.com/abiosoft/ishell"
1010

1111
func main(){
1212
// create new shell.
13+
// by default, new shell includes 'exit' and 'help' commands.
1314
shell := ishell.NewShell()
1415

1516
// display welcome info.
@@ -24,35 +25,36 @@ func main(){
2425
return "Hello "+name, nil
2526
})
2627

27-
// register a function for "exit" command.
28-
shell.Register("exit", func(cmd string, args []string) (string, error) {
29-
shell.Stop()
30-
return "bye!", nil
31-
})
32-
3328
// start shell
3429
shell.Start()
3530
}
3631
```
3732
Execution
3833
```
3934
Sample Interactive Shell
35+
>> help
36+
Commands:
37+
exit help greet
4038
>> greet Someone Somewhere
4139
Hello Someone Somewhere
4240
>> exit
43-
bye!
41+
$
4442
```
4543

4644
#### Reading input.
4745
```go
4846
// simulate an authentication
4947
shell.Register("login", func(cmd string, args []string) (string, error) {
48+
// disable the '>>' for cleaner same line input.
49+
shell.ShowPrompt(false)
50+
defer shell.ShowPrompt(true) // yes, revert after login.
51+
5052
// get username
51-
shell.Println("Username:")
53+
shell.Print("Username: ")
5254
username, _ := shell.ReadLine()
5355

5456
// get password. Does not echo characters.
55-
shell.Println("Password:")
57+
shell.Print("Password: ")
5658
password := shell.ReadPassword(false)
5759

5860
... // do something with username and password
@@ -62,10 +64,8 @@ shell.Register("login", func(cmd string, args []string) (string, error) {
6264
```
6365
Execution
6466
```
65-
Username:
66-
>> someusername
67+
Username: someusername
6768
Password:
68-
>>
6969
Authentication Successful.
7070
```
7171
Check example code for more.
@@ -89,5 +89,7 @@ ishell is in active development and can still change significantly.
8989
MIT
9090

9191
### Credits
92-
* github.com/flynn/go-shlex for splitting input into command and args.
93-
* github.com/howeyc/gopass for reading passwords.
92+
Library | Use
93+
------- | -----
94+
[github.com/flynn/go-shlex](http://github.com/flynn/go-shlex) | splitting input into command and args.
95+
[github.com/howeyc/gopass](http://github.com/howeyc/gopass) | reading passwords.

example/main.go

+8-10
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,9 @@ func main() {
1313
shell.Println("Sample Interactive Shell")
1414

1515
// handle exit
16-
shell.Register("exit", func(cmd string, args []string) (string, error) {
17-
shell.Println("Do you want to do more ? y/n:")
18-
line, _ := shell.ReadLine()
19-
if strings.ToLower(line) == "y" {
20-
doLogin(shell)
21-
}
22-
shell.Stop()
23-
return "bye!", nil
16+
shell.Register("login", func(cmd string, args []string) (string, error) {
17+
doLogin(shell)
18+
return "", nil
2419
})
2520

2621
// register a function for "greet" command.
@@ -37,12 +32,15 @@ func main() {
3732
}
3833

3934
func doLogin(shell *ishell.Shell) {
35+
shell.ShowPrompt(false)
36+
defer shell.ShowPrompt(true)
37+
4038
shell.Println("Let's simulate login")
4139

4240
// prompt for input
43-
shell.Println("Username:")
41+
shell.Print("Username: ")
4442
username, _ := shell.ReadLine()
45-
shell.Println("Password:")
43+
shell.Print("Password: ")
4644
password := shell.ReadPassword(false)
4745

4846
// do something with username and password

functions.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package ishell
2+
3+
// CmdFunc represents a command function that is called after an input to the shell.
4+
// The shell input is split into command and arguments like cli args.
5+
// The shell will print output if output != "".
6+
type CmdFunc func(command string, args []string) (output string, err error)
7+
8+
func exitFunc(s *Shell) CmdFunc {
9+
return func(command string, args []string) (output string, err error) {
10+
s.Stop()
11+
return "", nil
12+
}
13+
}
14+
15+
func helpFunc(s *Shell) CmdFunc{
16+
return func(command string, args []string) (output string, err error) {
17+
s.PrintCommands()
18+
return "", nil
19+
}
20+
}
21+
22+
func addDefaultFuncs(s *Shell){
23+
s.Register("exit", exitFunc(s))
24+
s.Register("help", helpFunc(s))
25+
}

ishell.go

+52-25
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,44 @@ import (
66
"fmt"
77
"io"
88
"os"
9+
"sort"
910
"strings"
1011
"sync"
1112

1213
"github.com/flynn/go-shlex"
1314
"github.com/howeyc/gopass"
1415
)
1516

16-
type (
17-
// CmdFunc represents a command function that is called after an input to the shell.
18-
// The shell input is split into command and arguments like cli args.
19-
// The shell will print output if output != "".
20-
CmdFunc func(command string, args []string) (output string, err error)
21-
22-
Shell struct {
23-
prompt string
24-
functions map[string]CmdFunc
25-
generic CmdFunc
26-
reader *shellReader
27-
writer io.Writer
28-
active bool
29-
activeMutex sync.RWMutex
30-
haltChan chan struct{}
31-
}
32-
)
33-
3417
const (
3518
defaultPrompt = ">> "
3619
)
3720

21+
type Shell struct {
22+
prompt string
23+
showPrompt bool
24+
functions map[string]CmdFunc
25+
generic CmdFunc
26+
reader *shellReader
27+
writer io.Writer
28+
active bool
29+
activeMutex sync.RWMutex
30+
haltChan chan struct{}
31+
}
32+
3833
// NewShell creates a new shell with default settings. Uses standard output and default prompt ">>".
3934
func NewShell() *Shell {
40-
return &Shell{
41-
prompt: defaultPrompt,
42-
functions: make(map[string]CmdFunc),
35+
shell := &Shell{
36+
prompt: defaultPrompt,
37+
showPrompt: true,
38+
functions: make(map[string]CmdFunc),
4339
reader: &shellReader{
4440
scanner: bufio.NewReader(os.Stdin),
4541
},
4642
writer: os.Stdout,
4743
haltChan: make(chan struct{}),
4844
}
45+
addDefaultFuncs(shell)
46+
return shell
4947
}
5048

5149
// Start starts the shell. It reads inputs from standard input and calls registered functions
@@ -184,7 +182,9 @@ func (s *Shell) ReadLine() (line string, err error) {
184182
}
185183

186184
func (s *Shell) readLine() (line string, err error) {
187-
s.Print(s.prompt)
185+
if s.showPrompt {
186+
s.Print(s.prompt)
187+
}
188188
consumer := make(chan lineString)
189189
s.reader.ReadLine(consumer)
190190
ls := <-consumer
@@ -196,10 +196,12 @@ func (s *Shell) nextLine() {
196196
}
197197

198198
// ReadPassword reads password from standard input without echoing the characters.
199-
// If mask is true, each chracter will be represented with astericks '*'. Note that
199+
// If mask is true, each character will be represented with asterisks '*'. Note that
200200
// this only works as expected when the standard input is a terminal.
201201
func (s *Shell) ReadPassword(mask bool) string {
202-
fmt.Fprint(s.writer, s.prompt)
202+
if s.showPrompt {
203+
s.Print(s.prompt)
204+
}
203205
if mask {
204206
return string(gopass.GetPasswdMasked())
205207
}
@@ -216,7 +218,7 @@ func (s *Shell) Print(val ...interface{}) {
216218
fmt.Fprint(s.writer, val...)
217219
}
218220

219-
// Register registers a function for command.
221+
// Register registers a function for command. It overwrites existing function, if any.
220222
func (s *Shell) Register(command string, function CmdFunc) {
221223
s.functions[command] = function
222224
}
@@ -239,7 +241,32 @@ func (s *Shell) SetPrompt(prompt string) {
239241
s.prompt = prompt
240242
}
241243

244+
// ShowPrompt sets whether prompt should show when requesting input for ReadLine and ReadPassword.
245+
// Defaults to true.
246+
func (s *Shell) ShowPrompt(show bool) {
247+
s.showPrompt = show
248+
}
249+
242250
// SetOut sets the writer to write outputs to.
243251
func (s *Shell) SetOut(writer io.Writer) {
244252
s.writer = writer
245253
}
254+
255+
// PrintCommands prints a space separated list of registered commands to the shell.
256+
func (s *Shell) PrintCommands() {
257+
out := strings.Join(s.Commands(), " ")
258+
if out != "" {
259+
s.Println("Commands:")
260+
s.Println(out)
261+
}
262+
}
263+
264+
// Commands returns a sorted list of all registered commands.
265+
func (s *Shell) Commands() []string {
266+
var commands []string
267+
for command := range s.functions {
268+
commands = append(commands, command)
269+
}
270+
sort.Strings(commands)
271+
return commands
272+
}

0 commit comments

Comments
 (0)