From c89571bbd5e648d021a6a003fc05ba5f9de0ca5a Mon Sep 17 00:00:00 2001 From: Wei Shen Date: Wed, 11 Apr 2018 00:10:02 +0800 Subject: [PATCH] v2.6.0: users can choose how to handle overwriting --- README.md | 74 +++++++++++++++++++++++++++++++++++++++++++--------- brename.go | 76 ++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 125 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 1a03984..30ae992 100755 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ - **Cross-platform**. Supporting Windows, Mac OS X and Linux. - **Safe**. By ***checking potential conflicts and errors***. +- **Overwrite can be detected and users can choose whether overwrite or leave it**. - **File filtering**. Supporting including and excluding files via regular expression. No need to run commands like `find ./ -name "*.html" -exec CMD`. - **Renaming submatch with corresponding value via key-value file**. @@ -48,19 +49,19 @@ #### Method 1: Download binaries -[brename v2.5.2](https://github.com/shenwei356/brename/releases/tag/v2.5.2) -[![Github Releases (by Release)](https://img.shields.io/github/downloads/shenwei356/brename/v2.5.2/total.svg)](https://github.com/shenwei356/brename/releases/tag/v2.5.2) +[brename v2.6.0](https://github.com/shenwei356/brename/releases/tag/v2.6.0) +[![Github Releases (by Release)](https://img.shields.io/github/downloads/shenwei356/brename/v2.6.0/total.svg)](https://github.com/shenwei356/brename/releases/tag/v2.6.0) ***Tip: run `brename -V` to check update !!!*** OS |Arch |File, 中国镜像 |Download Count :------|:---------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Linux |32-bit |[brename_linux_386.tar.gz](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_linux_386.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_linux_386.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_linux_386.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_linux_386.tar.gz) -Linux |**64-bit**|[**brename_linux_amd64.tar.gz**](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_linux_amd64.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_linux_amd64.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_linux_amd64.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_linux_amd64.tar.gz) -OS X |32-bit |[brename_darwin_386.tar.gz](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_darwin_386.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_darwin_386.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_darwin_386.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_darwin_386.tar.gz) -OS X |**64-bit**|[**brename_darwin_amd64.tar.gz**](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_darwin_amd64.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_darwin_amd64.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_darwin_amd64.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_darwin_amd64.tar.gz) -Windows|32-bit |[brename_windows_386.exe.tar.gz](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_windows_386.exe.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_windows_386.exe.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_windows_386.exe.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_windows_386.exe.tar.gz) -Windows|**64-bit**|[**brename_windows_amd64.exe.tar.gz**](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_windows_amd64.exe.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_windows_amd64.exe.tar.gz)|[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_windows_amd64.exe.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.5.2/brename_windows_amd64.exe.tar.gz) +Linux |32-bit |[brename_linux_386.tar.gz](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_linux_386.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_linux_386.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_linux_386.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_linux_386.tar.gz) +Linux |**64-bit**|[**brename_linux_amd64.tar.gz**](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_linux_amd64.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_linux_amd64.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_linux_amd64.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_linux_amd64.tar.gz) +OS X |32-bit |[brename_darwin_386.tar.gz](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_darwin_386.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_darwin_386.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_darwin_386.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_darwin_386.tar.gz) +OS X |**64-bit**|[**brename_darwin_amd64.tar.gz**](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_darwin_amd64.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_darwin_amd64.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_darwin_amd64.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_darwin_amd64.tar.gz) +Windows|32-bit |[brename_windows_386.exe.tar.gz](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_windows_386.exe.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_windows_386.exe.tar.gz) |[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_windows_386.exe.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_windows_386.exe.tar.gz) +Windows|**64-bit**|[**brename_windows_amd64.exe.tar.gz**](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_windows_amd64.exe.tar.gz),
[中国镜像](http://app.shenwei.me/data/brename/brename_windows_amd64.exe.tar.gz)|[![Github Releases (by Asset)](https://img.shields.io/github/downloads/shenwei356/brename/latest/brename_windows_amd64.exe.tar.gz.svg?maxAge=3600)](https://github.com/shenwei356/brename/releases/download/v2.6.0/brename_windows_amd64.exe.tar.gz) Just [download](https://github.com/shenwei356/brename/releases) compressed @@ -93,9 +94,10 @@ And then: ## Usage ``` + brename -- a practical cross-platform command-line tool for safely batch renaming files/directories via regular expression -Version: 2.5.2 +Version: 2.6.0 Author: Wei Shen @@ -103,8 +105,7 @@ Homepage: https://github.com/shenwei356/brename Attention: 1. Paths starting with "." is ignored. - 2. Overwriting existed files is not allowed. - 3. Flag -f/--include-filters and -F/--exclude-filters support multiple values, + 2. Flag -f/--include-filters and -F/--exclude-filters support multiple values, e.g., -f ".html" -f ".htm". But ATTENTION: comma in filter is treated as separater of multiple filters. @@ -155,11 +156,12 @@ Flags: -l, --list only list paths that match pattern -a, --list-abs list absolute path, using along with -l/--list --nr-width int minimum width for {nr} in flag -r/--replacement. e.g., formating "1" to "001" by --nr-width 3 (default 1) + -o, --overwrite-mode int overwrite mode (0 for reporting error, 1 for overwrite, 2 for not renaming) (default 0) -p, --pattern string search pattern (regular expression) -R, --recursive rename recursively -r, --replacement string replacement. capture variables supported. e.g. $1 represents the first submatch. ATTENTION: for *nix OS, use SINGLE quote NOT double quotes or use the \ escape character. Ascending integer is also supported by "{nr}" -n, --start-num int starting number when using {nr} in replacement (default 1) - -v, --verbose int verbose level (0 for all, 1 for warning and error, 2 for only error) + -v, --verbose int verbose level (0 for all, 1 for warning and error, 2 for only error) (default 0) -V, --version print version information and check for update ``` @@ -375,6 +377,54 @@ Take a directory for example: /home/shenwei/project/src/github.com/shenwei356/brename/binaries/brename_windows_386.exe.tar.gz /home/shenwei/project/src/github.com/shenwei356/brename/binaries/brename_windows_amd64.exe.tar.gz +1. Overwrite mode (`-o/--overwrite-mode`) + + $ ls *.tar.gz + brename_darwin_386.tar.gz brename_linux_386.tar.gz brename_windows_386.exe.tar.gz + brename_darwin_amd64.tar.gz brename_linux_amd64.tar.gz brename_windows_amd64.exe.tar.gz + + 1. default mode: reporting error + + $ brename -p 386 -r amd64 -d + [ERRO] checking: [ new path existed ] 'brename_darwin_386.tar.gz' -> 'brename_darwin_amd64.tar.gz' + [ERRO] checking: [ new path existed ] 'brename_linux_386.tar.gz' -> 'brename_linux_amd64.tar.gz' + [ERRO] checking: [ new path existed ] 'brename_windows_386.exe.tar.gz' -> 'brename_windows_amd64.exe.tar.gz' + [ERRO] 3 potential error(s) detected, please check + + 1. allowing overwrite + + $ brename -p 386 -r amd64 -d -o 1 + [WARN] checking: [ new path existed ] 'brename_darwin_386.tar.gz' -> 'brename_darwin_amd64.tar.gz' (will be overwrited) + [WARN] checking: [ new path existed ] 'brename_linux_386.tar.gz' -> 'brename_linux_amd64.tar.gz' (will be overwrited) + [WARN] checking: [ new path existed ] 'brename_windows_386.exe.tar.gz' -> 'brename_windows_amd64.exe.tar.gz' (will be overwrited) + [INFO] 3 path(s) to be renamed + + 1. leave it + + $ brename -p 386 -r amd64 -d -o 2 + [WARN] checking: [ new path existed ] 'brename_darwin_386.tar.gz' -> 'brename_darwin_amd64.tar.gz' (will NOT be overwrited) + [WARN] checking: [ new path existed ] 'brename_linux_386.tar.gz' -> 'brename_linux_amd64.tar.gz' (will NOT be overwrited) + [WARN] checking: [ new path existed ] 'brename_windows_386.exe.tar.gz' -> 'brename_windows_amd64.exe.tar.gz' (will NOT be overwrited) + [INFO] 0 path(s) to be renamed + + 1. this flag also works for another case, where two or more files are renamed to same new path + + $ brename -f 386 -p 'darwin|linux' -r unix-like -d + [INFO] checking: [ ok ] 'brename_darwin_386.tar.gz' -> 'brename_unix-like_386.tar.gz' + [ERRO] checking: [ overwriting newly renamed path ] 'brename_linux_386.tar.gz' -> 'brename_unix-like_386.tar.gz' + [ERRO] 1 potential error(s) detected, please check + + $ brename -f 386 -p 'darwin|linux' -r unix-like -d -o 1 + [INFO] checking: [ ok ] 'brename_darwin_386.tar.gz' -> 'brename_unix-like_386.tar.gz' + [WARN] checking: [ overwriting newly renamed path ] 'brename_linux_386.tar.gz' -> 'brename_unix-like_386.tar.gz' (will be overwrited) + [INFO] 2 path(s) to be renamed + + $ brename -f 386 -p 'darwin|linux' -r unix-like -d -o 2 + [INFO] checking: [ ok ] 'brename_darwin_386.tar.gz' -> 'brename_unix-like_386.tar.gz' + [WARN] checking: [ overwriting newly renamed path ] 'brename_linux_386.tar.gz' -> 'brename_unix-like_386.tar.gz' (will NOT be overwrited) + [INFO] 1 path(s) to be renamed + + ## Real-world examples diff --git a/brename.go b/brename.go index 2cfbfea..b5dd6fa 100644 --- a/brename.go +++ b/brename.go @@ -41,9 +41,12 @@ import ( var log *logging.Logger -var version = "2.5.2" +var version = "2.6.0" var app = "brename" +// for detecting one case where two or more files are renamed to same new path +var pathTree map[string]struct{} + // Options is the struct containing all global options type Options struct { Verbose int @@ -76,6 +79,8 @@ type Options struct { KeepKey bool KeyCaptIdx int KeyMissRepl string + + OverwriteMode int } var reNR = regexp.MustCompile(`\{(NR|nr)\}`) @@ -175,8 +180,20 @@ func getOptions(cmd *cobra.Command) *Options { log.Infof("%d pairs of key-value loaded", len(kvs)) } + verbose := getFlagNonNegativeInt(cmd, "verbose") + if verbose > 2 { + log.Errorf("illegal value of flag --verbose: %d, only 0/1/2 allowed", verbose) + os.Exit(1) + } + + overwriteMode := getFlagNonNegativeInt(cmd, "overwrite-mode") + if overwriteMode > 2 { + log.Errorf("illegal value of flag -o/--overwrite-mode: %d, only 0/1/2 allowed", overwriteMode) + os.Exit(1) + } + return &Options{ - Verbose: getFlagNonNegativeInt(cmd, "verbose"), + Verbose: verbose, Version: version, DryRun: getFlagBool(cmd, "dry-run"), @@ -206,6 +223,8 @@ func getOptions(cmd *cobra.Command) *Options { KeepKey: getFlagBool(cmd, "keep-key"), KeyCaptIdx: getFlagPositiveInt(cmd, "key-capt-idx"), KeyMissRepl: getFlagString(cmd, "key-miss-repl"), + + OverwriteMode: overwriteMode, } } @@ -220,7 +239,7 @@ func init() { logging.SetBackend(backendFormatter) log = logging.MustGetLogger(app) - RootCmd.Flags().IntP("verbose", "v", 0, "verbose level (0 for all, 1 for warning and error, 2 for only error)") + RootCmd.Flags().IntP("verbose", "v", 0, "verbose level (0 for all, 1 for warning and error, 2 for only error) (default 0)") RootCmd.Flags().BoolP("version", "V", false, "print version information and check for update") RootCmd.Flags().BoolP("dry-run", "d", false, "print rename operations but do not run") @@ -245,6 +264,8 @@ func init() { RootCmd.Flags().IntP("start-num", "n", 1, `starting number when using {nr} in replacement`) RootCmd.Flags().IntP("nr-width", "", 1, `minimum width for {nr} in flag -r/--replacement. e.g., formating "1" to "001" by --nr-width 3`) + RootCmd.Flags().IntP("overwrite-mode", "o", 0, "overwrite mode (0 for reporting error, 1 for overwrite, 2 for not renaming) (default 0)") + RootCmd.Example = ` 1. dry run and showing potential dangerous operations brename -p "abc" -d 2. dry run and only show operations that will cause error @@ -292,6 +313,8 @@ Additional help topics:{{range .Commands}}{{if .IsHelpCommand}} Use "{{.CommandPath}} --help" for more information about a command.{{end}} `) + + pathTree = make(map[string]struct{}, 1000) } func main() { @@ -399,8 +422,7 @@ Homepage: https://github.com/shenwei356/brename Attention: 1. Paths starting with "." is ignored. - 2. Overwriting existed files is not allowed. - 3. Flag -f/--include-filters and -F/--exclude-filters support multiple values, + 2. Flag -f/--include-filters and -F/--exclude-filters support multiple values, e.g., -f ".html" -f ".htm". But ATTENTION: comma in filter is treated as separater of multiple filters. @@ -447,18 +469,37 @@ Special replacement symbols: log.Infof("checking: %s\n", op) case codeUnchanged: log.Warningf("checking: %s\n", op) - case codeExisted: - log.Errorf("checking: %s\n", op) + case codeExisted, codeOverwriteNewPath: + switch opt.OverwriteMode { + case 0: // report error + log.Errorf("checking: %s\n", op) + case 1: // overwrite + log.Warningf("checking: %s (will be overwrited)\n", op) + case 2: // no renaming + log.Warningf("checking: %s (will NOT be overwrited)\n", op) + } case codeMissingTarget: log.Errorf("checking: %s\n", op) } } switch op.code { - case 0: + case codeOK: ops = append(ops, op) n++ - case 1: + case codeUnchanged: + case codeExisted, codeOverwriteNewPath: + switch opt.OverwriteMode { + case 0: // report error + hasErr = true + nErr++ + continue + case 1: // overwrite + ops = append(ops, op) + n++ + case 2: // no renaming + + } default: hasErr = true nErr++ @@ -528,6 +569,7 @@ const ( codeOK code = iota codeUnchanged codeExisted + codeOverwriteNewPath codeMissingTarget ) @@ -543,6 +585,8 @@ func (c code) String() string { return yellow("unchanged") case codeExisted: return red("new path existed") + case codeOverwriteNewPath: + return red("overwriting newly renamed path") case codeMissingTarget: return red("missing target") } @@ -601,20 +645,26 @@ func checkOperation(opt *Options, path string) (bool, operation) { } filename2 := opt.PatternRe.ReplaceAllString(filename, r) + ext + target := filepath.Join(dir, filename2) + if filename2 == "" { - return true, operation{path, filepath.Join(dir, filename2), codeMissingTarget} + return true, operation{path, target, codeMissingTarget} } if filename2 == filename+ext { - return true, operation{path, filepath.Join(dir, filename2), codeUnchanged} + return true, operation{path, target, codeUnchanged} } - target := filepath.Join(dir, filename2) if _, err := os.Stat(target); err == nil { return true, operation{path, target, codeExisted} } - return true, operation{path, filepath.Join(dir, filename2), codeOK} + if _, ok := pathTree[target]; ok { + return true, operation{path, target, codeOverwriteNewPath} + } + pathTree[target] = struct{}{} + + return true, operation{path, target, codeOK} } func ignore(opt *Options, path string) bool {