8 // A range of episodes with a start and possibly without an end.
9 type EpisodeRange struct {
10 Type EpisodeType // Must be equal to both the Start and End types; if End is nil, must be equal to the Start type
11 Start *Episode // The start of the range
12 End *Episode // The end of the range; may be nil, which represents an endless range
15 func EpisodeToRange(ep *Episode) *EpisodeRange {
23 // Converts the EpisodeRange into AniDB API range format.
24 func (er *EpisodeRange) String() string {
25 return er.Format(er.scale())
28 func (er *EpisodeRange) Format(width int) string {
29 if er.Start == er.End || (er.End != nil && *(er.Start) == *(er.End)) {
30 return er.Start.Format(width)
34 return fmt.Sprintf("%s-", er.Start.Format(width))
36 return fmt.Sprintf("%s-%s", er.Start.Format(width), er.End.Format(width))
39 func (er *EpisodeRange) FormatLog(max int) string {
40 return er.Format(scale(max))
43 func (er *EpisodeRange) scale() int {
47 s, e := er.Start.scale(), er.End.scale()
54 // If ec is an *Episode, returns true if the Episode is of the same type as the range
55 // and has a Number >= Start.Number; if End is defined, then the episode's Number must
56 // also be <= End.Number.
58 // If ec is an *EpisodeRange, returns true if they are both of the same type and
59 // the ec's Start.Number is >= this range's Start.Number;
60 // also returns true if this EpisodeRange is unbounded or if the ec is bounded
61 // and ec's End.Number is <= this range's End.Number.
63 // If ec is an EpisodeList, returns true if all listed EpisodeRanges are contained
64 // by this EpisodeRange.
66 // Returns false otherwise.
67 func (er *EpisodeRange) ContainsEpisodes(ec EpisodeContainer) bool {
71 if er.Start == nil || er.Start.Type != er.Type ||
72 (er.End != nil && er.End.Type != er.Type) {
73 panic("Invalid EpisodeRange used")
76 switch e := ec.(type) {
78 if e.Type == er.Type && e.Number >= er.Start.Number {
81 } else if e.Number <= er.End.Number {
86 if e.Type == er.Type {
87 if e.Start.Number >= er.Start.Number {
90 } else if e.End == nil {
91 return false // a finite set can't contain an infinite one
92 } else if e.End.Number <= er.End.Number {
98 for _, ec := range e {
99 if !er.ContainsEpisodes(ec) {
109 // Tries to merge a with b, returning a new *EpisodeRange that's
110 // a superset of both a and b.
112 // Returns nil if a and b don't intersect, or are not adjacent.
113 func (a *EpisodeRange) Merge(b *EpisodeRange) (c *EpisodeRange) {
115 c = &EpisodeRange{Type: a.Type}
118 case a.Start.Number == b.Start.Number:
120 case a.Start.Part < 0:
122 case b.Start.Part < 0:
124 case a.Start.Part <= b.Start.Part:
129 case a.Start.Number < b.Start.Number:
136 case a.End == nil || b.End == nil:
138 case a.End.Number == b.End.Number:
144 case a.End.Part >= b.End.Part:
149 case a.End.Number > b.End.Number:
158 // Check if the given range is not nil, has a defined start
159 // and, if it has an end, that the end ends after the start.
160 func (er *EpisodeRange) Valid() bool {
162 case er == nil, er.Start == nil:
166 case er.Start.Number < er.End.Number:
168 case er.Start.Number > er.End.Number:
170 case er.Start.Part <= er.End.Part:
177 // Simplifies the Start/End ranges if one contains the other.
178 // Sets the pointers to be identical if the range is modified.
180 // Modifies in-place, returns itself.
181 func (er *EpisodeRange) Simplify() *EpisodeRange {
183 case er.Start.ContainsEpisodes(er.End):
185 case er.End != nil && er.End.ContainsEpisodes(er.Start):
191 // Splits the range into one or two ranges, using the given
192 // Episode as the split point. The Episode is not included in
193 // the resulting ranges.
194 func (er *EpisodeRange) Split(ep *Episode) []*EpisodeRange {
195 if !er.ContainsEpisodes(ep) { // implies same type
196 return []*EpisodeRange{er}
199 return []*EpisodeRange{nil, nil}
216 case a.ContainsEpisodes(ep) && b.ContainsEpisodes(ep):
217 return []*EpisodeRange{nil, nil}
218 case a.ContainsEpisodes(ep):
224 if a.Number == b.Number && b.Parts > 0 {
233 return []*EpisodeRange{nil, r.Simplify()}
234 case b.ContainsEpisodes(ep):
240 if b.Number == a.Number {
244 } else if b.Part < 0 {
253 return []*EpisodeRange{r.Simplify(), nil}
280 return []*EpisodeRange{ra, rb}
284 // Returns true if both ranges are of the same type and
285 // have identical start/end positions
286 func (a *EpisodeRange) Equals(b *EpisodeRange) bool {
287 if a == b { // pointers to the same thing
290 if a == nil || b == nil {
294 if a.Type == b.Type {
295 if a.End == b.End || (a.End != nil && b.End != nil &&
296 a.End.Number == b.End.Number && a.End.Part == b.End.Part) {
297 if a.Start == b.Start || a.Start.Number == b.Start.Number && a.Start.Part == b.Start.Part {
305 // CORNER CASE: e.g. 1.3,2.0 (or 1.3,2) never touch,
306 // unless it's known that 1.3 is the last part.
307 func (a *EpisodeRange) touches(b *EpisodeRange) bool {
308 if a == nil || b == nil || a.Type != b.Type {
314 // log.Println("same pointers")
316 case a.Start == b.Start, a.End != nil && a.End == b.End:
317 // log.Println("share pointers")
323 // log.Println("both infinite")
326 case b.End.Number == a.Start.Number:
328 // either is whole, or parts are adjacent/overlap
329 case b.End.Part == -1, a.Start.Part == -1,
330 b.End.Part >= a.Start.Part-1:
331 // log.Printf("{ %s [} %s ...", b.End, a.Start)
334 // only if start of next range is whole or is first part
335 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
337 // end is whole, or is last part, or part count is unknown
338 case b.End.Part == -1, b.End.Parts == 0,
339 b.End.Part == b.End.Parts:
340 // log.Printf("{ %s }[ %s ...", b.End, a.Start)
343 case b.End.Number > a.Start.Number:
344 // log.Printf("{ %s [ } %s ...", b.End, a.Start)
350 case a.End.Number == b.Start.Number:
352 case a.End.Part == -1, b.Start.Part == -1,
353 a.End.Part >= b.Start.Part-1:
354 // log.Printf("[ %s {] %s ...", a.End, b.Start)
357 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
359 case a.End.Part == -1, a.End.Parts == 0,
360 a.End.Part == a.End.Parts:
361 // log.Printf("[ %s ]{ %s ...", a.End, b.Start)
364 case a.End.Number > b.Start.Number:
365 // log.Printf("[ %s { ] %s ...", a.End, b.Start)
369 case a.Start.Number == b.Start.Number:
372 // either is whole, or parts are immediately adjacent
373 case a.Start.Part == -1, b.Start.Part == -1,
374 a.Start.Part == b.Start.Part,
375 a.Start.Part == b.Start.Part-1,
376 a.Start.Part == b.Start.Part+1:
377 // log.Printf("[{ %s - %s ]}", a.End, b.Start)
380 case a.End.Number == b.End.Number:
382 case a.End.Part == -1, b.End.Part == -1,
383 a.End.Part == b.End.Part,
384 a.End.Part == b.End.Part-1,
385 a.End.Part == b.End.Part+1:
386 // log.Printf("{[ %s - %s }]", b.End, a.Start)
390 case a.End.Number < b.End.Number:
392 case a.End.Number == b.Start.Number:
394 case a.End.Part == -1, b.Start.Part == -1,
395 a.End.Part >= b.Start.Part-1:
396 // log.Printf("[ %s {] %s }", a.End, b.Start)
399 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
401 case a.End.Part == -1, a.End.Part == a.End.Parts-1,
402 a.End.Part == b.Start.Parts:
403 // log.Printf("[ %s ]{ %s }", a.End, b.Start)
406 case a.End.Number > b.Start.Number:
407 // log.Printf("[ %s { ] %s }", a.End, b.Start)
411 case b.End.Number < a.End.Number:
413 case b.End.Number == a.Start.Number:
415 case b.End.Part == -1, a.Start.Part == -1,
416 b.End.Part >= a.Start.Part-1:
417 // log.Printf("{ %s [} %s ]", b.End, a.Start)
420 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
422 case b.End.Part == -1, b.End.Part == b.End.Parts-1,
423 b.End.Part == b.End.Parts:
424 // log.Printf("{ %s }[ %s ]", b.End, a.Start)
427 case b.End.Number > a.Start.Number:
428 // log.Printf("{ %s [ } %s ]", b.End, a.Start)
435 // Parses a string in the AniDB API range format and converts into an EpisodeRange.
436 func ParseEpisodeRange(s string) *EpisodeRange {
437 parts := strings.Split(s, "-")
443 for i := range parts {
444 eps[i] = ParseEpisode(parts[i])
450 // Not an interval (just "epno") --
451 // convert into interval starting and ending in the same episode
456 if len(parts) > 1 && eps[1] != nil && eps[0].Type != eps[1].Type {
459 return &EpisodeRange{