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
18 type Formatter interface {
19 // Returns a string where the number portion is 0-padded to fit 'width' digits
20 Format(width int) string
22 // Returns a string where the number portion is 0-padded to be the same length
24 FormatLog(max int) string
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
38 func parseEpisodeType(typ string) EpisodeType {
41 return EpisodeTypeRegular
43 return EpisodeTypeSpecial
45 return EpisodeTypeCredits
47 return EpisodeTypeTrailer
49 return EpisodeTypeParody
51 return EpisodeTypeOther
56 func (et EpisodeType) String() string {
58 case EpisodeTypeRegular:
60 case EpisodeTypeSpecial:
62 case EpisodeTypeCredits:
64 case EpisodeTypeTrailer:
66 case EpisodeTypeParody:
68 case EpisodeTypeOther:
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))))
88 // Converts the Episode into AniDB API episode format.
89 func (ep Episode) String() string {
93 // returns how many digits are needed to represent this episode
94 func (ep *Episode) scale() int {
98 return scale(ep.Number)
101 func (ep *Episode) Episodes() chan Episode {
102 ch := make(chan Episode, 1)
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) {
119 basic := ep.Type == e.Type && ep.Number == e.Number
120 if ep.Part < 0 { // a whole episode contains any partial episodes
123 return basic && ep.Part == e.Part
126 return EpisodeList{&EpisodeRange{Type: ep.Type, Start: ep, End: ep}}.ContainsEpisodes(ep)
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)
136 if ep.Parts != 0 { // part X of Y
137 frac := float64(ep.Number) + float64(ep.Part)/float64(ep.Parts)
139 return fmt.Sprintf("%s%0"+strconv.Itoa(width)+".2f", ep.Type, frac)
142 return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d.%d", ep.Type, ep.Number, ep.Part)
145 func (ep *Episode) FormatLog(max int) string {
146 return ep.Format(scale(max))
149 func (ep *Episode) IncPart() {
150 if ep.Parts > 0 && ep.Part == ep.Parts-1 {
157 func (ep *Episode) IncNumber() {
163 func (ep *Episode) DecPart() {
171 func (ep *Episode) DecNumber() {
177 // Parses a string in the usual AniDB API episode format and converts into
179 func ParseEpisode(s string) *Episode {
182 parts := strings.Split(s, ".")
184 case 1: // no worries
187 p, _ = strconv.ParseInt(parts[1], 10, 32)
188 default: // too many dots
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 {
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)}