redo ptybuffer, move to wshutil to help with stdin processing. change wsh to use cobra

This commit is contained in:
sawka
2024-06-14 14:43:47 -07:00
parent 8a3a527343
commit 014c6fb2ec
10 changed files with 411 additions and 140 deletions
+174
View File
@@ -0,0 +1,174 @@
package wshutil
import (
"bytes"
"encoding/json"
"fmt"
"io"
"sync"
)
const (
Mode_Normal = "normal"
Mode_Esc = "esc"
Mode_WaveEsc = "waveesc"
BlockFile_Main = "main" // Assuming this is defined elsewhere
)
const MaxBufferedDataSize = 256 * 1024
type PtyBuffer struct {
CVar *sync.Cond
DataBuf *bytes.Buffer
EscMode string
EscSeqBuf []byte
InputReader io.Reader
CommandCh chan BlockCommand
AtEOF bool
Err error
}
func MakePtyBuffer(input io.Reader, commandCh chan BlockCommand) *PtyBuffer {
b := &PtyBuffer{
CVar: sync.NewCond(&sync.Mutex{}),
DataBuf: &bytes.Buffer{},
EscMode: Mode_Normal,
InputReader: input,
CommandCh: commandCh,
}
go b.run()
return b
}
func (b *PtyBuffer) setErr(err error) {
b.CVar.L.Lock()
defer b.CVar.L.Unlock()
if b.Err == nil {
b.Err = err
}
b.CVar.Broadcast()
}
func (b *PtyBuffer) setEOF() {
b.CVar.L.Lock()
defer b.CVar.L.Unlock()
b.AtEOF = true
b.CVar.Broadcast()
}
func (b *PtyBuffer) processWaveEscSeq(escSeq []byte) {
jmsg := make(map[string]any)
err := json.Unmarshal(escSeq, &jmsg)
if err != nil {
b.setErr(fmt.Errorf("error unmarshalling Wave OSC sequence data: %w", err))
return
}
cmd, err := ParseCmdMap(jmsg)
if err != nil {
b.setErr(fmt.Errorf("error parsing Wave OSC command: %w", err))
return
}
b.CommandCh <- cmd
}
func (b *PtyBuffer) run() {
defer close(b.CommandCh)
buf := make([]byte, 4096)
for {
n, err := b.InputReader.Read(buf)
b.processData(buf[:n])
if err == io.EOF {
b.setEOF()
return
}
if err != nil {
b.setErr(fmt.Errorf("error reading input: %w", err))
return
}
}
}
func (b *PtyBuffer) processData(data []byte) {
outputBuf := make([]byte, 0, len(data))
for _, ch := range data {
if b.EscMode == Mode_WaveEsc {
if ch == ESC {
// terminates the escape sequence (and the rest was invalid)
b.EscMode = Mode_Normal
outputBuf = append(outputBuf, b.EscSeqBuf...)
outputBuf = append(outputBuf, ch)
b.EscSeqBuf = nil
} else if ch == BEL || ch == ST {
// terminates the escpae sequence (is a valid Wave OSC command)
b.EscMode = Mode_Normal
waveEscSeq := b.EscSeqBuf[len(WaveOSCPrefix):]
b.EscSeqBuf = nil
b.processWaveEscSeq(waveEscSeq)
} else {
b.EscSeqBuf = append(b.EscSeqBuf, ch)
}
continue
}
if b.EscMode == Mode_Esc {
if ch == ESC || ch == BEL || ch == ST {
// these all terminate the escape sequence (invalid, not a Wave OSC)
b.EscMode = Mode_Normal
outputBuf = append(outputBuf, b.EscSeqBuf...)
outputBuf = append(outputBuf, ch)
} else {
if ch == WaveOSCPrefixBytes[len(b.EscSeqBuf)] {
// we're still building what could be a Wave OSC sequence
b.EscSeqBuf = append(b.EscSeqBuf, ch)
} else {
// this is not a Wave OSC sequence, just an escape sequence
b.EscMode = Mode_Normal
outputBuf = append(outputBuf, b.EscSeqBuf...)
outputBuf = append(outputBuf, ch)
continue
}
// check to see if we have a full Wave OSC prefix
if len(b.EscSeqBuf) == len(WaveOSCPrefixBytes) {
b.EscMode = Mode_WaveEsc
}
}
continue
}
// Mode_Normal
if ch == ESC {
b.EscMode = Mode_Esc
b.EscSeqBuf = []byte{ch}
continue
}
outputBuf = append(outputBuf, ch)
}
if len(outputBuf) > 0 {
b.writeData(outputBuf)
}
}
func (b *PtyBuffer) writeData(data []byte) {
b.CVar.L.Lock()
defer b.CVar.L.Unlock()
// only wait if buffer is currently over max size, otherwise allow this append to go through
for b.DataBuf.Len() > MaxBufferedDataSize {
b.CVar.Wait()
}
b.DataBuf.Write(data)
b.CVar.Broadcast()
}
func (b *PtyBuffer) Read(p []byte) (n int, err error) {
b.CVar.L.Lock()
defer b.CVar.L.Unlock()
for b.DataBuf.Len() == 0 {
if b.Err != nil {
return 0, b.Err
}
if b.AtEOF {
return 0, io.EOF
}
b.CVar.Wait()
}
b.CVar.Broadcast()
return b.DataBuf.Read(p)
}
+13
View File
@@ -19,6 +19,7 @@ const (
BlockCommand_Message = "message"
BlockCommand_SetView = "setview"
BlockCommand_SetMeta = "setmeta"
BlockCommand_GetMeta = "getmeta"
BlockCommand_Input = "controller:input"
BlockCommand_AppendBlockFile = "blockfile:append"
BlockCommand_AppendIJson = "blockfile:appendijson"
@@ -28,6 +29,7 @@ var CommandToTypeMap = map[string]reflect.Type{
BlockCommand_Input: reflect.TypeOf(BlockInputCommand{}),
BlockCommand_SetView: reflect.TypeOf(BlockSetViewCommand{}),
BlockCommand_SetMeta: reflect.TypeOf(BlockSetMetaCommand{}),
BlockCommand_GetMeta: reflect.TypeOf(BlockGetMetaCommand{}),
BlockCommand_Message: reflect.TypeOf(BlockMessageCommand{}),
BlockCommand_AppendBlockFile: reflect.TypeOf(BlockAppendFileCommand{}),
BlockCommand_AppendIJson: reflect.TypeOf(BlockAppendIJsonCommand{}),
@@ -98,8 +100,19 @@ func (svc *BlockSetViewCommand) GetCommand() string {
return BlockCommand_SetView
}
type BlockGetMetaCommand struct {
Command string `json:"command" tstype:"\"getmeta\""`
RpcId string `json:"rpcid"`
OID string `json:"oid"` // allows oref, 8-char oid, or full uuid
}
func (gmc *BlockGetMetaCommand) GetCommand() string {
return BlockCommand_GetMeta
}
type BlockSetMetaCommand struct {
Command string `json:"command" tstype:"\"setmeta\""`
OID string `json:"oid"` // allows oref, 8-char oid, or full uuid
Meta map[string]any `json:"meta"`
}
+7 -1
View File
@@ -13,6 +13,9 @@ import (
const WaveOSC = "23198"
const WaveOSCPrefix = "\x1b]" + WaveOSC + ";"
const WaveResponseOSC = "23199"
const WaveResponseOSCPrefix = "\x1b]" + WaveResponseOSC + ";"
const HexChars = "0123456789ABCDEF"
const BEL = 0x07
const ST = 0x9c
@@ -25,9 +28,12 @@ var WaveOSCPrefixBytes = []byte(WaveOSCPrefix)
// JSON = must escape all ASCII control characters ([\x00-\x1F\x7F])
// we can tell the difference between JSON and base64-JSON by the first character: '{' or not
// for responses (terminal -> program), we'll use OSC 23199
// same json format
func EncodeWaveOSCMessage(cmd BlockCommand) ([]byte, error) {
if cmd.GetCommand() == "" {
return nil, fmt.Errorf("Command field not set in struct")
return nil, fmt.Errorf("command field not set in struct")
}
ctype, ok := CommandToTypeMap[cmd.GetCommand()]
if !ok {