10 type EpisodeContainer interface {
11 // Returns true if this EpisodeContainer is equivalent or a superset of the given EpisodeContainer
12 ContainsEpisodes(EpisodeContainer) bool
15 type Formatter interface {
16 // Returns a string where the number portion is 0-padded to fit 'width' digits
17 Format(width int) string
19 // Returns a string where the number portion is 0-padded to be the same length
21 FormatLog(max int) string
27 EpisodeTypeRegular = EpisodeType(1 + iota)
28 EpisodeTypeSpecial // "S" episode
29 EpisodeTypeCredits // "C" episode
30 EpisodeTypeTrailer // "T" episode
31 EpisodeTypeParody // "P" episode
32 EpisodeTypeOther // "O" episode
35 func parseEpisodeType(typ string) EpisodeType {
38 return EpisodeTypeRegular
40 return EpisodeTypeSpecial
42 return EpisodeTypeCredits
44 return EpisodeTypeTrailer
46 return EpisodeTypeParody
48 return EpisodeTypeOther
53 func (et EpisodeType) String() string {
55 case EpisodeTypeRegular:
57 case EpisodeTypeSpecial:
59 case EpisodeTypeCredits:
61 case EpisodeTypeTrailer:
63 case EpisodeTypeParody:
65 case EpisodeTypeOther:
80 // returns how many digits are needed to represent this int
81 func scale(i int) int {
82 return 1 + int(math.Floor(math.Log10(float64(i))))
85 // Converts the Episode into AniDB API episode format.
86 func (ep *Episode) String() string {
90 // returns how many digits are needed to represent this episode
91 func (ep *Episode) scale() int {
95 return scale(ep.Number)
98 // Returns true if ec is an Episode and is identical to this episode,
99 // or if ec is a single episode EpisodeRange / EpisodeList that
100 // contain only this episode.
101 func (ep *Episode) ContainsEpisodes(ec EpisodeContainer) bool {
102 switch e := ec.(type) {
107 basic := ep.Type == e.Type && ep.Number == e.Number
108 if ep.Part < 0 { // a whole episode contains any partial episodes
111 return basic && ep.Part == e.Part
114 return EpisodeList{&EpisodeRange{Type: ep.Type, Start: ep, End: ep}}.ContainsEpisodes(ep)
120 func (ep *Episode) Format(width int) string {
121 if ep.Part < 0 { // whole episode
122 return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d", ep.Type, ep.Number)
124 if ep.Parts != 0 { // part X of Y
125 frac := float64(ep.Number) + float64(ep.Part)/float64(ep.Parts)
127 return fmt.Sprintf("%s%0"+strconv.Itoa(width)+".2f", ep.Type, frac)
130 return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d.%d", ep.Type, ep.Number, ep.Part)
133 func (ep *Episode) FormatLog(max int) string {
134 return ep.Format(scale(max))
137 // Parses a string in the usual AniDB API episode format and converts into
139 func ParseEpisode(s string) *Episode {
142 parts := strings.Split(s, ".")
144 case 1: // no worries
147 p, _ = strconv.ParseInt(parts[1], 10, 32)
148 default: // too many dots
152 if no, err := strconv.ParseInt(s, 10, 32); err == nil {
153 return &Episode{Type: EpisodeTypeRegular, Number: int(no), Part: int(p)}
154 } else if len(s) < 1 {
156 } else if no, err = strconv.ParseInt(s[1:], 10, 30); err == nil {
157 return &Episode{Type: parseEpisodeType(s[:1]), Number: int(no), Part: int(p)}