aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/function/stdlib/csv.go
blob: 5070a5adf579705cd2ac4bb6c6a988f3ae20f128 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package stdlib

import (
	"encoding/csv"
	"fmt"
	"io"
	"strings"

	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/function"
)

var CSVDecodeFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name: "str",
			Type: cty.String,
		},
	},
	Type: func(args []cty.Value) (cty.Type, error) {
		str := args[0]
		if !str.IsKnown() {
			return cty.DynamicPseudoType, nil
		}

		r := strings.NewReader(str.AsString())
		cr := csv.NewReader(r)
		headers, err := cr.Read()
		if err == io.EOF {
			return cty.DynamicPseudoType, fmt.Errorf("missing header line")
		}
		if err != nil {
			return cty.DynamicPseudoType, err
		}

		atys := make(map[string]cty.Type, len(headers))
		for _, name := range headers {
			if _, exists := atys[name]; exists {
				return cty.DynamicPseudoType, fmt.Errorf("duplicate column name %q", name)
			}
			atys[name] = cty.String
		}
		return cty.List(cty.Object(atys)), nil
	},
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		ety := retType.ElementType()
		atys := ety.AttributeTypes()
		str := args[0]
		r := strings.NewReader(str.AsString())
		cr := csv.NewReader(r)
		cr.FieldsPerRecord = len(atys)

		// Read the header row first, since that'll tell us which indices
		// map to which attribute names.
		headers, err := cr.Read()
		if err != nil {
			return cty.DynamicVal, err
		}

		var rows []cty.Value
		for {
			cols, err := cr.Read()
			if err == io.EOF {
				break
			}
			if err != nil {
				return cty.DynamicVal, err
			}

			vals := make(map[string]cty.Value, len(cols))
			for i, str := range cols {
				name := headers[i]
				vals[name] = cty.StringVal(str)
			}
			rows = append(rows, cty.ObjectVal(vals))
		}

		if len(rows) == 0 {
			return cty.ListValEmpty(ety), nil
		}
		return cty.ListVal(rows), nil
	},
})

// CSVDecode parses the given CSV (RFC 4180) string and, if it is valid,
// returns a list of objects representing the rows.
//
// The result is always a list of some object type. The first row of the
// input is used to determine the object attributes, and subsequent rows
// determine the values of those attributes.
func CSVDecode(str cty.Value) (cty.Value, error) {
	return CSVDecodeFunc.Call([]cty.Value{str})
}