Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
More simple readline functionality added to wfdr/moduled. Still have …
…to add cursor movement and unit tests. Not sure what terminals this will work on.
- Loading branch information
Showing
6 changed files
with
308 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package moduled | ||
|
||
type Seq byte | ||
|
||
var ( | ||
SEQ_NONE = Seq(0) | ||
SEQ_UP = Seq('A') | ||
SEQ_DOWN = Seq('B') | ||
SEQ_RIGHT = Seq('C') | ||
SEQ_LEFT = Seq('D') | ||
) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,195 @@ | ||
package moduled | ||
|
||
import ( | ||
"net/rpc" | ||
"errors" | ||
"bufio" | ||
"fmt" | ||
"io" | ||
) | ||
|
||
type Shell struct { | ||
conn *rpc.Client | ||
rd io.Reader | ||
hist [][]byte | ||
histpos int | ||
|
||
// Buffer of characters on the current line | ||
linebuf []byte | ||
// Current position of the cursor | ||
pos int | ||
|
||
// Where to read commands from | ||
rd *bufio.Reader | ||
wr io.Writer | ||
} | ||
|
||
func NewShell(conn *rpc.Client, rd io.Reader) { | ||
func NewShell(rd io.Reader, wr io.Writer) *Shell { | ||
s := new(Shell) | ||
s.conn = conn | ||
s.rd = rd | ||
s.rd = bufio.NewReader(rd) | ||
s.wr = wr | ||
return s | ||
} | ||
|
||
func (s *Shell) parseTokens() ([]string, error) { | ||
tokens := make([]string, 0) | ||
buf := make([]byte, 0) | ||
|
||
for _, b := range s.linebuf { | ||
switch b { | ||
case ' ', '\t': | ||
if buf != nil { | ||
tokens = append(tokens, string(buf)) | ||
buf = nil | ||
} | ||
default: | ||
buf = append(buf, b) | ||
} | ||
} | ||
tokens = append(tokens, string(buf)) | ||
return tokens, nil | ||
} | ||
|
||
// ReadCommand presents the user with an interactive prompt where they can enter a command, and includes facilities for backspace, and history. Returns the parsed command string (command and arguments), and an error, if any. | ||
func (s *Shell) ReadCommand() ([]string, error) { | ||
s.linebuf = nil | ||
s.histpos = -1 | ||
for { | ||
b, err := s.rd.ReadByte() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
switch b { | ||
case '\n': | ||
if s.histpos == -1 { | ||
s.hist = append(s.hist, s.linebuf) | ||
} else { | ||
s.hist[len(s.hist) - 1] = s.linebuf | ||
} | ||
return s.parseTokens() | ||
case 127: // Backspace (Actually DEL) | ||
s.bksp(3) | ||
if len(s.linebuf) == 0 || s.linebuf == nil { | ||
break | ||
} | ||
s.linebuf = s.linebuf[:len(s.linebuf)-1] | ||
case 27: // ESC | ||
seq, err := s.readEscSeq() | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = s.handleEscSeq(seq) | ||
if err != nil { | ||
return nil, err | ||
} | ||
default: | ||
s.linebuf = append(s.linebuf, b) | ||
} | ||
} | ||
panic("not reached!") | ||
} | ||
|
||
func (s *Shell) readEscSeq() (Seq, error) { | ||
b, err := s.rd.ReadByte() | ||
if err != nil { | ||
return SEQ_NONE, err | ||
} | ||
if b != '[' { | ||
return SEQ_NONE, errors.New("Expected '[' after ESC") | ||
} | ||
|
||
pmc := make([]byte, 0) | ||
for { | ||
b, err = s.rd.ReadByte() | ||
if err != nil { | ||
return SEQ_NONE, err | ||
} | ||
if (b < 22) { | ||
return SEQ_NONE, fmt.Errorf("Unexpected character %d ('%c')", b, b) | ||
} else if (b <= 47) { | ||
pmc = append(pmc, b) | ||
} else if (b <= 57) { | ||
// Number | ||
return SEQ_NONE, fmt.Errorf("Numbers in escape sequences not yet supported!") | ||
} else if (b <= 63) { | ||
return SEQ_NONE, fmt.Errorf("Unexpected character %d ('%c')", b, b) | ||
} else if (b <= 126) { | ||
return Seq(b), nil | ||
} else { | ||
return SEQ_NONE, fmt.Errorf("Unexpected character %d ('%c')", b, b) | ||
} | ||
} | ||
panic("Not reached!") | ||
} | ||
|
||
func (s *Shell) bksp(num int) { | ||
for i := 0; i < num; i++ { | ||
fmt.Fprintf(s.wr, "%c %c", 8, 8) | ||
} | ||
} | ||
|
||
func (s *Shell) handleEscSeq(seq Seq) error { | ||
if seq == SEQ_UP || seq == SEQ_DOWN { | ||
if s.histpos == -1 { | ||
s.hist = append(s.hist, s.linebuf) | ||
s.histpos = len(s.hist)-1 | ||
} | ||
} | ||
switch (seq) { | ||
case SEQ_UP: | ||
s.histpos-- | ||
if s.histpos < 0 { | ||
s.histpos = 0 | ||
s.bksp(4) | ||
return nil | ||
} | ||
case SEQ_DOWN: | ||
s.histpos++ | ||
if s.histpos >= len(s.hist) - 1 { | ||
s.histpos = len(s.hist) - 1 | ||
s.bksp(4) | ||
return nil | ||
} | ||
default: | ||
fmt.Printf("Read escape sequence of %d", seq) | ||
return fmt.Errorf("Read unsupported escape sequence of %d ('%c')", seq, seq) | ||
} | ||
if seq == SEQ_UP || seq == SEQ_DOWN { | ||
s.bksp(len(s.linebuf) + 4) | ||
s.linebuf = s.hist[s.histpos] | ||
fmt.Fprintf(s.wr, "%s", s.linebuf) | ||
} | ||
return nil | ||
} | ||
|
||
func (c *Conn) InterpretCommand(args []string) error { | ||
if args == nil || len(args) < 1 { | ||
return errors.New("No command provided!") | ||
} | ||
cmd := args[0] | ||
args = args[1:] | ||
|
||
switch (cmd) { | ||
case "start": | ||
for _, module := range args { | ||
err := c.Start(module) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
case "stop": | ||
for _, module := range args { | ||
if len(args) < 1 { | ||
return errors.New("Module name required!") | ||
} | ||
return c.Stop(module) | ||
} | ||
case "restart": | ||
for _, module := range args { | ||
if len(args) < 1 { | ||
return errors.New("Module name required!") | ||
} | ||
return c.Restart(module) | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package moduled | ||
|
||
import ( | ||
"os" | ||
"log" | ||
"os/exec" | ||
"errors" | ||
) | ||
|
||
type sttyState string | ||
|
||
func (stty sttyState) Undo() { | ||
proc := exec.Command("stty", string(stty)) | ||
proc.Stdin = os.Stdin | ||
err := proc.Run() | ||
if err != nil { | ||
log.Println("Warning: Failed to reset terminal options with to " + stty) | ||
return | ||
} | ||
return | ||
} | ||
|
||
func readStty() (sttyState, error) { | ||
proc := exec.Command("/bin/stty", "-g") | ||
proc.Stdin = os.Stdin | ||
output, err := proc.Output() | ||
if err != nil { | ||
return sttyState(output), errors.New("Reading tty state with 'stty -g': " + err.Error()) | ||
} | ||
return sttyState(output), nil | ||
} | ||
|
||
func SttyCbreak() (sttyState, error) { | ||
state, err := readStty() | ||
if err != nil { | ||
return state, err | ||
} | ||
proc := exec.Command("/bin/stty", "cbreak") | ||
proc.Stdin = os.Stdin | ||
err = proc.Run() | ||
if err != nil { | ||
return state, errors.New("Executing 'stty raw': " + err.Error()) | ||
} | ||
return state, nil | ||
} |
Oops, something went wrong.