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))))
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)
}
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 {
// 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
}
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
// <nil>
// <nil>
}
+
+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
+}
"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
- // <nil>
- // <nil>
- // S01-S10
+ // Output: 01-03,05,10-15,S1,S3-,C07-C10
}
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
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
}
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
}
}
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
}
}
"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
+ // <nil>
+ // <nil>
+ // 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
+ // <nil>
+ // <nil>
+ // 1-2.3
}