initial commit
This commit is contained in:
commit
ddfeb99a53
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/csvmagic.iml
|
||||
/.idea/
|
||||
366
csv.go
Normal file
366
csv.go
Normal file
@ -0,0 +1,366 @@
|
||||
package csvmagic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
LoadParams struct {
|
||||
Separator rune
|
||||
FirstRowIsHeader bool
|
||||
}
|
||||
|
||||
FieldModificationInfo struct {
|
||||
SerializedName string
|
||||
FieldIdx int
|
||||
TypeInfo reflect.Kind
|
||||
}
|
||||
|
||||
TypeConverter func(string, reflect.Value)
|
||||
)
|
||||
|
||||
var (
|
||||
//you can use this to override the string->type conversion. types are indexed by their [reflect.Kind]
|
||||
typeConverters = make([]*TypeConverter, 27, 27)
|
||||
typesInitialized = false
|
||||
)
|
||||
|
||||
func CsvLoadParams(firstRowIsHeader bool) *LoadParams {
|
||||
return NewLoadParams(',', firstRowIsHeader)
|
||||
}
|
||||
|
||||
func NewLoadParams(separator byte, firstRowIsHeader bool) *LoadParams {
|
||||
return &LoadParams{Separator: bytes.Runes([]byte{separator})[0], FirstRowIsHeader: firstRowIsHeader}
|
||||
}
|
||||
|
||||
//LoadCsvAsObjects The crown jewel of this (currently) 200 line library. You can pass in a nil-value for params to
|
||||
//use the default parameter of comma-delimited. If the CSV file does not contain headers, you will have to annotate
|
||||
//your structs with the `csv:"MyHeader"` style of tag
|
||||
func LoadCsvAsObjects[K any](pathlike string, params *LoadParams) *[]K {
|
||||
//"Why do we need to pass a parameter of type K? lol" you might ask. Honestly? I couldn't figure out how to get
|
||||
//reflect info out of the generic type. I feel like one allocation and one copy isn't going to be a high cost.
|
||||
rows := *loadFile(pathlike, params)
|
||||
typ := reflect.TypeOf((*K)(nil)).Elem()
|
||||
fieldInfo := mapTypeInfo(typ)
|
||||
objSlice := reflect.MakeSlice(reflect.SliceOf(typ), len(rows)-1, len(rows)-1)
|
||||
container := objSlice.Interface().([]K)
|
||||
|
||||
headers := rows[0]
|
||||
startingIdx := 1
|
||||
indexOffset := 1
|
||||
rowCount := len(rows)
|
||||
|
||||
if !params.FirstRowIsHeader {
|
||||
startingIdx = 0
|
||||
indexOffset = 0
|
||||
rowCount = len(rows) - 1
|
||||
headerLen := len(rows[0])
|
||||
headers = make([]string, headerLen, headerLen)
|
||||
for i := 0; i < headerLen; i++ {
|
||||
headers[i] = strconv.Itoa(i)
|
||||
}
|
||||
}
|
||||
|
||||
for i := startingIdx; i < rowCount; i++ {
|
||||
row := rows[i]
|
||||
err := inflate[K](&container[i-indexOffset], &fieldInfo, headers, row)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return &container
|
||||
}
|
||||
|
||||
func loadFile(pathlike string, params *LoadParams) *[][]string {
|
||||
f, err := os.OpenFile(pathlike, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
reader := csv.NewReader(f)
|
||||
|
||||
if params == nil {
|
||||
params = CsvLoadParams(true)
|
||||
}
|
||||
reader.Comma = params.Separator
|
||||
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &records
|
||||
}
|
||||
|
||||
func mapTypeInfo(typeOf reflect.Type) map[string]FieldModificationInfo {
|
||||
fields := reflect.VisibleFields(typeOf)
|
||||
m := make(map[string]FieldModificationInfo)
|
||||
for _, v := range fields {
|
||||
jsonParams := v.Tag.Get("csv")
|
||||
info := FieldModificationInfo{}
|
||||
info.FieldIdx = v.Index[0]
|
||||
info.SerializedName = strings.Split(jsonParams, ",")[0]
|
||||
info.TypeInfo = v.Type.Kind()
|
||||
m[info.SerializedName] = info
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func inflate[K any](objPtr *K, fieldInfo *map[string]FieldModificationInfo, headers []string, data []string) error {
|
||||
lMap := *fieldInfo
|
||||
obj := reflect.Indirect(reflect.ValueOf(objPtr))
|
||||
|
||||
for i := 0; i < len(headers); i++ {
|
||||
header := headers[i]
|
||||
value := data[i]
|
||||
|
||||
info, ok := lMap[header]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
field := obj.Field(info.FieldIdx)
|
||||
typeIdx := int(info.TypeInfo)
|
||||
converters := getConverters()
|
||||
converter := converters[typeIdx]
|
||||
|
||||
if converter == nil {
|
||||
converter = &youAreOnYourOwn
|
||||
}
|
||||
|
||||
(*converter)(value, field)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//<editor-fold name="typeConverters">
|
||||
/*===================================================================================================
|
||||
typeConverters
|
||||
===================================================================================================*/
|
||||
|
||||
func getConverters() []*TypeConverter {
|
||||
// Before you start casting stones at me, look, we need to memoize this thing because of how we
|
||||
//structured this whole thing. Yes, I know it's global state. Can it be turned into something much more
|
||||
// memory/branching friendly? It sure can. Did I spend the time doing it for this 'lil tiny project? No,
|
||||
// I did not.
|
||||
if !typesInitialized {
|
||||
populateConverters()
|
||||
typesInitialized = true
|
||||
}
|
||||
return typeConverters
|
||||
}
|
||||
|
||||
//OverrideConverter
|
||||
// idx int
|
||||
func OverrideConverter(idx int, converter *TypeConverter) {
|
||||
if len(typeConverters) < idx-1 {
|
||||
tmp := make([]*TypeConverter, idx+1, idx+1)
|
||||
copy(tmp[:len(typeConverters)], typeConverters[:])
|
||||
typeConverters = tmp
|
||||
}
|
||||
getConverters()
|
||||
typeConverters[idx] = converter
|
||||
}
|
||||
|
||||
//populateConverters this is what makes all the magic happen. It's populates a bit of a fat array for function pointers
|
||||
func populateConverters() {
|
||||
typeConverters[int(reflect.Invalid)] = nil
|
||||
typeConverters[int(reflect.Bool)] = &convertBool
|
||||
typeConverters[int(reflect.Int)] = &convertInt
|
||||
typeConverters[int(reflect.Int8)] = &convertInt8
|
||||
typeConverters[int(reflect.Int16)] = &convertInt16
|
||||
typeConverters[int(reflect.Int32)] = &convertInt32
|
||||
typeConverters[int(reflect.Int64)] = &convertInt64
|
||||
typeConverters[int(reflect.Uint)] = &convertUint
|
||||
typeConverters[int(reflect.Uint8)] = &convertUint8
|
||||
typeConverters[int(reflect.Uint16)] = &convertUint16
|
||||
typeConverters[int(reflect.Uint32)] = &convertUint32
|
||||
typeConverters[int(reflect.Uint64)] = &convertUint64
|
||||
typeConverters[int(reflect.Uintptr)] = nil
|
||||
typeConverters[int(reflect.Float32)] = &convertFloat32
|
||||
typeConverters[int(reflect.Float64)] = &convertFloat64
|
||||
typeConverters[int(reflect.Complex64)] = nil
|
||||
typeConverters[int(reflect.Complex128)] = nil
|
||||
|
||||
//exercise left for the reader lol
|
||||
typeConverters[int(reflect.Array)] = nil
|
||||
typeConverters[int(reflect.Chan)] = nil
|
||||
typeConverters[int(reflect.Func)] = nil
|
||||
typeConverters[int(reflect.Interface)] = nil
|
||||
typeConverters[int(reflect.Map)] = nil
|
||||
typeConverters[int(reflect.Pointer)] = nil
|
||||
typeConverters[int(reflect.Slice)] = nil
|
||||
|
||||
typeConverters[int(reflect.String)] = &convertString
|
||||
|
||||
typeConverters[int(reflect.Struct)] = nil
|
||||
typeConverters[int(reflect.UnsafePointer)] = nil
|
||||
|
||||
}
|
||||
|
||||
var convertBool TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
field.Set(reflect.ValueOf(false))
|
||||
}
|
||||
}
|
||||
|
||||
var convertInt TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(i)
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertInt8 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseInt(s, 0, 8)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(int8(i))
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertInt16 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseInt(s, 0, 16)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(int16(i))
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertInt32 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseInt(s, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(int32(i))
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertInt64 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseInt(s, 0, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(i)
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertUint TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseUint(s, 10, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(uint(i))
|
||||
field.Set(settable)
|
||||
}
|
||||
var convertUint8 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseUint(s, 0, 8)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(uint8(i))
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertUint16 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseUint(s, 0, 16)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(uint16(i))
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertUint32 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseUint(s, 0, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(uint32(i))
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertUint64 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
i, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
settable := reflect.ValueOf(i)
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var convertFloat32 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
f, err := strconv.ParseFloat(s, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
field.SetFloat(f)
|
||||
}
|
||||
|
||||
var convertFloat64 TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" || len(s) == 0 {
|
||||
s = "0"
|
||||
}
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
field.SetFloat(f)
|
||||
}
|
||||
|
||||
var convertString TypeConverter = func(s string, field reflect.Value) {
|
||||
if s == "NULL" {
|
||||
s = ""
|
||||
}
|
||||
settable := reflect.ValueOf(s)
|
||||
field.Set(settable)
|
||||
}
|
||||
|
||||
var youAreOnYourOwn TypeConverter = func(s string, field reflect.Value) {
|
||||
panic(`type[` + field.Type().Name() + "\t" + field.Kind().String() + ` (` + strconv.Itoa(int(field.Kind())) + `)] has not been implemented. See OverrideConverter for more info.`)
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
Loading…
Reference in New Issue
Block a user