From 8c42ea05beb9fea233e451e61dad7c16608aa4bd Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sat, 4 Sep 2021 00:20:48 -0400 Subject: [PATCH] Add basic CLI tool to print results --- categorize/categorizer.go | 5 +- categorize/tv/show.go | 6 +-- cmd/main.go | 104 ++++++++++++++++++++++++++++++++++++++ go.mod | 5 +- go.sum | 11 ++++ 5 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 cmd/main.go diff --git a/categorize/categorizer.go b/categorize/categorizer.go index 7167c36..757abed 100644 --- a/categorize/categorizer.go +++ b/categorize/categorizer.go @@ -6,6 +6,7 @@ import ( "io/fs" "os" + "github.com/ollien/gobbler/categorize/match" "github.com/ollien/gobbler/categorize/tv" ) @@ -35,7 +36,9 @@ func newCategorizerForFS(libraryFs fs.FS) Categorizer { // (though the season folder need not exist) func (c Categorizer) CategorizeFile(filename string) (tv.Location, error) { showFolder, err := c.findShowFolder(filename) - if err != nil { + if errors.Is(err, tv.ErrNotTvShow) { + return tv.Location{}, match.ErrNoMatches + } else if err != nil { return tv.Location{}, fmt.Errorf("could not find show folder for categorization: %w", err) } diff --git a/categorize/tv/show.go b/categorize/tv/show.go index a38e136..c15a91e 100644 --- a/categorize/tv/show.go +++ b/categorize/tv/show.go @@ -7,14 +7,14 @@ import ( "github.com/ollien/gobbler/categorize/match" ) -var errNotTvShow = errors.New("no tv-like information found in show name") +var ErrNotTvShow = errors.New("no tv-like information found in show name") // FindShowFolder finds the show that the given filename likey belongs to, among the given show folders. -// Returns errNotTvShow if no match can be found +// Returns ErrNotTvShow if no match can be found func FindShowFolder(filename string, showFolders []string) (string, error) { possibleShowName, err := extractShowName(filename) if err != nil { - return "", errNotTvShow + return "", ErrNotTvShow } return matchToFolder(possibleShowName, showFolders) diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..df971d5 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" + + "github.com/ollien/gobbler/categorize" + "github.com/ollien/gobbler/categorize/match" + "github.com/ollien/gobbler/categorize/tv" + + "github.com/fatih/color" +) + +type fileLocationMap = map[string]tv.Location + +func main() { + if len(os.Args) != 3 { + fmt.Fprintln(os.Stderr, "Usage: ./gobbler sourceFolder libraryFolder") + os.Exit(1) + } + + sourceFolder, libraryFolder := os.Args[1], os.Args[2] + + sourceFolderContents, err := getDirEntries(sourceFolder) + if err != nil { + panic(err) + } + + categorizer, err := categorize.NewCategorizer(libraryFolder) + if err != nil { + panic(err) + } + + locations := performCategorization(categorizer, sourceFolderContents) + prettyPrintLocations(locations) + + numSkipped := len(sourceFolderContents) - len(locations) + fmt.Fprintf(os.Stderr, "%d items not categorized\n", numSkipped) +} + +func getDirEntries(path string) ([]string, error) { + fd, err := os.Open(path) + if err != nil { + return nil, err + } + + defer fd.Close() + + return fd.Readdirnames(-1) +} + +// performCategorization categorizes the given files through the given categorizer, converting them to a map of +// filename to Locations +func performCategorization(categorizer categorize.Categorizer, sourceFolderContents []string) fileLocationMap { + locations := map[string]tv.Location{} + for _, entry := range sourceFolderContents { + location, err := categorizer.CategorizeFile(entry) + if errors.Is(err, match.ErrNoMatches) { + continue + } else if err != nil { + fmt.Fprintf(os.Stderr, "Failed to categorize '%s':\n\t%s\n", entry, err) + continue + } + + locations[entry] = location + } + + return locations +} + +func prettyPrintLocations(locations fileLocationMap) { + for filename, location := range locations { + color.New(color.FgGreen, color.Bold).Println(filename) + seasonString := "Season" + if len(location.Seasons()) > 1 { + seasonString = "Seasons" + } + + fmt.Printf(" ├── %s\n", location.MediaTitle()) + fmt.Printf(" └── %s %s\n", seasonString, joinIntsGramatically(location.Seasons())) + } +} + +// joinIntsGramatically joins a seqwuence of ints as a comma separated list of strings, compelte with "and" and +// oxford comma (e.g. "1, 2, 3, 4, and 5") +func joinIntsGramatically(nums []int) string { + if len(nums) == 0 { + return "" + } else if len(nums) == 1 { + return strconv.Itoa(nums[0]) + } + + numStrings := make([]string, len(nums)) + for i, num := range nums { + numStrings[i] = strconv.Itoa(num) + } + + joined := strings.Join(numStrings[:len(numStrings)-1], ", ") + + return joined + ", and " + numStrings[len(numStrings)-1] +} diff --git a/go.mod b/go.mod index fd745c9..d4b8d00 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ollien/gobbler go 1.16 require ( - github.com/agnivade/levenshtein v1.1.1 // indirect - github.com/stretchr/testify v1.7.0 // indirect + github.com/agnivade/levenshtein v1.1.1 + github.com/fatih/color v1.12.0 + github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 5a4e39d..c702b9f 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,21 @@ github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/bevacqua/fuzzysearch v1.0.2 h1:23dOu/69YB8gzAIc2vk4eWtY2lg8YgCcTMQwg2QVyvA= github.com/bevacqua/fuzzysearch v1.0.2/go.mod h1:Pjn0r0ohRYG/5PnkllhikX34r67ITFOdCM32/uJnxl4= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/lithammer/fuzzysearch v1.1.2 h1:ePUtm14xKxbpCxozcFbIDRtvANxnVnE+RKpJUqkr2gA= github.com/lithammer/fuzzysearch v1.1.2/go.mod h1:v6tYW/9kpfV6LNcweXdSjQsfCku/1M/oObmSox1fzP8= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= @@ -16,6 +24,9 @@ github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=