Repos / gorts / 54cb328165
commit 54cb328165e3a9294226cfecf0f2c8a349fcfe24
Author: Nhân <hi@imnhan.com>
Date: Wed Jun 21 18:55:48 2023 +0700
implement netstring helpers on both Go & Tcl
diff --git a/Makefile b/Makefile
index 885462b..31971fd 100644
--- a/Makefile
+++ b/Makefile
@@ -20,6 +20,10 @@ dist/GORTS-Linux.zip: linux
watch:
find . -name '*.go' -o -name '*.tcl' | entr -rc go run .
+# I only have tests on the netstring package now
+test:
+ find ./netstring -name '*.go' | entr -rc go test ./netstring
+
gorts.png: gorts.svg
convert -background transparent -density 300 -resize 256x256 gorts.svg gorts.png
diff --git a/netstring/netstring.go b/netstring/netstring.go
new file mode 100644
index 0000000..20c2506
--- /dev/null
+++ b/netstring/netstring.go
@@ -0,0 +1,38 @@
+// This package implements just enough [netstrings]
+// for our homebrew IPC solution.
+//
+// [netstrings]: https://cr.yp.to/proto/netstrings.txt
+package netstring
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+)
+
+func Encode(s string) string {
+ return fmt.Sprintf("%d:%s,", len(s), s)
+}
+
+// A SplitFunc to be used in a bufio.Scanner
+func SplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
+ colonIndex := bytes.IndexRune(data, ':')
+ if colonIndex == -1 {
+ // Haven't fully read the length part yet => skip:
+ return 0, nil, nil
+ }
+
+ length, err := strconv.Atoi(string(data[:colonIndex]))
+ if err != nil {
+ return 0, nil, fmt.Errorf("split netstring: %w", err)
+ }
+
+ rest := data[colonIndex+1:]
+ if len(rest) < length+1 { // +1 for "," at the end
+ // Haven't read the whole netstring yet => skip:
+ return 0, nil, nil
+ }
+
+ // The whole netstring should now be within the buffer
+ return colonIndex + 1 + length + 1, rest[:length], nil
+}
diff --git a/netstring/netstring_test.go b/netstring/netstring_test.go
new file mode 100644
index 0000000..5636180
--- /dev/null
+++ b/netstring/netstring_test.go
@@ -0,0 +1,50 @@
+package netstring
+
+import (
+ "bufio"
+ "strings"
+ "testing"
+)
+
+func TestEncode(t *testing.T) {
+ var tests = []struct {
+ in string
+ out string
+ }{
+ {"hello world!", "12:hello world!,"},
+ {"", "0:,"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.in, func(t *testing.T) {
+ ans := Encode(tt.in)
+ if ans != tt.out {
+ t.Errorf("Encode(): got %s | want %s", ans, tt.out)
+ }
+ })
+ }
+}
+
+func TestSplit(t *testing.T) {
+ in := strings.NewReader("5:hello,6:world!,0:,")
+ want := []string{"hello", "world!", ""}
+
+ t.Run("Split", func(t *testing.T) {
+ var results []string
+ scanner := bufio.NewScanner(in)
+ scanner.Split(SplitFunc)
+ for scanner.Scan() {
+ netstring := scanner.Text()
+ results = append(results, netstring)
+ }
+
+ if len(results) != len(want) {
+ t.Errorf("Split(): got %s | want %s", results, want)
+ }
+ for i, actual := range results {
+ if actual != want[i] {
+ t.Errorf("Split()[%d]: got %s | want %s", i, results, want)
+ }
+ }
+ })
+}
diff --git a/tcl/netstring.tcl b/tcl/netstring.tcl
new file mode 100644
index 0000000..fa510b1
--- /dev/null
+++ b/tcl/netstring.tcl
@@ -0,0 +1,25 @@
+proc netstring {s} {
+ set len [string length $s]
+ return "$len:$s,"
+}
+
+proc netstrings {strings} {
+ set result ""
+ foreach s $strings {
+ set result [string cat $result [netstring $s]]
+ }
+ return [netstring $result]
+}
+
+proc readnetstring {chan} {
+ set data ""
+ set char ""
+ while {$char != ":"} {
+ set char [read $chan 1]
+ set data [string cat $data $char]
+ }
+ set nslen [string range $data 0 {end-1}]
+ set nstr [read $chan $nslen]
+ read $chan 1; # consume the trailing ","
+ return $nstr
+}