repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

commit
f33ac35
parent
568a6ac
author
Eric Bower
date
2026-01-10 13:30:43 -0500 EST
chore(prose): list parser tests
2 files changed,  +404, -1
M pkg/shared/listparser.go
+10, -1
 1@@ -12,6 +12,15 @@ import (
 2 	"github.com/araddon/dateparse"
 3 )
 4 
 5+/*
 6+=> https://www.youtube.com/watch?v=HxaD_trXwRE Lexical Scanning in Go - Rob Pike
 7+func run() {
 8+	for state := startState; state != nil {
 9+		state = stateFn(lexer);
10+	}
11+}
12+*/
13+
14 var reIndent = regexp.MustCompile(`^[[:blank:]]+`)
15 
16 type ListParsedText struct {
17@@ -99,7 +108,7 @@ func PublishAtDate(date string) (*time.Time, error) {
18 
19 func TokenToMetaField(meta *ListMetaData, token *SplitToken) error {
20 	switch token.Key {
21-	case "publish_at":
22+	case "date":
23 		publishAt, err := PublishAtDate(token.Value)
24 		if err == nil {
25 			meta.PublishAt = publishAt
A pkg/shared/listparser_test.go
+394, -0
  1@@ -0,0 +1,394 @@
  2+package shared
  3+
  4+import (
  5+	"reflect"
  6+	"testing"
  7+)
  8+
  9+func TestListParseText(t *testing.T) {
 10+	t.Run("TestEmptyList", func(t *testing.T) {
 11+		text := ""
 12+		parsed := ListParseText(text)
 13+		if parsed == nil {
 14+			t.Fatal("parsed should not be nil")
 15+		}
 16+		if len(parsed.Items) != 0 {
 17+			t.Fatalf("expected 0 items, got %d", len(parsed.Items))
 18+		}
 19+		if parsed.ListMetaData == nil {
 20+			t.Fatal("ListMetaData should not be nil")
 21+		}
 22+	})
 23+
 24+	t.Run("TestSimpleListItems", func(t *testing.T) {
 25+		text := "First item\nSecond item"
 26+		parsed := ListParseText(text)
 27+		if parsed == nil {
 28+			t.Fatal("parsed should not be nil")
 29+		}
 30+		if len(parsed.Items) != 2 {
 31+			t.Fatalf("expected 2 items, got %d", len(parsed.Items))
 32+		}
 33+		if parsed.Items[0].Value != "First item" {
 34+			t.Errorf("expected 'First item', got '%s'", parsed.Items[0].Value)
 35+		}
 36+		if !parsed.Items[0].IsText {
 37+			t.Error("expected IsText to be true")
 38+		}
 39+		if parsed.Items[1].Value != "Second item" {
 40+			t.Errorf("expected 'Second item', got '%s'", parsed.Items[1].Value)
 41+		}
 42+		if !parsed.Items[1].IsText {
 43+			t.Error("expected IsText to be true")
 44+		}
 45+	})
 46+
 47+	t.Run("TestListWithEmptyLines", func(t *testing.T) {
 48+		text := "First item\n\nSecond item"
 49+		parsed := ListParseText(text)
 50+		if parsed == nil {
 51+			t.Fatal("parsed should not be nil")
 52+		}
 53+		if len(parsed.Items) != 2 {
 54+			t.Fatalf("expected 2 items, got %d", len(parsed.Items))
 55+		}
 56+		if parsed.Items[0].Value != "First item" {
 57+			t.Errorf("expected 'First item', got '%s'", parsed.Items[0].Value)
 58+		}
 59+		if parsed.Items[1].Value != "Second item" {
 60+			t.Errorf("expected 'Second item', got '%s'", parsed.Items[1].Value)
 61+		}
 62+	})
 63+
 64+	t.Run("TestHyperlinks", func(t *testing.T) {
 65+		text := "=> https://pico.sh\n=> https://prose.sh blog platform"
 66+		parsed := ListParseText(text)
 67+		if parsed == nil {
 68+			t.Fatal("parsed should not be nil")
 69+		}
 70+		if len(parsed.Items) != 2 {
 71+			t.Fatalf("expected 2 items, got %d", len(parsed.Items))
 72+		}
 73+
 74+		if !parsed.Items[0].IsURL {
 75+			t.Error("expected IsURL to be true")
 76+		}
 77+		if string(parsed.Items[0].URL) != "https://pico.sh" {
 78+			t.Errorf("expected URL 'https://pico.sh', got '%s'", parsed.Items[0].URL)
 79+		}
 80+		if parsed.Items[0].Value != "https://pico.sh" {
 81+			t.Errorf("expected Value 'https://pico.sh', got '%s'", parsed.Items[0].Value)
 82+		}
 83+
 84+		if !parsed.Items[1].IsURL {
 85+			t.Error("expected IsURL to be true")
 86+		}
 87+		if string(parsed.Items[1].URL) != "https://prose.sh" {
 88+			t.Errorf("expected URL 'https://prose.sh', got '%s'", parsed.Items[1].URL)
 89+		}
 90+		if parsed.Items[1].Value != "blog platform" {
 91+			t.Errorf("expected Value 'blog platform', got '%s'", parsed.Items[1].Value)
 92+		}
 93+	})
 94+
 95+	t.Run("TestImages", func(t *testing.T) {
 96+		text := "=< https://i.imgur.com/iXMNUN5.jpg\n=< https://i.imgur.com/iXMNUN5.jpg I use arch, btw"
 97+		parsed := ListParseText(text)
 98+		if parsed == nil {
 99+			t.Fatal("parsed should not be nil")
100+		}
101+		if len(parsed.Items) != 2 {
102+			t.Fatalf("expected 2 items, got %d", len(parsed.Items))
103+		}
104+
105+		if !parsed.Items[0].IsImg {
106+			t.Error("expected IsImg to be true")
107+		}
108+		if string(parsed.Items[0].URL) != "https://i.imgur.com/iXMNUN5.jpg" {
109+			t.Errorf("expected URL 'https://i.imgur.com/iXMNUN5.jpg', got '%s'", parsed.Items[0].URL)
110+		}
111+		if parsed.Items[0].Value != "https://i.imgur.com/iXMNUN5.jpg" {
112+			t.Errorf("expected Value 'https://i.imgur.com/iXMNUN5.jpg', got '%s'", parsed.Items[0].Value)
113+		}
114+
115+		if !parsed.Items[1].IsImg {
116+			t.Error("expected IsImg to be true")
117+		}
118+		if string(parsed.Items[1].URL) != "https://i.imgur.com/iXMNUN5.jpg" {
119+			t.Errorf("expected URL 'https://i.imgur.com/iXMNUN5.jpg', got '%s'", parsed.Items[1].URL)
120+		}
121+		if parsed.Items[1].Value != "I use arch, btw" {
122+			t.Errorf("expected Value 'I use arch, btw', got '%s'", parsed.Items[1].Value)
123+		}
124+	})
125+
126+	t.Run("TestHeaders", func(t *testing.T) {
127+		text := "# Header One\n## Header Two"
128+		parsed := ListParseText(text)
129+		if parsed == nil {
130+			t.Fatal("parsed should not be nil")
131+		}
132+		if len(parsed.Items) != 2 {
133+			t.Fatalf("expected 2 items, got %d", len(parsed.Items))
134+		}
135+
136+		if !parsed.Items[0].IsHeaderOne {
137+			t.Error("expected IsHeaderOne to be true")
138+		}
139+		if parsed.Items[0].Value != " Header One" {
140+			t.Errorf("expected ' Header One', got '%s'", parsed.Items[0].Value)
141+		}
142+
143+		if !parsed.Items[1].IsHeaderTwo {
144+			t.Error("expected IsHeaderTwo to be true")
145+		}
146+		if parsed.Items[1].Value != " Header Two" {
147+			t.Errorf("expected ' Header Two', got '%s'", parsed.Items[1].Value)
148+		}
149+	})
150+
151+	t.Run("TestBlockquotes", func(t *testing.T) {
152+		text := "> This is a blockquote."
153+		parsed := ListParseText(text)
154+		if parsed == nil {
155+			t.Fatal("parsed should not be nil")
156+		}
157+		if len(parsed.Items) != 1 {
158+			t.Fatalf("expected 1 item, got %d", len(parsed.Items))
159+		}
160+
161+		if !parsed.Items[0].IsBlock {
162+			t.Error("expected IsBlock to be true")
163+		}
164+		if parsed.Items[0].Value != " This is a blockquote." {
165+			t.Errorf("expected ' This is a blockquote.', got '%s'", parsed.Items[0].Value)
166+		}
167+	})
168+
169+	t.Run("TestPreformattedText", func(t *testing.T) {
170+		text := "```\nsimple preformatted\nline 2\n```"
171+		parsed := ListParseText(text)
172+		if parsed == nil {
173+			t.Fatal("parsed should not be nil")
174+		}
175+		if len(parsed.Items) != 1 {
176+			t.Fatalf("expected 1 item, got %d", len(parsed.Items))
177+		}
178+
179+		if !parsed.Items[0].IsPre {
180+			t.Error("expected IsPre to be true")
181+		}
182+		expected := "\nsimple preformatted\nline 2"
183+		if expected != parsed.Items[0].Value {
184+			t.Errorf("expected '%s', got '%s'", expected, parsed.Items[0].Value)
185+		}
186+	})
187+
188+	t.Run("TestUnclosedPreformattedText", func(t *testing.T) {
189+		text := "```\nsimple unclosed"
190+		parsed := ListParseText(text)
191+		if parsed == nil {
192+			t.Fatal("parsed should not be nil")
193+		}
194+		if len(parsed.Items) != 1 {
195+			t.Fatalf("expected 1 item, got %d", len(parsed.Items))
196+		}
197+		if !parsed.Items[0].IsPre {
198+			t.Error("expected IsPre to be true")
199+		}
200+		expected := "\nsimple unclosed"
201+		if expected != parsed.Items[0].Value {
202+			t.Errorf("expected '%s', got '%s'", expected, parsed.Items[0].Value)
203+		}
204+	})
205+
206+	t.Run("TestVariableParsing", func(t *testing.T) {
207+		text := "=: title Hello World\n=: tags one, two, three\nAn actual list item"
208+		parsed := ListParseText(text)
209+		if parsed == nil {
210+			t.Fatal("parsed should not be nil")
211+		}
212+		if len(parsed.Items) != 3 {
213+			t.Fatalf("expected 3 items, got %d", len(parsed.Items))
214+		}
215+		if parsed.Items[0].Value != "=: title Hello World" {
216+			t.Errorf("expected '=: title Hello World', got '%s'", parsed.Items[0].Value)
217+		}
218+		if parsed.Items[1].Value != "=: tags one, two, three" {
219+			t.Errorf("expected '=: tags one, two, three', got '%s'", parsed.Items[1].Value)
220+		}
221+		if parsed.Items[2].Value != "An actual list item" {
222+			t.Errorf("expected 'An actual list item', got '%s'", parsed.Items[2].Value)
223+		}
224+
225+		if parsed.Title != "Hello World" {
226+			t.Errorf("expected Title 'Hello World', got '%s'", parsed.Title)
227+		}
228+		expectedTags := []string{"one", "two", "three"}
229+		if !reflect.DeepEqual(expectedTags, parsed.Tags) {
230+			t.Errorf("expected Tags '%v', got '%v'", expectedTags, parsed.Tags)
231+		}
232+	})
233+
234+	t.Run("TestNestedListWithSpaces", func(t *testing.T) {
235+		text := "first item\n  second item\n    third item\nlast item"
236+		parsed := ListParseText(text)
237+		if parsed == nil {
238+			t.Fatal("parsed should not be nil")
239+		}
240+		if len(parsed.Items) != 4 {
241+			t.Fatalf("expected 4 items, got %d", len(parsed.Items))
242+		}
243+
244+		if parsed.Items[0].Indent != 0 {
245+			t.Errorf("expected Indent 0, got %d", parsed.Items[0].Indent)
246+		}
247+		if parsed.Items[1].Indent != 1 {
248+			t.Errorf("expected Indent 1, got %d", parsed.Items[1].Indent)
249+		}
250+		if parsed.Items[2].Indent != 2 {
251+			t.Errorf("expected Indent 2, got %d", parsed.Items[2].Indent)
252+		}
253+		if parsed.Items[3].Indent != 0 {
254+			t.Errorf("expected Indent 0, got %d", parsed.Items[3].Indent)
255+		}
256+	})
257+
258+	t.Run("TestNestedListWithTabs", func(t *testing.T) {
259+		text := "first item\n\tsecond item\n\t\tthird item\nlast item"
260+		parsed := ListParseText(text)
261+		if parsed == nil {
262+			t.Fatal("parsed should not be nil")
263+		}
264+		if len(parsed.Items) != 4 {
265+			t.Fatalf("expected 4 items, got %d", len(parsed.Items))
266+		}
267+
268+		if parsed.Items[0].Indent != 0 {
269+			t.Errorf("expected Indent 0, got %d", parsed.Items[0].Indent)
270+		}
271+		if parsed.Items[1].Indent != 1 {
272+			t.Errorf("expected Indent 1, got %d", parsed.Items[1].Indent)
273+		}
274+		if parsed.Items[2].Indent != 2 {
275+			t.Errorf("expected Indent 2, got %d", parsed.Items[2].Indent)
276+		}
277+		if parsed.Items[3].Indent != 0 {
278+			t.Errorf("expected Indent 0, got %d", parsed.Items[3].Indent)
279+		}
280+	})
281+
282+	t.Run("TestDeeplyNestedList", func(t *testing.T) {
283+		text := "1\n  2\n    3\n      4\n        5"
284+		parsed := ListParseText(text)
285+		if parsed == nil {
286+			t.Fatal("parsed should not be nil")
287+		}
288+		if len(parsed.Items) != 5 {
289+			t.Fatalf("expected 5 items, got %d", len(parsed.Items))
290+		}
291+
292+		if parsed.Items[0].Indent != 0 {
293+			t.Errorf("expected Indent 0, got %d", parsed.Items[0].Indent)
294+		}
295+		if parsed.Items[1].Indent != 1 {
296+			t.Errorf("expected Indent 1, got %d", parsed.Items[1].Indent)
297+		}
298+		if parsed.Items[2].Indent != 2 {
299+			t.Errorf("expected Indent 2, got %d", parsed.Items[2].Indent)
300+		}
301+		if parsed.Items[3].Indent != 3 {
302+			t.Errorf("expected Indent 3, got %d", parsed.Items[3].Indent)
303+		}
304+		if parsed.Items[4].Indent != 4 {
305+			t.Errorf("expected Indent 4, got %d", parsed.Items[4].Indent)
306+		}
307+	})
308+
309+	t.Run("TestMixedSpaceAndTabIndentation", func(t *testing.T) {
310+		// spec advises against this, but we test the behavior
311+		text := "first\n\tsecond\n  third"
312+		parsed := ListParseText(text)
313+
314+		if len(parsed.Items) != 3 {
315+			t.Fatalf("expected 3 items, got %d", len(parsed.Items))
316+		}
317+		if parsed.Items[0].Indent != 0 {
318+			t.Errorf("expected Indent 0, got %d", parsed.Items[0].Indent)
319+		}
320+		if parsed.Items[1].Indent != 1 {
321+			t.Errorf("expected Indent 1, got %d", parsed.Items[1].Indent)
322+		}
323+		if parsed.Items[2].Indent != 2 {
324+			t.Errorf("expected Indent 2, got %d", parsed.Items[2].Indent)
325+		}
326+	})
327+
328+	t.Run("TestComprehensiveList", func(t *testing.T) {
329+		text := `=: title My Awesome List
330+# Big Header
331+Here is a normal item.
332+=> https://pico.sh
333+  * nested item 1
334+> A wise man once said...
335+`
336+		parsed := ListParseText(text)
337+		if parsed == nil {
338+			t.Fatal("parsed should not be nil")
339+		}
340+		if parsed.Title != "My Awesome List" {
341+			t.Errorf("expected Title 'My Awesome List', got '%s'", parsed.Title)
342+		}
343+
344+		if len(parsed.Items) != 6 {
345+			t.Fatalf("expected 6 items, got %d", len(parsed.Items))
346+		}
347+
348+		if parsed.Items[0].Value != "=: title My Awesome List" {
349+			t.Errorf("expected '=: title My Awesome List', got '%s'", parsed.Items[0].Value)
350+		}
351+		if !parsed.Items[1].IsHeaderOne {
352+			t.Error("expected IsHeaderOne to be true")
353+		}
354+		if parsed.Items[1].Value != " Big Header" {
355+			t.Errorf("expected ' Big Header', got '%s'", parsed.Items[1].Value)
356+		}
357+		if !parsed.Items[2].IsText {
358+			t.Error("expected IsText to be true")
359+		}
360+		if parsed.Items[2].Value != "Here is a normal item." {
361+			t.Errorf("expected 'Here is a normal item.', got '%s'", parsed.Items[2].Value)
362+		}
363+		if !parsed.Items[3].IsURL {
364+			t.Error("expected IsURL to be true")
365+		}
366+		if parsed.Items[4].Value != "* nested item 1" {
367+			t.Errorf("expected '* nested item 1', got '%s'", parsed.Items[4].Value)
368+		}
369+		if !parsed.Items[5].IsBlock {
370+			t.Error("expected IsBlock to be true")
371+		}
372+	})
373+
374+	t.Run("TestMalformedLines", func(t *testing.T) {
375+		text := "= > malformed url\n= < malformed image"
376+		parsed := ListParseText(text)
377+		if len(parsed.Items) != 2 {
378+			t.Fatalf("expected 2 items, got %d", len(parsed.Items))
379+		}
380+
381+		if !parsed.Items[0].IsText {
382+			t.Error("expected IsText to be true")
383+		}
384+		if parsed.Items[0].Value != "= > malformed url" {
385+			t.Errorf("expected '= > malformed url', got '%s'", parsed.Items[0].Value)
386+		}
387+
388+		if !parsed.Items[1].IsText {
389+			t.Error("expected IsText to be true")
390+		}
391+		if parsed.Items[1].Value != "= < malformed image" {
392+			t.Errorf("expected '= < malformed image', got '%s'", parsed.Items[1].Value)
393+		}
394+	})
395+}