From 4f76f8a997abc63ee35ff67dccaee023aa40ebb7 Mon Sep 17 00:00:00 2001 From: "Diogo Franco (Kovensky)" Date: Tue, 16 Jul 2013 13:52:51 -0300 Subject: [PATCH] misc: Support partial episodes --- misc/episode.go | 40 ++++++++++-- misc/episode_test.go | 35 ++++++---- misc/episodelist_test.go | 26 ++------ misc/episoderange.go | 130 +++++++++++++++++++++++++++++++++----- misc/episoderange_test.go | 47 ++++++++++++-- 5 files changed, 220 insertions(+), 58 deletions(-) diff --git a/misc/episode.go b/misc/episode.go index 0530f55..72140d4 100644 --- a/misc/episode.go +++ b/misc/episode.go @@ -73,6 +73,10 @@ func (et EpisodeType) String() string { type Episode struct { Type EpisodeType Number int + Part int + Parts int +} + // returns how many digits are needed to represent this int func scale(i int) int { return 1 + int(math.Floor(math.Log10(float64(i)))) @@ -97,7 +101,14 @@ func (ep *Episode) scale() int { func (ep *Episode) ContainsEpisodes(ec EpisodeContainer) bool { switch e := ec.(type) { case *Episode: - return ep != nil && ep.Type == e.Type && ep.Number == e.Number + if ep == nil { + return false + } + basic := ep.Type == e.Type && ep.Number == e.Number + if ep.Part < 0 { // a whole episode contains any partial episodes + return basic + } + return basic && ep.Part == e.Part case *EpisodeRange: case *EpisodeList: return EpisodeList{&EpisodeRange{Type: ep.Type, Start: ep, End: ep}}.ContainsEpisodes(ep) @@ -107,7 +118,16 @@ func (ep *Episode) ContainsEpisodes(ec EpisodeContainer) bool { } func (ep *Episode) Format(width int) string { - return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d", ep.Type, ep.Number) + if ep.Part < 0 { // whole episode + return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d", ep.Type, ep.Number) + } + if ep.Parts != 0 { // part X of Y + frac := float64(ep.Number) + float64(ep.Part)/float64(ep.Parts) + + return fmt.Sprintf("%s%0"+strconv.Itoa(width)+".2f", ep.Type, frac) + } + // part N + return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d.%d", ep.Type, ep.Number, ep.Part) } func (ep *Episode) FormatLog(max int) string { @@ -117,12 +137,24 @@ func (ep *Episode) FormatLog(max int) string { // Parses a string in the usual AniDB API episode format and converts into // an Episode. func ParseEpisode(s string) *Episode { + p := int64(-1) + + parts := strings.Split(s, ".") + switch len(parts) { + case 1: // no worries + case 2: + s = parts[0] + p, _ = strconv.ParseInt(parts[1], 10, 32) + default: // too many dots + return nil + } + if no, err := strconv.ParseInt(s, 10, 32); err == nil { - return &Episode{Type: EpisodeTypeRegular, Number: int(no)} + return &Episode{Type: EpisodeTypeRegular, Number: int(no), Part: int(p)} } else if len(s) < 1 { // s too short } else if no, err = strconv.ParseInt(s[1:], 10, 30); err == nil { - return &Episode{Type: parseEpisodeType(s[:1]), Number: int(no)} + return &Episode{Type: parseEpisodeType(s[:1]), Number: int(no), Part: int(p)} } return nil } diff --git a/misc/episode_test.go b/misc/episode_test.go index 373e805..33116fc 100644 --- a/misc/episode_test.go +++ b/misc/episode_test.go @@ -12,21 +12,12 @@ func ExampleParseEpisode() { fmt.Printf("%#v\n", misc.ParseEpisode("")) // invalid episode // Output: - // &misc.Episode{Type:1, Number:1} - // &misc.Episode{Type:2, Number:2} - // &misc.Episode{Type:1, Number:3} + // &misc.Episode{Type:1, Number:1, Part:-1, Parts:0} + // &misc.Episode{Type:2, Number:2, Part:-1, Parts:0} + // &misc.Episode{Type:1, Number:3, Part:-1, Parts:0} // (*misc.Episode)(nil) } -// ParseEpisodeRange("1") <=> ep := ParseEpisode("1"); -// &EpisodeRange{Type: EpisodeTypeRegular, Start: ep, End: ep} -// ParseEpisodeRange("S1-") <=> -// &EpisodeRange{Type: EpisodeTypeSpecial, Start: ParseEpisode("S1")} -// ParseEpisodeRange("T1-T3") <=> -// &EpisodeRange{Type: EpisodeTypeTrailer, Start: ParseEpisode("T1"), End: ParseEpisode("T3")} -// ParseEpisodeRange("5-S3") <=> nil // different episode types in range -// ParseEpisodeRange("") <=> nil // invalid start of range - func ExampleParseEpisodeRange() { fmt.Println(misc.ParseEpisodeRange("01")) fmt.Println(misc.ParseEpisodeRange("S1-")) // endless range @@ -41,3 +32,23 @@ func ExampleParseEpisodeRange() { // // } + +func ExamplePartialEpisode() { + eps := []*misc.Episode{ + misc.ParseEpisode("1.0"), + misc.ParseEpisode("1.1"), + } + for _, ep := range eps { + fmt.Printf("%#v %s\n", ep, ep) + } + for _, ep := range eps { + ep.Parts = 2 + fmt.Printf("%s\n", ep) + } + + // Output: + // &misc.Episode{Type:1, Number:1, Part:0, Parts:0} 1.0 + // &misc.Episode{Type:1, Number:1, Part:1, Parts:0} 1.1 + // 1.00 + // 1.50 +} diff --git a/misc/episodelist_test.go b/misc/episodelist_test.go index 753a4da..a756746 100644 --- a/misc/episodelist_test.go +++ b/misc/episodelist_test.go @@ -5,27 +5,9 @@ import ( "github.com/Kovensky/go-anidb/misc" ) -func ExampleEpisodeRange_Merge() { - a := misc.ParseEpisodeRange("5-7") - b := misc.ParseEpisodeRange("8-12") - fmt.Println(a.Merge(b)) // 5-7 + 8-12 +func ExampleEpisodeList_Simplify() { + a := misc.ParseEpisodeList("1,2,3,5,10-14,13-15,,S3-S6,C7-C10,S1,S7,S8-") + fmt.Println(a.Simplify()) - b = misc.ParseEpisodeRange("3-6") - fmt.Println(a.Merge(b)) // 5-7 + 3-6 - - b = misc.ParseEpisodeRange("10-12") - fmt.Println(a.Merge(b)) // 5-7 + 10-12 (invalid, not touching) - - b = misc.ParseEpisodeRange("S1-S3") - fmt.Println(a.Merge(b)) // 5-7 + S1-S3 (invalid, different types) - - a = misc.ParseEpisodeRange("S3-S10") - fmt.Println(a.Merge(b)) // S3-S10 + S1-S3 - - // Output: - // 05-12 - // 3-7 - // - // - // S01-S10 + // Output: 01-03,05,10-15,S1,S3-,C07-C10 } diff --git a/misc/episoderange.go b/misc/episoderange.go index fbc5cd3..0f11b0c 100644 --- a/misc/episoderange.go +++ b/misc/episoderange.go @@ -106,7 +106,13 @@ func (a *EpisodeRange) Merge(b *EpisodeRange) (c *EpisodeRange) { if a.touches(b) { c = &EpisodeRange{Type: a.Type} - if a.Start.Number <= b.Start.Number { + if a.Start.Number == b.Start.Number { + if a.Start.Part <= b.Start.Part { + c.Start = a.Start + } else { + c.Start = b.Start + } + } else if a.Start.Number < b.Start.Number { c.Start = a.Start } else { c.Start = b.Start @@ -115,7 +121,13 @@ func (a *EpisodeRange) Merge(b *EpisodeRange) (c *EpisodeRange) { switch { case a.End == nil || b.End == nil: c.End = nil - case a.End.Number >= b.End.Number: + case a.End.Number == b.End.Number: + if a.End.Part >= b.End.Part { + c.End = a.End + } else { + c.End = b.End + } + case a.End.Number > b.End.Number: c.End = a.End default: c.End = b.End @@ -135,8 +147,9 @@ func (a *EpisodeRange) Equals(b *EpisodeRange) bool { } if a.Type == b.Type { - if a.End == b.End || (a.End != nil && b.End != nil && a.End.Number == b.End.Number) { - if a.Start == b.Start || a.Start.Number == b.Start.Number { + if a.End == b.End || (a.End != nil && b.End != nil && + a.End.Number == b.End.Number && a.End.Part == b.End.Part) { + if a.Start == b.Start || a.Start.Number == b.Start.Number && a.Start.Part == b.Start.Part { return true } } @@ -144,46 +157,131 @@ func (a *EpisodeRange) Equals(b *EpisodeRange) bool { return false } +// CORNER CASE: e.g. 1.3,2.0 (or 1.3,2) always touch, +// even if there's an unlisted 1.4 between them; unless +// the part count is known. func (a *EpisodeRange) touches(b *EpisodeRange) bool { if a == nil || b == nil || a.Type != b.Type { return false } switch { + case a == b: + // log.Println("same pointers") + return true + case a.Start == b.Start, a.End != nil && a.End == b.End: + // log.Println("share pointers") + return true + case a.End == nil: switch { case b.End == nil: - // both infinite + // log.Println("both infinite") return true - case b.End.Number >= a.Start.Number-1: - // {b [ } a ... - // start-1 so it's still true when they're only adjacent + case b.End.Number == a.Start.Number: + switch { + // either is whole, or parts are adjacent/overlap + case b.End.Part == -1, a.Start.Part == -1, + b.End.Part >= a.Start.Part-1: + // log.Printf("{ %s [} %s ...", b.End, a.Start) + return true + } + // only if start of next range is whole or is first part + case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0: + switch { + // end is whole, or is last part, or part count is unknown + case b.End.Part == -1, b.End.Parts == 0, + b.End.Part == b.End.Parts: + // log.Printf("{ %s }[ %s ...", b.End, a.Start) + return true + } + case b.End.Number > a.Start.Number: + // log.Printf("{ %s [ } %s ...", b.End, a.Start) return true } case b.End == nil: switch { - case a.End.Number >= b.Start.Number-1: - // [a { ] b ... + case a.End.Number == b.Start.Number: + switch { + case a.End.Part == -1, b.Start.Part == -1, + a.End.Part >= b.Start.Part-1: + // log.Printf("[ %s {] %s ...", a.End, b.Start) + return true + } + case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0: + switch { + case a.End.Part == -1, a.End.Parts == 0, + a.End.Part == a.End.Parts: + // log.Printf("[ %s ]{ %s ...", a.End, b.Start) + return true + } + case a.End.Number > b.Start.Number: + // log.Printf("[ %s { ] %s ...", a.End, b.Start) return true } - case a.Start.Number == b.Start.Number || a.End.Number == b.End.Number: + case a.Start.Number == b.Start.Number: // touching - return true + switch { + // either is whole, or parts are immediately adjacent + case a.Start.Part == -1, b.Start.Part == -1, + a.Start.Part == b.Start.Part, + a.Start.Part == b.Start.Part-1, + a.Start.Part == b.Start.Part+1: + // log.Printf("[{ %s - %s ]}", a.End, b.Start) + return true + } + case a.End.Number == b.End.Number: + switch { + case a.End.Part == -1, b.End.Part == -1, + a.End.Part == b.End.Part, + a.End.Part == b.End.Part-1, + a.End.Part == b.End.Part+1: + // log.Printf("{[ %s - %s }]", b.End, a.Start) + return true + } case a.End.Number < b.End.Number: switch { - case a.End.Number >= b.Start.Number-1: - // [a { ] b} + case a.End.Number == b.Start.Number: + switch { + case a.End.Part == -1, b.Start.Part == -1, + a.End.Part >= b.Start.Part-1: + // log.Printf("[ %s {] %s }", a.End, b.Start) + return true + } + case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0: + switch { + case b.End.Part == -1, b.End.Parts == 0, + b.End.Part == b.End.Parts: + // log.Printf("[ %s ]{ %s }", a.End, b.Start) + return true + } + case a.End.Number > b.Start.Number: + // log.Printf("[ %s { ] %s }", a.End, b.Start) return true } case b.End.Number < a.End.Number: switch { - case b.End.Number >= a.Start.Number-1: - // {b [ } a] + case b.End.Number == a.Start.Number: + switch { + case b.End.Part == -1, a.Start.Part == -1, + b.End.Part >= a.Start.Part-1: + // log.Printf("{ %s [} %s ]", b.End, a.Start) + return true + } + case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0: + switch { + case b.End.Part == -1, b.End.Parts == 0, + b.End.Part == b.End.Parts: + // log.Printf("{ %s }[ %s ]", b.End, a.Start) + return true + } + case b.End.Number > a.Start.Number: + // log.Printf("{ %s [ } %s ]", b.End, a.Start) return true } } diff --git a/misc/episoderange_test.go b/misc/episoderange_test.go index a756746..df67268 100644 --- a/misc/episoderange_test.go +++ b/misc/episoderange_test.go @@ -5,9 +5,48 @@ import ( "github.com/Kovensky/go-anidb/misc" ) -func ExampleEpisodeList_Simplify() { - a := misc.ParseEpisodeList("1,2,3,5,10-14,13-15,,S3-S6,C7-C10,S1,S7,S8-") - fmt.Println(a.Simplify()) +func ExampleEpisodeRange_Merge() { + a := misc.ParseEpisodeRange("5-7") + b := misc.ParseEpisodeRange("8-12") + fmt.Println(a.Merge(b)) // 5-7 + 8-12 - // Output: 01-03,05,10-15,S1,S3-,C07-C10 + b = misc.ParseEpisodeRange("3-6") + fmt.Println(a.Merge(b)) // 5-7 + 3-6 + + b = misc.ParseEpisodeRange("10-12") + fmt.Println(a.Merge(b)) // 5-7 + 10-12 (invalid, not touching) + + b = misc.ParseEpisodeRange("S1-S3") + fmt.Println(a.Merge(b)) // 5-7 + S1-S3 (invalid, different types) + + a = misc.ParseEpisodeRange("S3-S10") + fmt.Println(a.Merge(b)) // S3-S10 + S1-S3 + + // Output: + // 05-12 + // 3-7 + // + // + // S01-S10 +} + +func ExampleEpisodeRange_PartialMerge() { + a := misc.ParseEpisodeRange("2.1-2.3") + b := misc.ParseEpisodeRange("3.0") + fmt.Println(a.Merge(b)) // 2.1-2.3 + 3.0 + + b = misc.ParseEpisodeRange("3.1") + fmt.Println(a.Merge(b)) // 2.1-2.3 + 3.1 + + b = misc.ParseEpisodeRange("1") + fmt.Println(a.Merge(b)) // 2.1-2.3 + 1 + + a = misc.ParseEpisodeRange("2.0-2.3") + fmt.Println(a.Merge(b)) // 2.0-2.3 + 1 + + // Output: + // 2.1-3.0 + // + // + // 1-2.3 } -- 2.44.0