diff --git a/README.md b/README.md index 1be3bd3..5331157 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Yet another very specific tool that only I need. By default it requires a terminal that supports [terminal graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/), but you can fallback to Sixel by passing `--sixel` to the command. -https://github.com/graynk/imagesorter/assets/3626328/0d978bd0-107d-41e6-aaac-ed923fc6ac0a +https://github.com/graynk/imagesorter/assets/3626328/7535af34-4030-4955-ad69-e5c4b9806156 You can install it by grabbing a binary from [releases](https://github.com/graynk/imagesorter/releases) page, or by running ```bash @@ -14,8 +14,14 @@ You use it like this: ```bash imagesorter [--sixel] /path/to/source cool_pictures not_so_cool_pictures can_be_deleted ``` +or like this +```bash +imagesorter [--sixel] [--scan] /path/to/source /path/to/target +``` Then it will read every PNG/JPG in source directory, display it in the terminal and ask you to which of the target directories it should be moved. -Then it will move it accordingly. +Then it will move it accordingly. + +Optional `--scan` argument will scan every directory it `target` and create a list of target directories that way. Note: it uses `os.Rename` to move the file because I am lazy, so it won't move the file between different drives. diff --git a/main.go b/main.go index 407dc5b..c806d39 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "log" "os" "path" + "slices" "strconv" "strings" ) @@ -18,28 +19,40 @@ func main() { log.SetFlags(0) if strings.Contains(os.Args[1], "help") { fmt.Println("Pass in a source directory and then a list of directories into which you want to sort all images from source") - fmt.Println("Example: imagesorter ~/Pictures cool_stuff mediocre_stuff can_be_deleted_safely") + fmt.Println("You can pass --sixel arg to fallback from Kitty graphics protocol to Sixels") + fmt.Println("Example: imagesorter [--sixel] Pictures cool_stuff mediocre_stuff can_be_deleted_safely") + fmt.Println("You can also pass --scan to change the behaviour of the tool - it will take a single target directory," + + "and scan the directories inside it, treating them as new target directories") os.Exit(0) } isSixel := false + isScan := false var source string sortingDirectories := make([]string, 0, 2) for _, arg := range os.Args[1:] { switch { case arg == "--sixel": isSixel = true + case arg == "--scan": + isScan = true case source == "": source = arg default: sortingDirectories = append(sortingDirectories, arg) } } - if len(sortingDirectories) < 2 { - log.Fatal("Please provide a source directory and at least 2 target directories") - } entries := readFileEntries(source) - warnings := createSortingDirectories(sortingDirectories) + warnings := make([]string, 0, 1) + if isScan { + if len(sortingDirectories) != 1 { + log.Fatalf("when using --scan, you have to provide exactly one target directory") + } + target := sortingDirectories[0] + sortingDirectories = scanSortingDirectories(target) + } else { + warnings = createSortingDirectories(sortingDirectories) + } question := buildQuestion(sortingDirectories) loopOverFiles(source, question, entries, sortingDirectories, warnings, isSixel) @@ -53,17 +66,27 @@ func readFileEntries(path string) []os.DirEntry { return entries } +func scanSortingDirectories(target string) []string { + dirEntries, err := os.ReadDir(target) + if err != nil { + log.Fatalf("Failed to read target dir %s\n%v", target, err) + } + sortingDirectories := make([]string, 0, 1) + for _, dirEntry := range dirEntries { + if !dirEntry.IsDir() { + continue + } + sortingDirectories = append(sortingDirectories, path.Join(target, dirEntry.Name())) + } + return sortingDirectories +} + func createSortingDirectories(sortingDirectories []string) []string { warnings := make([]string, 0, 1) for _, sortingDirectory := range sortingDirectories { - err := os.Mkdir(sortingDirectory, 0750) - if os.IsExist(err) { - warning := fmt.Sprintf("warning: directory %s already exists, continuing without an error", sortingDirectory) - warnings = append(warnings, warning) - continue - } + err := createNewDir(sortingDirectory) if err != nil { - log.Fatalf("failed to create %s\n%v", sortingDirectory, err) + warnings = append(warnings, err.Error()) } } return warnings @@ -72,7 +95,7 @@ func createSortingDirectories(sortingDirectories []string) []string { func buildQuestion(sortingDirectories []string) string { questionBuilder := strings.Builder{} - questionBuilder.WriteString("where do you want to move the image?\n") + questionBuilder.WriteString("where do you want to move the image? press enter to skip\n") for i, directory := range sortingDirectories { questionBuilder.WriteString(fmt.Sprintf("[%d] %s\n", i+1, directory)) } @@ -109,7 +132,22 @@ func loopOverFiles(source, question string, entries []os.DirEntry, sortingDirect fmt.Printf("Note that in case of a name conflict, the file in the target directory will be overwritten\n\n") warnings = warnings[:0] } - number := checkUserResponse(question, len(sortingDirectories), reader) + number, newDir := checkUserResponse(question, len(sortingDirectories), reader) + if newDir != "" { + if i := slices.Index(sortingDirectories, newDir); i != -1 { + number = i + } else { + err := createNewDir(newDir) + if err != nil { + warnings = append(warnings, err.Error()) + } + sortingDirectories = append(sortingDirectories, newDir) + question = buildQuestion(sortingDirectories) + number = len(sortingDirectories) + } + } else if number == 0 { + continue + } moveFileOrFail(source, sortingDirectories[number-1], pictureName) } @@ -118,6 +156,17 @@ func loopOverFiles(source, question string, entries []os.DirEntry, sortingDirect fmt.Println("all done") } +func createNewDir(newDir string) error { + err := os.Mkdir(newDir, 0750) + if os.IsExist(err) { + return fmt.Errorf("warning: directory %s already exists, continuing without an error", newDir) + } else if err != nil { + log.Fatalf("failed to create %s\n%v", newDir, err) + } + + return nil +} + func openImageOrFail(source, pictureName string) *os.File { f, err := os.Open(path.Join(source, pictureName)) if err != nil { @@ -136,7 +185,7 @@ func decodeImageOrFail(f *os.File) image.Image { return img } -func checkUserResponse(question string, numberOfOptions int, reader *bufio.Reader) int { +func checkUserResponse(question string, numberOfOptions int, reader *bufio.Reader) (int, string) { for { fmt.Println(question) input, err := reader.ReadString('\n') @@ -144,12 +193,19 @@ func checkUserResponse(question string, numberOfOptions int, reader *bufio.Reade // Probably just Ctrl+D os.Exit(0) } - number, err := strconv.Atoi(strings.TrimSpace(input)) - if err != nil || number <= 0 || number > numberOfOptions { + if input == "\n" { + return 0, "" + } + input = strings.TrimSpace(input) + number, err := strconv.Atoi(input) + if err != nil { + return 0, input + } + if number <= 0 || number > numberOfOptions { fmt.Printf("Please enter a number between 1 and %d\n", numberOfOptions) continue } - return number + return number, "" } }