Skip to content

Commit

Permalink
allow to create new directories on the fly. also allow to scan for ex…
Browse files Browse the repository at this point in the history
…isting ones
  • Loading branch information
graynk committed Jan 6, 2024
1 parent 7f2608e commit 51acfb8
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 20 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
92 changes: 74 additions & 18 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"log"
"os"
"path"
"slices"
"strconv"
"strings"
)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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))
}
Expand Down Expand Up @@ -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)
}

Expand All @@ -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 {
Expand All @@ -136,20 +185,27 @@ 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')
if err != nil {
// 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, ""
}
}

Expand Down

0 comments on commit 51acfb8

Please sign in to comment.