File: record.go

package info (click to toggle)
golang-github-wildducktheories-go-csv 0.0~git20210709.8745000-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, sid, trixie
  • size: 200 kB
  • sloc: makefile: 5
file content (141 lines) | stat: -rw-r--r-- 3,602 bytes parent folder | download | duplicates (2)
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package csv

import (
	"fmt"
	"github.com/wildducktheories/go-csv/utils"
	"os"
)

//Record provides keyed access to the fields of data records where each field
//of a data record is keyed by the value of the corresponding field in the header record.
type Record interface {
	// Return the header of the record.
	Header() []string
	// Gets the value of the field specified by the key. Returns the empty string
	// if the field does not exist in the record.
	Get(key string) string
	// Puts the value into the field specified by the key.
	Put(key string, value string)
	// Return the contents of the record as a map. Mutation of the map is not supported.
	AsMap() map[string]string
	// Return the contents of the record as a slice. Mutation of the slice is not supported.
	AsSlice() []string
	// Puts all the matching values from the specified record into the receiving record
	PutAll(r Record)
	// Return true if the receiver and the specified record have the same header.
	SameHeader(r Record) bool
}

type record struct {
	header []string
	index  map[string]int
	fields []string
	cache  map[string]string
}

type RecordBuilder func(fields []string) Record

// NewRecordBuilder returns a function that can be used to create new Records
// for a CSV stream with the specified header.
//
// This can be used with raw encoding/csv streams in cases where a CSV stream contains
// more than one record type.
func NewRecordBuilder(header []string) RecordBuilder {
	index := utils.NewIndex(header)
	return func(fields []string) Record {
		if len(header) < len(fields) {
			fmt.Fprintf(os.Stderr, "invariant violated: [%d]fields=%v, [%d]header=%v\n", len(fields), fields, len(header), header)
		}
		tmp := make([]string, len(header), len(header))
		copy(tmp, fields)
		return &record{
			header: header,
			index:  index,
			fields: tmp,
		}
	}
}

func (r *record) Header() []string {
	return r.header
}

// Answer the value of the field indexed by the column containing the specified header value.
func (r *record) Get(key string) string {
	x, ok := r.index[key]
	if ok && x < len(r.fields) {
		return r.fields[x]
	}
	return ""
}

// Puts the specified value into the record at the index determined by the key value.
func (r *record) Put(key string, value string) {
	x, ok := r.index[key]
	if ok && x < cap(r.fields) {
		if x > len(r.fields) {
			r.fields = r.fields[0:x]
		}
		if r.cache != nil {
			r.cache[key] = value
		}
		r.fields[x] = value
	}
}

// Return a map containing a copy of the contents of the record.
func (r *record) AsMap() map[string]string {
	if r.cache != nil {
		return r.cache
	}

	result := make(map[string]string)
	for i, h := range r.header {
		if i < len(r.fields) {
			result[h] = r.fields[i]
		} else {
			result[h] = ""
		}
	}
	r.cache = result
	return result
}

// Return the record values as a slice.
func (r *record) AsSlice() []string {
	return r.fields
}

// Puts all the specified value into the record.
func (r *record) PutAll(o Record) {
	if r.SameHeader(o) {
		copy(r.fields, o.AsSlice())
		r.cache = nil
	} else {
		for i, k := range r.header {
			v := o.Get(k)
			r.fields[i] = v
			if r.cache != nil {
				r.cache[k] = v
			}
		}
	}
}

// Efficiently check that the receiver and specified records have the same header
func (r *record) SameHeader(o Record) bool {
	h := o.Header()
	if len(r.header) != len(h) {
		return false
	} else if len(h) == 0 || &h[0] == &r.header[0] {
		// two slices with the same address and length have the same contents
		return true
	} else {
		for i, k := range r.header {
			if h[i] != k {
				return false
			}
		}
		return true
	}
}