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 // Converts the EpisodeRange into AniDB API range format.
16 func (er *EpisodeRange) String() string {
17 return er.Format(er.scale())
20 func (er *EpisodeRange) Format(width int) string {
21 if er.Start == er.End || (er.End != nil && *(er.Start) == *(er.End)) {
22 return er.Start.Format(width)
26 return fmt.Sprintf("%s-", er.Start.Format(width))
28 return fmt.Sprintf("%s-%s", er.Start.Format(width), er.End.Format(width))
31 func (er *EpisodeRange) scale() int {
35 s, e := er.Start.scale(), er.End.scale()
42 // If ec is an *Episode, returns true if the Episode is of the same type as the range
43 // and has a Number >= Start.Number; if End is defined, then the episode's Number must
44 // also be <= End.Number.
46 // If ec is an *EpisodeRange, returns true if they are both of the same type and
47 // the ec's Start.Number is >= this range's Start.Number;
48 // also returns true if this EpisodeRange is unbounded or if the ec is bounded
49 // and ec's End.Number is <= this range's End.Number.
51 // If ec is an EpisodeList, returns true if all listed EpisodeRanges are contained
52 // by this EpisodeRange.
54 // Returns false otherwise.
55 func (er *EpisodeRange) ContainsEpisodes(ec EpisodeContainer) bool {
59 if er.Start == nil || er.Start.Type != er.Type ||
60 (er.End != nil && er.End.Type != er.Type) {
61 panic("Invalid EpisodeRange used")
64 switch e := ec.(type) {
66 if e.Type == er.Type && e.Number >= er.Start.Number {
69 } else if e.Number <= er.End.Number {
74 if e.Type == er.Type {
75 if e.Start.Number >= er.Start.Number {
78 } else if e.End == nil {
79 return false // a finite set can't contain an infinite one
80 } else if e.End.Number <= er.End.Number {
86 for _, ec := range e {
87 if !er.ContainsEpisodes(ec) {
97 // Tries to merge a with b, returning a new *EpisodeRange that's
98 // a superset of both a and b.
100 // Returns nil if a and b don't intersect, or are not adjacent.
101 func (a *EpisodeRange) Merge(b *EpisodeRange) (c *EpisodeRange) {
103 c = &EpisodeRange{Type: a.Type}
105 if a.Start.Number <= b.Start.Number {
112 case a.End == nil || b.End == nil:
114 case a.End.Number >= b.End.Number:
123 // Returns true if both ranges are of the same type and
124 // have identical start/end positions
125 func (a *EpisodeRange) Equals(b *EpisodeRange) bool {
126 if a == b { // pointers to the same thing
129 if a == nil || b == nil {
133 if a.Type == b.Type {
134 if a.End == b.End || (a.End != nil && b.End != nil && a.End.Number == b.End.Number) {
135 if a.Start == b.Start || a.Start.Number == b.Start.Number {
143 func (a *EpisodeRange) touches(b *EpisodeRange) bool {
144 if a == nil || b == nil || a.Type != b.Type {
155 case b.End.Number >= a.Start.Number-1:
157 // start-1 so it's still true when they're only adjacent
163 case a.End.Number >= b.Start.Number-1:
168 case a.Start.Number == b.Start.Number || a.End.Number == b.End.Number:
172 case a.End.Number < b.End.Number:
174 case a.End.Number >= b.Start.Number-1:
179 case b.End.Number < a.End.Number:
181 case b.End.Number >= a.Start.Number-1:
189 // Parses a string in the AniDB API range format and converts into an EpisodeRange.
190 func ParseEpisodeRange(s string) *EpisodeRange {
191 parts := strings.Split(s, "-")
197 for i := range parts {
198 eps[i] = ParseEpisode(parts[i])
204 // Not an interval (just "epno") --
205 // convert into interval starting and ending in the same episode
210 if len(parts) > 1 && eps[1] != nil && eps[0].Type != eps[1].Type {
213 return &EpisodeRange{