github.com/mithrandie/csvq@v1.18.1/lib/query/string_formatter.go (about) 1 package query 2 3 import ( 4 "bytes" 5 "fmt" 6 "math" 7 "reflect" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/mithrandie/csvq/lib/option" 13 "github.com/mithrandie/csvq/lib/value" 14 ) 15 16 const EOF = -1 17 18 type StringFormatter struct { 19 format []rune 20 formatPos int 21 offset int 22 values []value.Primary 23 24 buf bytes.Buffer 25 26 err error 27 } 28 29 func NewStringFormatter() *StringFormatter { 30 return &StringFormatter{} 31 } 32 33 func (f *StringFormatter) Format(format string, values []value.Primary) (string, error) { 34 f.format = []rune(format) 35 f.values = values 36 f.formatPos = 0 37 f.buf.Reset() 38 39 var placeholder bytes.Buffer 40 placeholderOrder := 0 41 42 for { 43 ch := f.next() 44 if ch == EOF { 45 break 46 } 47 48 if ch != '%' { 49 f.buf.WriteRune(ch) 50 continue 51 } 52 53 ch = f.next() 54 if ch == '%' { 55 f.buf.WriteRune(ch) 56 continue 57 } 58 59 if len(values) <= placeholderOrder { 60 return "", NewFormatStringLengthNotMatchError() 61 } 62 63 placeholder.Reset() 64 placeholder.WriteRune('%') 65 66 var flag rune = -1 67 width := -1 68 precision := -1 69 70 if f.isFlag(ch) { 71 placeholder.WriteRune(ch) 72 flag = ch 73 ch = f.next() 74 } 75 76 if f.isDecimal(ch) { 77 f.offset = 1 78 f.scanDecimal() 79 placeholder.WriteString(f.literal()) 80 width = f.integer() 81 ch = f.next() 82 } 83 84 if ch == '.' { 85 ch = f.next() 86 if f.isDecimal(ch) { 87 f.offset = 1 88 f.scanDecimal() 89 precision = f.integer() 90 ch = f.next() 91 } else { 92 precision = 0 93 } 94 } 95 96 switch ch { 97 case 's', 'q', 'i', 'T': 98 placeholder.WriteRune('s') 99 default: 100 if -1 < precision { 101 placeholder.WriteRune('.') 102 placeholder.WriteString(strconv.Itoa(precision)) 103 } 104 105 placeholder.WriteRune(ch) 106 } 107 108 var s string 109 110 switch ch { 111 case 'b', 'o', 'd', 'x', 'X': 112 p := value.ToInteger(values[placeholderOrder]) 113 if !value.IsNull(p) { 114 val := p.(*value.Integer).Raw() 115 s = fmt.Sprintf(placeholder.String(), val) 116 } else if -1 < width { 117 s = strings.Repeat(" ", width) 118 } 119 case 'e', 'E', 'f': 120 p := value.ToFloat(values[placeholderOrder]) 121 if !value.IsNull(p) { 122 val := p.(*value.Float).Raw() 123 if -1 < precision { 124 s = fmt.Sprintf(placeholder.String(), val) 125 } else { 126 sign := f.numericSign(flag, val < 0) 127 val := math.Abs(val) 128 s = strconv.FormatFloat(val, byte(ch), -1, 64) 129 switch flag { 130 case '0': 131 if -1 < width { 132 padLen := width - len(s) - len(sign) 133 if padLen < 0 { 134 padLen = 0 135 } 136 s = sign + strings.Repeat("0", padLen) + s 137 } else { 138 s = sign + s 139 } 140 default: 141 s = sign + s 142 if -1 < width { 143 padLen := width - len(s) 144 if padLen < 0 { 145 padLen = 0 146 } 147 if flag == '-' { 148 s = s + strings.Repeat(" ", padLen) 149 } else { 150 s = strings.Repeat(" ", padLen) + s 151 } 152 } 153 } 154 } 155 } else if -1 < width { 156 s = strings.Repeat(" ", width) 157 } 158 case 's', 'q', 'i': 159 switch values[placeholderOrder].(type) { 160 case *value.String: 161 s = values[placeholderOrder].(*value.String).Raw() 162 case *value.Datetime: 163 s = values[placeholderOrder].(*value.Datetime).Format(time.RFC3339Nano) 164 default: 165 s = values[placeholderOrder].String() 166 } 167 168 if -1 < precision { 169 s = s[:precision] 170 } 171 172 switch ch { 173 case 'q': 174 s = option.QuoteString(s) 175 case 'i': 176 s = option.QuoteIdentifier(s) 177 } 178 179 s = fmt.Sprintf(placeholder.String(), s) 180 case 'T': 181 rv := reflect.ValueOf(values[placeholderOrder]).Elem().Interface() 182 s = reflect.TypeOf(rv).Name() 183 if -1 < precision { 184 s = s[:precision] 185 } 186 187 s = fmt.Sprintf(placeholder.String(), s) 188 case EOF: 189 return "", NewFormatUnexpectedTerminationError() 190 default: 191 return "", NewUnknownFormatPlaceholderError(ch) 192 } 193 194 f.buf.WriteString(s) 195 196 placeholderOrder++ 197 } 198 199 if placeholderOrder < len(values) { 200 return "", NewFormatStringLengthNotMatchError() 201 } 202 203 return f.buf.String(), nil 204 } 205 206 func (f *StringFormatter) runes() []rune { 207 return f.format[(f.formatPos - f.offset):f.formatPos] 208 } 209 210 func (f *StringFormatter) literal() string { 211 return string(f.runes()) 212 } 213 214 func (f *StringFormatter) integer() int { 215 i, _ := strconv.Atoi(string(f.runes())) 216 return i 217 } 218 219 func (f *StringFormatter) peek() rune { 220 if len(f.format) <= f.formatPos { 221 return EOF 222 } 223 return f.format[f.formatPos] 224 } 225 226 func (f *StringFormatter) next() rune { 227 ch := f.peek() 228 if ch == EOF { 229 return ch 230 } 231 232 f.formatPos++ 233 f.offset++ 234 return ch 235 } 236 237 func (f *StringFormatter) isFlag(ch rune) bool { 238 switch ch { 239 case '+', '-', ' ', '0': 240 return true 241 default: 242 return false 243 } 244 } 245 246 func (f *StringFormatter) isDecimal(ch rune) bool { 247 return '0' <= ch && ch <= '9' 248 } 249 250 func (f *StringFormatter) scanDecimal() { 251 for f.isDecimal(f.peek()) { 252 f.next() 253 } 254 } 255 256 func (f *StringFormatter) numericSign(flag rune, minus bool) string { 257 if minus { 258 return "-" 259 } 260 switch flag { 261 case '+', ' ': 262 return string(flag) 263 } 264 return "" 265 }