go-hep.org/x/hep@v0.40.0/csvutil/csv.go (about)

     1  // Copyright ©2016 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package csvutil exposes functions and types to easily handle CSV files.
     6  // csvutil builds upon the encoding/csv package and provides the Table type.
     7  // A Table can read data from a CSV file into a struct value whose fields are
     8  // the various columns of the CSV file.
     9  // Conversely, a Table can write data into a CSV file from a struct value.
    10  package csvutil // import "go-hep.org/x/hep/csvutil"
    11  
    12  import (
    13  	"bufio"
    14  	"encoding/csv"
    15  	"fmt"
    16  	"io"
    17  	"math"
    18  	"os"
    19  	"reflect"
    20  	"strconv"
    21  	"strings"
    22  )
    23  
    24  func formatValue(val any, quotes bool, recBuilder *strings.Builder) error {
    25  	rv := reflect.Indirect(reflect.ValueOf(val))
    26  	rt := rv.Type()
    27  	switch rt.Kind() {
    28  	case reflect.Bool:
    29  		recBuilder.WriteString(strconv.FormatBool(rv.Bool()))
    30  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    31  		recBuilder.WriteString(strconv.FormatInt(rv.Int(), 10))
    32  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
    33  		recBuilder.WriteString(strconv.FormatUint(rv.Uint(), 10))
    34  	case reflect.Float32, reflect.Float64:
    35  		recBuilder.WriteString(strconv.FormatFloat(rv.Float(), 'g', -1, rt.Bits()))
    36  	case reflect.String:
    37  		if quotes {
    38  			recBuilder.WriteString("'" + rv.String() + "'")
    39  		} else {
    40  			recBuilder.WriteString(rv.String())
    41  		}
    42  	case reflect.Slice:
    43  		recBuilder.WriteString("[")
    44  		for i := range rv.Len() {
    45  			if i > 0 {
    46  				recBuilder.WriteString(", ")
    47  			}
    48  			err := formatValue(rv.Index(i).Interface(), true, recBuilder)
    49  			if err != nil {
    50  				return err
    51  			}
    52  		}
    53  		recBuilder.WriteString("]")
    54  	default:
    55  		return fmt.Errorf("csvutil: invalid type (%[1]T) %[1]v (kind=%[2]v)", val, rt.Kind())
    56  	}
    57  	return nil
    58  }
    59  
    60  // Open opens a Table in read mode connected to a CSV file.
    61  func Open(fname string) (*Table, error) {
    62  	r, err := os.Open(fname)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	table := &Table{
    67  		Reader: csv.NewReader(bufio.NewReader(r)),
    68  		f:      r,
    69  	}
    70  	return table, err
    71  }
    72  
    73  // Create creates a new CSV file and returns a Table in write mode.
    74  func Create(fname string) (*Table, error) {
    75  	w, err := os.Create(fname)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	table := &Table{
    80  		Writer: csv.NewWriter(bufio.NewWriter(w)),
    81  		f:      w,
    82  	}
    83  	return table, err
    84  }
    85  
    86  // Append opens an already existing CSV file and returns a Table in write mode.
    87  // The file cursor is positioned at the end of the file so new data can be
    88  // appended via the returned Table.
    89  func Append(fname string) (*Table, error) {
    90  	f, err := os.OpenFile(fname, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	_, err = f.Seek(0, io.SeekEnd)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	table := &Table{
   101  		Writer: csv.NewWriter(bufio.NewWriter(f)),
   102  		f:      f,
   103  	}
   104  	return table, err
   105  }
   106  
   107  // Table provides read- or write-access to a CSV file.
   108  // Table supports reading and writing data to/from a struct value.
   109  type Table struct {
   110  	Reader *csv.Reader
   111  	Writer *csv.Writer
   112  
   113  	f      *os.File
   114  	closed bool
   115  	err    error
   116  }
   117  
   118  // Close closes the table and the underlying CSV file.
   119  func (tbl *Table) Close() error {
   120  	if tbl.closed {
   121  		return tbl.err
   122  	}
   123  
   124  	if tbl.Writer != nil {
   125  		tbl.Writer.Flush()
   126  		tbl.err = tbl.Writer.Error()
   127  	}
   128  
   129  	if tbl.f != nil {
   130  		err := tbl.f.Close()
   131  		if err != nil && tbl.err == nil {
   132  			tbl.err = err
   133  		}
   134  		tbl.f = nil
   135  		tbl.closed = true
   136  	}
   137  	return tbl.err
   138  }
   139  
   140  // ReadRows returns a row iterator semantically equivalent to [beg,end).
   141  // If end==-1, the iterator will be configured to read rows until EOF.
   142  func (tbl *Table) ReadRows(beg, end int64) (*Rows, error) {
   143  	inc := int64(1)
   144  	rows := &Rows{
   145  		tbl: tbl,
   146  		i:   0,
   147  		n:   end - beg,
   148  		inc: inc,
   149  		cur: beg - inc,
   150  	}
   151  	if end == -1 {
   152  		rows.n = math.MaxInt64
   153  	}
   154  	if beg > 0 {
   155  		err := rows.skip(beg)
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  	}
   160  	return rows, nil
   161  }
   162  
   163  // WriteHeader writes a header to the underlying CSV file
   164  func (tbl *Table) WriteHeader(hdr string) error {
   165  	if !strings.HasSuffix(hdr, "\n") {
   166  		hdr += "\n"
   167  	}
   168  	_, err := tbl.f.WriteString(hdr)
   169  	return err
   170  }
   171  
   172  // WriteRow writes the data into the columns at the current row.
   173  func (tbl *Table) WriteRow(args ...any) error {
   174  	var err error
   175  	if tbl.Writer == nil {
   176  		return fmt.Errorf("csvutil: Table is not in write mode")
   177  	}
   178  
   179  	switch len(args) {
   180  	case 0:
   181  		return fmt.Errorf("csvutil: Table.WriteRow needs at least one argument")
   182  
   183  	case 1:
   184  		// maybe special case: struct?
   185  		rv := reflect.Indirect(reflect.ValueOf(args[0]))
   186  		rt := rv.Type()
   187  		switch rt.Kind() {
   188  		case reflect.Struct:
   189  			err = tbl.writeStruct(rv)
   190  			return err
   191  		}
   192  	}
   193  
   194  	err = tbl.write(args...)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	return err
   200  }
   201  
   202  func (tbl *Table) write(args ...any) error {
   203  	rec := make([]string, len(args))
   204  	var recBuilder strings.Builder
   205  	for i, arg := range args {
   206  		recBuilder.Reset()
   207  		err := formatValue(arg, false, &recBuilder)
   208  		if err != nil {
   209  			return err
   210  		}
   211  		rec[i] = recBuilder.String()
   212  	}
   213  	return tbl.Writer.Write(rec)
   214  }
   215  
   216  func (tbl *Table) writeStruct(rv reflect.Value) error {
   217  	rt := rv.Type()
   218  	args := make([]any, rt.NumField())
   219  	for i := range args {
   220  		args[i] = rv.Field(i).Interface()
   221  	}
   222  
   223  	return tbl.write(args...)
   224  }
   225  
   226  // Rows is an iterator over an interval of rows inside a CSV file.
   227  type Rows struct {
   228  	tbl    *Table
   229  	i      int64    // number of rows iterated over
   230  	n      int64    // number of rows this iterator iters over
   231  	inc    int64    // number of rows to increment by at each iteration
   232  	cur    int64    // current row index
   233  	record []string // last read record
   234  	closed bool
   235  	err    error // last error
   236  }
   237  
   238  // Err returns the error, if any, that was encountered during iteration.
   239  // Err may be called after an explicit or implicit Close.
   240  func (rows *Rows) Err() error {
   241  	return rows.err
   242  }
   243  
   244  // Close closes the Rows, preventing further enumeration.
   245  // Close is idempotent and does not affect the result of Err.
   246  func (rows *Rows) Close() error {
   247  	if rows.closed {
   248  		return nil
   249  	}
   250  	rows.closed = true
   251  	rows.tbl = nil
   252  	return nil
   253  }
   254  
   255  // NumFields returns the number of fields in the current CSV-record.
   256  // NumFields assumes Rows.Next() has been called at least once.
   257  func (rows *Rows) NumFields() int {
   258  	return len(rows.record)
   259  }
   260  
   261  // Fields returns the raw string values of the fields of the current CSV-record.
   262  // Fields assumes Rows.Next() has been called at least once.
   263  func (rows *Rows) Fields() []string {
   264  	fields := make([]string, len(rows.record))
   265  	copy(fields, rows.record)
   266  	return fields
   267  }
   268  
   269  // Scan copies the columns in the current row into the values pointed at by
   270  // dest.
   271  // dest can be either:
   272  // - a pointer to a struct value (whose fields will be filled with column values)
   273  // - a slice of values
   274  func (rows *Rows) Scan(dest ...any) error {
   275  	var err error
   276  	defer func() {
   277  		rows.err = err
   278  	}()
   279  
   280  	switch len(dest) {
   281  	case 0:
   282  		err = fmt.Errorf("csvutil: Rows.Scan needs at least one argument")
   283  		return err
   284  
   285  	case 1:
   286  		// maybe special case: struct?
   287  		rv := reflect.ValueOf(dest[0]).Elem()
   288  		rt := rv.Type()
   289  		switch rt.Kind() {
   290  		case reflect.Struct:
   291  			err = rows.scanStruct(rv)
   292  			return err
   293  		}
   294  	}
   295  
   296  	err = rows.scan(dest...)
   297  	return err
   298  }
   299  
   300  func (rows *Rows) scan(args ...any) error {
   301  	var err error
   302  	n := min(len(rows.record), len(args))
   303  	for i := range n {
   304  		rec := rows.record[i]
   305  		rv := reflect.ValueOf(args[i]).Elem()
   306  		rt := reflect.TypeOf(args[i]).Elem()
   307  		switch rt.Kind() {
   308  		case reflect.Bool:
   309  			v, err := strconv.ParseBool(rec)
   310  			if err != nil {
   311  				return err
   312  			}
   313  			rv.SetBool(v)
   314  
   315  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   316  			v, err := strconv.ParseInt(rec, 10, rt.Bits())
   317  			if err != nil {
   318  				return err
   319  			}
   320  			rv.SetInt(v)
   321  
   322  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   323  			v, err := strconv.ParseUint(rec, 10, rt.Bits())
   324  			if err != nil {
   325  				return err
   326  			}
   327  			rv.SetUint(v)
   328  
   329  		case reflect.Float32, reflect.Float64:
   330  			v, err := strconv.ParseFloat(rec, rt.Bits())
   331  			if err != nil {
   332  				return err
   333  			}
   334  			rv.SetFloat(v)
   335  
   336  		case reflect.String:
   337  			rv.SetString(rec)
   338  
   339  		default:
   340  			return fmt.Errorf("csvutil: invalid type (%T) %q (kind=%v)", rv.Interface(), rec, rt.Kind())
   341  		}
   342  	}
   343  
   344  	return err
   345  }
   346  
   347  func (rows *Rows) scanStruct(rv reflect.Value) error {
   348  	rt := rv.Type()
   349  	args := make([]any, rt.NumField())
   350  	for i := range rt.NumField() {
   351  		args[i] = rv.Field(i).Addr().Interface()
   352  	}
   353  	return rows.scan(args...)
   354  }
   355  
   356  func (rows *Rows) skip(n int64) error {
   357  	var err error
   358  	for range n {
   359  		_, err = rows.tbl.Reader.Read()
   360  		if err != nil {
   361  			return err
   362  		}
   363  		rows.cur++
   364  	}
   365  	return err
   366  }
   367  
   368  // Next prepares the next result row for reading with the Scan method.
   369  // It returns true on success, false if there is no next result row.
   370  // Every call to Scan, even the first one, must be preceded by a call to Next.
   371  func (rows *Rows) Next() bool {
   372  	if rows.closed {
   373  		return false
   374  	}
   375  	if rows.err != nil {
   376  		return false
   377  	}
   378  	next := rows.i < rows.n
   379  	rows.cur += rows.inc
   380  	rows.i += rows.inc
   381  	if !next {
   382  		rows.err = rows.Close()
   383  		return next
   384  	}
   385  
   386  	var err error
   387  	rows.record, err = rows.tbl.Reader.Read()
   388  	if err != nil {
   389  		rows.err = err
   390  		return false
   391  	}
   392  
   393  	return next
   394  }