package sysconf import ( "bytes" "encoding/csv" "errors" "fmt" "reflect" "strings" ) var ErrNilCSVValue = errors.New("nil csv value") type CSV struct { header []string text [][]string } type CSVRow struct { header []string data []string } type CSVValue struct { key string value string } func ParseCSV(data []byte, hasHeader bool) (csvData CSV, err error) { if len(data) == 0 { return CSV{}, fmt.Errorf("cannot parse data,invalid data format") } reader := csv.NewReader(bytes.NewReader(data)) records, err := reader.ReadAll() if err != nil { return CSV{}, err } if len(records) == 0 { return CSV{}, fmt.Errorf("cannot parse data,invalid data format") } start := 0 if hasHeader { csvData.header = append([]string(nil), records[0]...) start = 1 } else { for i := range records[0] { csvData.header = append(csvData.header, fmt.Sprint(i)) } } for _, record := range records[start:] { if len(record) != len(csvData.header) { return CSV{}, fmt.Errorf("cannot parse data line,got %d values but need %d", len(record), len(csvData.header)) } csvData.text = append(csvData.text, append([]string(nil), record...)) } return csvData, nil } func (csvData *CSV) Header() []string { return csvData.header } func (csvData *CSV) Data() [][]string { return csvData.text } func (csvData *CSV) Row(row int) *CSVRow { if csvData == nil || row < 0 || row >= len(csvData.text) { return nil } return &CSVRow{header: csvData.header, data: csvData.text[row]} } func (row *CSVRow) Get(key string) *CSVValue { if row == nil { return nil } for idx, header := range row.header { if header == key { return &CSVValue{key: key, value: row.data[idx]} } } return nil } func (row *CSVRow) Col(key int) *CSVValue { if row == nil || key < 0 || key >= len(row.header) { return nil } return &CSVValue{key: row.header[key], value: row.data[key]} } func (row *CSVRow) Header() []string { return row.header } func (csvData *CSV) MapData() []map[string]string { var result []map[string]string for _, record := range csvData.text { item := make(map[string]string, len(csvData.header)) for idx, header := range csvData.header { item[header] = record[idx] } result = append(result, item) } return result } func CsvAnalyse(data string) []string { return csvAnalyse(data) } func csvAnalyse(data string) []string { reader := csv.NewReader(strings.NewReader(data)) record, err := reader.Read() if err != nil { return []string{} } return record } func MarshalCSV(header []string, ins interface{}) ([]byte, error) { v := reflect.ValueOf(ins) if v.Kind() == reflect.Ptr { if v.IsNil() { return nil, ErrNilCSVValue } v = v.Elem() } if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { return nil, fmt.Errorf("not a Slice or Array") } rows := make([][]string, 0, v.Len()) for i := 0; i < v.Len(); i++ { item := v.Index(i) if item.Kind() == reflect.Ptr { if item.IsNil() { continue } item = item.Elem() } switch item.Kind() { case reflect.Slice, reflect.Array: row := make([]string, 0, item.Len()) for j := 0; j < item.Len(); j++ { row = append(row, fmt.Sprint(item.Index(j).Interface())) } rows = append(rows, row) case reflect.Struct: row := make([]string, 0, item.NumField()) for j := 0; j < item.NumField(); j++ { field := item.Field(j) if !field.CanInterface() { continue } row = append(row, fmt.Sprint(field.Interface())) } rows = append(rows, row) } } width := 0 if len(header) > 0 { width = len(header) } else if len(rows) > 0 { width = len(rows[0]) } for idx, row := range rows { if len(row) != width { return nil, fmt.Errorf("line %d got length %d ,but need %d", idx, len(row), width) } } var buf bytes.Buffer writer := csv.NewWriter(&buf) if len(header) > 0 { if err := writer.Write(header); err != nil { return nil, err } } for _, row := range rows { if err := writer.Write(row); err != nil { return nil, err } } writer.Flush() return buf.Bytes(), writer.Error() }