260 lines · 6.1 KB · Go
RawDownload
package transform import (	"context"	"errors"	"slices"	"strings"	"sync" 	"github.com/rclone/rclone/fs") type transform struct {	key   Algo   // for example, "prefix"	value string // for example, "some_prefix_"	tag   tag    // file, dir, or all} // tag controls which part of the file path is affected (file, dir, all)type tag int // tag modesconst (	file tag = iota // Only transform the leaf name of files (default)	dir             // Only transform name of directories - these may appear anywhere in the path	all             // Transform the entire path for files and directories) // Transforming returns true when transforms are in usefunc Transforming(ctx context.Context) bool {	ci := fs.GetConfig(ctx)	return len(ci.NameTransform) > 0} // SetOptions sets the options in ctx from flags passed in.// Any existing flags will be overwritten.// s should be in the same format as cmd line flags, i.e. "all,prefix=XXX"func SetOptions(ctx context.Context, s ...string) (err error) {	ci := fs.GetConfig(ctx)	ci.NameTransform = s	_, err = getOptions(ctx)	return err} // cache to minimize re-parsingvar (	cachedNameTransform []string	cachedOpt           []transform	cacheLock           sync.Mutex) // getOptions sets the options from flags passed in.func getOptions(ctx context.Context) (opt []transform, err error) {	if !Transforming(ctx) {		return opt, nil	} 	ci := fs.GetConfig(ctx) 	// return cached opt if available	if cachedNameTransform != nil && slices.Equal(ci.NameTransform, cachedNameTransform) {		return cachedOpt, nil	} 	for _, transform := range ci.NameTransform {		t, err := parse(transform)		if err != nil {			return opt, err		}		opt = append(opt, t)	}	updateCache(ci.NameTransform, opt)	return opt, nil} func updateCache(nt []string, o []transform) {	cacheLock.Lock()	cachedNameTransform = slices.Clone(nt)	cachedOpt = o	cacheLock.Unlock()} // parse a single instance of --name-transformfunc parse(s string) (t transform, err error) {	if s == "" {		return t, nil	}	s = t.parseTag(s)	err = t.parseKeyVal(s)	return t, err} // parse the tag (file/dir/all), set the option accordingly, and return the trimmed string//// we don't worry about errors here because it will error anyway as an invalid keyfunc (t *transform) parseTag(s string) string {	if strings.HasPrefix(s, "file,") {		t.tag = file		return strings.TrimPrefix(s, "file,")	}	if strings.HasPrefix(s, "dir,") {		t.tag = dir		return strings.TrimPrefix(s, "dir,")	}	if strings.HasPrefix(s, "all,") {		t.tag = all		return strings.TrimPrefix(s, "all,")	}	return s} // parse key and value (if any) by splitting on '=' sign// (file/dir/all tag has already been trimmed)func (t *transform) parseKeyVal(s string) (err error) {	if !strings.ContainsRune(s, '=') {		err = t.key.Set(s)		if err != nil {			return err		}		if t.requiresValue() {			fs.Debugf(nil, "received %v", s)			return errors.New("value is required for " + t.key.String())		}		return nil	}	split := strings.Split(s, "=")	if len(split) != 2 {		return errors.New("too many values")	}	if split[0] == "" {		return errors.New("key cannot be blank")	}	err = t.key.Set(split[0])	if err != nil {		return err	}	t.value = split[1]	return nil} // returns true if this particular algorithm requires a valuefunc (t *transform) requiresValue() bool {	switch t.key {	case ConvFindReplace:		return true	case ConvPrefix:		return true	case ConvSuffix:		return true	case ConvSuffixKeepExtension:		return true	case ConvTrimPrefix:		return true	case ConvTrimSuffix:		return true	case ConvIndex:		return true	case ConvDate:		return true	case ConvTruncate:		return true	case ConvTruncateKeepExtension:		return true	case ConvTruncateBytes:		return true	case ConvTruncateBytesKeepExtension:		return true	case ConvEncoder:		return true	case ConvDecoder:		return true	case ConvRegex:		return true	case ConvCommand:		return true	}	return false} // Algo describes conversion settingtype Algo = fs.Enum[transformChoices] // Supported transform optionsconst (	ConvNone Algo = iota	ConvToNFC	ConvToNFD	ConvToNFKC	ConvToNFKD	ConvFindReplace	ConvPrefix	ConvSuffix	ConvSuffixKeepExtension	ConvTrimPrefix	ConvTrimSuffix	ConvIndex	ConvDate	ConvTruncate	ConvTruncateKeepExtension	ConvTruncateBytes	ConvTruncateBytesKeepExtension	ConvBase64Encode	ConvBase64Decode	ConvEncoder	ConvDecoder	ConvISO8859_1	ConvWindows1252	ConvMacintosh	ConvCharmap	ConvLowercase	ConvUppercase	ConvTitlecase	ConvASCII	ConvURL	ConvRegex	ConvCommand) type transformChoices struct{} func (transformChoices) Choices() []string {	return []string{		ConvNone:                       "none",		ConvToNFC:                      "nfc",		ConvToNFD:                      "nfd",		ConvToNFKC:                     "nfkc",		ConvToNFKD:                     "nfkd",		ConvFindReplace:                "replace",		ConvPrefix:                     "prefix",		ConvSuffix:                     "suffix",		ConvSuffixKeepExtension:        "suffix_keep_extension",		ConvTrimPrefix:                 "trimprefix",		ConvTrimSuffix:                 "trimsuffix",		ConvIndex:                      "index",		ConvDate:                       "date",		ConvTruncate:                   "truncate",		ConvTruncateKeepExtension:      "truncate_keep_extension",		ConvTruncateBytes:              "truncate_bytes",		ConvTruncateBytesKeepExtension: "truncate_bytes_keep_extension",		ConvBase64Encode:               "base64encode",		ConvBase64Decode:               "base64decode",		ConvEncoder:                    "encoder",		ConvDecoder:                    "decoder",		ConvISO8859_1:                  "ISO-8859-1",		ConvWindows1252:                "Windows-1252",		ConvMacintosh:                  "Macintosh",		ConvCharmap:                    "charmap",		ConvLowercase:                  "lowercase",		ConvUppercase:                  "uppercase",		ConvTitlecase:                  "titlecase",		ConvASCII:                      "ascii",		ConvURL:                        "url",		ConvRegex:                      "regex",		ConvCommand:                    "command",	}} func (transformChoices) Type() string {	return "string"}