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 }