Skip to content

Commit

Permalink
More simple readline functionality added to wfdr/moduled. Still have …
Browse files Browse the repository at this point in the history
…to add cursor movement and unit tests. Not sure what terminals this will work on.
  • Loading branch information
crazy2be committed Apr 4, 2012
1 parent ee41bf1 commit cc60708
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 38 deletions.
11 changes: 11 additions & 0 deletions moduled/escapeseq.go
@@ -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')
)
25 changes: 0 additions & 25 deletions moduled/modules.go

This file was deleted.

46 changes: 44 additions & 2 deletions moduled/rpc.go
Expand Up @@ -6,8 +6,12 @@ import (
"net/rpc/jsonrpc"
)

type Conn struct {
client *rpc.Client
}

// Connects to the pipe files, in order to allow this program to sent commands to the process management deamon.
func RPCConnect() (*rpc.Client, error) {
func RPCConnect() (*Conn, error) {
// Pipes are reversed from what you would expect because we are connecting as a client, and they are named based on how the server uses them. Thus, the out pipe for the server is the in pipe for us.
outpipe := "cache/wfdr-deamon-pipe-in"
inpipe := "cache/wfdr-deamon-pipe-out"
Expand All @@ -23,5 +27,43 @@ func RPCConnect() (*rpc.Client, error) {

rwc := &PipeReadWriteCloser{Input: infile, Output: outfile}

return jsonrpc.NewClient(rwc), nil
return &Conn{client: jsonrpc.NewClient(rwc)}, nil
}

func (c *Conn) Start(name string) error {
var dummy int = 1000
err := c.client.Call("ModuleSrv.Start", &name, &dummy)
if err != nil {
return errors.New("Error starting " + name + ": " + err.Error())
}
return nil
}

func (c *Conn) Stop(name string) error {
var dummy int = 1000
err := c.client.Call("ModuleSrv.Stop", &name, &dummy)
if err != nil {
return errors.New("Error stopping " + name + ": " + err.Error())
}
return nil
}

func (c *Conn) Restart(name string) error {
err := c.Stop(name)
if err != nil {
return err
}
return c.Start(name)
}

func (c *Conn) Status(name string) (running bool, err error) {
err = c.client.Call("ModuleSrv.Status", &name, &running)
if err != nil {
return false, errors.New("Error getting status for " + name + ": " + err.Error())
}
return running, nil
}

func (c *Conn) Close() error {
return c.client.Close()
}
190 changes: 184 additions & 6 deletions moduled/shell.go
@@ -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
}
45 changes: 45 additions & 0 deletions moduled/stty.go
@@ -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
}

0 comments on commit cc60708

Please sign in to comment.