github.com/mithrandie/csvq@v1.18.1/lib/option/flags.go (about) 1 package option 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "time" 12 13 "github.com/mithrandie/go-text" 14 txjson "github.com/mithrandie/go-text/json" 15 ) 16 17 const ( 18 VariableSign = "@" 19 FlagSign = "@@" 20 EnvironmentVariableSign = "@%" 21 RuntimeInformationSign = "@#" 22 ) 23 const DelimitAutomatically = "SPACES" 24 25 const ( 26 RepositoryFlag = "REPOSITORY" 27 TimezoneFlag = "TIMEZONE" 28 DatetimeFormatFlag = "DATETIME_FORMAT" 29 AnsiQuotesFlag = "ANSI_QUOTES" 30 StrictEqualFlag = "STRICT_EQUAL" 31 WaitTimeoutFlag = "WAIT_TIMEOUT" 32 ImportFormatFlag = "IMPORT_FORMAT" 33 DelimiterFlag = "DELIMITER" 34 AllowUnevenFieldsFlag = "ALLOW_UNEVEN_FIELDS" 35 DelimiterPositionsFlag = "DELIMITER_POSITIONS" 36 JsonQueryFlag = "JSON_QUERY" 37 EncodingFlag = "ENCODING" 38 NoHeaderFlag = "NO_HEADER" 39 WithoutNullFlag = "WITHOUT_NULL" 40 StripEndingLineBreakFlag = "STRIP_ENDING_LINE_BREAK" 41 FormatFlag = "FORMAT" 42 ExportEncodingFlag = "WRITE_ENCODING" 43 ExportDelimiterFlag = "WRITE_DELIMITER" 44 ExportDelimiterPositionsFlag = "WRITE_DELIMITER_POSITIONS" 45 WithoutHeaderFlag = "WITHOUT_HEADER" 46 LineBreakFlag = "LINE_BREAK" 47 EncloseAllFlag = "ENCLOSE_ALL" 48 JsonEscapeFlag = "JSON_ESCAPE" 49 PrettyPrintFlag = "PRETTY_PRINT" 50 ScientificNotationFlag = "SCIENTIFIC_NOTATION" 51 EastAsianEncodingFlag = "EAST_ASIAN_ENCODING" 52 CountDiacriticalSignFlag = "COUNT_DIACRITICAL_SIGN" 53 CountFormatCodeFlag = "COUNT_FORMAT_CODE" 54 ColorFlag = "COLOR" 55 QuietFlag = "QUIET" 56 LimitRecursion = "LIMIT_RECURSION" 57 CPUFlag = "CPU" 58 StatsFlag = "STATS" 59 ) 60 61 var FlagList = []string{ 62 RepositoryFlag, 63 TimezoneFlag, 64 DatetimeFormatFlag, 65 AnsiQuotesFlag, 66 StrictEqualFlag, 67 WaitTimeoutFlag, 68 ImportFormatFlag, 69 DelimiterFlag, 70 AllowUnevenFieldsFlag, 71 DelimiterPositionsFlag, 72 JsonQueryFlag, 73 EncodingFlag, 74 NoHeaderFlag, 75 WithoutNullFlag, 76 StripEndingLineBreakFlag, 77 FormatFlag, 78 ExportEncodingFlag, 79 ExportDelimiterFlag, 80 ExportDelimiterPositionsFlag, 81 WithoutHeaderFlag, 82 LineBreakFlag, 83 EncloseAllFlag, 84 JsonEscapeFlag, 85 PrettyPrintFlag, 86 ScientificNotationFlag, 87 EastAsianEncodingFlag, 88 CountDiacriticalSignFlag, 89 CountFormatCodeFlag, 90 ColorFlag, 91 QuietFlag, 92 LimitRecursion, 93 CPUFlag, 94 StatsFlag, 95 } 96 97 type Format int 98 99 const ( 100 AutoSelect Format = -1 + iota 101 CSV 102 TSV 103 FIXED 104 JSON 105 JSONL 106 LTSV 107 GFM 108 ORG 109 BOX 110 TEXT 111 ) 112 113 var FormatLiteral = map[Format]string{ 114 CSV: "CSV", 115 TSV: "TSV", 116 FIXED: "FIXED", 117 JSON: "JSON", 118 JSONL: "JSONL", 119 LTSV: "LTSV", 120 GFM: "GFM", 121 ORG: "ORG", 122 BOX: "BOX", 123 TEXT: "TEXT", 124 } 125 126 func (f Format) String() string { 127 return FormatLiteral[f] 128 } 129 130 var ImportFormats = []Format{ 131 CSV, 132 TSV, 133 FIXED, 134 JSON, 135 JSONL, 136 LTSV, 137 } 138 139 var JsonEscapeTypeLiteral = map[txjson.EscapeType]string{ 140 txjson.Backslash: "BACKSLASH", 141 txjson.HexDigits: "HEX", 142 txjson.AllWithHexDigits: "HEXALL", 143 } 144 145 func JsonEscapeTypeToString(escapeType txjson.EscapeType) string { 146 return JsonEscapeTypeLiteral[escapeType] 147 } 148 149 const ( 150 CsvExt = ".csv" 151 TsvExt = ".tsv" 152 JsonExt = ".json" 153 JsonlExt = ".jsonl" 154 LtsvExt = ".ltsv" 155 GfmExt = ".md" 156 OrgExt = ".org" 157 SqlExt = ".sql" 158 CsvqProcExt = ".cql" 159 TextExt = ".txt" 160 ) 161 162 type ImportOptions struct { 163 Format Format 164 Delimiter rune 165 AllowUnevenFields bool 166 DelimiterPositions []int 167 SingleLine bool 168 JsonQuery string 169 Encoding text.Encoding 170 NoHeader bool 171 WithoutNull bool 172 } 173 174 func (ops ImportOptions) Copy() ImportOptions { 175 var dp []int 176 if ops.DelimiterPositions != nil { 177 dp = make([]int, len(ops.DelimiterPositions)) 178 copy(dp, ops.DelimiterPositions) 179 } 180 181 ret := ops 182 ret.DelimiterPositions = dp 183 return ret 184 } 185 186 func NewImportOptions() ImportOptions { 187 return ImportOptions{ 188 Format: CSV, 189 Delimiter: ',', 190 AllowUnevenFields: false, 191 DelimiterPositions: nil, 192 SingleLine: false, 193 JsonQuery: "", 194 Encoding: text.AUTO, 195 NoHeader: false, 196 WithoutNull: false, 197 } 198 } 199 200 type ExportOptions struct { 201 StripEndingLineBreak bool 202 Format Format 203 Encoding text.Encoding 204 Delimiter rune 205 DelimiterPositions []int 206 SingleLine bool 207 WithoutHeader bool 208 LineBreak text.LineBreak 209 EncloseAll bool 210 JsonEscape txjson.EscapeType 211 PrettyPrint bool 212 ScientificNotation bool 213 214 // For Calculation of String Width 215 EastAsianEncoding bool 216 CountDiacriticalSign bool 217 CountFormatCode bool 218 219 Color bool 220 } 221 222 func (ops ExportOptions) Copy() ExportOptions { 223 var dp []int 224 if ops.DelimiterPositions != nil { 225 dp = make([]int, len(ops.DelimiterPositions)) 226 copy(dp, ops.DelimiterPositions) 227 } 228 229 ret := ops 230 ret.DelimiterPositions = dp 231 return ret 232 } 233 234 func NewExportOptions() ExportOptions { 235 return ExportOptions{ 236 StripEndingLineBreak: false, 237 Format: TEXT, 238 Encoding: text.UTF8, 239 Delimiter: ',', 240 DelimiterPositions: nil, 241 SingleLine: false, 242 WithoutHeader: false, 243 LineBreak: text.LF, 244 EncloseAll: false, 245 JsonEscape: txjson.Backslash, 246 PrettyPrint: false, 247 ScientificNotation: false, 248 EastAsianEncoding: false, 249 CountDiacriticalSign: false, 250 CountFormatCode: false, 251 Color: false, 252 } 253 } 254 255 type Flags struct { 256 // Common Settings 257 Repository string 258 Location string 259 DatetimeFormat []string 260 AnsiQuotes bool 261 StrictEqual bool 262 263 WaitTimeout float64 264 265 // For Import 266 ImportOptions ImportOptions 267 268 // For Export 269 ExportOptions ExportOptions 270 271 // System Use 272 Quiet bool 273 LimitRecursion int64 274 CPU int 275 Stats bool 276 277 defaultTimeLocation *time.Location 278 } 279 280 func GetDefaultNumberOfCPU() int { 281 n := runtime.NumCPU() / 2 282 if n < 1 { 283 n = 1 284 } 285 return n 286 } 287 288 func NewFlags(env *Environment) (*Flags, error) { 289 var datetimeFormat []string 290 var location = "Local" 291 var AnsiQuotes = false 292 293 if env != nil { 294 datetimeFormat = make([]string, 0, len(env.DatetimeFormat)) 295 for _, v := range env.DatetimeFormat { 296 datetimeFormat = AppendStrIfNotExist(datetimeFormat, v) 297 } 298 299 if env.Timezone != nil { 300 location = *env.Timezone 301 } 302 303 if env.AnsiQuotes != nil { 304 AnsiQuotes = *env.AnsiQuotes 305 } 306 } else { 307 datetimeFormat = make([]string, 0, 4) 308 } 309 310 defaultTimeLocation, err := GetLocation(location) 311 if err != nil { 312 return nil, err 313 } 314 315 return &Flags{ 316 Repository: "", 317 Location: location, 318 DatetimeFormat: datetimeFormat, 319 AnsiQuotes: AnsiQuotes, 320 StrictEqual: false, 321 WaitTimeout: 10, 322 ImportOptions: NewImportOptions(), 323 ExportOptions: NewExportOptions(), 324 Quiet: false, 325 LimitRecursion: 1000, 326 CPU: GetDefaultNumberOfCPU(), 327 Stats: false, 328 defaultTimeLocation: defaultTimeLocation, 329 }, nil 330 } 331 332 func (f *Flags) GetTimeLocation() *time.Location { 333 return f.defaultTimeLocation 334 } 335 336 func (f *Flags) SetRepository(s string) error { 337 if len(s) < 1 { 338 f.Repository = "" 339 return nil 340 } 341 342 path, err := filepath.Abs(s) 343 if err != nil { 344 path = s 345 } 346 347 stat, err := os.Stat(path) 348 if err != nil { 349 return errors.New("repository does not exist") 350 } 351 if !stat.IsDir() { 352 return errors.New("repository must be a directory path") 353 } 354 355 f.Repository = path 356 return nil 357 } 358 359 func (f *Flags) SetLocation(s string) error { 360 if len(s) < 1 || strings.EqualFold(s, "Local") { 361 s = "Local" 362 } else if strings.EqualFold(s, "UTC") { 363 s = "UTC" 364 } 365 366 l, err := GetLocation(s) 367 if err != nil { 368 return err 369 } 370 371 f.Location = s 372 f.defaultTimeLocation = l 373 return nil 374 } 375 376 func (f *Flags) SetDatetimeFormat(s string) { 377 if len(s) < 1 { 378 return 379 } 380 381 var formats []string 382 if err := json.Unmarshal([]byte(s), &formats); err == nil { 383 for _, v := range formats { 384 f.DatetimeFormat = AppendStrIfNotExist(f.DatetimeFormat, v) 385 } 386 } else { 387 f.DatetimeFormat = append(f.DatetimeFormat, s) 388 } 389 } 390 391 func (f *Flags) SetAnsiQuotes(b bool) { 392 f.AnsiQuotes = b 393 } 394 395 func (f *Flags) SetStrictEqual(b bool) { 396 f.StrictEqual = b 397 } 398 399 func (f *Flags) SetWaitTimeout(t float64) { 400 if t < 0 { 401 t = 0 402 } 403 404 f.WaitTimeout = t 405 return 406 } 407 408 func (f *Flags) SetImportFormat(s string) error { 409 fm, _, err := ParseFormat(s, f.ExportOptions.JsonEscape) 410 if err != nil { 411 return errors.New("import format must be one of CSV|TSV|FIXED|JSON|JSONL|LTSV") 412 } 413 414 switch fm { 415 case CSV, TSV, FIXED, JSON, JSONL, LTSV: 416 f.ImportOptions.Format = fm 417 return nil 418 } 419 420 return errors.New("import format must be one of CSV|TSV|FIXED|JSON|JSONL|LTSV") 421 } 422 423 func (f *Flags) SetDelimiter(s string) error { 424 if len(s) < 1 { 425 return nil 426 } 427 428 delimiter, err := ParseDelimiter(s) 429 if err != nil { 430 return err 431 } 432 433 f.ImportOptions.Delimiter = delimiter 434 return nil 435 } 436 437 func (f *Flags) SetAllowUnevenFields(b bool) { 438 f.ImportOptions.AllowUnevenFields = b 439 } 440 441 func (f *Flags) SetDelimiterPositions(s string) error { 442 if len(s) < 1 { 443 return nil 444 } 445 delimiterPositions, singleLine, err := ParseDelimiterPositions(s) 446 if err != nil { 447 return err 448 } 449 450 f.ImportOptions.DelimiterPositions = delimiterPositions 451 f.ImportOptions.SingleLine = singleLine 452 return nil 453 } 454 455 func (f *Flags) SetJsonQuery(s string) { 456 f.ImportOptions.JsonQuery = TrimSpace(s) 457 } 458 459 func (f *Flags) SetEncoding(s string) error { 460 if len(s) < 1 { 461 return nil 462 } 463 464 encoding, err := ParseEncoding(s) 465 if err != nil { 466 return err 467 } 468 469 f.ImportOptions.Encoding = encoding 470 return nil 471 } 472 473 func (f *Flags) SetNoHeader(b bool) { 474 f.ImportOptions.NoHeader = b 475 } 476 477 func (f *Flags) SetWithoutNull(b bool) { 478 f.ImportOptions.WithoutNull = b 479 } 480 481 func (f *Flags) SetFormat(s string, outfile string, canOutputToPipe bool) error { 482 if len(s) < 1 { 483 if len(outfile) < 1 { 484 if canOutputToPipe { 485 f.ExportOptions.Format = CSV 486 } else { 487 f.ExportOptions.Format = TEXT 488 } 489 return nil 490 } 491 492 switch strings.ToLower(filepath.Ext(outfile)) { 493 case CsvExt: 494 f.ExportOptions.Format = CSV 495 case TsvExt: 496 f.ExportOptions.Format = TSV 497 case JsonExt: 498 f.ExportOptions.Format = JSON 499 case JsonlExt: 500 f.ExportOptions.Format = JSONL 501 case LtsvExt: 502 f.ExportOptions.Format = LTSV 503 case GfmExt: 504 f.ExportOptions.Format = GFM 505 case OrgExt: 506 f.ExportOptions.Format = ORG 507 default: 508 f.ExportOptions.Format = TEXT 509 } 510 return nil 511 } 512 513 fm, escape, err := ParseFormat(s, f.ExportOptions.JsonEscape) 514 if err != nil { 515 return err 516 } 517 518 f.ExportOptions.Format = fm 519 f.ExportOptions.JsonEscape = escape 520 return nil 521 } 522 523 func (f *Flags) SetWriteEncoding(s string) error { 524 if len(s) < 1 { 525 return nil 526 } 527 528 encoding, err := ParseEncoding(s) 529 if err != nil || encoding == text.AUTO { 530 return errors.New("write-encoding must be one of UTF8|UTF8M|UTF16|UTF16BE|UTF16LE|UTF16BEM|UTF16LEM|SJIS") 531 } 532 533 f.ExportOptions.Encoding = encoding 534 return nil 535 } 536 537 func (f *Flags) SetWriteDelimiter(s string) error { 538 if len(s) < 1 { 539 return nil 540 } 541 542 delimiter, err := ParseDelimiter(s) 543 if err != nil { 544 return errors.New("write-delimiter must be one character") 545 } 546 547 f.ExportOptions.Delimiter = delimiter 548 return nil 549 } 550 551 func (f *Flags) SetWriteDelimiterPositions(s string) error { 552 if len(s) < 1 { 553 return nil 554 } 555 delimiterPositions, singleLine, err := ParseDelimiterPositions(s) 556 if err != nil { 557 return errors.New(fmt.Sprintf("write-delimiter-positions must be %q or a JSON array of integers", DelimitAutomatically)) 558 } 559 560 f.ExportOptions.DelimiterPositions = delimiterPositions 561 f.ExportOptions.SingleLine = singleLine 562 return nil 563 } 564 565 func (f *Flags) SetWithoutHeader(b bool) { 566 f.ExportOptions.WithoutHeader = b 567 } 568 569 func (f *Flags) SetLineBreak(s string) error { 570 if len(s) < 1 { 571 return nil 572 } 573 574 lb, err := ParseLineBreak(s) 575 if err != nil { 576 return err 577 } 578 579 f.ExportOptions.LineBreak = lb 580 return nil 581 } 582 583 func (f *Flags) SetJsonEscape(s string) error { 584 var escape txjson.EscapeType 585 var err error 586 587 if escape, err = ParseJsonEscapeType(s); err != nil { 588 return err 589 } 590 591 f.ExportOptions.JsonEscape = escape 592 return nil 593 } 594 595 func (f *Flags) SetPrettyPrint(b bool) { 596 f.ExportOptions.PrettyPrint = b 597 } 598 599 func (f *Flags) SetScientificNotation(b bool) { 600 f.ExportOptions.ScientificNotation = b 601 } 602 603 func (f *Flags) SetStripEndingLineBreak(b bool) { 604 f.ExportOptions.StripEndingLineBreak = b 605 } 606 607 func (f *Flags) SetEncloseAll(b bool) { 608 f.ExportOptions.EncloseAll = b 609 } 610 611 func (f *Flags) SetColor(b bool) { 612 f.ExportOptions.Color = b 613 } 614 615 func (f *Flags) SetEastAsianEncoding(b bool) { 616 f.ExportOptions.EastAsianEncoding = b 617 } 618 619 func (f *Flags) SetCountDiacriticalSign(b bool) { 620 f.ExportOptions.CountDiacriticalSign = b 621 } 622 623 func (f *Flags) SetCountFormatCode(b bool) { 624 f.ExportOptions.CountFormatCode = b 625 } 626 627 func (f *Flags) SetQuiet(b bool) { 628 f.Quiet = b 629 } 630 631 func (f *Flags) SetLimitRecursion(i int64) { 632 if i < 0 { 633 i = -1 634 } 635 f.LimitRecursion = i 636 } 637 638 func (f *Flags) SetCPU(i int) { 639 if i < 1 { 640 i = 1 641 } 642 643 if runtime.NumCPU() < i { 644 i = runtime.NumCPU() 645 } 646 647 f.CPU = i 648 } 649 650 func (f *Flags) SetStats(b bool) { 651 f.Stats = b 652 }