]> git.lizzy.rs Git - go-anidb.git/blob - misc/episode.go
Modernize
[go-anidb.git] / misc / episode.go
1 package misc
2
3 import (
4         "fmt"
5         "math"
6         "strconv"
7         "strings"
8 )
9
10 type EpisodeContainer interface {
11         // Returns true if this EpisodeContainer is equivalent or a superset of the given EpisodeContainer
12         ContainsEpisodes(EpisodeContainer) bool
13         // Returns a channel meant for iterating with for/range.
14         // Sends all contained episodes in order.
15         Episodes() chan Episode
16 }
17
18 type Formatter interface {
19         // Returns a string where the number portion is 0-padded to fit 'width' digits
20         Format(width int) string
21
22         // Returns a string where the number portion is 0-padded to be the same length
23         // as max.
24         FormatLog(max int) string
25 }
26
27 type EpisodeType int
28
29 const (
30         EpisodeTypeRegular = EpisodeType(1 + iota)
31         EpisodeTypeSpecial // "S" episode
32         EpisodeTypeCredits // "C" episode
33         EpisodeTypeTrailer // "T" episode
34         EpisodeTypeParody  // "P" episode
35         EpisodeTypeOther   // "O" episode
36 )
37
38 func parseEpisodeType(typ string) EpisodeType {
39         switch typ {
40         case "":
41                 return EpisodeTypeRegular
42         case "S":
43                 return EpisodeTypeSpecial
44         case "C":
45                 return EpisodeTypeCredits
46         case "T":
47                 return EpisodeTypeTrailer
48         case "P":
49                 return EpisodeTypeParody
50         case "O":
51                 return EpisodeTypeOther
52         }
53         return 0
54 }
55
56 func (et EpisodeType) String() string {
57         switch et {
58         case EpisodeTypeRegular:
59                 return ""
60         case EpisodeTypeSpecial:
61                 return "S"
62         case EpisodeTypeCredits:
63                 return "C"
64         case EpisodeTypeTrailer:
65                 return "T"
66         case EpisodeTypeParody:
67                 return "P"
68         case EpisodeTypeOther:
69                 return "O"
70         default:
71                 return "!"
72         }
73 }
74
75 // An episode (duh).
76 type Episode struct {
77         Type   EpisodeType
78         Number int
79         Part   int
80         Parts  int
81 }
82
83 // returns how many digits are needed to represent this int
84 func scale(i int) int {
85         return 1 + int(math.Floor(math.Log10(float64(i))))
86 }
87
88 // Converts the Episode into AniDB API episode format.
89 func (ep Episode) String() string {
90         return ep.Format(1)
91 }
92
93 // returns how many digits are needed to represent this episode
94 func (ep *Episode) scale() int {
95         if ep == nil {
96                 return 1
97         }
98         return scale(ep.Number)
99 }
100
101 func (ep *Episode) Episodes() chan Episode {
102         ch := make(chan Episode, 1)
103         if ep != nil {
104                 ch <- *ep
105         }
106         close(ch)
107         return ch
108 }
109
110 // Returns true if ec is an Episode and is identical to this episode,
111 // or if ec is a single episode EpisodeRange / EpisodeList that
112 // contain only this episode.
113 func (ep *Episode) ContainsEpisodes(ec EpisodeContainer) bool {
114         switch e := ec.(type) {
115         case *Episode:
116                 if ep == nil {
117                         return false
118                 }
119                 basic := ep.Type == e.Type && ep.Number == e.Number
120                 if ep.Part < 0 { // a whole episode contains any partial episodes
121                         return basic
122                 }
123                 return basic && ep.Part == e.Part
124         case *EpisodeRange:
125         case *EpisodeList:
126                 return EpisodeList{&EpisodeRange{Type: ep.Type, Start: ep, End: ep}}.ContainsEpisodes(ep)
127         default:
128         }
129         return false
130 }
131
132 func (ep Episode) Format(width int) string {
133         if ep.Part < 0 { // whole episode
134                 return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d", ep.Type, ep.Number)
135         }
136         if ep.Parts != 0 { // part X of Y
137                 frac := float64(ep.Number) + float64(ep.Part)/float64(ep.Parts)
138
139                 return fmt.Sprintf("%s%0"+strconv.Itoa(width)+".2f", ep.Type, frac)
140         }
141         // part N
142         return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d.%d", ep.Type, ep.Number, ep.Part)
143 }
144
145 func (ep *Episode) FormatLog(max int) string {
146         return ep.Format(scale(max))
147 }
148
149 func (ep *Episode) IncPart() {
150         if ep.Parts > 0 && ep.Part == ep.Parts-1 {
151                 ep.IncNumber()
152         } else {
153                 ep.Part++
154         }
155 }
156
157 func (ep *Episode) IncNumber() {
158         ep.Part = -1
159         ep.Parts = 0
160         ep.Number++
161 }
162
163 func (ep *Episode) DecPart() {
164         if ep.Part > 0 {
165                 ep.Part--
166         } else {
167                 ep.DecNumber()
168         }
169 }
170
171 func (ep *Episode) DecNumber() {
172         ep.Part = -1
173         ep.Parts = 0
174         ep.Number--
175 }
176
177 // Parses a string in the usual AniDB API episode format and converts into
178 // an Episode.
179 func ParseEpisode(s string) *Episode {
180         p := int64(-1)
181
182         parts := strings.Split(s, ".")
183         switch len(parts) {
184         case 1: // no worries
185         case 2:
186                 s = parts[0]
187                 p, _ = strconv.ParseInt(parts[1], 10, 32)
188         default: // too many dots
189                 return nil
190         }
191
192         if no, err := strconv.ParseInt(s, 10, 32); err == nil {
193                 return &Episode{Type: EpisodeTypeRegular, Number: int(no), Part: int(p)}
194         } else if len(s) < 1 {
195                 // s too short
196         } else if no, err = strconv.ParseInt(s[1:], 10, 30); err == nil {
197                 return &Episode{Type: parseEpisodeType(s[:1]), Number: int(no), Part: int(p)}
198         }
199         return nil
200 }