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) FormatLog(max int) string {
32 return er.Format(scale(max))
35 func (er *EpisodeRange) scale() int {
39 s, e := er.Start.scale(), er.End.scale()
46 // If ec is an *Episode, returns true if the Episode is of the same type as the range
47 // and has a Number >= Start.Number; if End is defined, then the episode's Number must
48 // also be <= End.Number.
50 // If ec is an *EpisodeRange, returns true if they are both of the same type and
51 // the ec's Start.Number is >= this range's Start.Number;
52 // also returns true if this EpisodeRange is unbounded or if the ec is bounded
53 // and ec's End.Number is <= this range's End.Number.
55 // If ec is an EpisodeList, returns true if all listed EpisodeRanges are contained
56 // by this EpisodeRange.
58 // Returns false otherwise.
59 func (er *EpisodeRange) ContainsEpisodes(ec EpisodeContainer) bool {
63 if er.Start == nil || er.Start.Type != er.Type ||
64 (er.End != nil && er.End.Type != er.Type) {
65 panic("Invalid EpisodeRange used")
68 switch e := ec.(type) {
70 if e.Type == er.Type && e.Number >= er.Start.Number {
73 } else if e.Number <= er.End.Number {
78 if e.Type == er.Type {
79 if e.Start.Number >= er.Start.Number {
82 } else if e.End == nil {
83 return false // a finite set can't contain an infinite one
84 } else if e.End.Number <= er.End.Number {
90 for _, ec := range e {
91 if !er.ContainsEpisodes(ec) {
101 // Tries to merge a with b, returning a new *EpisodeRange that's
102 // a superset of both a and b.
104 // Returns nil if a and b don't intersect, or are not adjacent.
105 func (a *EpisodeRange) Merge(b *EpisodeRange) (c *EpisodeRange) {
107 c = &EpisodeRange{Type: a.Type}
109 if a.Start.Number == b.Start.Number {
110 if a.Start.Part <= b.Start.Part {
115 } else if a.Start.Number < b.Start.Number {
122 case a.End == nil || b.End == nil:
124 case a.End.Number == b.End.Number:
125 if a.End.Part >= b.End.Part {
130 case a.End.Number > b.End.Number:
139 // Returns true if both ranges are of the same type and
140 // have identical start/end positions
141 func (a *EpisodeRange) Equals(b *EpisodeRange) bool {
142 if a == b { // pointers to the same thing
145 if a == nil || b == nil {
149 if a.Type == b.Type {
150 if a.End == b.End || (a.End != nil && b.End != nil &&
151 a.End.Number == b.End.Number && a.End.Part == b.End.Part) {
152 if a.Start == b.Start || a.Start.Number == b.Start.Number && a.Start.Part == b.Start.Part {
160 // CORNER CASE: e.g. 1.3,2.0 (or 1.3,2) always touch,
161 // even if there's an unlisted 1.4 between them; unless
162 // the part count is known.
163 func (a *EpisodeRange) touches(b *EpisodeRange) bool {
164 if a == nil || b == nil || a.Type != b.Type {
170 // log.Println("same pointers")
172 case a.Start == b.Start, a.End != nil && a.End == b.End:
173 // log.Println("share pointers")
179 // log.Println("both infinite")
182 case b.End.Number == a.Start.Number:
184 // either is whole, or parts are adjacent/overlap
185 case b.End.Part == -1, a.Start.Part == -1,
186 b.End.Part >= a.Start.Part-1:
187 // log.Printf("{ %s [} %s ...", b.End, a.Start)
190 // only if start of next range is whole or is first part
191 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
193 // end is whole, or is last part, or part count is unknown
194 case b.End.Part == -1, b.End.Parts == 0,
195 b.End.Part == b.End.Parts:
196 // log.Printf("{ %s }[ %s ...", b.End, a.Start)
199 case b.End.Number > a.Start.Number:
200 // log.Printf("{ %s [ } %s ...", b.End, a.Start)
206 case a.End.Number == b.Start.Number:
208 case a.End.Part == -1, b.Start.Part == -1,
209 a.End.Part >= b.Start.Part-1:
210 // log.Printf("[ %s {] %s ...", a.End, b.Start)
213 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
215 case a.End.Part == -1, a.End.Parts == 0,
216 a.End.Part == a.End.Parts:
217 // log.Printf("[ %s ]{ %s ...", a.End, b.Start)
220 case a.End.Number > b.Start.Number:
221 // log.Printf("[ %s { ] %s ...", a.End, b.Start)
225 case a.Start.Number == b.Start.Number:
228 // either is whole, or parts are immediately adjacent
229 case a.Start.Part == -1, b.Start.Part == -1,
230 a.Start.Part == b.Start.Part,
231 a.Start.Part == b.Start.Part-1,
232 a.Start.Part == b.Start.Part+1:
233 // log.Printf("[{ %s - %s ]}", a.End, b.Start)
236 case a.End.Number == b.End.Number:
238 case a.End.Part == -1, b.End.Part == -1,
239 a.End.Part == b.End.Part,
240 a.End.Part == b.End.Part-1,
241 a.End.Part == b.End.Part+1:
242 // log.Printf("{[ %s - %s }]", b.End, a.Start)
246 case a.End.Number < b.End.Number:
248 case a.End.Number == b.Start.Number:
250 case a.End.Part == -1, b.Start.Part == -1,
251 a.End.Part >= b.Start.Part-1:
252 // log.Printf("[ %s {] %s }", a.End, b.Start)
255 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
257 case b.End.Part == -1, b.End.Parts == 0,
258 b.End.Part == b.End.Parts:
259 // log.Printf("[ %s ]{ %s }", a.End, b.Start)
262 case a.End.Number > b.Start.Number:
263 // log.Printf("[ %s { ] %s }", a.End, b.Start)
267 case b.End.Number < a.End.Number:
269 case b.End.Number == a.Start.Number:
271 case b.End.Part == -1, a.Start.Part == -1,
272 b.End.Part >= a.Start.Part-1:
273 // log.Printf("{ %s [} %s ]", b.End, a.Start)
276 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
278 case b.End.Part == -1, b.End.Parts == 0,
279 b.End.Part == b.End.Parts:
280 // log.Printf("{ %s }[ %s ]", b.End, a.Start)
283 case b.End.Number > a.Start.Number:
284 // log.Printf("{ %s [ } %s ]", b.End, a.Start)
291 // Parses a string in the AniDB API range format and converts into an EpisodeRange.
292 func ParseEpisodeRange(s string) *EpisodeRange {
293 parts := strings.Split(s, "-")
299 for i := range parts {
300 eps[i] = ParseEpisode(parts[i])
306 // Not an interval (just "epno") --
307 // convert into interval starting and ending in the same episode
312 if len(parts) > 1 && eps[1] != nil && eps[0].Type != eps[1].Type {
315 return &EpisodeRange{