func (cmd *showCommand) Execute(args []string) error {
	seriesList := loadSeries()
	if Options.JSON {
		seriesJSON, err := json.MarshalIndent(seriesList, "", "   ")
		if err != nil {
			return err
		}
		log.Println(string(seriesJSON))
	} else {
		log.Print(getSeriesTable(seriesList))
	}
	return nil
}
Execute is the callback of the series show command.
package main
import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"goirate/series"
	"goirate/torrents"
	"goirate/utils"
	"github.com/BurntSushi/toml"
	"github.com/olekukonko/tablewriter"
)
type SeriesCommand struct {
	Add    addCommand    `command:"add" description:"Add a series to the watchlist."`
	Remove removeCommand `command:"remove" alias:"rm" description:"Remove a series from the watchlist."`
	Search searchCommand `command:"search" alias:"s" description:"Search for a series given a query string."`
	Show   showCommand   `command:"show" alias:"ls" description:"Print out the current series watchlist."`
	Scan   scanCommand   `command:"scan" description:"Perform a scan for new episodes on the existing watchlist."`
}
type addCommand struct {
	LastEpisode      string                `long:"last-episode" short:"e" description:"The last episode that came out."`
	MinQuality       torrents.VideoQuality `long:"min-quality" description:"The minimum video quality to accept when scanning for torrents of this series."`
	VerifiedUploader bool                  `long:"trusted" description:"Only accepted torrents from trusted or verified uploaders for this series."`
	Force            bool                  `long:"force" short:"f" description:"Overwrite this series if it already exists in the watchlist."`
	Show             bool                  `long:"ls" description:"Execute the show command after adding."`
	Args             struct {
		Title string `positional-arg-name:"<title | imdbID>"`
	} `positional-args:"1" required:"1"`
}
type removeCommand struct {
	Show bool `long:"ls" description:"Execute the show command after removing."`
	Args struct {
		Title string `positional-arg-name:"<title | id>"`
	} `positional-args:"1" required:"1"`
}
type searchCommand struct {
	Count int `long:"count" short:"c" description:"Limit the number of results."`
	Args  struct {
		Query string `positional-arg-name:"<title | imdb | tvmaze id>"`
	} `positional-args:"1" required:"1"`
}
type showCommand struct{}
type scanCommand struct {
	torrentSearchArgs
	DryRun   bool `long:"dry-run" description:"Perform the scan for new episodes without downloading torrents, sending notifications or updating the episode numbers in the watchlist."`
	NoUpdate bool `long:"no-update" description:"Perform the scan for new episodes without updating the last episode aired in the watchlist."`
	Quiet    bool `long:"quiet" short:"q" description:"Do not print anything to the standard output."`
	Quick    bool `long:"quick" description:"Perform a quick scan, only searching for torrents for episodes that were found on the tvmaze API."`
}
type seriesTorrent struct {
	Episode series.Episode   `json:"episode"`
	Torrent torrents.Torrent `json:"torrent"`
}
type seriesTorrents struct {
	Series   *series.Series  `json:"series"`
	Torrents []seriesTorrent `json:"torrents"`
}
func (cmd *addCommand) Execute(args []string) error {
	tvmazeToken, err := tvmazeLogin()
	if err != nil {
		return err
	}
	show, err := tvmazeToken.SearchFirst(cmd.Args.Title)
	if err != nil {
		return err
	}
	var episode series.Episode
	if cmd.LastEpisode != "" {
		episode = series.ParseEpisodeString(cmd.LastEpisode)
		if episode.Season == 0 && episode.Episode == 0 {
			return fmt.Errorf("unable to parse the last episode number from: %v", cmd.LastEpisode)
		}
	} else {
		episode, err = tvmazeToken.LastEpisode(show.ID)
		if err != nil {
			return err
		}
	}
	ser := series.Series{
		ID:               show.ID,
		Title:            show.Name,
		MinQuality:       cmd.MinQuality,
		VerifiedUploader: cmd.VerifiedUploader,
		LastEpisode:      episode,
	}
	ser.Actions.Emails = []string{}
	seriesList := loadSeries()
	if containsID(seriesList, ser.ID) {
		if cmd.Force {
			remove(&seriesList, ser.ID, "")
		} else {
			return fmt.Errorf("series %v already on the watchlist", ser.Title)
		}
	}
	seriesList = append(seriesList, ser)
	storeSeries(seriesList)
	if cmd.Show {
		var showCmd showCommand
		return showCmd.Execute(args)
	}
	return nil
}
func (cmd *removeCommand) Execute(args []string) error {
	cmd.Args.Title = utils.NormalizeQuery(cmd.Args.Title)
	id, _ := strconv.Atoi(cmd.Args.Title)
	seriesList := loadSeries()
	if !remove(&seriesList, id, cmd.Args.Title) {
		return fmt.Errorf("no series found on the watchlist matching: %v\nhint: goirate series show", cmd.Args.Title)
	}
	storeSeries(seriesList)
	if cmd.Show {
		var showCmd showCommand
		return showCmd.Execute(args)
	}
	return nil
}
func (cmd *searchCommand) Execute(args []string) error {
	tvmazeToken, err := tvmazeLogin()
	if err != nil {
		return err
	}
	searchResult, err := tvmazeToken.Search(cmd.Args.Query)
	if err != nil {
		return err
	}
	if Options.JSON {
		seriesJSON, err := json.MarshalIndent(searchResult, "", "   ")
		if err != nil {
			return err
		}
		log.Println(string(seriesJSON))
	} else {
		log.Println(getSeriesSearchTable(searchResult, cmd.Count))
	}
	return nil
}