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  }