mirror of
https://github.com/rclone/rclone.git
synced 2025-06-24 22:11:45 +02:00
convmv: fix spurious "error running command echo" on Windows
Before this change the help for convmv was generated by running the examples each time rclone started up. Unfortunately this involved running the echo command which did not work on Windows. This pre-generates the help into `transform.md` and embeds it. It can be re-generated with `go generate` which is a better solution. See: https://forum.rclone.org/t/invoke-of-1-70-0-complains-of-echo-not-found/51618
This commit is contained in:
parent
b064cc2116
commit
4280ec75cc
@ -34,7 +34,7 @@ var commandDefinition = &cobra.Command{
|
|||||||
Long: strings.ReplaceAll(`
|
Long: strings.ReplaceAll(`
|
||||||
convmv supports advanced path name transformations for converting and renaming files and directories by applying prefixes, suffixes, and other alterations.
|
convmv supports advanced path name transformations for converting and renaming files and directories by applying prefixes, suffixes, and other alterations.
|
||||||
|
|
||||||
`+transform.SprintList()+`
|
`+transform.Help()+`
|
||||||
|
|
||||||
Multiple transformations can be used in sequence, applied in the order they are specified on the command line.
|
Multiple transformations can be used in sequence, applied in the order they are specified on the command line.
|
||||||
|
|
||||||
|
@ -14,6 +14,9 @@ var (
|
|||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CharmapChoices is an enum of the character map choices.
|
||||||
|
type CharmapChoices = fs.Enum[cmapChoices]
|
||||||
|
|
||||||
type cmapChoices struct{}
|
type cmapChoices struct{}
|
||||||
|
|
||||||
func (cmapChoices) Choices() []string {
|
func (cmapChoices) Choices() []string {
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
package transform
|
// Create the help text for transform
|
||||||
|
//
|
||||||
|
// Run with go generate (defined in transform.go)
|
||||||
|
//
|
||||||
|
//go:build none
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/lib/encoder"
|
"github.com/rclone/rclone/lib/encoder"
|
||||||
|
"github.com/rclone/rclone/lib/transform"
|
||||||
)
|
)
|
||||||
|
|
||||||
type commands struct {
|
type commands struct {
|
||||||
@ -75,11 +83,11 @@ func (e example) command() string {
|
|||||||
|
|
||||||
func (e example) output() string {
|
func (e example) output() string {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
err := SetOptions(ctx, e.flags...)
|
err := transform.SetOptions(ctx, e.flags...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Errorf(nil, "error generating help text: %v", err)
|
fs.Errorf(nil, "error generating help text: %v", err)
|
||||||
}
|
}
|
||||||
return Path(ctx, e.path, false)
|
return transform.Path(ctx, e.path, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// go run ./ convmv --help
|
// go run ./ convmv --help
|
||||||
@ -102,13 +110,11 @@ func commandTable() string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
var generatingHelpText bool
|
|
||||||
|
|
||||||
// SprintList returns the example help text as a string
|
// SprintList returns the example help text as a string
|
||||||
func SprintList() string {
|
func SprintList() string {
|
||||||
var algos transformAlgo
|
var algos transform.Algo
|
||||||
var charmaps fs.Enum[cmapChoices]
|
var charmaps transform.CharmapChoices
|
||||||
generatingHelpText = true
|
|
||||||
s := commandTable()
|
s := commandTable()
|
||||||
s += fmt.Sprintln("Conversion modes: \n```")
|
s += fmt.Sprintln("Conversion modes: \n```")
|
||||||
for _, v := range algos.Choices() {
|
for _, v := range algos.Choices() {
|
||||||
@ -130,11 +136,20 @@ func SprintList() string {
|
|||||||
|
|
||||||
s += sprintExamples()
|
s += sprintExamples()
|
||||||
|
|
||||||
generatingHelpText = false
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintList prints the example help text to stdout
|
// Output the help to stdout
|
||||||
func PrintList() {
|
func main() {
|
||||||
fmt.Println(SprintList())
|
out := os.Stdout
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
var err error
|
||||||
|
out, err = os.Create(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
fs.Fatalf(nil, "Open output failed: %v", err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
}
|
||||||
|
fmt.Fprintf(out, "<!--- Docs generated by help.go - use go generate to rebuild - DO NOT EDIT --->\n\n")
|
||||||
|
fmt.Fprintln(out, SprintList())
|
||||||
}
|
}
|
@ -11,9 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type transform struct {
|
type transform struct {
|
||||||
key transformAlgo // for example, "prefix"
|
key Algo // for example, "prefix"
|
||||||
value string // for example, "some_prefix_"
|
value string // for example, "some_prefix_"
|
||||||
tag tag // file, dir, or all
|
tag tag // file, dir, or all
|
||||||
}
|
}
|
||||||
|
|
||||||
// tag controls which part of the file path is affected (file, dir, all)
|
// tag controls which part of the file path is affected (file, dir, all)
|
||||||
@ -171,12 +171,12 @@ func (t *transform) requiresValue() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// transformAlgo describes conversion setting
|
// Algo describes conversion setting
|
||||||
type transformAlgo = fs.Enum[transformChoices]
|
type Algo = fs.Enum[transformChoices]
|
||||||
|
|
||||||
// Supported transform options
|
// Supported transform options
|
||||||
const (
|
const (
|
||||||
ConvNone transformAlgo = iota
|
ConvNone Algo = iota
|
||||||
ConvToNFC
|
ConvToNFC
|
||||||
ConvToNFD
|
ConvToNFD
|
||||||
ConvToNFKC
|
ConvToNFKC
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
// Package transform holds functions for path name transformations
|
// Package transform holds functions for path name transformations
|
||||||
|
//
|
||||||
|
//go:generate go run gen_help.go transform.md
|
||||||
package transform
|
package transform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
_ "embed"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -24,6 +27,16 @@ import (
|
|||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed transform.md
|
||||||
|
var help string
|
||||||
|
|
||||||
|
// Help returns the help string cleaned up to simplify appending
|
||||||
|
func Help() string {
|
||||||
|
// Chop off auto generated message
|
||||||
|
nl := strings.IndexRune(help, '\n')
|
||||||
|
return strings.TrimSpace(help[nl:]) + "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
// Path transforms a path s according to the --name-transform options in use
|
// Path transforms a path s according to the --name-transform options in use
|
||||||
//
|
//
|
||||||
// If no transforms are in use, s is returned unchanged
|
// If no transforms are in use, s is returned unchanged
|
||||||
@ -53,7 +66,7 @@ func Path(ctx context.Context, s string, isDir bool) string {
|
|||||||
fs.Errorf(s, "Failed to transform: %v", err)
|
fs.Errorf(s, "Failed to transform: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if old != s && !generatingHelpText {
|
if old != s {
|
||||||
fs.Debugf(old, "transformed to: %v", s)
|
fs.Debugf(old, "transformed to: %v", s)
|
||||||
}
|
}
|
||||||
if strings.Count(old, "/") != strings.Count(s, "/") {
|
if strings.Count(old, "/") != strings.Count(s, "/") {
|
||||||
@ -181,7 +194,7 @@ func transformPathSegment(s string, t transform) (string, error) {
|
|||||||
case ConvMacintosh:
|
case ConvMacintosh:
|
||||||
return encodeWithReplacement(s, charmap.Macintosh), nil
|
return encodeWithReplacement(s, charmap.Macintosh), nil
|
||||||
case ConvCharmap:
|
case ConvCharmap:
|
||||||
var cmapType fs.Enum[cmapChoices]
|
var cmapType CharmapChoices
|
||||||
err := cmapType.Set(t.value)
|
err := cmapType.Set(t.value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
|
224
lib/transform/transform.md
Normal file
224
lib/transform/transform.md
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
<!--- Docs generated by help.go - use go generate to rebuild - DO NOT EDIT --->
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|------|------|
|
||||||
|
| `--name-transform prefix=XXXX` | Prepends XXXX to the file name. |
|
||||||
|
| `--name-transform suffix=XXXX` | Appends XXXX to the file name after the extension. |
|
||||||
|
| `--name-transform suffix_keep_extension=XXXX` | Appends XXXX to the file name while preserving the original file extension. |
|
||||||
|
| `--name-transform trimprefix=XXXX` | Removes XXXX if it appears at the start of the file name. |
|
||||||
|
| `--name-transform trimsuffix=XXXX` | Removes XXXX if it appears at the end of the file name. |
|
||||||
|
| `--name-transform regex=/pattern/replacement/` | Applies a regex-based transformation. |
|
||||||
|
| `--name-transform replace=old:new` | Replaces occurrences of old with new in the file name. |
|
||||||
|
| `--name-transform date={YYYYMMDD}` | Appends or prefixes the specified date format. |
|
||||||
|
| `--name-transform truncate=N` | Truncates the file name to a maximum of N characters. |
|
||||||
|
| `--name-transform base64encode` | Encodes the file name in Base64. |
|
||||||
|
| `--name-transform base64decode` | Decodes a Base64-encoded file name. |
|
||||||
|
| `--name-transform encoder=ENCODING` | Converts the file name to the specified encoding (e.g., ISO-8859-1, Windows-1252, Macintosh). |
|
||||||
|
| `--name-transform decoder=ENCODING` | Decodes the file name from the specified encoding. |
|
||||||
|
| `--name-transform charmap=MAP` | Applies a character mapping transformation. |
|
||||||
|
| `--name-transform lowercase` | Converts the file name to lowercase. |
|
||||||
|
| `--name-transform uppercase` | Converts the file name to UPPERCASE. |
|
||||||
|
| `--name-transform titlecase` | Converts the file name to Title Case. |
|
||||||
|
| `--name-transform ascii` | Strips non-ASCII characters. |
|
||||||
|
| `--name-transform url` | URL-encodes the file name. |
|
||||||
|
| `--name-transform nfc` | Converts the file name to NFC Unicode normalization form. |
|
||||||
|
| `--name-transform nfd` | Converts the file name to NFD Unicode normalization form. |
|
||||||
|
| `--name-transform nfkc` | Converts the file name to NFKC Unicode normalization form. |
|
||||||
|
| `--name-transform nfkd` | Converts the file name to NFKD Unicode normalization form. |
|
||||||
|
| `--name-transform command=/path/to/my/programfile names.` | Executes an external program to transform |
|
||||||
|
|
||||||
|
|
||||||
|
Conversion modes:
|
||||||
|
```
|
||||||
|
none
|
||||||
|
nfc
|
||||||
|
nfd
|
||||||
|
nfkc
|
||||||
|
nfkd
|
||||||
|
replace
|
||||||
|
prefix
|
||||||
|
suffix
|
||||||
|
suffix_keep_extension
|
||||||
|
trimprefix
|
||||||
|
trimsuffix
|
||||||
|
index
|
||||||
|
date
|
||||||
|
truncate
|
||||||
|
base64encode
|
||||||
|
base64decode
|
||||||
|
encoder
|
||||||
|
decoder
|
||||||
|
ISO-8859-1
|
||||||
|
Windows-1252
|
||||||
|
Macintosh
|
||||||
|
charmap
|
||||||
|
lowercase
|
||||||
|
uppercase
|
||||||
|
titlecase
|
||||||
|
ascii
|
||||||
|
url
|
||||||
|
regex
|
||||||
|
command
|
||||||
|
```
|
||||||
|
Char maps:
|
||||||
|
```
|
||||||
|
|
||||||
|
IBM-Code-Page-037
|
||||||
|
IBM-Code-Page-437
|
||||||
|
IBM-Code-Page-850
|
||||||
|
IBM-Code-Page-852
|
||||||
|
IBM-Code-Page-855
|
||||||
|
Windows-Code-Page-858
|
||||||
|
IBM-Code-Page-860
|
||||||
|
IBM-Code-Page-862
|
||||||
|
IBM-Code-Page-863
|
||||||
|
IBM-Code-Page-865
|
||||||
|
IBM-Code-Page-866
|
||||||
|
IBM-Code-Page-1047
|
||||||
|
IBM-Code-Page-1140
|
||||||
|
ISO-8859-1
|
||||||
|
ISO-8859-2
|
||||||
|
ISO-8859-3
|
||||||
|
ISO-8859-4
|
||||||
|
ISO-8859-5
|
||||||
|
ISO-8859-6
|
||||||
|
ISO-8859-7
|
||||||
|
ISO-8859-8
|
||||||
|
ISO-8859-9
|
||||||
|
ISO-8859-10
|
||||||
|
ISO-8859-13
|
||||||
|
ISO-8859-14
|
||||||
|
ISO-8859-15
|
||||||
|
ISO-8859-16
|
||||||
|
KOI8-R
|
||||||
|
KOI8-U
|
||||||
|
Macintosh
|
||||||
|
Macintosh-Cyrillic
|
||||||
|
Windows-874
|
||||||
|
Windows-1250
|
||||||
|
Windows-1251
|
||||||
|
Windows-1252
|
||||||
|
Windows-1253
|
||||||
|
Windows-1254
|
||||||
|
Windows-1255
|
||||||
|
Windows-1256
|
||||||
|
Windows-1257
|
||||||
|
Windows-1258
|
||||||
|
X-User-Defined
|
||||||
|
```
|
||||||
|
Encoding masks:
|
||||||
|
```
|
||||||
|
Asterisk
|
||||||
|
BackQuote
|
||||||
|
BackSlash
|
||||||
|
Colon
|
||||||
|
CrLf
|
||||||
|
Ctl
|
||||||
|
Del
|
||||||
|
Dollar
|
||||||
|
Dot
|
||||||
|
DoubleQuote
|
||||||
|
Exclamation
|
||||||
|
Hash
|
||||||
|
InvalidUtf8
|
||||||
|
LeftCrLfHtVt
|
||||||
|
LeftPeriod
|
||||||
|
LeftSpace
|
||||||
|
LeftTilde
|
||||||
|
LtGt
|
||||||
|
None
|
||||||
|
Percent
|
||||||
|
Pipe
|
||||||
|
Question
|
||||||
|
Raw
|
||||||
|
RightCrLfHtVt
|
||||||
|
RightPeriod
|
||||||
|
RightSpace
|
||||||
|
Semicolon
|
||||||
|
SingleQuote
|
||||||
|
Slash
|
||||||
|
SquareBracket
|
||||||
|
```
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!.txt" --name-transform "all,uppercase"
|
||||||
|
// Output: STORIES/THE QUICK BROWN FOX!.TXT
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!.txt" --name-transform "all,replace=Fox:Turtle" --name-transform "all,replace=Quick:Slow"
|
||||||
|
// Output: stories/The Slow Brown Turtle!.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!.txt" --name-transform "all,base64encode"
|
||||||
|
// Output: c3Rvcmllcw==/VGhlIFF1aWNrIEJyb3duIEZveCEudHh0
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "c3Rvcmllcw==/VGhlIFF1aWNrIEJyb3duIEZveCEudHh0" --name-transform "all,base64decode"
|
||||||
|
// Output: stories/The Quick Brown Fox!.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown 🦊 Fox Went to the Café!.txt" --name-transform "all,nfc"
|
||||||
|
// Output: stories/The Quick Brown 🦊 Fox Went to the Café!.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown 🦊 Fox Went to the Café!.txt" --name-transform "all,nfd"
|
||||||
|
// Output: stories/The Quick Brown 🦊 Fox Went to the Café!.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown 🦊 Fox!.txt" --name-transform "all,ascii"
|
||||||
|
// Output: stories/The Quick Brown Fox!.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!.txt" --name-transform "all,trimsuffix=.txt"
|
||||||
|
// Output: stories/The Quick Brown Fox!
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!.txt" --name-transform "all,prefix=OLD_"
|
||||||
|
// Output: OLD_stories/OLD_The Quick Brown Fox!.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown 🦊 Fox Went to the Café!.txt" --name-transform "all,charmap=ISO-8859-7"
|
||||||
|
// Output: stories/The Quick Brown _ Fox Went to the Caf_!.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox: A Memoir [draft].txt" --name-transform "all,encoder=Colon,SquareBracket"
|
||||||
|
// Output: stories/The Quick Brown Fox: A Memoir [draft].txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown 🦊 Fox Went to the Café!.txt" --name-transform "all,truncate=21"
|
||||||
|
// Output: stories/The Quick Brown 🦊 Fox
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!.txt" --name-transform "all,command=echo"
|
||||||
|
// Output: stories/The Quick Brown Fox!.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!" --name-transform "date=-{YYYYMMDD}"
|
||||||
|
// Output: stories/The Quick Brown Fox!-20250618
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!" --name-transform "date=-{macfriendlytime}"
|
||||||
|
// Output: stories/The Quick Brown Fox!-2025-06-18 0148PM
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone convmv "stories/The Quick Brown Fox!.txt" --name-transform "all,regex=[\\.\\w]/ab"
|
||||||
|
// Output: ababababababab/ababab ababababab ababababab ababab!abababab
|
||||||
|
```
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user