mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
176 lines
3.8 KiB
Go
176 lines
3.8 KiB
Go
package csvx
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Delimiter selects the separator used by a delimited value's CSV form.
|
|
type Delimiter interface {
|
|
Separator() string
|
|
}
|
|
|
|
// Comma separates values with a comma.
|
|
type Comma struct{}
|
|
|
|
func (Comma) Separator() string { return "," }
|
|
|
|
// Space separates values with whitespace.
|
|
type Space struct{}
|
|
|
|
func (Space) Separator() string { return " " }
|
|
|
|
// Tab separates values with a tab.
|
|
type Tab struct{}
|
|
|
|
func (Tab) Separator() string { return "\t" }
|
|
|
|
// Strings is a string slice with a delimiter-specific CSV form.
|
|
// Its SQL and JSON forms are JSON arrays.
|
|
type Strings[D Delimiter] []string
|
|
|
|
// Value implements driver.Valuer as a JSON array.
|
|
func (s Strings[D]) Value() (driver.Value, error) {
|
|
if s == nil {
|
|
s = Strings[D]{}
|
|
}
|
|
return json.Marshal([]string(s))
|
|
}
|
|
|
|
// Scan implements sql.Scanner from a JSON array.
|
|
func (s *Strings[D]) Scan(value any) error {
|
|
switch v := value.(type) {
|
|
case nil:
|
|
*s = nil
|
|
return nil
|
|
case []byte:
|
|
return s.scanJSON(v)
|
|
case string:
|
|
return s.scanJSON([]byte(v))
|
|
case Strings[D]:
|
|
*s = append((*s)[:0], v...)
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("csvx.Strings: cannot scan %T", value)
|
|
}
|
|
}
|
|
|
|
func (s *Strings[D]) scanJSON(data []byte) error {
|
|
if len(data) == 0 {
|
|
*s = nil
|
|
return nil
|
|
}
|
|
var values []string
|
|
if err := json.Unmarshal(data, &values); err != nil {
|
|
return fmt.Errorf("csvx.Strings: %w", err)
|
|
}
|
|
*s = values
|
|
return nil
|
|
}
|
|
|
|
// MarshalCSV implements csvutil.Marshaler.
|
|
func (s Strings[D]) MarshalCSV() ([]byte, error) {
|
|
if len(s) == 0 {
|
|
return nil, nil
|
|
}
|
|
var delimiter D
|
|
return []byte(strings.Join(s, delimiter.Separator())), nil
|
|
}
|
|
|
|
// UnmarshalCSV implements csvutil.Unmarshaler.
|
|
func (s *Strings[D]) UnmarshalCSV(data []byte) error {
|
|
if len(data) == 0 {
|
|
*s = nil
|
|
return nil
|
|
}
|
|
var delimiter D
|
|
if delimiter.Separator() == (Space{}).Separator() {
|
|
*s = Strings[D](strings.Fields(string(data)))
|
|
return nil
|
|
}
|
|
*s = Strings[D](strings.Split(string(data), delimiter.Separator()))
|
|
return nil
|
|
}
|
|
|
|
// Ints is an int64 slice with a delimiter-specific CSV form.
|
|
// Its SQL and JSON forms are JSON arrays.
|
|
type Ints[D Delimiter] []int64
|
|
|
|
// Value implements driver.Valuer as a JSON array.
|
|
func (s Ints[D]) Value() (driver.Value, error) {
|
|
if s == nil {
|
|
s = Ints[D]{}
|
|
}
|
|
return json.Marshal([]int64(s))
|
|
}
|
|
|
|
// Scan implements sql.Scanner from a JSON array.
|
|
func (s *Ints[D]) Scan(value any) error {
|
|
switch v := value.(type) {
|
|
case nil:
|
|
*s = nil
|
|
return nil
|
|
case []byte:
|
|
return s.scanJSON(v)
|
|
case string:
|
|
return s.scanJSON([]byte(v))
|
|
case Ints[D]:
|
|
*s = append((*s)[:0], v...)
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("csvx.Ints: cannot scan %T", value)
|
|
}
|
|
}
|
|
|
|
func (s *Ints[D]) scanJSON(data []byte) error {
|
|
if len(data) == 0 {
|
|
*s = nil
|
|
return nil
|
|
}
|
|
var values []int64
|
|
if err := json.Unmarshal(data, &values); err != nil {
|
|
return fmt.Errorf("csvx.Ints: %w", err)
|
|
}
|
|
*s = values
|
|
return nil
|
|
}
|
|
|
|
// MarshalCSV implements csvutil.Marshaler.
|
|
func (s Ints[D]) MarshalCSV() ([]byte, error) {
|
|
if len(s) == 0 {
|
|
return nil, nil
|
|
}
|
|
var delimiter D
|
|
values := make([]string, len(s))
|
|
for i, value := range s {
|
|
values[i] = strconv.FormatInt(value, 10)
|
|
}
|
|
return []byte(strings.Join(values, delimiter.Separator())), nil
|
|
}
|
|
|
|
// UnmarshalCSV implements csvutil.Unmarshaler.
|
|
func (s *Ints[D]) UnmarshalCSV(data []byte) error {
|
|
if len(data) == 0 {
|
|
*s = nil
|
|
return nil
|
|
}
|
|
var delimiter D
|
|
parts := strings.Split(string(data), delimiter.Separator())
|
|
if delimiter.Separator() == (Space{}).Separator() {
|
|
parts = strings.Fields(string(data))
|
|
}
|
|
values := make(Ints[D], len(parts))
|
|
for i, part := range parts {
|
|
value, err := strconv.ParseInt(strings.TrimSpace(part), 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("csvx.Ints: parse %q: %w", part, err)
|
|
}
|
|
values[i] = value
|
|
}
|
|
*s = values
|
|
return nil
|
|
}
|