public API cleanup
This commit is contained in:
parent
f4102dbce5
commit
4441600775
|
|
@ -192,7 +192,7 @@ func windowMode(args []string) error {
|
|||
// Update frontmatter if applicable
|
||||
if supportsFrontmatter && len(body) > 0 {
|
||||
// Parse existing frontmatter to preserve Identifier and FileType
|
||||
existing, fileType, err := frontmatter.Unmarshal(string(body), ext)
|
||||
existing, fileType, err := frontmatter.Unmarshal(body, ext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse existing frontmatter: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ func ExtractFrontMatter(path string) (*metadata.FrontMatter, metadata.FileType,
|
|||
return nil, "", fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
return frontmatter.Unmarshal(string(content), ext)
|
||||
return frontmatter.Unmarshal(content, ext)
|
||||
}
|
||||
|
||||
// UpdateFrontMatter updates the front matter in a file.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ package fs
|
|||
|
||||
import (
|
||||
"denote/internal/disk"
|
||||
"denote/pkg/encoding/results"
|
||||
"denote/pkg/metadata"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -820,7 +821,7 @@ func (s *server) pathToNoteIndex(path string) int {
|
|||
func (s *server) getIndexContent() string {
|
||||
if s.filterQuery == "" {
|
||||
// No filter: return all notes
|
||||
return string(s.notes.Bytes())
|
||||
return string(results.Marshal(s.notes))
|
||||
}
|
||||
|
||||
// Return filtered notes
|
||||
|
|
@ -828,7 +829,7 @@ func (s *server) getIndexContent() string {
|
|||
for _, note := range s.filteredNotes {
|
||||
filtered = append(filtered, note)
|
||||
}
|
||||
return string(filtered.Bytes())
|
||||
return string(results.Marshal(filtered))
|
||||
}
|
||||
|
||||
func (s *server) getBacklinks(targetID string) string {
|
||||
|
|
@ -837,8 +838,8 @@ func (s *server) getBacklinks(targetID string) string {
|
|||
denoteDir := s.denoteDir
|
||||
s.mu.RUnlock()
|
||||
|
||||
results := disk.FindBacklinks(targetID, denoteDir, notes)
|
||||
return string(results.Bytes())
|
||||
res := disk.FindBacklinks(targetID, denoteDir, notes)
|
||||
return string(results.Marshal(res))
|
||||
}
|
||||
|
||||
func (s *server) getFileContent(path string) string {
|
||||
|
|
|
|||
20
main.go
20
main.go
|
|
@ -5,6 +5,7 @@ import (
|
|||
p9client "denote/internal/p9/client"
|
||||
p9server "denote/internal/p9/server"
|
||||
"denote/pkg/encoding/frontmatter"
|
||||
"denote/pkg/encoding/results"
|
||||
"denote/pkg/metadata"
|
||||
"fmt"
|
||||
"log"
|
||||
|
|
@ -36,14 +37,13 @@ func readIndex(f *client.Fsys) (metadata.Results, error) {
|
|||
return nil, fmt.Errorf("failed to read index: %w", err)
|
||||
}
|
||||
|
||||
var results metadata.Results
|
||||
results, err = results.FromString(indexContent)
|
||||
rs, err := results.Unmarshal([]byte(indexContent))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate Path for each note
|
||||
for _, note := range results {
|
||||
for _, note := range rs {
|
||||
path, err := p9client.ReadFile(f, "n/"+note.Identifier+"/path")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read path for %s: %w", note.Identifier, err)
|
||||
|
|
@ -51,7 +51,7 @@ func readIndex(f *client.Fsys) (metadata.Results, error) {
|
|||
note.Path = path
|
||||
}
|
||||
|
||||
return results, nil
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// setFilter sets or clears the filter on the 9P server.
|
||||
|
|
@ -180,18 +180,17 @@ func main() {
|
|||
}
|
||||
|
||||
// Parse index
|
||||
var results metadata.Results
|
||||
results, err = results.FromString(indexContent)
|
||||
rs, err := results.Unmarshal([]byte(indexContent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
if len(rs) == 0 {
|
||||
return fmt.Errorf("no note found with identifier %s", identifier)
|
||||
}
|
||||
|
||||
// Read path for first result
|
||||
notePath, err = p9client.ReadFile(f, "n/"+results[0].Identifier+"/path")
|
||||
notePath, err = p9client.ReadFile(f, "n/"+rs[0].Identifier+"/path")
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -357,8 +356,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Parse the window content
|
||||
var emptyResults metadata.Results
|
||||
updated, err := emptyResults.FromString(string(body))
|
||||
updated, err := results.Unmarshal(body)
|
||||
if err != nil {
|
||||
log.Printf("failed to parse window: %v", err)
|
||||
break
|
||||
|
|
@ -463,7 +461,7 @@ func performSearch(w *acme.Win, searchText string) {
|
|||
|
||||
func refreshWindow(w *acme.Win, rs metadata.Results) {
|
||||
w.Addr(",")
|
||||
w.Write("data", rs.Bytes())
|
||||
w.Write("data", results.Marshal(rs))
|
||||
w.Ctl("show")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,9 +78,9 @@ func Marshal(fm *metadata.FrontMatter, fileType metadata.FileType) []byte {
|
|||
// Unmarshal extracts front matter from file content.
|
||||
// ext should be the file extension (e.g., ".md", ".org", ".txt").
|
||||
// Returns the parsed frontmatter and the detected FileType.
|
||||
func Unmarshal(content string, ext string) (*metadata.FrontMatter, metadata.FileType, error) {
|
||||
func Unmarshal(content []byte, ext string) (*metadata.FrontMatter, metadata.FileType, error) {
|
||||
ext = strings.ToLower(ext)
|
||||
text := content
|
||||
text := string(content)
|
||||
|
||||
fm := &metadata.FrontMatter{}
|
||||
var fileType metadata.FileType
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ identifier: 20240101T120000
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, fileType, err := Unmarshal(tt.content, tt.ext)
|
||||
got, fileType, err := Unmarshal([]byte(tt.content), tt.ext)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal() error = %v", err)
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@ func TestUnmarshalMissingFields(t *testing.T) {
|
|||
date: 2024-01-01
|
||||
---`
|
||||
|
||||
got, _, err := Unmarshal(content, ".md")
|
||||
got, _, err := Unmarshal([]byte(content), ".md")
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal() error = %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package results
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"denote/pkg/metadata"
|
||||
)
|
||||
|
||||
// Marshal serializes Results to a pipe-delimited byte format.
|
||||
// Format: identifier | title | tags (comma-separated)
|
||||
func Marshal(rs metadata.Results) []byte {
|
||||
var buf strings.Builder
|
||||
for _, e := range rs {
|
||||
title := e.Title
|
||||
if title == "" {
|
||||
title = "(untitled)"
|
||||
}
|
||||
|
||||
tags := strings.Join(e.Tags, ",")
|
||||
fmt.Fprintf(&buf, "%s | %s | %s\n", e.Identifier, title, tags)
|
||||
}
|
||||
return []byte(buf.String())
|
||||
}
|
||||
|
||||
// Unmarshal parses pipe-delimited byte data into Results.
|
||||
// Format: identifier | title | tags (comma-separated)
|
||||
func Unmarshal(data []byte) (metadata.Results, error) {
|
||||
var results metadata.Results
|
||||
lines := bytes.Split(bytes.TrimSpace(data), []byte("\n"))
|
||||
// Allow lowercase Latin letters, other letters (CJK, etc.), and digits, no spaces
|
||||
tagPattern := regexp.MustCompile(`^([\p{Ll}\p{Lo}\p{Nd}]+,)*[\p{Ll}\p{Lo}\p{Nd}]+$`)
|
||||
|
||||
for lineNum, line := range lines {
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := bytes.Split(line, []byte("|"))
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("line %d: expected 3 columns, got %d (line: %q)", lineNum+1, len(parts), line)
|
||||
}
|
||||
|
||||
identifier := string(bytes.TrimSpace(parts[0]))
|
||||
title := string(bytes.TrimSpace(parts[1]))
|
||||
tagsStr := string(bytes.TrimSpace(parts[2]))
|
||||
|
||||
if identifier == "" {
|
||||
return nil, fmt.Errorf("line %d: identifier cannot be empty", lineNum+1)
|
||||
}
|
||||
|
||||
var tags []string
|
||||
if tagsStr != "" {
|
||||
if !tagPattern.MatchString(tagsStr) {
|
||||
return nil, fmt.Errorf("line %d: tags must be comma-delimited lowercase unicode words (no spaces): got '%s'", lineNum+1, tagsStr)
|
||||
}
|
||||
tags = strings.Split(tagsStr, ",")
|
||||
} else {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
results = append(results, &metadata.Metadata{
|
||||
Identifier: identifier,
|
||||
Title: title,
|
||||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package results
|
||||
|
||||
import (
|
||||
"denote/pkg/metadata"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMarshal validates serialization
|
||||
func TestMarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input metadata.Results
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "single note with tags",
|
||||
input: metadata.Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "Test Note",
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | Test Note | tag1,tag2\n",
|
||||
},
|
||||
{
|
||||
name: "note without tags",
|
||||
input: metadata.Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "Simple Note",
|
||||
Tags: []string{},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | Simple Note | \n",
|
||||
},
|
||||
{
|
||||
name: "note without title",
|
||||
input: metadata.Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "",
|
||||
Tags: []string{"tag"},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | (untitled) | tag\n",
|
||||
},
|
||||
{
|
||||
name: "multiple notes",
|
||||
input: metadata.Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b", "c"}},
|
||||
},
|
||||
want: "20240101T120000 | First | a\n20240102T120000 | Second | b,c\n",
|
||||
},
|
||||
{
|
||||
name: "empty results",
|
||||
input: metadata.Results{},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := string(Marshal(tt.input))
|
||||
if got != tt.want {
|
||||
t.Errorf("Marshal() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnmarshal validates parsing from byte format
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
want metadata.Results
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "single note with tags",
|
||||
input: []byte("20240101T120000 | Test Note | tag1,tag2"),
|
||||
want: metadata.Results{
|
||||
{Identifier: "20240101T120000", Title: "Test Note", Tags: []string{"tag1", "tag2"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "note without tags",
|
||||
input: []byte("20240101T120000 | Simple Note | "),
|
||||
want: metadata.Results{
|
||||
{Identifier: "20240101T120000", Title: "Simple Note", Tags: []string{}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple notes",
|
||||
input: []byte("20240101T120000 | First | a\n20240102T120000 | Second | b,c"),
|
||||
want: metadata.Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b", "c"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
input: []byte(""),
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "wrong column count",
|
||||
input: []byte("20240101T120000 | Title"),
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty identifier",
|
||||
input: []byte(" | Title | tags"),
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tags with spaces",
|
||||
input: []byte("20240101T120000 | Title | tag with spaces"),
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tags with uppercase",
|
||||
input: []byte("20240101T120000 | Title | Tag1,tag2"),
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid lowercase unicode tags",
|
||||
input: []byte("20240101T120000 | Title | tag1,测试,αβγ"),
|
||||
want: metadata.Results{
|
||||
{Identifier: "20240101T120000", Title: "Title", Tags: []string{"tag1", "测试", "αβγ"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "blank lines ignored",
|
||||
input: []byte("20240101T120000 | First | a\n\n20240102T120000 | Second | b"),
|
||||
want: metadata.Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Unmarshal(tt.input)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
|
||||
if len(got) != len(tt.want) {
|
||||
t.Fatalf("Unmarshal() length = %d, want %d", len(got), len(tt.want))
|
||||
}
|
||||
|
||||
for i := range got {
|
||||
if got[i].Identifier != tt.want[i].Identifier {
|
||||
t.Errorf("Result[%d].Identifier = %q, want %q", i, got[i].Identifier, tt.want[i].Identifier)
|
||||
}
|
||||
if got[i].Title != tt.want[i].Title {
|
||||
t.Errorf("Result[%d].Title = %q, want %q", i, got[i].Title, tt.want[i].Title)
|
||||
}
|
||||
if !slices.Equal(got[i].Tags, tt.want[i].Tags) {
|
||||
t.Errorf("Result[%d].Tags = %v, want %v", i, got[i].Tags, tt.want[i].Tags)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -21,65 +21,6 @@ type Metadata struct {
|
|||
|
||||
type Results []*Metadata
|
||||
|
||||
func (es Results) Bytes() []byte {
|
||||
var buf strings.Builder
|
||||
for _, e := range es {
|
||||
title := e.Title
|
||||
if title == "" {
|
||||
title = "(untitled)"
|
||||
}
|
||||
|
||||
tags := strings.Join(e.Tags, ",")
|
||||
fmt.Fprintf(&buf, "%s | %s | %s\n", e.Identifier, title, tags)
|
||||
}
|
||||
return []byte(buf.String())
|
||||
}
|
||||
|
||||
func (es Results) FromString(data string) (Results, error) {
|
||||
var results Results
|
||||
lines := strings.Split(strings.TrimSpace(data), "\n")
|
||||
// Allow lowercase Latin letters, other letters (CJK, etc.), and digits, no spaces
|
||||
tagPattern := regexp.MustCompile(`^([\p{Ll}\p{Lo}\p{Nd}]+,)*[\p{Ll}\p{Lo}\p{Nd}]+$`)
|
||||
|
||||
for lineNum, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("line %d: expected 3 columns, got %d (line: %q)", lineNum+1, len(parts), line)
|
||||
}
|
||||
|
||||
identifier := strings.TrimSpace(parts[0])
|
||||
title := strings.TrimSpace(parts[1])
|
||||
tagsStr := strings.TrimSpace(parts[2])
|
||||
|
||||
if identifier == "" {
|
||||
return nil, fmt.Errorf("line %d: identifier cannot be empty", lineNum+1)
|
||||
}
|
||||
|
||||
var tags []string
|
||||
if tagsStr != "" {
|
||||
if !tagPattern.MatchString(tagsStr) {
|
||||
return nil, fmt.Errorf("line %d: tags must be comma-delimited lowercase unicode words (no spaces): got '%s'", lineNum+1, tagsStr)
|
||||
}
|
||||
tags = strings.Split(tagsStr, ",")
|
||||
} else {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
results = append(results, &Metadata{
|
||||
Identifier: identifier,
|
||||
Title: title,
|
||||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type SortBy string
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -503,184 +503,4 @@ func TestSort(t *testing.T) {
|
|||
t.Errorf("Third item title = %q, want %q", testData[2].Title, "zebra")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestResultsBytes validates serialization
|
||||
func TestResultsBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input Results
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "single note with tags",
|
||||
input: Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "Test Note",
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | Test Note | tag1,tag2\n",
|
||||
},
|
||||
{
|
||||
name: "note without tags",
|
||||
input: Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "Simple Note",
|
||||
Tags: []string{},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | Simple Note | \n",
|
||||
},
|
||||
{
|
||||
name: "note without title",
|
||||
input: Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "",
|
||||
Tags: []string{"tag"},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | (untitled) | tag\n",
|
||||
},
|
||||
{
|
||||
name: "multiple notes",
|
||||
input: Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b", "c"}},
|
||||
},
|
||||
want: "20240101T120000 | First | a\n20240102T120000 | Second | b,c\n",
|
||||
},
|
||||
{
|
||||
name: "empty results",
|
||||
input: Results{},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := string(tt.input.Bytes())
|
||||
if got != tt.want {
|
||||
t.Errorf("Results.Bytes() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestResultsFromString validates parsing from string format
|
||||
func TestResultsFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want Results
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "single note with tags",
|
||||
input: "20240101T120000 | Test Note | tag1,tag2",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "Test Note", Tags: []string{"tag1", "tag2"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "note without tags",
|
||||
input: "20240101T120000 | Simple Note | ",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "Simple Note", Tags: []string{}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple notes",
|
||||
input: "20240101T120000 | First | a\n20240102T120000 | Second | b,c",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b", "c"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
input: "",
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "wrong column count",
|
||||
input: "20240101T120000 | Title",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty identifier",
|
||||
input: " | Title | tags",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tags with spaces",
|
||||
input: "20240101T120000 | Title | tag with spaces",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tags with uppercase",
|
||||
input: "20240101T120000 | Title | Tag1,tag2",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid lowercase unicode tags",
|
||||
input: "20240101T120000 | Title | tag1,测试,αβγ",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "Title", Tags: []string{"tag1", "测试", "αβγ"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "blank lines ignored",
|
||||
input: "20240101T120000 | First | a\n\n20240102T120000 | Second | b",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var r Results
|
||||
got, err := r.FromString(tt.input)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Results.FromString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
|
||||
if len(got) != len(tt.want) {
|
||||
t.Fatalf("Results.FromString() length = %d, want %d", len(got), len(tt.want))
|
||||
}
|
||||
|
||||
for i := range got {
|
||||
if got[i].Identifier != tt.want[i].Identifier {
|
||||
t.Errorf("Result[%d].Identifier = %q, want %q", i, got[i].Identifier, tt.want[i].Identifier)
|
||||
}
|
||||
if got[i].Title != tt.want[i].Title {
|
||||
t.Errorf("Result[%d].Title = %q, want %q", i, got[i].Title, tt.want[i].Title)
|
||||
}
|
||||
if !slices.Equal(got[i].Tags, tt.want[i].Tags) {
|
||||
t.Errorf("Result[%d].Tags = %v, want %v", i, got[i].Tags, tt.want[i].Tags)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue