github.com/mithrandie/csvq@v1.18.1/lib/option/utils.go (about) 1 package option 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "strconv" 9 "strings" 10 "unicode" 11 12 "github.com/mithrandie/go-text" 13 txjson "github.com/mithrandie/go-text/json" 14 ) 15 16 func EscapeString(s string) string { 17 runes := []rune(s) 18 var buf bytes.Buffer 19 20 for _, r := range runes { 21 switch r { 22 case '\a': 23 buf.WriteString("\\a") 24 case '\b': 25 buf.WriteString("\\b") 26 case '\f': 27 buf.WriteString("\\f") 28 case '\n': 29 buf.WriteString("\\n") 30 case '\r': 31 buf.WriteString("\\r") 32 case '\t': 33 buf.WriteString("\\t") 34 case '\v': 35 buf.WriteString("\\v") 36 case '\'': 37 buf.WriteString("\\'") 38 case '\\': 39 buf.WriteString("\\\\") 40 default: 41 buf.WriteRune(r) 42 } 43 } 44 return buf.String() 45 } 46 47 func UnescapeString(s string, quote rune) string { 48 runes := []rune(s) 49 var buf bytes.Buffer 50 51 escaped := false 52 quoteRune := rune(0) 53 for _, r := range runes { 54 if 0 < quoteRune { 55 buf.WriteRune(quoteRune) 56 if r == quoteRune { 57 quoteRune = 0 58 continue 59 } 60 quoteRune = 0 61 } 62 63 if escaped { 64 switch r { 65 case 'a': 66 buf.WriteRune('\a') 67 case 'b': 68 buf.WriteRune('\b') 69 case 'f': 70 buf.WriteRune('\f') 71 case 'n': 72 buf.WriteRune('\n') 73 case 'r': 74 buf.WriteRune('\r') 75 case 't': 76 buf.WriteRune('\t') 77 case 'v': 78 buf.WriteRune('\v') 79 case '"', '\'', '\\': 80 buf.WriteRune(r) 81 default: 82 buf.WriteRune('\\') 83 buf.WriteRune(r) 84 } 85 escaped = false 86 continue 87 } 88 89 switch r { 90 case '\\': 91 escaped = true 92 case quote: 93 quoteRune = r 94 default: 95 buf.WriteRune(r) 96 } 97 } 98 if escaped { 99 buf.WriteRune('\\') 100 } 101 102 return buf.String() 103 } 104 105 func EscapeIdentifier(s string) string { 106 runes := []rune(s) 107 var buf bytes.Buffer 108 109 for _, r := range runes { 110 switch r { 111 case '\a': 112 buf.WriteString("\\a") 113 case '\b': 114 buf.WriteString("\\b") 115 case '\f': 116 buf.WriteString("\\f") 117 case '\n': 118 buf.WriteString("\\n") 119 case '\r': 120 buf.WriteString("\\r") 121 case '\t': 122 buf.WriteString("\\t") 123 case '\v': 124 buf.WriteString("\\v") 125 case '`': 126 buf.WriteString("\\`") 127 case '\\': 128 buf.WriteString("\\\\") 129 default: 130 buf.WriteRune(r) 131 } 132 } 133 return buf.String() 134 } 135 136 func UnescapeIdentifier(s string, quote rune) string { 137 runes := []rune(s) 138 var buf bytes.Buffer 139 140 escaped := false 141 quoteRune := rune(0) 142 for _, r := range runes { 143 if 0 < quoteRune { 144 buf.WriteRune(quoteRune) 145 if r == quoteRune { 146 quoteRune = 0 147 continue 148 } 149 quoteRune = 0 150 } 151 152 if escaped { 153 switch r { 154 case 'a': 155 buf.WriteRune('\a') 156 case 'b': 157 buf.WriteRune('\b') 158 case 'f': 159 buf.WriteRune('\f') 160 case 'n': 161 buf.WriteRune('\n') 162 case 'r': 163 buf.WriteRune('\r') 164 case 't': 165 buf.WriteRune('\t') 166 case 'v': 167 buf.WriteRune('\v') 168 case '"', '`', '\\': 169 buf.WriteRune(r) 170 default: 171 buf.WriteRune('\\') 172 buf.WriteRune(r) 173 } 174 escaped = false 175 continue 176 } 177 178 switch r { 179 case '\\': 180 escaped = true 181 case quote: 182 quoteRune = r 183 default: 184 buf.WriteRune(r) 185 } 186 } 187 if escaped { 188 buf.WriteRune('\\') 189 } 190 191 return buf.String() 192 } 193 194 func QuoteString(s string) string { 195 return "'" + EscapeString(s) + "'" 196 } 197 198 func QuoteIdentifier(s string) string { 199 return "`" + EscapeIdentifier(s) + "`" 200 } 201 202 func VariableSymbol(s string) string { 203 return VariableSign + s 204 } 205 206 func FlagSymbol(s string) string { 207 return FlagSign + s 208 } 209 210 func EnvironmentVariableSymbol(s string) string { 211 if MustBeEnclosed(s) { 212 s = QuoteIdentifier(s) 213 } 214 return EnvironmentVariableSign + s 215 } 216 217 func EnclosedEnvironmentVariableSymbol(s string) string { 218 return EnvironmentVariableSign + QuoteIdentifier(s) 219 } 220 221 func MustBeEnclosed(s string) bool { 222 if len(s) == 0 { 223 return false 224 } 225 226 runes := []rune(s) 227 228 if runes[0] != '_' && !unicode.IsLetter(runes[0]) { 229 return true 230 } 231 232 for i := 1; i < len(runes); i++ { 233 if s[i] != '_' && !unicode.IsLetter(runes[i]) && !unicode.IsDigit(runes[i]) { 234 return true 235 } 236 } 237 return false 238 } 239 240 func RuntimeInformationSymbol(s string) string { 241 return RuntimeInformationSign + s 242 } 243 244 func FormatInt(i int, thousandsSeparator string) string { 245 return FormatNumber(float64(i), 0, ".", thousandsSeparator, "") 246 } 247 248 func FormatNumber(f float64, precision int, decimalPoint string, thousandsSeparator string, decimalSeparator string) string { 249 s := strconv.FormatFloat(f, 'f', precision, 64) 250 251 parts := strings.Split(s, ".") 252 intPart := parts[0] 253 decPart := "" 254 if 1 < len(parts) { 255 decPart = parts[1] 256 } 257 258 intPlaces := make([]string, 0, (len(intPart)/3)+1) 259 intLen := len(intPart) 260 for i := intLen / 3; i >= 0; i-- { 261 end := intLen - i*3 262 if end == 0 { 263 continue 264 } 265 266 start := intLen - (i+1)*3 267 if start < 0 { 268 start = 0 269 } 270 intPlaces = append(intPlaces, intPart[start:end]) 271 } 272 273 decPlaces := make([]string, 0, (len(decPart)/3)+1) 274 for i := 0; i < len(decPart); i = i + 3 { 275 end := i + 3 276 if len(decPart) < end { 277 end = len(decPart) 278 } 279 decPlaces = append(decPlaces, decPart[i:end]) 280 } 281 282 formatted := strings.Join(intPlaces, thousandsSeparator) 283 if 0 < len(decPlaces) { 284 formatted = formatted + decimalPoint + strings.Join(decPlaces, decimalSeparator) 285 } 286 287 return formatted 288 } 289 290 func ParseEncoding(s string) (text.Encoding, error) { 291 encoding, err := text.ParseEncoding(s) 292 if err != nil { 293 err = errors.New("encoding must be one of AUTO|UTF8|UTF8M|UTF16|UTF16BE|UTF16LE|UTF16BEM|UTF16LEM|SJIS") 294 } 295 return encoding, err 296 } 297 298 func ParseLineBreak(s string) (text.LineBreak, error) { 299 lb, err := text.ParseLineBreak(s) 300 if err != nil { 301 err = errors.New("line-break must be one of CRLF|CR|LF") 302 } 303 return lb, err 304 } 305 func ParseDelimiter(s string) (rune, error) { 306 r := []rune(UnescapeString(s, '\'')) 307 if len(r) != 1 { 308 return 0, errors.New("delimiter must be one character") 309 } 310 return r[0], nil 311 } 312 313 func ParseDelimiterPositions(s string) ([]int, bool, error) { 314 s = UnescapeString(s, '\'') 315 var delimiterPositions []int = nil 316 singleLine := false 317 318 if !strings.EqualFold(DelimitAutomatically, s) { 319 if strings.HasPrefix(s, "s[") || strings.HasPrefix(s, "S[") { 320 singleLine = true 321 s = s[1:] 322 } 323 err := json.Unmarshal([]byte(s), &delimiterPositions) 324 if err != nil { 325 return delimiterPositions, singleLine, errors.New(fmt.Sprintf("delimiter positions must be %q or a JSON array of integers", DelimitAutomatically)) 326 } 327 } 328 return delimiterPositions, singleLine, nil 329 } 330 331 func ParseFormat(s string, et txjson.EscapeType) (Format, txjson.EscapeType, error) { 332 var fm Format 333 switch strings.ToUpper(s) { 334 case "CSV": 335 fm = CSV 336 case "TSV": 337 fm = TSV 338 case "FIXED": 339 fm = FIXED 340 case "JSON": 341 fm = JSON 342 case "JSONL": 343 fm = JSONL 344 case "LTSV": 345 fm = LTSV 346 case "GFM": 347 fm = GFM 348 case "ORG": 349 fm = ORG 350 case "BOX": 351 fm = BOX 352 case "TEXT": 353 fm = TEXT 354 case "JSONH": 355 fm = JSON 356 et = txjson.HexDigits 357 case "JSONA": 358 fm = JSON 359 et = txjson.AllWithHexDigits 360 default: 361 return fm, et, errors.New("format must be one of CSV|TSV|FIXED|JSON|JSONL|LTSV|GFM|ORG|BOX|TEXT") 362 } 363 return fm, et, nil 364 } 365 366 func ParseJsonEscapeType(s string) (txjson.EscapeType, error) { 367 var escape txjson.EscapeType 368 switch strings.ToUpper(s) { 369 case "BACKSLASH": 370 escape = txjson.Backslash 371 case "HEX": 372 escape = txjson.HexDigits 373 case "HEXALL": 374 escape = txjson.AllWithHexDigits 375 default: 376 return escape, errors.New("json escape type must be one of BACKSLASH|HEX|HEXALL") 377 } 378 return escape, nil 379 } 380 381 func AppendStrIfNotExist(list []string, elem string) []string { 382 if len(elem) < 1 { 383 return list 384 } 385 for _, v := range list { 386 if elem == v { 387 return list 388 } 389 } 390 return append(list, elem) 391 } 392 393 func TextWidth(s string, flags *Flags) int { 394 return text.Width(s, flags.ExportOptions.EastAsianEncoding, flags.ExportOptions.CountDiacriticalSign, flags.ExportOptions.CountFormatCode) 395 } 396 397 func RuneWidth(r rune, flags *Flags) int { 398 return text.RuneWidth(r, flags.ExportOptions.EastAsianEncoding, flags.ExportOptions.CountDiacriticalSign, flags.ExportOptions.CountFormatCode) 399 } 400 401 func TrimSpace(s string) string { 402 if 0 < len(s) && (unicode.IsSpace(rune(s[0])) || unicode.IsSpace(rune(s[len(s)-1]))) { 403 s = strings.TrimSpace(s) 404 } 405 return s 406 }