Skip to content

Commit

Permalink
Add support for limiting replacements
Browse files Browse the repository at this point in the history
- Also update string mode replacement implementation
  • Loading branch information
ayoisaiah committed May 4, 2021
1 parent 49194d3 commit c48f281
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 310 deletions.
19 changes: 13 additions & 6 deletions src/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ func GetApp() *cli.App {
Aliases: []string{"r"},
Usage: "Replacement `<string>`. If omitted, defaults to an empty string. Supports built-in and regex capture variables",
},
&cli.UintFlag{
Name: "replace-limit",
Aliases: []string{"l"},
Usage: "Limit the number of replacements to be made (replaces all matches if set to 0)",
Value: 0,
DefaultText: "0",
},
&cli.BoolFlag{
Name: "string-mode",
Aliases: []string{"s"},
Usage: "Opt into string literal mode by treating find expressions as non-regex strings",
},
&cli.StringSliceFlag{
Name: "exclude",
Aliases: []string{"E"},
Expand All @@ -113,7 +125,7 @@ func GetApp() *cli.App {
Aliases: []string{"R"},
Usage: "Rename files recursively",
},
&cli.IntFlag{
&cli.UintFlag{
Name: "max-depth",
Aliases: []string{"m"},
Usage: "positive `<integer>` indicating the maximum depth for a recursive search (set to 0 for no limit)",
Expand Down Expand Up @@ -168,11 +180,6 @@ func GetApp() *cli.App {
Aliases: []string{"F"},
Usage: "Fix any detected conflicts with auto indexing",
},
&cli.BoolFlag{
Name: "string-mode",
Aliases: []string{"s"},
Usage: "Opt into string literal mode by treating find expressions as non-regex strings",
},
},
UseShortOptionHandling: true,
Action: func(c *cli.Context) error {
Expand Down
78 changes: 36 additions & 42 deletions src/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,33 @@ type renameError struct {

// Operation represents a batch renaming operation
type Operation struct {
paths []Change
matches []Change
conflicts map[conflict][]Conflict
findString string
replacement string
startNumber int
exec bool
fixConflicts bool
includeHidden bool
includeDir bool
onlyDir bool
ignoreCase bool
ignoreExt bool
searchRegex *regexp.Regexp
directories []string
recursive bool
workingDir string
stringMode bool
excludeFilter []string
maxDepth int
sort string
reverseSort bool
quiet bool
errors []renameError
revert bool
numberOffset []int
paths []Change
matches []Change
conflicts map[conflict][]Conflict
findString string
replacement string
startNumber int
exec bool
fixConflicts bool
includeHidden bool
includeDir bool
onlyDir bool
ignoreCase bool
ignoreExt bool
searchRegex *regexp.Regexp
directories []string
recursive bool
workingDir string
stringLiteralMode bool
excludeFilter []string
maxDepth int
sort string
reverseSort bool
quiet bool
errors []renameError
revert bool
numberOffset []int
replaceLimit int
}

type backupFile struct {
Expand Down Expand Up @@ -427,20 +428,6 @@ func (op *Operation) findMatches() error {
f = filenameWithoutExtension(f)
}

if op.stringMode {
findStr := op.findString

if op.ignoreCase {
f = strings.ToLower(f)
findStr = strings.ToLower(findStr)
}

if strings.Contains(f, findStr) {
op.matches = append(op.matches, v)
}
continue
}

matched := op.searchRegex.MatchString(f)
if matched {
op.matches = append(op.matches, v)
Expand Down Expand Up @@ -564,11 +551,12 @@ func setOptions(op *Operation, c *cli.Context) error {
op.recursive = c.Bool("recursive")
op.directories = c.Args().Slice()
op.onlyDir = c.Bool("only-dir")
op.stringMode = c.Bool("string-mode")
op.stringLiteralMode = c.Bool("string-mode")
op.excludeFilter = c.StringSlice("exclude")
op.maxDepth = c.Int("max-depth")
op.maxDepth = int(c.Uint("max-depth"))
op.quiet = c.Bool("quiet")
op.revert = c.Bool("undo")
op.replaceLimit = int(c.Uint("replace-limit"))

// Sorting
if c.String("sort") != "" {
Expand All @@ -583,6 +571,12 @@ func setOptions(op *Operation, c *cli.Context) error {
}

findPattern := c.String("find")

// Escape all regular expression metacharacters in string literal mode
if op.stringLiteralMode {
findPattern = regexp.QuoteMeta(findPattern)
}

// Match entire string if find pattern is empty
if findPattern == "" {
findPattern = ".*"
Expand Down
39 changes: 0 additions & 39 deletions src/operation_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package f2

import (
"path/filepath"
"regexp"
"testing"
)

Expand Down Expand Up @@ -59,41 +58,3 @@ func TestCaseConversion(t *testing.T) {

runFindReplace(t, cases)
}

func TestTransformation(t *testing.T) {
cases := []struct {
input string
transform string
find string
output string
}{
{
input: `abc<>_{}*?\/\.epub`,
transform: `\Twin`,
find: `abc.*`,
output: "abc_{}.epub",
},
{
input: `abc<>_{}*:?\/\.epub`,
transform: `\Tmac`,
find: `abc.*`,
output: `abc<>_{}*?\/\.epub`,
},
}

for _, v := range cases {
op := &Operation{}
op.replacement = v.transform
regex, err := regexp.Compile(v.find)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

op.searchRegex = regex
out := op.replaceString(v.input)

if out != v.output {
t.Fatalf("Expected %s, but got: %s", v.output, out)
}
}
}
186 changes: 0 additions & 186 deletions src/operation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,192 +236,6 @@ func runFindReplace(t *testing.T, cases []testCase) {
}
}

func TestFindReplace(t *testing.T) {
testDir := setupFileSystem(t)

cases := []testCase{
{
want: []Change{
{
Source: "No Pressure (2021) S1.E1.1080p.mkv",
BaseDir: testDir,
Target: "1.mkv",
},
{
Source: "No Pressure (2021) S1.E2.1080p.mkv",
BaseDir: testDir,
Target: "2.mkv",
},
{
Source: "No Pressure (2021) S1.E3.1080p.mkv",
BaseDir: testDir,
Target: "3.mkv",
},
},
args: []string{
"-f",
".*E(\\d+).*",
"-r",
"$1.mkv",
testDir,
},
},
{
want: []Change{
{
Source: "No Pressure (2021) S1.E1.1080p.mkv",
BaseDir: testDir,
Target: "No Pressure 98.mkv",
},
{
Source: "No Pressure (2021) S1.E2.1080p.mkv",
BaseDir: testDir,
Target: "No Pressure 99.mkv",
},
{
Source: "No Pressure (2021) S1.E3.1080p.mkv",
BaseDir: testDir,
Target: "No Pressure 100.mkv",
},
},
args: []string{
"-f",
"(No Pressure).*",
"-r",
"$1 98%d.mkv",
testDir,
},
},
{
want: []Change{
{
Source: "index.js",
BaseDir: filepath.Join(testDir, "scripts"),
Target: "index.ts",
},
{
Source: "main.js",
BaseDir: filepath.Join(testDir, "scripts"),
Target: "main.ts",
},
},
args: []string{
"-f",
"js",
"-r",
"ts",
filepath.Join(testDir, "scripts"),
},
},
{
want: []Change{
{
Source: "index.js",
BaseDir: filepath.Join(testDir, "scripts"),
Target: "i n d e x .js",
},
{
Source: "main.js",
BaseDir: filepath.Join(testDir, "scripts"),
Target: "m a i n .js",
},
},
args: []string{
"-f",
"(.)",
"-r",
"$1 ",
"-e",
filepath.Join(testDir, "scripts"),
},
},
{
want: []Change{
{
Source: "a.jpg",
BaseDir: filepath.Join(testDir, "images"),
Target: "a.jpeg",
},
{
Source: "b.jPg",
BaseDir: filepath.Join(testDir, "images"),
Target: "b.jpeg",
},
{
Source: "123.JPG",
BaseDir: filepath.Join(testDir, "images", "pics"),
Target: "123.jpeg",
},
{
Source: "free.jpg",
BaseDir: filepath.Join(testDir, "images", "pics"),
Target: "free.jpeg",
},
{
Source: "img.jpg",
BaseDir: filepath.Join(testDir, "morepics", "nested"),
Target: "img.jpeg",
},
},
args: []string{
"-f",
"jpg",
"-r",
"jpeg",
"-R",
"-i",
testDir,
},
},
{
want: []Change{
{
Source: "pics",
IsDir: true,
BaseDir: filepath.Join(testDir, "images"),
Target: "images",
},
{
Source: "morepics",
IsDir: true,
BaseDir: testDir,
Target: "moreimages",
},
{
Source: "pic-1.avif",
BaseDir: filepath.Join(testDir, "morepics"),
Target: "image-1.avif",
},
{
Source: "pic-2.avif",
BaseDir: filepath.Join(testDir, "morepics"),
Target: "image-2.avif",
},
},
args: []string{"-f", "pic", "-r", "image", "-d", "-R", testDir},
},
{
want: []Change{
{
Source: "pics",
IsDir: true,
BaseDir: filepath.Join(testDir, "images"),
Target: "images",
},
{
Source: "morepics",
IsDir: true,
BaseDir: testDir,
Target: "moreimages",
},
},
args: []string{"-f", "pic", "-r", "image", "-D", "-R", testDir},
},
}

runFindReplace(t, cases)
}

func TestHidden(t *testing.T) {
testDir := setupFileSystem(t)
cases := []testCase{
Expand Down
Loading

0 comments on commit c48f281

Please sign in to comment.