summaryrefslogtreecommitdiff
path: root/gopher/gophermap
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-01-28 14:52:35 -0700
committertjpcc <tjp@ctrl-c.club>2023-01-28 15:01:41 -0700
commit66a1b1f39a1e1d5499b548b36d18c8daa872d7da (patch)
tree96471dbd5486ede1a908790ac23e0c55b226dfad /gopher/gophermap
parenta27b879accb191b6a6c6e76a6251ed751967f73a (diff)
gopher support.
Some of the contrib packages were originally built gemini-specific and had to be refactored into generic core functionality and thin protocol-specific wrappers for each of gemini and gopher.
Diffstat (limited to 'gopher/gophermap')
-rw-r--r--gopher/gophermap/parse.go61
-rw-r--r--gopher/gophermap/parse_test.go96
2 files changed, 157 insertions, 0 deletions
diff --git a/gopher/gophermap/parse.go b/gopher/gophermap/parse.go
new file mode 100644
index 0000000..302aef0
--- /dev/null
+++ b/gopher/gophermap/parse.go
@@ -0,0 +1,61 @@
+package gophermap
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+
+ "tildegit.org/tjp/gus"
+ "tildegit.org/tjp/gus/gopher"
+)
+
+// Parse reads a gophermap document from a reader.
+func Parse(input io.Reader) (gopher.MapDocument, error) {
+ rdr := bufio.NewReader(input)
+ doc := gopher.MapDocument{}
+
+ num := 0
+ for {
+ num += 1
+ line, err := rdr.ReadBytes('\n')
+ isEOF := errors.Is(err, io.EOF)
+ if err != nil && !isEOF {
+ return nil, err
+ }
+
+ if len(line) > 2 && !bytes.Equal(line, []byte(".\r\n")) {
+ if line[len(line)-2] != '\r' || line[len(line)-1] != '\n' {
+ return nil, InvalidLine(num)
+ }
+
+ item := gopher.MapItem{Type: gus.Status(line[0])}
+
+ spl := bytes.Split(line[1:len(line)-2], []byte{'\t'})
+ if len(spl) != 4 {
+ return nil, InvalidLine(num)
+ }
+ item.Display = string(spl[0])
+ item.Selector = string(spl[1])
+ item.Hostname = string(spl[2])
+ item.Port = string(spl[3])
+
+ doc = append(doc, item)
+ }
+
+ if isEOF {
+ break
+ }
+ }
+
+ return doc, nil
+}
+
+// InvalidLine is returned from Parse when the reader contains a line which is invalid gophermap.
+type InvalidLine int
+
+// Error implements the error interface.
+func (il InvalidLine) Error() string {
+ return fmt.Sprintf("Invalid gophermap on line %d.", il)
+}
diff --git a/gopher/gophermap/parse_test.go b/gopher/gophermap/parse_test.go
new file mode 100644
index 0000000..0e5c09e
--- /dev/null
+++ b/gopher/gophermap/parse_test.go
@@ -0,0 +1,96 @@
+package gophermap_test
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "tildegit.org/tjp/gus/gopher"
+ "tildegit.org/tjp/gus/gopher/gophermap"
+)
+
+func TestParse(t *testing.T) {
+ tests := []struct {
+ doc string
+ lines gopher.MapDocument
+ }{
+ {
+ doc: `
+iI am informational text localhost 70
+icontinued on this line localhost 70
+i localhost 70
+0this is my text file /file.txt localhost 70
+i localhost 70
+1here's a sub-menu /sub/ localhost 70
+.
+`[1:],
+ lines: gopher.MapDocument{
+ gopher.MapItem{
+ Type: gopher.InfoMessageType,
+ Display: "I am informational text",
+ Selector: "",
+ Hostname: "localhost",
+ Port: "70",
+ },
+ gopher.MapItem{
+ Type: gopher.InfoMessageType,
+ Display: "continued on this line",
+ Selector: "",
+ Hostname: "localhost",
+ Port: "70",
+ },
+ gopher.MapItem{
+ Type: gopher.InfoMessageType,
+ Display: "",
+ Selector: "",
+ Hostname: "localhost",
+ Port: "70",
+ },
+ gopher.MapItem{
+ Type: gopher.TextFileType,
+ Display: "this is my text file",
+ Selector: "/file.txt",
+ Hostname: "localhost",
+ Port: "70",
+ },
+ gopher.MapItem{
+ Type: gopher.InfoMessageType,
+ Display: "",
+ Selector: "",
+ Hostname: "localhost",
+ Port: "70",
+ },
+ gopher.MapItem{
+ Type: gopher.MenuType,
+ Display: "here's a sub-menu",
+ Selector: "/sub/",
+ Hostname: "localhost",
+ Port: "70",
+ },
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.lines[0].Display, func(t *testing.T) {
+ text := strings.ReplaceAll(test.doc, "\n", "\r\n")
+ doc, err := gophermap.Parse(bytes.NewBufferString(text))
+ require.Nil(t, err)
+
+ if assert.Equal(t, len(test.lines), len(doc)) {
+ for i, line := range doc {
+ expect := test.lines[i]
+
+ assert.Equal(t, expect.Type, line.Type)
+ assert.Equal(t, expect.Display, line.Display)
+ assert.Equal(t, expect.Selector, line.Selector)
+ assert.Equal(t, expect.Hostname, line.Hostname)
+ assert.Equal(t, expect.Port, line.Port)
+ }
+ }
+ })
+ }
+}