Skip to content

Commit

Permalink
add ability to sort matches with custom variables
Browse files Browse the repository at this point in the history
  • Loading branch information
ayoisaiah committed Oct 29, 2024
1 parent 6848a37 commit d0a3767
Show file tree
Hide file tree
Showing 28 changed files with 1,196 additions and 696 deletions.
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ offers several options for fine-grained control over the renaming process.`,
flagSort,
flagSortr,
flagSortPerDir,
flagSortVar,
flagStringMode,
flagTargetDir,
flagVerbose,
Expand Down
24 changes: 16 additions & 8 deletions app/app_test/testdata/help_stdout.golden
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Project repository: https://github.com/ayoisaiah/f2
-p, --pair
Enable pair renaming to rename files with the same name (but different
extensions) in the same directory to the same new name. In pair mode,
file extensions are ignored and --sort/--sortr has no effect.
file extensions are ignored.

Example:
Before: DSC08533.ARW DSC08533.JPG DSC08534.ARW DSC08534.JPG
Expand Down Expand Up @@ -163,13 +163,16 @@ Project repository: https://github.com/ayoisaiah/f2
Sorts matches in ascending order based on the provided criteria.

Allowed values:
* 'default' : Lexicographical order.
* 'size' : Sort by file size.
* 'natural' : Sort according to natural order.
* 'mtime' : Sort by file last modified time.
* 'btime' : Sort by file creation time.
* 'atime' : Sort by file last access time.
* 'ctime' : Sort by file metadata last change time.
* 'default' : Lexicographical order.
* 'size' : Sort by file size.
* 'natural' : Sort according to natural order.
* 'mtime' : Sort by file last modified time.
* 'btime' : Sort by file creation time.
* 'atime' : Sort by file last access time.
* 'ctime' : Sort by file metadata last change time.
* 'time_var' : Sort by time variable.
* 'int_var' : Sort by integer variable.
* 'string_var' : Sort lexicographically by string variable.

--sortr
Accepts the same values as --sort but sorts matches in descending order.
Expand All @@ -178,6 +181,11 @@ Project repository: https://github.com/ayoisaiah/f2
Ensures sorting is performed separately within each directory rather than
globally.

--sort-var
Active when using --sort/--sortr with time_var, int_var, or string_var.
Provide a supported variable to sort the files based on file metadata.
See https://f2.freshman.tech/guide/sorting for more details.

-s, --string-mode
Treats the search pattern (specified by -f/--find) as a literal string
instead of a regular expression.
Expand Down
27 changes: 19 additions & 8 deletions app/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ var (
Usage: `
Enable pair renaming to rename files with the same name (but different
extensions) in the same directory to the same new name. In pair mode,
file extensions are ignored and --sort/--sortr has no effect.
file extensions are ignored.
Example:
Before: DSC08533.ARW DSC08533.JPG DSC08534.ARW DSC08534.JPG
Expand Down Expand Up @@ -256,13 +256,16 @@ var (
Sorts matches in ascending order based on the provided criteria.
Allowed values:
* 'default' : Lexicographical order.
* 'size' : Sort by file size.
* 'natural' : Sort according to natural order.
* 'mtime' : Sort by file last modified time.
* 'btime' : Sort by file creation time.
* 'atime' : Sort by file last access time.
* 'ctime' : Sort by file metadata last change time.`,
* 'default' : Lexicographical order.
* 'size' : Sort by file size.
* 'natural' : Sort according to natural order.
* 'mtime' : Sort by file last modified time.
* 'btime' : Sort by file creation time.
* 'atime' : Sort by file last access time.
* 'ctime' : Sort by file metadata last change time.
* 'time_var' : Sort by time variable.
* 'int_var' : Sort by integer variable.
* 'string_var' : Sort lexicographically by string variable.`,
DefaultText: "<sort>",
}

Expand All @@ -280,6 +283,14 @@ var (
globally.`,
}

flagSortVar = &cli.StringFlag{
Name: "sort-var",
Usage: `
Active when using --sort/--sortr with time_var, int_var, or string_var.
Provide a supported variable to sort the files based on file metadata.
See https://f2.freshman.tech/guide/sorting for more details.`,
}

flagStringMode = &cli.BoolFlag{
Name: "string-mode",
Aliases: []string{"s"},
Expand Down
9 changes: 9 additions & 0 deletions app/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ func helpText(app *cli.App) string {
flagSortPerDir.GetUsage(),
)

flagSortVarHelp := fmt.Sprintf(
`%s %s`,
pterm.Green("--", flagSortVar.Name),
flagSortVar.GetUsage(),
)

flagStringModeHelp := fmt.Sprintf(
`%s, %s %s`,
pterm.Green("-", flagStringMode.Aliases[0]),
Expand Down Expand Up @@ -303,6 +309,8 @@ Project repository: https://github.com/ayoisaiah/f2
%s
%s
%s
%s
%s
Expand Down Expand Up @@ -348,6 +356,7 @@ Project repository: https://github.com/ayoisaiah/f2
flagSortHelp,
flagSortrHelp,
flagSortPerDirHelp,
flagSortVarHelp,
flagStringModeHelp,
flagTargetDirHelp,
flagVerboseHelp,
Expand Down
21 changes: 15 additions & 6 deletions find/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,28 @@ func handleCSV(conf *config.Config) (file.Changes, error) {
return nil, err
}

currentDir, err := os.Getwd()
if err != nil {
return nil, err
}

// Change to the directory of the CSV file
err = os.Chdir(conf.WorkingDir)
if err != nil {
return nil, err
}

defer func() {
_ = os.Chdir(currentDir)
}()

for i, record := range records {
if len(record) == 0 {
continue
}

source := strings.TrimSpace(record[0])

// Change to the directory of the CSV file
err := os.Chdir(conf.WorkingDir)
if err != nil {
return nil, err
}

fileInfo, statErr := os.Stat(source)
if statErr != nil {
// Skip missing source files
Expand Down
70 changes: 63 additions & 7 deletions find/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ import (
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/araddon/dateparse"

"github.com/ayoisaiah/f2/internal/config"
"github.com/ayoisaiah/f2/internal/file"
"github.com/ayoisaiah/f2/internal/osutil"
"github.com/ayoisaiah/f2/internal/pathutil"
"github.com/ayoisaiah/f2/internal/sortfiles"
"github.com/ayoisaiah/f2/internal/status"
"github.com/ayoisaiah/f2/replace/variables"
)

const (
dotCharacter = 46
)

var vars variables.Variables

// shouldFilter decides whether a match should be included in the final
// pool of files for renaming.
func shouldFilter(conf *config.Config, match *file.Change) bool {
Expand Down Expand Up @@ -98,6 +104,44 @@ func isMaxDepth(rootPath, currentPath string, maxDepth int) bool {
return depthCount > maxDepth
}

func extractCustomSort(
conf *config.Config,
ch *file.Change,
vars *variables.Variables,
) error {
// Temporarily set Target to SortVariable due to how variables.Replace() works
ch.Target = conf.SortVariable

err := variables.Replace(conf, ch, vars)
if err != nil {
return err
}

if conf.Sort == config.SortTimeVar {
// if variable cannot be parsed into a valid time, default to zero value
timeVal, _ := dateparse.ParseAny(ch.Target)

ch.CustomSort.Time = timeVal
}

if conf.Sort == config.SortStringVar {
ch.CustomSort.String = ch.Target
}

if conf.Sort == config.SortIntVar {
// if variable cannot be parsed into a valid integer, default to zero
intVal, _ := strconv.Atoi(ch.Target)

ch.CustomSort.Int = intVal
}

// Reset to an empty string once custom sort variable has been extracted and
// assigned accordingly
ch.Target = ""

return nil
}

func createFileChange(
conf *config.Config,
dirPath string,
Expand Down Expand Up @@ -146,6 +190,11 @@ func searchPaths(conf *config.Config) (file.Changes, error) {
match := createFileChange(conf, rootPath, fileInfo)

if !shouldFilter(conf, match) {
err := extractCustomSort(conf, match, &vars)
if err != nil {
return nil, err
}

matches = append(matches, match)
}
}
Expand Down Expand Up @@ -201,6 +250,7 @@ func searchPaths(conf *config.Config) (file.Changes, error) {

entryIsDir := entry.IsDir()

// FIXME:: Pairing should not affect how files are matched
if conf.IgnoreExt && !entryIsDir {
fileName = pathutil.StripExtension(fileName)
}
Expand All @@ -214,6 +264,11 @@ func searchPaths(conf *config.Config) (file.Changes, error) {
match := createFileChange(conf, currentPath, fileInfo)

if !shouldFilter(conf, match) {
err := extractCustomSort(conf, match, &vars)
if err != nil {
return err
}

matches = append(matches, match)
}
}
Expand Down Expand Up @@ -298,23 +353,24 @@ func loadFromBackup(conf *config.Config) (file.Changes, error) {
// Find returns a collection of files and directories that match the search
// pattern or explicitly included as command-line arguments.
func Find(conf *config.Config) (changes file.Changes, err error) {
if conf.SortVariable != "" {
vars, err = variables.Extract(conf.SortVariable)
if err != nil {
return nil, err
}
}

if conf.Revert {
return loadFromBackup(conf)
}

defer func() {
if conf.Pair && err == nil {
sortfiles.Pairs(changes, conf.PairOrder)
return
}

if conf.Sort != config.SortDefault && err == nil {
sortfiles.Changes(
changes,
conf.Sort,
conf.ReverseSort,
conf.SortPerDir,
)
sortfiles.Changes(changes, conf)
}
}()

Expand Down
Loading

0 comments on commit d0a3767

Please sign in to comment.