From e53bfa3aeb134ae3a00c28446d2b18eda5ffdbf4 Mon Sep 17 00:00:00 2001 From: Martin Polden Date: Tue, 21 Sep 2021 21:24:47 +0200 Subject: parser: Support episode numbers using roman numerals --- parser/parser.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++++-- parser/parser_test.go | 48 +++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index 1404542..a2087bf 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -17,7 +17,7 @@ var ( regexp.MustCompile(`^(?P.+?)\.S(?P\d{2})(?:E(?P\d{2}))?`), // S01, S01E04 regexp.MustCompile(`^(?P.+?)\.E(?P\d{2})`), // E04 regexp.MustCompile(`^(?P.+?)\.(?P\d{1,2})x(?P\d{2})`), // 1x04, 01x04 - regexp.MustCompile(`^(?P.+?)\.Part\.?(?P\d{1,2})`), // Part4, Part11, Part.4, Part.11 + regexp.MustCompile(`^(?P.+?)\.P(?:ar)?t\.?(?P([^.]+))`), // P(ar)t(.)11, Pt(.)XI } splitPattern = regexp.MustCompile(`[-_.]`) ) @@ -114,12 +114,15 @@ func Show(s string) (Media, error) { case "season": season, err = strconv.Atoi(group) if err != nil { - return Media{}, fmt.Errorf("invalid input: %q: %s", s, err) + return Media{}, fmt.Errorf("invalid input: %q: %w", s, err) } case "episode": episode, err = strconv.Atoi(group) if err != nil { - return Media{}, fmt.Errorf("invalid input: %q: %s", s, err) + episode, err = rtoi(group) + if err != nil { + return Media{}, fmt.Errorf("invalid input: %q: %w", s, err) + } } } } @@ -165,3 +168,72 @@ func codec(s string) string { return false }) } + +var numerals = []numeral{ + // units + {"IX", 9}, + {"VIII", 8}, + {"VII", 7}, + {"VI", 6}, + {"IV", 4}, + {"III", 3}, + {"II", 2}, + {"V", 5}, + {"I", 1}, + // tens + {"XC", 90}, + {"LXXX", 80}, + {"LXX", 70}, + {"LX", 60}, + {"XL", 40}, + {"XXX", 30}, + {"XX", 20}, + {"L", 50}, + {"X", 10}, + // hundreds + {"CM", 900}, + {"DCCC", 800}, + {"DCC", 700}, + {"DC", 600}, + {"CD", 400}, + {"CCC", 300}, + {"CC", 200}, + {"D", 500}, + {"C", 100}, + // thousands + {"MMM", 3000}, + {"MM", 2000}, + {"M", 1000}, +} + +type numeral struct { + s string + n int +} + +func (n numeral) parse(s string, lastIndex int) (string, int, int) { + i := strings.LastIndex(s, n.s) + if i > -1 && i < lastIndex { + rest := s[:i] + s[i+len(n.s):] + return rest, i, n.n + } + return s, lastIndex, 0 +} + +func rtoi(s string) (int, error) { + // needlessly complete parsing of roman numerals + sum := 0 + lastIndex := len(s) + for _, num := range numerals { + var n int + s, lastIndex, n = num.parse(s, lastIndex) + sum += n + if s == "" { + break + } + } + if s != "" { + return 0, fmt.Errorf("invalid roman numeral: %q", s) + } + return sum, nil +} diff --git a/parser/parser_test.go b/parser/parser_test.go index caa8a02..cb6c3d7 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -172,6 +172,15 @@ func TestShow(t *testing.T) { Episode: 16, Codec: "xvid", }}, + {"Generation.Kill.Pt.VII.Bomb.in.the.Garden.720p.Bluray.X264-DIMENSION", + Media{ + Release: "Generation.Kill.Pt.VII.Bomb.in.the.Garden.720p.Bluray.X264-DIMENSION", + Name: "Generation.Kill", + Season: 1, + Episode: 7, + Resolution: "720p", + Codec: "x264", + }}, } for _, tt := range tests { got, err := Show(tt.in) @@ -200,3 +209,42 @@ func TestReplaceName(t *testing.T) { t.Errorf("Expected %q, got %q", want, m.Name) } } + +func TestRtoi(t *testing.T) { + assertRtoiErr(t, "foo") + assertRtoiErr(t, "VIM") + assertRtoiErr(t, "DMVI") + + assertRtoi(t, "XXXIX", 39) + assertRtoi(t, "CCXLVI", 246) + assertRtoi(t, "DCCLXXXIX", 789) + assertRtoi(t, "MMCDXXI", 2421) + + assertRtoi(t, "CLX", 160) + assertRtoi(t, "CCVII", 207) + assertRtoi(t, "MIX", 1009) + assertRtoi(t, "MLXVI", 1066) + + assertRtoi(t, "MDCCLXXVI", 1776) + assertRtoi(t, "MCMXVIII", 1918) + assertRtoi(t, "MCMLIV", 1954) + assertRtoi(t, "MMXIV", 2014) + + assertRtoi(t, "MMMCMXCIX", 3999) +} + +func assertRtoiErr(t *testing.T, rnum string) { + if n, err := rtoi(rnum); err == nil { + t.Fatalf("rtoi(%q) = (%d, %v), want error", rnum, n, err) + } +} + +func assertRtoi(t *testing.T, rnum string, expected int) { + n, err := rtoi(rnum) + if err != nil { + t.Fatal(err) + } + if n != expected { + t.Errorf("rtoi(%q) = %d, want %d", rnum, n, expected) + } +} -- cgit v1.2.3