github.com/jxskiss/gopkg@v0.17.3/strutil/format.go (about) 1 package strutil 2 3 import ( 4 "fmt" 5 "reflect" 6 "strconv" 7 "unsafe" 8 9 "github.com/jxskiss/gopkg/reflectx" 10 ) 11 12 // Format mimics subset features of the python string.format function. 13 // 14 // It formats the string using given keyword arguments and positional arguments. 15 // kwArgs can be a map[string]interface{}, map[string]string or a struct 16 // or a pointer to a struct. 17 // 18 // If bracket is needed in string it can be created by escaping (two brackets). 19 // 20 // All standard formatting options from fmt work. To specify them, add colon 21 // after key name or position number and specify fmt package compatible formatting 22 // options. The percent sign is optional. For example: 23 // 24 // // returns "3.14, 3.1416" 25 // Format("{pi:%.2f}, {pi:.4f}", map[string]interface{}{"pi": math.Pi}) 26 // 27 // If a replacement is not found in kwArgs and posArgs, the placeholder will be 28 // output as the same in the given format. 29 func Format(format string, kwArgs interface{}, posArgs ...interface{}) string { 30 var ( 31 defaultFormat = []rune("%v") 32 33 // newFormat holds the new format string 34 newFormat = make([]rune, 0, len(format)) 35 newFormatArgs []interface{} 36 37 prevChar rune 38 currentName = make([]rune, 0, 10) 39 currentFormat = make([]rune, 0, 10) 40 41 inWing bool 42 inWingParam bool 43 44 isAutoNumber bool 45 isManualNumber bool 46 argIndex int 47 48 kwGetter = getKeywordArgFunc(kwArgs) 49 ) 50 51 for i, char := range format { 52 if i > 0 { 53 prevChar = rune(format[i-1]) 54 } 55 switch char { 56 case '{': 57 if inWing && prevChar == '{' { 58 inWing = false 59 newFormat = append(newFormat, char) 60 break 61 } 62 inWing = true 63 case '}': 64 if !inWing { 65 if prevChar == '}' { 66 newFormat = append(newFormat, char) 67 } 68 break 69 } 70 isInvalid := false 71 72 // find the argument 73 name := string(currentName) 74 if name == "" { 75 if isManualNumber || argIndex > len(posArgs) { 76 isInvalid = true 77 } else { 78 arg := posArgs[argIndex] 79 newFormatArgs = append(newFormatArgs, arg) 80 argIndex++ 81 isAutoNumber = true 82 } 83 } else if IsASCIIDigit(name) { 84 argNum, _ := strconv.ParseInt(name, 10, 64) 85 if isAutoNumber || int(argNum) >= len(posArgs) { 86 isInvalid = true 87 } else { 88 arg := posArgs[argNum] 89 newFormatArgs = append(newFormatArgs, arg) 90 isManualNumber = true 91 } 92 } else { 93 arg, ok := kwGetter(name) 94 if !ok { 95 isInvalid = true 96 } else { 97 newFormatArgs = append(newFormatArgs, arg) 98 } 99 } 100 if isInvalid { 101 newFormat = append(newFormat, '{') 102 newFormat = append(newFormat, currentName...) 103 if len(currentFormat) > 0 { 104 newFormat = append(newFormat, ':') 105 newFormat = append(newFormat, '%') 106 newFormat = append(newFormat, currentFormat...) 107 } 108 newFormat = append(newFormat, '}') 109 } else { 110 if len(currentFormat) > 0 { 111 newFormat = append(newFormat, currentFormat...) 112 } else { 113 newFormat = append(newFormat, defaultFormat...) 114 } 115 } 116 currentName = currentName[:0] 117 currentFormat = currentFormat[:0] 118 119 inWing = false 120 inWingParam = false 121 case ':': 122 if inWing { 123 inWingParam = true 124 } 125 default: 126 if inWing { 127 if inWingParam { 128 if prevChar == ':' && char != '%' { 129 currentFormat = append(currentFormat, '%') 130 } 131 currentFormat = append(currentFormat, char) 132 } else { 133 currentName = append(currentName, char) 134 } 135 } else { 136 newFormat = append(newFormat, char) 137 } 138 } 139 } 140 141 return fmt.Sprintf(string(newFormat), newFormatArgs...) 142 } 143 144 var strInterfaceMapTyp = reflect.TypeOf(map[string]interface{}(nil)) 145 146 func isStringInterfaceMap(typ reflect.Type) bool { 147 return typ.Kind() == reflect.Map && 148 typ.Key().Kind() == reflect.String && 149 typ.Elem() == strInterfaceMapTyp.Elem() 150 } 151 152 func castStringInterfaceMap(v interface{}) map[string]interface{} { 153 eface := reflectx.EfaceOf(&v) 154 strMap := *(*map[string]interface{})(unsafe.Pointer(&eface.Word)) 155 return strMap 156 } 157 158 func getKeywordArgFunc(kwArgs interface{}) func(key string) (interface{}, bool) { 159 if kwArgs == nil { 160 return func(string) (interface{}, bool) { return nil, false } 161 } 162 kwTyp := reflect.TypeOf(kwArgs) 163 if isStringInterfaceMap(kwTyp) { 164 kwMap := castStringInterfaceMap(kwArgs) 165 return func(key string) (interface{}, bool) { 166 val, ok := kwMap[key] 167 return val, ok 168 } 169 } 170 if kwTyp.Kind() == reflect.Map && kwTyp.Key().Kind() == reflect.String { 171 kwValue := reflect.ValueOf(kwArgs) 172 return func(key string) (interface{}, bool) { 173 val := kwValue.MapIndex(reflect.ValueOf(key)) 174 if val.IsValid() { 175 return val.Interface(), true 176 } 177 return nil, false 178 } 179 } 180 value := reflect.Indirect(reflect.ValueOf(kwArgs)) 181 if value.Kind() == reflect.Struct { 182 return func(field string) (interface{}, bool) { 183 x := value.FieldByName(field) 184 if x.IsValid() { 185 return x, true 186 } 187 return nil, false 188 } 189 } 190 return func(string) (interface{}, bool) { return nil, false } 191 }