Import lists from CSV or text and match each entry
The list editor gets an Import button. A .csv/.tsv/.txt file or pasted text is parsed on the server: header columns (Title, Year, Artist or Director, Note, Rank), numbered lines and trailing "(1999)" years are understood, and Canon's own export round-trips. Each row is then searched with the list's provider and scored. A row is matched automatically only when one result fits exactly and nothing else fits as well; a year or artist in the file breaks ties, an exact title beats a bracketed version like "(Live)", and "Artist - Title" lines are tried both ways. Custom lists need a year or subtitle to be certain. Everything else is shown for review with a recommended pick, the other results, "Add as written", Skip, and a search-again box. Entries are added in file order after the existing ones, with any notes from the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aadacaff95
commit
dadf835be3
10 files changed
+1131
No files matched your search
@@ -0,0 +1,93 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/cdnutter/canon/internal/providers"
|
||||
)
|
||||
|
||||
func TestParseText(t *testing.T) {
|
||||
got, err := Parse([]byte("1. Heat (1995)\n2) 2001: A Space Odyssey\n\n3 - Alien [1979]\n"), "list.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []Row{{Title: "Heat", Year: "1995"}, {Title: "2001: A Space Odyssey"}, {Title: "Alien", Year: "1979"}}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got %+v", got)
|
||||
}
|
||||
// Not every line is numbered, so nothing is stripped.
|
||||
got, _ = Parse([]byte("2001: A Space Odyssey\nHeat\n1917\n"), "x.txt")
|
||||
if got[0].Title != "2001: A Space Odyssey" || got[2].Title != "1917" {
|
||||
t.Fatalf("got %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCSV(t *testing.T) {
|
||||
csv := "\xef\xbb\xbfRank,Title,Year,Director,Why\n2,Alien,1979,Ridley Scott,\"Tense, perfect\"\n1,Heat,1995,Michael Mann,\n"
|
||||
got, err := Parse([]byte(csv), "films.csv")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []Row{
|
||||
{Title: "Heat", Year: "1995", Subtitle: "Michael Mann"},
|
||||
{Title: "Alien", Year: "1979", Subtitle: "Ridley Scott", Note: "Tense, perfect"},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got %+v", got)
|
||||
}
|
||||
// Canon's own export: one quoted column, no header.
|
||||
got, _ = Parse([]byte("\xef\xbb\xbf\"Heat\"\r\n\"Crouching Tiger, Hidden Dragon\"\r\n"), "films.csv")
|
||||
if len(got) != 2 || got[1].Title != "Crouching Tiger, Hidden Dragon" {
|
||||
t.Fatalf("got %+v", got)
|
||||
}
|
||||
// Headerless rank,title,year.
|
||||
got, _ = Parse([]byte("1,Heat,1995\n2,Alien,1979\n"), "x.csv")
|
||||
if got[1] != (Row{Title: "Alien", Year: "1979"}) {
|
||||
t.Fatalf("got %+v", got)
|
||||
}
|
||||
if _, err := Parse([]byte("\n \n"), "x.txt"); err != ErrEmpty {
|
||||
t.Fatalf("empty file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func res(title, year, sub string) providers.Result {
|
||||
return providers.Result{Provider: "t", ProviderID: title + year + sub, Title: title, Year: year, Subtitle: sub}
|
||||
}
|
||||
|
||||
func TestRank(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
row Row
|
||||
category string
|
||||
results []providers.Result
|
||||
status string
|
||||
top string
|
||||
}{
|
||||
{"exact and alone", Row{Title: "Alien"}, "movie", []providers.Result{res("Alien", "1979", ""), res("Aliens", "1986", "")}, Matched, "Alien1979"},
|
||||
{"remake, no year", Row{Title: "Heat"}, "movie", []providers.Result{res("Heat", "1995", ""), res("Heat", "1986", "")}, Review, "Heat1995"},
|
||||
{"remake, year given", Row{Title: "Heat", Year: "1986"}, "movie", []providers.Result{res("Heat", "1995", ""), res("Heat", "1986", "")}, Matched, "Heat1986"},
|
||||
{"loose title", Row{Title: "Star Wars"}, "movie", []providers.Result{res("Star Wars: A New Hope", "1977", "")}, Review, "Star Wars: A New Hope1977"},
|
||||
{"punctuation and accents", Row{Title: "amelie"}, "movie", []providers.Result{res("Amélie", "2001", "")}, Matched, "Amélie2001"},
|
||||
{"wikipedia qualifier", Row{Title: "The Matrix", Year: "1999"}, "custom", []providers.Result{res("The Matrix (1999 film)", "1999", "")}, Matched, "The Matrix (1999 film)1999"},
|
||||
{"artist - title", Row{Title: "Radiohead - Creep"}, "song", []providers.Result{res("Creep", "1992", "TLC"), res("Creep", "1992", "Radiohead")}, Matched, "Creep1992Radiohead"},
|
||||
{"title, no artist", Row{Title: "Creep"}, "song", []providers.Result{res("Creep", "1992", "TLC"), res("Creep", "1992", "Radiohead")}, Review, "Creep1992TLC"},
|
||||
{"same album twice", Row{Title: "OK Computer"}, "album", []providers.Result{res("OK Computer", "1997", "Radiohead"), res("OK Computer", "1997", "Radiohead")}, Matched, "OK Computer1997Radiohead"},
|
||||
{"wrong year", Row{Title: "Alien", Year: "1990"}, "movie", []providers.Result{res("Alien", "1979", "")}, Review, "Alien1979"},
|
||||
{"exact beats a bracketed version", Row{Title: "Radiohead - Creep"}, "song", []providers.Result{res("Creep (Acoustic)", "", "Radiohead"), res("Creep", "", "Radiohead")}, Matched, "CreepRadiohead"},
|
||||
{"only bracketed versions", Row{Title: "Hey Jude"}, "song", []providers.Result{res("Hey Jude (Remastered 2015)", "", "The Beatles")}, Matched, "Hey Jude (Remastered 2015)The Beatles"},
|
||||
{"year beats no year", Row{Title: "The Matrix", Year: "1999"}, "custom", []providers.Result{res("The Matrix", "", "American media franchise"), res("The Matrix", "1999", "Film by the Wachowskis")}, Matched, "The Matrix1999Film by the Wachowskis"},
|
||||
{"artist confirmed beats a literal title", Row{Title: "Radiohead - Creep"}, "song", []providers.Result{res("Radiohead - Creep", "", "Klangsberg"), res("Creep", "", "Radiohead")}, Matched, "CreepRadiohead"},
|
||||
{"custom needs confirming", Row{Title: "Heat"}, "custom", []providers.Result{res("Heat", "", "Type of energy transfer")}, Review, "HeatType of energy transfer"},
|
||||
{"nothing", Row{Title: "zzz"}, "movie", nil, None, ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
status, scored := Rank(c.row, c.category, c.results)
|
||||
if status != c.status {
|
||||
t.Errorf("%s: status %s, want %s", c.name, status, c.status)
|
||||
}
|
||||
if len(scored) > 0 && scored[0].ProviderID != c.top {
|
||||
t.Errorf("%s: top %s, want %s", c.name, scored[0].ProviderID, c.top)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user