github.com/mithrandie/csvq@v1.18.1/lib/query/encode.go (about) 1 package query 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "errors" 8 "fmt" 9 "io" 10 "strconv" 11 "time" 12 13 "github.com/mithrandie/csvq/lib/json" 14 "github.com/mithrandie/csvq/lib/option" 15 "github.com/mithrandie/csvq/lib/value" 16 17 "github.com/mithrandie/go-text" 18 "github.com/mithrandie/go-text/color" 19 "github.com/mithrandie/go-text/csv" 20 "github.com/mithrandie/go-text/fixedlen" 21 txjson "github.com/mithrandie/go-text/json" 22 "github.com/mithrandie/go-text/ltsv" 23 "github.com/mithrandie/go-text/table" 24 "github.com/mithrandie/ternary" 25 ) 26 27 var EmptyResultSetError = errors.New("empty result set") 28 var DataEmpty = errors.New("data empty") 29 30 func EncodeView(ctx context.Context, fp io.Writer, view *View, options option.ExportOptions, palette *color.Palette) (string, error) { 31 switch options.Format { 32 case option.FIXED: 33 return "", encodeFixedLengthFormat(ctx, fp, view, options) 34 case option.JSON: 35 return "", encodeJson(ctx, fp, view, options, palette) 36 case option.JSONL: 37 return "", encodeJsonLines(ctx, fp, view, options, palette) 38 case option.LTSV: 39 return "", encodeLTSV(ctx, fp, view, options) 40 case option.GFM, option.ORG, option.BOX, option.TEXT: 41 return encodeText(ctx, fp, view, options, palette) 42 case option.TSV: 43 options.Delimiter = '\t' 44 fallthrough 45 default: // option.CSV 46 return "", encodeCSV(ctx, fp, view, options) 47 } 48 } 49 50 func encodeCSV(ctx context.Context, fp io.Writer, view *View, options option.ExportOptions) error { 51 w, err := csv.NewWriter(fp, options.LineBreak, options.Encoding) 52 if err != nil { 53 return NewDataEncodingError(err.Error()) 54 } 55 w.Delimiter = options.Delimiter 56 57 fields := make([]csv.Field, view.FieldLen()) 58 59 if !options.WithoutHeader { 60 for i := range view.Header { 61 fields[i] = csv.NewField(view.Header[i].Column, options.EncloseAll) 62 } 63 if err := w.Write(fields); err != nil { 64 return NewSystemError(err.Error()) 65 } 66 } else if view.RecordLen() < 1 { 67 return DataEmpty 68 } 69 70 for i := range view.RecordSet { 71 if i&15 == 0 && ctx.Err() != nil { 72 err = ConvertContextError(ctx.Err()) 73 break 74 } 75 76 for j := range view.RecordSet[i] { 77 str, effect, _ := ConvertFieldContents(view.RecordSet[i][j][0], false, options.ScientificNotation) 78 quote := false 79 if options.EncloseAll && (effect == option.StringEffect || effect == option.DatetimeEffect) { 80 quote = true 81 } 82 fields[j] = csv.NewField(str, quote) 83 } 84 if err := w.Write(fields); err != nil { 85 return NewSystemError(err.Error()) 86 } 87 } 88 if err = w.Flush(); err != nil { 89 return NewSystemError(err.Error()) 90 } 91 return nil 92 } 93 94 func encodeFixedLengthFormat(ctx context.Context, fp io.Writer, view *View, options option.ExportOptions) error { 95 if options.DelimiterPositions == nil { 96 m := fixedlen.NewMeasure() 97 m.Encoding = options.Encoding 98 99 var fieldList [][]fixedlen.Field = nil 100 var recordStartPos = 0 101 var fieldLen = view.FieldLen() 102 103 if options.WithoutHeader { 104 if view.RecordLen() < 1 { 105 return DataEmpty 106 } 107 fieldList = make([][]fixedlen.Field, view.RecordLen()) 108 } else { 109 fieldList = make([][]fixedlen.Field, view.RecordLen()+1) 110 recordStartPos = 1 111 112 fields := make([]fixedlen.Field, fieldLen) 113 for i := range view.Header { 114 fields[i] = fixedlen.NewField(view.Header[i].Column, text.NotAligned) 115 } 116 fieldList[0] = fields 117 m.Measure(fields) 118 } 119 120 for i := range view.RecordSet { 121 if i&15 == 0 && ctx.Err() != nil { 122 return ConvertContextError(ctx.Err()) 123 } 124 125 fields := make([]fixedlen.Field, fieldLen) 126 for j := range view.RecordSet[i] { 127 str, _, a := ConvertFieldContents(view.RecordSet[i][j][0], false, options.ScientificNotation) 128 fields[j] = fixedlen.NewField(str, a) 129 } 130 fieldList[i+recordStartPos] = fields 131 m.Measure(fields) 132 } 133 134 options.DelimiterPositions = m.GeneratePositions() 135 w, err := fixedlen.NewWriter(fp, options.DelimiterPositions, options.LineBreak, options.Encoding) 136 if err != nil { 137 return NewDataEncodingError(err.Error()) 138 } 139 w.InsertSpace = true 140 for i := range fieldList { 141 if i&15 == 0 && ctx.Err() != nil { 142 return ConvertContextError(ctx.Err()) 143 } 144 145 if err := w.Write(fieldList[i]); err != nil { 146 return NewDataEncodingError(err.Error()) 147 } 148 } 149 if err = w.Flush(); err != nil { 150 return NewSystemError(err.Error()) 151 } 152 153 } else { 154 w, err := fixedlen.NewWriter(fp, options.DelimiterPositions, options.LineBreak, options.Encoding) 155 if err != nil { 156 return NewDataEncodingError(err.Error()) 157 } 158 w.SingleLine = options.SingleLine 159 160 fields := make([]fixedlen.Field, view.FieldLen()) 161 162 if options.WithoutHeader { 163 if view.RecordLen() < 1 { 164 return DataEmpty 165 } 166 } else if !options.SingleLine { 167 for i := range view.Header { 168 fields[i] = fixedlen.NewField(view.Header[i].Column, text.NotAligned) 169 } 170 if err := w.Write(fields); err != nil { 171 return NewDataEncodingError(err.Error()) 172 } 173 } 174 175 for i := range view.RecordSet { 176 if i&15 == 0 && ctx.Err() != nil { 177 return ConvertContextError(ctx.Err()) 178 } 179 180 for j := range view.RecordSet[i] { 181 str, _, a := ConvertFieldContents(view.RecordSet[i][j][0], false, options.ScientificNotation) 182 fields[j] = fixedlen.NewField(str, a) 183 } 184 if err := w.Write(fields); err != nil { 185 return NewDataEncodingError(err.Error()) 186 } 187 } 188 if err = w.Flush(); err != nil { 189 return NewSystemError(err.Error()) 190 } 191 } 192 return nil 193 } 194 195 func encodeJson(ctx context.Context, fp io.Writer, view *View, options option.ExportOptions, palette *color.Palette) error { 196 header := view.Header.TableColumnNames() 197 records := make([][]value.Primary, view.RecordLen()) 198 for i := range view.RecordSet { 199 if i&15 == 0 && ctx.Err() != nil { 200 return ConvertContextError(ctx.Err()) 201 } 202 203 row := make([]value.Primary, view.FieldLen()) 204 for j := range view.RecordSet[i] { 205 row[j] = view.RecordSet[i][j][0] 206 } 207 records[i] = row 208 } 209 210 data, err := json.ConvertTableValueToJsonStructure(ctx, header, records) 211 if err != nil { 212 if ctx.Err() != nil { 213 return ConvertContextError(ctx.Err()) 214 } 215 return NewDataEncodingError(err.Error()) 216 } 217 218 e := txjson.NewEncoder() 219 e.EscapeType = options.JsonEscape 220 e.LineBreak = options.LineBreak 221 e.PrettyPrint = options.PrettyPrint 222 e.FloatFormat = jsonFloatFormat(options.ScientificNotation) 223 if options.PrettyPrint && options.Color { 224 e.Palette = palette 225 } 226 defer func() { 227 if options.Color { 228 palette.Enable() 229 } else { 230 palette.Disable() 231 } 232 }() 233 234 s, err := e.Encode(data) 235 if err != nil { 236 return NewDataEncodingError(fmt.Sprintf("%s in JSON encoding", err.Error())) 237 } 238 239 w := bufio.NewWriter(fp) 240 if _, err = w.WriteString(s); err != nil { 241 return NewSystemError(err.Error()) 242 } 243 if err = w.Flush(); err != nil { 244 return NewSystemError(err.Error()) 245 } 246 return nil 247 } 248 249 func encodeJsonLines(ctx context.Context, fp io.Writer, view *View, options option.ExportOptions, palette *color.Palette) error { 250 fields := view.Header.TableColumnNames() 251 pathes, err := json.ParsePathes(fields) 252 if err != nil { 253 return err 254 } 255 256 e := txjson.NewEncoder() 257 e.EscapeType = options.JsonEscape 258 e.LineBreak = options.LineBreak 259 e.PrettyPrint = options.PrettyPrint 260 e.FloatFormat = jsonFloatFormat(options.ScientificNotation) 261 if options.PrettyPrint && options.Color { 262 e.Palette = palette 263 } 264 defer func() { 265 if options.Color { 266 palette.Enable() 267 } else { 268 palette.Disable() 269 } 270 }() 271 272 lineBreak := e.LineBreak.Value() 273 w := bufio.NewWriter(fp) 274 row := make([]value.Primary, view.FieldLen()) 275 276 for i := range view.RecordSet { 277 if i&15 == 0 && ctx.Err() != nil { 278 return ConvertContextError(ctx.Err()) 279 } 280 281 for j := range view.RecordSet[i] { 282 row[j] = view.RecordSet[i][j][0] 283 } 284 rowStrct, err := json.ConvertRecordValueToJsonStructure(pathes, row) 285 if err != nil { 286 return NewDataEncodingError(err.Error()) 287 } 288 rowStr, err := e.Encode(rowStrct) 289 if err != nil { 290 return NewDataEncodingError(fmt.Sprintf("%s in JSON encoding", err.Error())) 291 } 292 293 if _, err = w.WriteString(rowStr); err != nil { 294 return NewSystemError(err.Error()) 295 } 296 if _, err = w.WriteString(lineBreak); err != nil { 297 return NewSystemError(err.Error()) 298 } 299 } 300 if err = w.Flush(); err != nil { 301 return NewSystemError(err.Error()) 302 } 303 304 return nil 305 } 306 307 func encodeText(ctx context.Context, fp io.Writer, view *View, options option.ExportOptions, palette *color.Palette) (string, error) { 308 isPlainTable := false 309 310 var tableFormat = table.PlainTable 311 switch options.Format { 312 case option.GFM: 313 tableFormat = table.GFMTable 314 case option.ORG: 315 tableFormat = table.OrgTable 316 default: 317 if options.Format == option.BOX { 318 tableFormat = table.BoxTable 319 } 320 if view.FieldLen() < 1 { 321 return "Empty Fields", EmptyResultSetError 322 } 323 if view.RecordLen() < 1 { 324 return "Empty RecordSet", EmptyResultSetError 325 } 326 isPlainTable = true 327 } 328 329 e := table.NewEncoder(tableFormat, view.RecordLen()) 330 e.LineBreak = options.LineBreak 331 e.EastAsianEncoding = options.EastAsianEncoding 332 e.CountDiacriticalSign = options.CountDiacriticalSign 333 e.CountFormatCode = options.CountFormatCode 334 e.WithoutHeader = options.WithoutHeader 335 e.Encoding = options.Encoding 336 337 fieldLen := view.FieldLen() 338 339 if !options.WithoutHeader || (options.Format != option.GFM && options.Format != option.ORG) { 340 hfields := make([]table.Field, fieldLen) 341 for i := range view.Header { 342 hfields[i] = table.NewField(view.Header[i].Column, text.Centering) 343 } 344 e.SetHeader(hfields) 345 } else if view.RecordLen() < 1 { 346 return "", DataEmpty 347 } 348 349 aligns := make([]text.FieldAlignment, fieldLen) 350 351 var textStrBuf bytes.Buffer 352 var textLineBuf bytes.Buffer 353 for i := range view.RecordSet { 354 if i&15 == 0 && ctx.Err() != nil { 355 return "", ConvertContextError(ctx.Err()) 356 } 357 358 rfields := make([]table.Field, fieldLen) 359 for j := range view.RecordSet[i] { 360 str, effect, align := ConvertFieldContents(view.RecordSet[i][j][0], isPlainTable, options.ScientificNotation) 361 if options.Format == option.TEXT || options.Format == option.BOX { 362 textStrBuf.Reset() 363 textLineBuf.Reset() 364 365 runes := []rune(str) 366 pos := 0 367 for { 368 if len(runes) <= pos { 369 if 0 < textLineBuf.Len() { 370 textStrBuf.WriteString(palette.Render(effect, textLineBuf.String())) 371 } 372 break 373 } 374 375 r := runes[pos] 376 switch r { 377 case '\r': 378 if (pos+1) < len(runes) && runes[pos+1] == '\n' { 379 pos++ 380 } 381 fallthrough 382 case '\n': 383 if 0 < textLineBuf.Len() { 384 textStrBuf.WriteString(palette.Render(effect, textLineBuf.String())) 385 } 386 textStrBuf.WriteByte('\n') 387 textLineBuf.Reset() 388 default: 389 textLineBuf.WriteRune(r) 390 } 391 392 pos++ 393 } 394 str = textStrBuf.String() 395 } 396 rfields[j] = table.NewField(str, align) 397 398 if i == 0 { 399 aligns[j] = align 400 } 401 } 402 e.AppendRecord(rfields) 403 } 404 405 if options.Format == option.GFM { 406 e.SetFieldAlignments(aligns) 407 } 408 409 s, err := e.Encode() 410 if err != nil { 411 return "", NewDataEncodingError(err.Error()) 412 } 413 w := bufio.NewWriter(fp) 414 if _, err = w.WriteString(s); err != nil { 415 return "", NewSystemError(err.Error()) 416 } 417 if err = w.Flush(); err != nil { 418 return "", NewSystemError(err.Error()) 419 } 420 return "", nil 421 } 422 423 func encodeLTSV(ctx context.Context, fp io.Writer, view *View, options option.ExportOptions) error { 424 if view.RecordLen() < 1 { 425 return DataEmpty 426 } 427 428 hfields := make([]string, view.FieldLen()) 429 for i := range view.Header { 430 hfields[i] = view.Header[i].Column 431 } 432 433 w, err := ltsv.NewWriter(fp, hfields, options.LineBreak, options.Encoding) 434 if err != nil { 435 return NewDataEncodingError(err.Error()) 436 } 437 438 fields := make([]string, view.FieldLen()) 439 for i := range view.RecordSet { 440 if i&15 == 0 && ctx.Err() != nil { 441 return ConvertContextError(ctx.Err()) 442 } 443 444 for j := range view.RecordSet[i] { 445 fields[j], _, _ = ConvertFieldContents(view.RecordSet[i][j][0], false, options.ScientificNotation) 446 } 447 if err := w.Write(fields); err != nil { 448 return NewDataEncodingError(err.Error()) 449 } 450 } 451 if err = w.Flush(); err != nil { 452 return NewSystemError(err.Error()) 453 } 454 return nil 455 } 456 457 func jsonFloatFormat(useScientificNotation bool) txjson.FloatFormat { 458 if useScientificNotation { 459 return txjson.ENotationForLargeExponents 460 } 461 return txjson.NoExponent 462 } 463 464 func ConvertFieldContents(val value.Primary, forTextTable bool, useScientificNotation bool) (string, string, text.FieldAlignment) { 465 var s string 466 var effect = option.NoEffect 467 var align = text.NotAligned 468 469 switch v := val.(type) { 470 case *value.String: 471 s = v.Raw() 472 effect = option.StringEffect 473 case *value.Integer: 474 s = v.String() 475 effect = option.NumberEffect 476 align = text.RightAligned 477 case *value.Float: 478 s = value.Float64ToStr(v.Raw(), useScientificNotation) 479 effect = option.NumberEffect 480 align = text.RightAligned 481 case *value.Boolean: 482 s = v.String() 483 effect = option.BooleanEffect 484 align = text.Centering 485 case *value.Ternary: 486 if forTextTable { 487 s = v.Ternary().String() 488 effect = option.TernaryEffect 489 align = text.Centering 490 } else if v.Ternary() != ternary.UNKNOWN { 491 s = strconv.FormatBool(v.Ternary().ParseBool()) 492 effect = option.BooleanEffect 493 align = text.Centering 494 } 495 case *value.Datetime: 496 s = v.Format(time.RFC3339Nano) 497 effect = option.DatetimeEffect 498 case *value.Null: 499 if forTextTable { 500 s = "NULL" 501 effect = option.NullEffect 502 align = text.Centering 503 } 504 } 505 506 return s, effect, align 507 }