9 "github.com/zclconf/go-cty/cty"
10 "github.com/zclconf/go-cty/cty/function"
13 var CSVDecodeFunc = function.New(&function.Spec{
14 Params: []function.Parameter{
20 Type: func(args []cty.Value) (cty.Type, error) {
23 return cty.DynamicPseudoType, nil
26 r := strings.NewReader(str.AsString())
27 cr := csv.NewReader(r)
28 headers, err := cr.Read()
30 return cty.DynamicPseudoType, fmt.Errorf("missing header line")
33 return cty.DynamicPseudoType, err
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)
41 atys[name] = cty.String
43 return cty.List(cty.Object(atys)), nil
45 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
46 ety := retType.ElementType()
47 atys := ety.AttributeTypes()
49 r := strings.NewReader(str.AsString())
50 cr := csv.NewReader(r)
51 cr.FieldsPerRecord = len(atys)
53 // Read the header row first, since that'll tell us which indices
54 // map to which attribute names.
55 headers, err := cr.Read()
57 return cty.DynamicVal, err
62 cols, err := cr.Read()
67 return cty.DynamicVal, err
70 vals := make(map[string]cty.Value, len(cols))
71 for i, str := range cols {
73 vals[name] = cty.StringVal(str)
75 rows = append(rows, cty.ObjectVal(vals))
79 return cty.ListValEmpty(ety), nil
81 return cty.ListVal(rows), nil
85 // CSVDecode parses the given CSV (RFC 4180) string and, if it is valid,
86 // returns a list of objects representing the rows.
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.
91 func CSVDecode(str cty.Value) (cty.Value, error) {
92 return CSVDecodeFunc.Call([]cty.Value{str})