Skip to content

Commit

Permalink
Add support for undoing an operation
Browse files Browse the repository at this point in the history
  • Loading branch information
ayoisaiah committed May 26, 2020
1 parent ebf2aae commit 3c83d05
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 6 deletions.
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Goname - Command-line batch renaming tool

[![Go Report Card](https://goreportcard.com/badge/github.com/ayoisaiah/goname)](https://goreportcard.com/report/github.com/ayoisaiah/goname)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/7136493cf477467387381890cb25dc9e)](https://www.codacy.com/manual/ayoisaiah/goname?utm_source=github.com&utm_medium=referral&utm_content=ayoisaiah/goname&utm_campaign=Badge_Grade)
[![HitCount](http://hits.dwyl.com/ayoisaiah/goname.svg)](http://hits.dwyl.com/ayoisaiah/goname)
Expand All @@ -14,8 +15,9 @@ Goname is a cross-platform command-line tool for batch renaming files and direct
- Supports piping files through other programs like `find` or `rg`.
- Detects potential conflicts and errors and reports them to you.
- Supports recursive renaming of both files and directories.
- Supports renaming using a template
- Supports renaming using a template.
- Supports using an ascending integer for renaming (e.g 001, 002, 003, e.t.c.).
- Supports undoing the last successful operation.

## Installation

Expand All @@ -29,7 +31,7 @@ Otherwise, you can download precompiled binaries for Linux, Windows, and macOS [

## Usage

**Note**: running these commands will only print out the changes to be made. If you want to proceed, use the `-x` flag.
**Note**: running these commands will only print out the changes to be made. If you want to carry out the operation, use the `-x` or `--exec` flag.

All the examples below assume the following directory structure:

Expand Down Expand Up @@ -196,14 +198,24 @@ Use the -F flag to ignore conflicts and rename anyway
$ goname --find "pic1-bad.jpg" --replace ""
Error detected: Operation resulted in empty filename
pic1-bad.jpg ➟ [Empty filename] ❌
```

- If you change your mind regarding a renaming operation, you can undo your changes using the `--undo` or `-U` flag. This only works for the last successful operation.

```bash
$ goname -U
pic2-bad.png ➟ pic2-good.png ✅
pic1-bad.jpg ➟ pic1-good.jpg ✅
morebad/pic4-bad.webp ➟ morebad/pic4-good.webp ✅
morebad/pic3-bad.jpg ➟ morebad/pic3-good.jpg ✅
morebad ➟ moregood ✅
```

## TODO

- [ ] Write tests
- [ ] Add undo support

## Credit and sources
## Credits

Goname relies heavily on other open source software listed below:

Expand All @@ -212,7 +224,7 @@ Goname relies heavily on other open source software listed below:

## Contribute

Bug reports, or pull requests are much welcome!
Bug reports, feature requests, or pull requests are much welcome!

## Licence

Expand Down
12 changes: 12 additions & 0 deletions cmd/goname/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ func main() {
Aliases: []string{"x"},
Usage: "By default, goname will do a 'dry run' so that you can inspect the results and confirm that it looks correct. Add this flag to proceed with renaming the files.",
},
&cli.BoolFlag{
Name: "undo",
Aliases: []string{"U"},
Usage: "Undo the LAST successful operation",
},
&cli.BoolFlag{
Name: "include-dir",
Aliases: []string{"D"},
Expand All @@ -59,6 +64,13 @@ func main() {
},
},
Action: func(c *cli.Context) error {
if c.Bool("undo") {
op := &Operation{}
op.ignoreConflicts = c.Bool("force")
op.exec = c.Bool("exec")
return op.Undo()
}

op, err := NewOperation(c)
if err != nil {
return err
Expand Down
109 changes: 108 additions & 1 deletion cmd/goname/operation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bufio"
"fmt"
"io/ioutil"
"os"
Expand All @@ -19,6 +20,8 @@ var (
yellow = color.FgYellow.Render
)

const opsFile = ".goname-operation.txt"

// Change represents a single filename change
type Change struct {
source string
Expand All @@ -39,6 +42,108 @@ type Operation struct {
searchRegex *regexp.Regexp
}

// WriteToFile writes the details of the last successful operation
// to a file so that it may be reversed if necessary
func (op *Operation) WriteToFile() error {
// Create or truncate file
file, err := os.Create(opsFile)
if err != nil {
return err
}

defer file.Close()

writer := bufio.NewWriter(file)
for _, v := range op.matches {
_, err = writer.WriteString(v.target + "|" + v.source + "\n")
if err != nil {
return err
}
}

return writer.Flush()
}

// Undo reverses the last successful renaming operation
func (op *Operation) Undo() error {
file, err := os.Open(opsFile)
if err != nil {
return err
}

defer file.Close()

fi, err := file.Stat()
if err != nil {
return err
}

// If file is empty
if fi.Size() == 0 {
return fmt.Errorf("No operation to undo")
}

scanner := bufio.NewScanner(file)
for scanner.Scan() {
slice := strings.Split(scanner.Text(), "|")
if len(slice) != 2 {
return fmt.Errorf("Corrupted data. Cannot undo")
}
source, target := slice[0], slice[1]
ch := Change{}
ch.source = source
ch.target = target

op.matches = append(op.matches, ch)
}

for i, v := range op.matches {
isDir, err := isDirectory(v.source)
if err != nil {
// An error may mean that the path does not exist
// which indicates that the directory containing the file
// was also renamed.
if os.IsNotExist(err) {
dir := filepath.Dir(v.source)

// Get the directory that is changing
var d Change
for _, m := range op.matches {
if m.target == dir {
d = m
break
}
}

re, err := regexp.Compile(d.target)
if err != nil {
return err
}

srcFile, srcDir := filepath.Base(v.source), filepath.Dir(v.source)
targetFile, targetDir := filepath.Base(v.target), filepath.Dir(v.target)

// Update the directory of the path to the current name
// instead of the old one which no longer exists
srcDir = re.ReplaceAllString(srcDir, d.source)
targetDir = re.ReplaceAllString(targetDir, d.source)

v.source = filepath.Join(srcDir, srcFile)
v.target = filepath.Join(targetDir, targetFile)
} else {
return err
}
}

v.isDir = isDir
op.matches[i] = v
}

op.SortMatches()

return op.Apply()
}

// Apply will check for conflicts and print
// the changes to be made or apply them directly
// if in execute mode. Conflicts will be ignored if
Expand All @@ -65,7 +170,9 @@ func (op *Operation) Apply() error {
}
}

if !op.exec && len(op.matches) > 0 {
if op.exec && len(op.matches) > 0 {
return op.WriteToFile()
} else if !op.exec && len(op.matches) > 0 {
color.Style{color.FgYellow, color.OpBold}.Println("*** Use the -x flag to apply the above changes ***")
}

Expand Down

0 comments on commit 3c83d05

Please sign in to comment.