1package stdlib 2 3import ( 4 "encoding/csv" 5 "fmt" 6 "io" 7 "strings" 8 9 "github.com/zclconf/go-cty/cty" 10 "github.com/zclconf/go-cty/cty/function" 11) 12 13var CSVDecodeFunc = function.New(&function.Spec{ 14 Params: []function.Parameter{ 15 { 16 Name: "str", 17 Type: cty.String, 18 }, 19 }, 20 Type: func(args []cty.Value) (cty.Type, error) { 21 str := args[0] 22 if !str.IsKnown() { 23 return cty.DynamicPseudoType, nil 24 } 25 26 r := strings.NewReader(str.AsString()) 27 cr := csv.NewReader(r) 28 headers, err := cr.Read() 29 if err == io.EOF { 30 return cty.DynamicPseudoType, fmt.Errorf("missing header line") 31 } 32 if err != nil { 33 return cty.DynamicPseudoType, err 34 } 35 36 atys := make(map[string]cty.Type, len(headers)) 37 for _, name := range headers { 38 if _, exists := atys[name]; exists { 39 return cty.DynamicPseudoType, fmt.Errorf("duplicate column name %q", name) 40 } 41 atys[name] = cty.String 42 } 43 return cty.List(cty.Object(atys)), nil 44 }, 45 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 46 ety := retType.ElementType() 47 atys := ety.AttributeTypes() 48 str := args[0] 49 r := strings.NewReader(str.AsString()) 50 cr := csv.NewReader(r) 51 cr.FieldsPerRecord = len(atys) 52 53 // Read the header row first, since that'll tell us which indices 54 // map to which attribute names. 55 headers, err := cr.Read() 56 if err != nil { 57 return cty.DynamicVal, err 58 } 59 60 var rows []cty.Value 61 for { 62 cols, err := cr.Read() 63 if err == io.EOF { 64 break 65 } 66 if err != nil { 67 return cty.DynamicVal, err 68 } 69 70 vals := make(map[string]cty.Value, len(cols)) 71 for i, str := range cols { 72 name := headers[i] 73 vals[name] = cty.StringVal(str) 74 } 75 rows = append(rows, cty.ObjectVal(vals)) 76 } 77 78 if len(rows) == 0 { 79 return cty.ListValEmpty(ety), nil 80 } 81 return cty.ListVal(rows), nil 82 }, 83}) 84 85// CSVDecode parses the given CSV (RFC 4180) string and, if it is valid, 86// returns a list of objects representing the rows. 87// 88// The result is always a list of some object type. The first row of the 89// input is used to determine the object attributes, and subsequent rows 90// determine the values of those attributes. 91func CSVDecode(str cty.Value) (cty.Value, error) { 92 return CSVDecodeFunc.Call([]cty.Value{str}) 93} 94