summaryrefslogtreecommitdiff
path: root/gemini
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-04-29 18:10:50 -0600
committertjpcc <tjp@ctrl-c.club>2023-04-29 18:10:50 -0600
commit3ff04cf88571f8ed1aca78da4efe4929ad583ca6 (patch)
treebb2bc248f4049d3c7833a8d6661d17c91f03e642 /gemini
parent9e09825537e4ae91119987f979ec4272d1727a2e (diff)
spartan =: prompt line support in gemtext
Diffstat (limited to 'gemini')
-rw-r--r--gemini/gemtext/parse_line.go43
-rw-r--r--gemini/gemtext/parse_line_test.go51
-rw-r--r--gemini/gemtext/parse_test.go39
-rw-r--r--gemini/gemtext/types.go26
4 files changed, 142 insertions, 17 deletions
diff --git a/gemini/gemtext/parse_line.go b/gemini/gemtext/parse_line.go
index 39187a8..e31e5b6 100644
--- a/gemini/gemtext/parse_line.go
+++ b/gemini/gemtext/parse_line.go
@@ -10,10 +10,16 @@ func ParseLine(line []byte) Line {
switch line[0] {
case '=':
- if len(line) == 1 || line[1] != '>' {
+ if len(line) == 1 {
break
}
- return parseLinkLine(line)
+ if line[1] == '>' {
+ return parseLinkLine(line)
+ }
+ if line[1] == ':' {
+ return parsePromptLine(line)
+ }
+ break
case '`':
if len(line) < 3 || line[1] != '`' || line[2] != '`' {
break
@@ -73,6 +79,39 @@ func parseLinkLine(raw []byte) LinkLine {
return line
}
+func parsePromptLine(raw []byte) PromptLine {
+ line := PromptLine{raw: raw}
+
+ // move past =:[<whitespace>]
+ raw = bytes.TrimLeft(raw[2:], " \t")
+
+ // find the next space or tab
+ spIdx := bytes.IndexByte(raw, ' ')
+ tbIdx := bytes.IndexByte(raw, '\t')
+ idx := spIdx
+ if idx == -1 {
+ idx = tbIdx
+ }
+ if tbIdx >= 0 && tbIdx < idx {
+ idx = tbIdx
+ }
+
+ if idx < 0 {
+ line.url = bytes.TrimRight(raw, "\r\n")
+ return line
+ }
+
+ line.url = raw[:idx]
+ raw = raw[idx+1:]
+
+ label := bytes.TrimRight(bytes.TrimLeft(raw, " \t"), "\r\n")
+ if len(label) > 0 {
+ line.label = label
+ }
+
+ return line
+}
+
func parsePreformatToggleLine(raw []byte) PreformatToggleLine {
line := PreformatToggleLine{raw: raw}
diff --git a/gemini/gemtext/parse_line_test.go b/gemini/gemtext/parse_line_test.go
index a07fa3b..82b0c28 100644
--- a/gemini/gemtext/parse_line_test.go
+++ b/gemini/gemtext/parse_line_test.go
@@ -57,6 +57,57 @@ func TestParseLinkLine(t *testing.T) {
}
}
+func TestParsePromptLine(t *testing.T) {
+ tests := []struct {
+ input string
+ url string
+ label string
+ }{
+ {
+ input: "=: gemini.ctrl-c.club/~tjp/ home page\r\n",
+ url: "gemini.ctrl-c.club/~tjp/",
+ label: "home page",
+ },
+ {
+ input: "=: gemi.dev/\n",
+ url: "gemi.dev/",
+ },
+ {
+ input: "=: /gemlog/foobar 2023-01-13 - Foo Bar\n",
+ url: "/gemlog/foobar",
+ label: "2023-01-13 - Foo Bar",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ line := gemtext.ParseLine([]byte(test.input))
+ if line == nil {
+ t.Fatal("ParseLine() returned nil line")
+ }
+ if string(line.Raw()) != string(test.input) {
+ t.Error("Raw() does not match input")
+ }
+
+ if line.Type() != gemtext.LineTypePrompt{
+ t.Errorf("expected LineTypePrompt, got %d", line.Type())
+ }
+ link, ok := line.(gemtext.PromptLine)
+ if !ok {
+ t.Fatalf("expected a PromptLine, got %T", line)
+ }
+
+ if link.URL() != test.url {
+ t.Errorf("expected url %q, got %q", test.url, link.URL())
+ }
+
+ if link.Label() != test.label {
+ t.Errorf("expected label %q, got %q", test.label, link.Label())
+ }
+ })
+ }
+}
+
func TestParsePreformatToggleLine(t *testing.T) {
tests := []struct {
input string
diff --git a/gemini/gemtext/parse_test.go b/gemini/gemtext/parse_test.go
index d2860ff..6b35431 100644
--- a/gemini/gemtext/parse_test.go
+++ b/gemini/gemtext/parse_test.go
@@ -24,6 +24,8 @@ This is some non-blank regular text.
=> gemini://google.com/ as if
+=: spartan://foo.bar/baz this should be a spartan prompt
+
> this is a quote
> -tjp
@@ -37,7 +39,7 @@ This is some non-blank regular text.
doc, err := gemtext.Parse(bytes.NewBuffer(docBytes))
require.Nil(t, err)
- require.Equal(t, 18, len(doc))
+ require.Equal(t, 20, len(doc))
assert.Equal(t, gemtext.LineTypeHeading1, doc[0].Type())
assert.Equal(t, "# top-level header line\n", string(doc[0].Raw()))
@@ -74,26 +76,33 @@ This is some non-blank regular text.
assertEmptyLine(t, doc[11])
- assert.Equal(t, gemtext.LineTypeQuote, doc[12].Type())
- assert.Equal(t, "> this is a quote\n", string(doc[12].Raw()))
- assert.Equal(t, " this is a quote", doc[12].(gemtext.QuoteLine).Body())
+ assert.Equal(t, gemtext.LineTypePrompt, doc[12].Type())
+ assert.Equal(t, "=: spartan://foo.bar/baz this should be a spartan prompt\n", string(doc[12].Raw()))
+ assert.Equal(t, "spartan://foo.bar/baz", doc[12].(gemtext.PromptLine).URL())
+ assert.Equal(t, "this should be a spartan prompt", doc[12].(gemtext.PromptLine).Label())
- assert.Equal(t, gemtext.LineTypeQuote, doc[13].Type())
- assert.Equal(t, "> -tjp\n", string(doc[13].Raw()))
- assert.Equal(t, " -tjp", doc[13].(gemtext.QuoteLine).Body())
+ assertEmptyLine(t, doc[13])
- assertEmptyLine(t, doc[14])
+ assert.Equal(t, gemtext.LineTypeQuote, doc[14].Type())
+ assert.Equal(t, "> this is a quote\n", string(doc[14].Raw()))
+ assert.Equal(t, " this is a quote", doc[14].(gemtext.QuoteLine).Body())
- assert.Equal(t, gemtext.LineTypePreformatToggle, doc[15].Type())
- assert.Equal(t, "```pre-formatted code\n", string(doc[15].Raw()))
- assert.Equal(t, "pre-formatted code", doc[15].(gemtext.PreformatToggleLine).AltText())
+ assert.Equal(t, gemtext.LineTypeQuote, doc[15].Type())
+ assert.Equal(t, "> -tjp\n", string(doc[15].Raw()))
+ assert.Equal(t, " -tjp", doc[15].(gemtext.QuoteLine).Body())
- assert.Equal(t, gemtext.LineTypePreformattedText, doc[16].Type())
- assert.Equal(t, "doc := gemtext.Parse(req.Body)\n", string(doc[16].Raw()))
+ assertEmptyLine(t, doc[16])
assert.Equal(t, gemtext.LineTypePreformatToggle, doc[17].Type())
- assert.Equal(t, "```ignored closing alt-text\n", string(doc[17].Raw()))
- assert.Equal(t, "", doc[17].(gemtext.PreformatToggleLine).AltText())
+ assert.Equal(t, "```pre-formatted code\n", string(doc[17].Raw()))
+ assert.Equal(t, "pre-formatted code", doc[17].(gemtext.PreformatToggleLine).AltText())
+
+ assert.Equal(t, gemtext.LineTypePreformattedText, doc[18].Type())
+ assert.Equal(t, "doc := gemtext.Parse(req.Body)\n", string(doc[18].Raw()))
+
+ assert.Equal(t, gemtext.LineTypePreformatToggle, doc[19].Type())
+ assert.Equal(t, "```ignored closing alt-text\n", string(doc[19].Raw()))
+ assert.Equal(t, "", doc[19].(gemtext.PreformatToggleLine).AltText())
// ensure we can rebuild the original doc from all the line.Raw()s
buf := &bytes.Buffer{}
diff --git a/gemini/gemtext/types.go b/gemini/gemtext/types.go
index 440fed4..3965b11 100644
--- a/gemini/gemtext/types.go
+++ b/gemini/gemtext/types.go
@@ -16,6 +16,13 @@ const (
// The line is a LinkLine.
LineTypeLink
+ // LineTypePrompt is a spartan =: prompt line.
+ //
+ // =:[<ws>]<url>[<ws><label>][\r]\n
+ //
+ // The line is a PromptLine.
+ LineTypePrompt
+
// LineTypePreformatToggle switches the document between pre-formatted text or not.
//
// ```[<alt-text>][\r]\n
@@ -111,6 +118,25 @@ func (ll LinkLine) URL() string { return string(ll.url) }
// Label returns the label portion of the line.
func (ll LinkLine) Label() string { return string(ll.label) }
+// PromptLine is a Spartan =: prompt line.
+type PromptLine struct {
+ raw []byte
+ url []byte
+ label []byte
+}
+
+func (pl PromptLine) Type() LineType { return LineTypePrompt }
+func (pl PromptLine) Raw() []byte { return pl.raw }
+func (pl PromptLine) String() string { return string(pl.raw) }
+
+// URL returns the original url portion of the line.
+//
+// It is not guaranteed to be a valid URL.
+func (pl PromptLine) URL() string { return string(pl.url) }
+
+// Label retrns the label portion of the line.
+func (pl PromptLine) Label() string { return string(pl.label) }
+
// PreformatToggleLine is a preformatted text toggle line.
type PreformatToggleLine struct {
raw []byte