]> git.lizzy.rs Git - go-anidb.git/blob - misc/episoderange.go
anidb: Simplify documentation
[go-anidb.git] / misc / episoderange.go
1 package misc
2
3 import (
4         "fmt"
5         "strings"
6 )
7
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
13 }
14
15 // Converts the EpisodeRange into AniDB API range format.
16 func (er *EpisodeRange) String() string {
17         return er.Format(er.scale())
18 }
19
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)
23         }
24
25         if er.End == nil {
26                 return fmt.Sprintf("%s-", er.Start.Format(width))
27         }
28         return fmt.Sprintf("%s-%s", er.Start.Format(width), er.End.Format(width))
29 }
30
31 func (er *EpisodeRange) scale() int {
32         if er == nil {
33                 return 1
34         }
35         s, e := er.Start.scale(), er.End.scale()
36         if e > s {
37                 return e
38         }
39         return s
40 }
41
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.
45 //
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.
50 //
51 // If ec is an EpisodeList, returns true if all listed EpisodeRanges are contained
52 // by this EpisodeRange.
53 //
54 // Returns false otherwise.
55 func (er *EpisodeRange) ContainsEpisodes(ec EpisodeContainer) bool {
56         if er == nil {
57                 return false
58         }
59         if er.Start == nil || er.Start.Type != er.Type ||
60                 (er.End != nil && er.End.Type != er.Type) {
61                 panic("Invalid EpisodeRange used")
62         }
63
64         switch e := ec.(type) {
65         case *Episode:
66                 if e.Type == er.Type && e.Number >= er.Start.Number {
67                         if er.End == nil {
68                                 return true
69                         } else if e.Number <= er.End.Number {
70                                 return true
71                         }
72                 }
73         case *EpisodeRange:
74                 if e.Type == er.Type {
75                         if e.Start.Number >= er.Start.Number {
76                                 if er.End == nil {
77                                         return true
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 {
81                                         return true
82                                 }
83                         }
84                 }
85         case EpisodeList:
86                 for _, ec := range e {
87                         if !er.ContainsEpisodes(ec) {
88                                 return false
89                         }
90                 }
91                 return true
92         default:
93         }
94         return false
95 }
96
97 // Tries to merge a with b, returning a new *EpisodeRange that's
98 // a superset of both a and b.
99 //
100 // Returns nil if a and b don't intersect, or are not adjacent.
101 func (a *EpisodeRange) Merge(b *EpisodeRange) (c *EpisodeRange) {
102         if a.touches(b) {
103                 c = &EpisodeRange{Type: a.Type}
104
105                 if a.Start.Number <= b.Start.Number {
106                         c.Start = a.Start
107                 } else {
108                         c.Start = b.Start
109                 }
110
111                 switch {
112                 case a.End == nil || b.End == nil:
113                         c.End = nil
114                 case a.End.Number >= b.End.Number:
115                         c.End = a.End
116                 default:
117                         c.End = b.End
118                 }
119         }
120         return
121 }
122
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
127                 return true
128         }
129         if a == nil || b == nil {
130                 return false
131         }
132
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 {
136                                 return true
137                         }
138                 }
139         }
140         return false
141 }
142
143 func (a *EpisodeRange) touches(b *EpisodeRange) bool {
144         if a == nil || b == nil || a.Type != b.Type {
145                 return false
146         }
147
148         switch {
149         case a.End == nil:
150                 switch {
151                 case b.End == nil:
152                         // both infinite
153                         return true
154
155                 case b.End.Number >= a.Start.Number-1:
156                         // {b  [ }  a ...
157                         // start-1 so it's still true when they're only adjacent
158                         return true
159                 }
160
161         case b.End == nil:
162                 switch {
163                 case a.End.Number >= b.Start.Number-1:
164                         // [a  { ]  b ...
165                         return true
166                 }
167
168         case a.Start.Number == b.Start.Number || a.End.Number == b.End.Number:
169                 // touching
170                 return true
171
172         case a.End.Number < b.End.Number:
173                 switch {
174                 case a.End.Number >= b.Start.Number-1:
175                         // [a  { ]  b}
176                         return true
177                 }
178
179         case b.End.Number < a.End.Number:
180                 switch {
181                 case b.End.Number >= a.Start.Number-1:
182                         // {b  [ }  a]
183                         return true
184                 }
185         }
186         return false
187 }
188
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, "-")
192         if len(parts) > 2 {
193                 return nil
194         }
195
196         eps := [2]*Episode{}
197         for i := range parts {
198                 eps[i] = ParseEpisode(parts[i])
199         }
200         if eps[0] == nil {
201                 return nil
202         }
203
204         // Not an interval (just "epno") --
205         // convert into interval starting and ending in the same episode
206         if len(parts) == 1 {
207                 eps[1] = eps[0]
208         }
209
210         if len(parts) > 1 && eps[1] != nil && eps[0].Type != eps[1].Type {
211                 return nil
212         }
213         return &EpisodeRange{
214                 Type:  eps[0].Type,
215                 Start: eps[0],
216                 End:   eps[1],
217         }
218 }