16
16
package cmd
17
17
18
18
import (
19
+ "fmt"
19
20
"log"
20
21
"net/http"
22
+ "regexp"
23
+ "sort"
21
24
"strconv"
25
+ "strings"
22
26
23
27
"github.com/cheggaaa/pb"
28
+ "github.com/kvannotten/pcd"
24
29
"github.com/spf13/cobra"
25
30
)
26
31
@@ -29,11 +34,37 @@ var downloadCmd = &cobra.Command{
29
34
Use : "download <podcast> <episode_id>" ,
30
35
Aliases : []string {"d" },
31
36
Short : "Downloads an episode of a podcast." ,
32
- Long : `This command will download an episode of a podcast that you define. The episode number can
33
- be obtained by running 'pcd ls <podcast>' For example:
37
+ Long : `
38
+ This command will download one or multiple episode(s) of a podcast that you
39
+ define.
40
+
41
+ The episode number can be obtained by running 'pcd ls <podcast>'
42
+
43
+ For example:
44
+
45
+ To download one episode
34
46
35
47
pcd ls gnu_open_world
36
- pcd download gnu_open_world 1` ,
48
+ pcd download gnu_open_world 1
49
+
50
+ To download episode ranges:
51
+
52
+ pcd download gnu_open_world '20-30,!25'
53
+
54
+ This will download episode 20 to 30 and skip the 25.
55
+
56
+ Available formats:
57
+
58
+ Episode numbers: '1,5,105'
59
+ Ranges: '2-15'
60
+ Skipping: '!102,!121'
61
+
62
+ Combining those as follow:
63
+
64
+ pcd download gnu_open_world '1-30,40-47,!15,!17,!20,102'
65
+
66
+ Make sure to use the single-quote on bash otherwise the !105 will expand your
67
+ bash history.` ,
37
68
Args : cobra .MinimumNArgs (1 ),
38
69
Run : download ,
39
70
}
@@ -51,16 +82,23 @@ func download(cmd *cobra.Command, args []string) {
51
82
log .Fatalf ("Could not load podcast: %#v" , err )
52
83
}
53
84
54
- var episodeN int
55
- if len (args ) > 1 {
56
- episodeN , err = strconv .Atoi (args [1 ])
57
- } else {
58
- episodeN = len (podcast .Episodes ) // download latest
85
+ if len (args ) < 2 {
86
+ // download latest
87
+ downloadEpisode (podcast , len (podcast .Episodes ))
88
+ return
59
89
}
90
+
91
+ episodes , err := parseRangeArg (args [1 ])
60
92
if err != nil {
61
93
log .Fatalf ("Could not parse episode number %s: %#v" , args [1 ], err )
62
94
}
63
95
96
+ for _ , n := range episodes {
97
+ downloadEpisode (podcast , n )
98
+ }
99
+ }
100
+
101
+ func downloadEpisode (podcast * pcd.Podcast , episodeN int ) {
64
102
if episodeN > len (podcast .Episodes ) {
65
103
log .Fatalf ("There's only %d episodes in this podcast." , len (podcast .Episodes ))
66
104
}
@@ -110,3 +148,91 @@ func init() {
110
148
// is called directly, e.g.:
111
149
// downloadCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
112
150
}
151
+
152
+ // parseRangeArg parses episodes number with the following format
153
+ // 1,2,3-5,!4 returns [1, 2, 3, 5]
154
+ func parseRangeArg (arg string ) ([]int , error ) {
155
+ if len (arg ) == 0 {
156
+ return nil , nil
157
+ }
158
+
159
+ // we try to convert the arg as a single episode
160
+ n , err := strconv .Atoi (arg )
161
+ if err == nil {
162
+ return []int {n }, nil
163
+ }
164
+
165
+ // this map helps preventing duplicate
166
+ unique := make (map [int ]bool )
167
+
168
+ // extract negative numbers !X
169
+ negatives := regexp .MustCompile (`!\d+` )
170
+ notWanted := negatives .FindAllString (arg , - 1 )
171
+
172
+ arg = negatives .ReplaceAllString (arg , "" )
173
+
174
+ // extract ranges X-Y
175
+ rangesPattern := regexp .MustCompile (`\d+-\d+` )
176
+ ranges := rangesPattern .FindAllString (arg , - 1 )
177
+
178
+ arg = rangesPattern .ReplaceAllString (arg , "" )
179
+
180
+ // extract the remaining single digit X
181
+ digitsPattern := regexp .MustCompile (`\d+` )
182
+ digits := digitsPattern .FindAllString (arg , - 1 )
183
+
184
+ for _ , r := range ranges {
185
+ parts := strings .Split (r , "-" )
186
+ if len (parts ) != 2 {
187
+ return nil , fmt .Errorf ("range %s must have the format start-end" , r )
188
+ }
189
+
190
+ start , err := strconv .Atoi (parts [0 ])
191
+ if err != nil {
192
+ return nil , err
193
+ }
194
+
195
+ end , err := strconv .Atoi (parts [1 ])
196
+ if err != nil {
197
+ return nil , err
198
+ }
199
+
200
+ for i := start ; i <= end ; i ++ {
201
+ // make sure it's wanted
202
+ wanted := true
203
+ for _ , nw := range notWanted {
204
+ if fmt .Sprintf ("!%d" , i ) == nw {
205
+ wanted = false
206
+ break
207
+ }
208
+ }
209
+
210
+ if ! wanted {
211
+ continue
212
+ }
213
+
214
+ unique [i ] = true
215
+ }
216
+ }
217
+
218
+ // let's add the remaining digits
219
+ for _ , d := range digits {
220
+ i , err := strconv .Atoi (d )
221
+ if err != nil {
222
+ return nil , err
223
+ }
224
+
225
+ unique [i ] = true
226
+ }
227
+
228
+ // we turn the unique map into the slice of episode numbers
229
+ var results []int
230
+ for k , _ := range unique {
231
+ results = append (results , k )
232
+ }
233
+
234
+ // we sort the result
235
+ sort .Ints (results )
236
+
237
+ return results , nil
238
+ }
0 commit comments