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 }