github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/bisync/bilib/names.go (about)

     1  package bilib
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  )
    11  
    12  // Names comprises a set of file names
    13  type Names map[string]interface{}
    14  
    15  // ToNames converts string slice to a set of names
    16  func ToNames(list []string) Names {
    17  	ns := Names{}
    18  	for _, f := range list {
    19  		ns.Add(f)
    20  	}
    21  	return ns
    22  }
    23  
    24  // Add adds new file name to the set
    25  func (ns Names) Add(name string) {
    26  	ns[name] = nil
    27  }
    28  
    29  // Has checks whether given name is present in the set
    30  func (ns Names) Has(name string) bool {
    31  	_, ok := ns[name]
    32  	return ok
    33  }
    34  
    35  // NotEmpty checks whether set is not empty
    36  func (ns Names) NotEmpty() bool {
    37  	return len(ns) > 0
    38  }
    39  
    40  // ToList converts name set to string slice
    41  func (ns Names) ToList() []string {
    42  	list := []string{}
    43  	for file := range ns {
    44  		list = append(list, file)
    45  	}
    46  	sort.Strings(list)
    47  	return list
    48  }
    49  
    50  // Save saves name set in a text file
    51  func (ns Names) Save(path string) error {
    52  	return SaveList(ns.ToList(), path)
    53  }
    54  
    55  // SaveList saves file name list in a text file
    56  func SaveList(list []string, path string) error {
    57  	buf := &bytes.Buffer{}
    58  	for _, s := range list {
    59  		_, _ = buf.WriteString(strconv.Quote(s))
    60  		_ = buf.WriteByte('\n')
    61  	}
    62  	return os.WriteFile(path, buf.Bytes(), PermSecure)
    63  }
    64  
    65  // AliasMap comprises a pair of names that are not equal but treated as equal for comparison purposes
    66  // For example, when normalizing unicode and casing
    67  // This helps reduce repeated normalization functions, which really slow things down
    68  type AliasMap map[string]string
    69  
    70  // Add adds new pair to the set, in both directions
    71  func (am AliasMap) Add(name1, name2 string) {
    72  	if name1 != name2 {
    73  		am[name1] = name2
    74  		am[name2] = name1
    75  	}
    76  }
    77  
    78  // Alias returns the alternate version, if any, else the original.
    79  func (am AliasMap) Alias(name1 string) string {
    80  	// note: we don't need to check normalization settings, because we already did it in March.
    81  	// the AliasMap will only exist if March paired up two unequal filenames.
    82  	name2, ok := am[name1]
    83  	if ok {
    84  		return name2
    85  	}
    86  	return name1
    87  }
    88  
    89  // ParseGlobs determines whether a string contains {brackets}
    90  // and returns the substring (including both brackets) for replacing
    91  // substring is first opening bracket to last closing bracket --
    92  // good for {{this}} but not {this}{this}
    93  func ParseGlobs(s string) (hasGlobs bool, substring string) {
    94  	open := strings.Index(s, "{")
    95  	close := strings.LastIndex(s, "}")
    96  	if open >= 0 && close > open {
    97  		return true, s[open : close+1]
    98  	}
    99  	return false, ""
   100  }
   101  
   102  // TrimBrackets converts {{this}} to this
   103  func TrimBrackets(s string) string {
   104  	return strings.Trim(s, "{}")
   105  }
   106  
   107  // TimeFormat converts a user-supplied string to a Go time constant, if possible
   108  func TimeFormat(timeFormat string) string {
   109  	switch timeFormat {
   110  	case "Layout":
   111  		timeFormat = time.Layout
   112  	case "ANSIC":
   113  		timeFormat = time.ANSIC
   114  	case "UnixDate":
   115  		timeFormat = time.UnixDate
   116  	case "RubyDate":
   117  		timeFormat = time.RubyDate
   118  	case "RFC822":
   119  		timeFormat = time.RFC822
   120  	case "RFC822Z":
   121  		timeFormat = time.RFC822Z
   122  	case "RFC850":
   123  		timeFormat = time.RFC850
   124  	case "RFC1123":
   125  		timeFormat = time.RFC1123
   126  	case "RFC1123Z":
   127  		timeFormat = time.RFC1123Z
   128  	case "RFC3339":
   129  		timeFormat = time.RFC3339
   130  	case "RFC3339Nano":
   131  		timeFormat = time.RFC3339Nano
   132  	case "Kitchen":
   133  		timeFormat = time.Kitchen
   134  	case "Stamp":
   135  		timeFormat = time.Stamp
   136  	case "StampMilli":
   137  		timeFormat = time.StampMilli
   138  	case "StampMicro":
   139  		timeFormat = time.StampMicro
   140  	case "StampNano":
   141  		timeFormat = time.StampNano
   142  	case "DateTime":
   143  		// timeFormat = time.DateTime // missing in go1.19
   144  		timeFormat = "2006-01-02 15:04:05"
   145  	case "DateOnly":
   146  		// timeFormat = time.DateOnly // missing in go1.19
   147  		timeFormat = "2006-01-02"
   148  	case "TimeOnly":
   149  		// timeFormat = time.TimeOnly // missing in go1.19
   150  		timeFormat = "15:04:05"
   151  	case "MacFriendlyTime", "macfriendlytime", "mac":
   152  		timeFormat = "2006-01-02 0304PM" // not actually a Go constant -- but useful as macOS filenames can't have colons
   153  	}
   154  	return timeFormat
   155  }
   156  
   157  // AppyTimeGlobs converts "myfile-{DateOnly}.txt" to "myfile-2006-01-02.txt"
   158  func AppyTimeGlobs(s string, t time.Time) string {
   159  	hasGlobs, substring := ParseGlobs(s)
   160  	if !hasGlobs {
   161  		return s
   162  	}
   163  	timeString := t.Local().Format(TimeFormat(TrimBrackets(substring)))
   164  	return strings.ReplaceAll(s, substring, timeString)
   165  }