Skip to content

Commit

Permalink
implemented ranges and skip for download episode (#21)
Browse files Browse the repository at this point in the history
* implemented ranges and skip for download episode closes #20 

* added return when there's no episode id provided

* prevent duplicates from being returned in the parseRangeArg function
  • Loading branch information
dstpierre authored Nov 15, 2020
1 parent c814019 commit 815dd2d
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 8 deletions.
142 changes: 134 additions & 8 deletions cmd/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
package cmd

import (
"fmt"
"log"
"net/http"
"regexp"
"sort"
"strconv"
"strings"

"github.com/cheggaaa/pb"
"github.com/kvannotten/pcd"
"github.com/spf13/cobra"
)

Expand All @@ -29,11 +34,37 @@ var downloadCmd = &cobra.Command{
Use: "download <podcast> <episode_id>",
Aliases: []string{"d"},
Short: "Downloads an episode of a podcast.",
Long: `This command will download an episode of a podcast that you define. The episode number can
be obtained by running 'pcd ls <podcast>' For example:
Long: `
This command will download one or multiple episode(s) of a podcast that you
define.
The episode number can be obtained by running 'pcd ls <podcast>'
For example:
To download one episode
pcd ls gnu_open_world
pcd download gnu_open_world 1`,
pcd download gnu_open_world 1
To download episode ranges:
pcd download gnu_open_world '20-30,!25'
This will download episode 20 to 30 and skip the 25.
Available formats:
Episode numbers: '1,5,105'
Ranges: '2-15'
Skipping: '!102,!121'
Combining those as follow:
pcd download gnu_open_world '1-30,40-47,!15,!17,!20,102'
Make sure to use the single-quote on bash otherwise the !105 will expand your
bash history.`,
Args: cobra.MinimumNArgs(1),
Run: download,
}
Expand All @@ -51,16 +82,23 @@ func download(cmd *cobra.Command, args []string) {
log.Fatalf("Could not load podcast: %#v", err)
}

var episodeN int
if len(args) > 1 {
episodeN, err = strconv.Atoi(args[1])
} else {
episodeN = len(podcast.Episodes) // download latest
if len(args) < 2 {
// download latest
downloadEpisode(podcast, len(podcast.Episodes))
return
}

episodes, err := parseRangeArg(args[1])
if err != nil {
log.Fatalf("Could not parse episode number %s: %#v", args[1], err)
}

for _, n := range episodes {
downloadEpisode(podcast, n)
}
}

func downloadEpisode(podcast *pcd.Podcast, episodeN int) {
if episodeN > len(podcast.Episodes) {
log.Fatalf("There's only %d episodes in this podcast.", len(podcast.Episodes))
}
Expand Down Expand Up @@ -110,3 +148,91 @@ func init() {
// is called directly, e.g.:
// downloadCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

// parseRangeArg parses episodes number with the following format
// 1,2,3-5,!4 returns [1, 2, 3, 5]
func parseRangeArg(arg string) ([]int, error) {
if len(arg) == 0 {
return nil, nil
}

// we try to convert the arg as a single episode
n, err := strconv.Atoi(arg)
if err == nil {
return []int{n}, nil
}

// this map helps preventing duplicate
unique := make(map[int]bool)

// extract negative numbers !X
negatives := regexp.MustCompile(`!\d+`)
notWanted := negatives.FindAllString(arg, -1)

arg = negatives.ReplaceAllString(arg, "")

// extract ranges X-Y
rangesPattern := regexp.MustCompile(`\d+-\d+`)
ranges := rangesPattern.FindAllString(arg, -1)

arg = rangesPattern.ReplaceAllString(arg, "")

// extract the remaining single digit X
digitsPattern := regexp.MustCompile(`\d+`)
digits := digitsPattern.FindAllString(arg, -1)

for _, r := range ranges {
parts := strings.Split(r, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("range %s must have the format start-end", r)
}

start, err := strconv.Atoi(parts[0])
if err != nil {
return nil, err
}

end, err := strconv.Atoi(parts[1])
if err != nil {
return nil, err
}

for i := start; i <= end; i++ {
// make sure it's wanted
wanted := true
for _, nw := range notWanted {
if fmt.Sprintf("!%d", i) == nw {
wanted = false
break
}
}

if !wanted {
continue
}

unique[i] = true
}
}

// let's add the remaining digits
for _, d := range digits {
i, err := strconv.Atoi(d)
if err != nil {
return nil, err
}

unique[i] = true
}

// we turn the unique map into the slice of episode numbers
var results []int
for k, _ := range unique {
results = append(results, k)
}

// we sort the result
sort.Ints(results)

return results, nil
}
43 changes: 43 additions & 0 deletions cmd/download_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright © 2018 Kristof Vannotten <kristof@vannotten.be>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package cmd

import (
"reflect"
"testing"
)

func TestEpisodeRangeArgs(t *testing.T) {
cases := make(map[string][]int)
cases["1"] = []int{1}
cases["2-5"] = []int{2, 3, 4, 5}
cases["2-5,!4"] = []int{2, 3, 5}
cases["1,2,!99,!102,97-105,!103"] = []int{1, 2, 97, 98, 100, 101, 104, 105}
cases["!25,25-27,!26"] = []int{27}
cases["22,12,10-13,!11,!22,!12"] = []int{10, 12, 13, 22}
cases["101-106,7,!105,!104"] = []int{7, 101, 102, 103, 106}
cases["1-5,!3,4-6"] = []int{1, 2, 4, 5, 6}
cases[""] = nil

for arg, want := range cases {
got, err := parseRangeArg(arg)
if err != nil {
t.Error(err)
} else if reflect.DeepEqual(want, got) == false {
t.Errorf("missmatch for %s: got %v want %v", arg, got, want)
}
}
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module github.com/kvannotten/pcd

go 1.15

require (
github.com/BurntSushi/toml v0.3.0 // indirect
github.com/cheggaaa/pb v1.0.25
Expand Down

0 comments on commit 815dd2d

Please sign in to comment.