monitoring dashboard dependency vendor
Signed-off-by: junotx <junotx@126.com>
This commit is contained in:
89
vendor/github.com/jszwec/csvutil/.gitignore
generated
vendored
Normal file
89
vendor/github.com/jszwec/csvutil/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
# Created by https://www.gitignore.io/api/osx,go,windows,linux
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### Go ###
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
21
vendor/github.com/jszwec/csvutil/LICENSE
generated
vendored
Normal file
21
vendor/github.com/jszwec/csvutil/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Jacek Szwec
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
668
vendor/github.com/jszwec/csvutil/README.md
generated
vendored
Normal file
668
vendor/github.com/jszwec/csvutil/README.md
generated
vendored
Normal file
@@ -0,0 +1,668 @@
|
||||
csvutil [](https://pkg.go.dev/github.com/jszwec/csvutil?tab=doc)  [](https://goreportcard.com/report/github.com/jszwec/csvutil) [](https://codecov.io/gh/jszwec/csvutil)
|
||||
=================
|
||||
|
||||
<p align="center">
|
||||
<img style="float: right;" src="https://user-images.githubusercontent.com/3941256/33054906-52b4bc08-ce4a-11e7-9651-b70c5a47c921.png"/ width=200>
|
||||
</p>
|
||||
|
||||
Package csvutil provides fast, idiomatic, and dependency free mapping between CSV and Go (golang) values.
|
||||
|
||||
This package is not a CSV parser, it is based on the [Reader](https://godoc.org/github.com/jszwec/csvutil#Reader) and [Writer](https://godoc.org/github.com/jszwec/csvutil#Writer)
|
||||
interfaces which are implemented by eg. std Go (golang) [csv package](https://golang.org/pkg/encoding/csv). This gives a possibility
|
||||
of choosing any other CSV writer or reader which may be more performant.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
go get github.com/jszwec/csvutil
|
||||
|
||||
Requirements
|
||||
-------------
|
||||
|
||||
* Go1.7+
|
||||
|
||||
Index
|
||||
------
|
||||
|
||||
1. [Examples](#examples)
|
||||
1. [Unmarshal](#examples_unmarshal)
|
||||
2. [Marshal](#examples_marshal)
|
||||
3. [Unmarshal and metadata](#examples_unmarshal_and_metadata)
|
||||
4. [But my CSV file has no header...](#examples_but_my_csv_has_no_header)
|
||||
5. [Decoder.Map - data normalization](#examples_decoder_map)
|
||||
6. [Different separator/delimiter](#examples_different_separator)
|
||||
7. [Custom Types](#examples_custom_types)
|
||||
8. [Custom time.Time format](#examples_time_format)
|
||||
9. [Custom struct tags](#examples_struct_tags)
|
||||
10. [Slice and Map fields](#examples_slice_and_map_field)
|
||||
11. [Nested/Embedded structs](#examples_nested_structs)
|
||||
12. [Inline tag](#examples_inlined_structs)
|
||||
2. [Performance](#performance)
|
||||
1. [Unmarshal](#performance_unmarshal)
|
||||
2. [Marshal](#performance_marshal)
|
||||
|
||||
Example <a name="examples"></a>
|
||||
--------
|
||||
|
||||
### Unmarshal <a name="examples_unmarshal"></a>
|
||||
|
||||
Nice and easy Unmarshal is using the Go std [csv.Reader](https://golang.org/pkg/encoding/csv/#Reader) with its default options. Use [Decoder](https://godoc.org/github.com/jszwec/csvutil#Decoder) for streaming and more advanced use cases.
|
||||
|
||||
```go
|
||||
var csvInput = []byte(`
|
||||
name,age,CreatedAt
|
||||
jacek,26,2012-04-01T15:00:00Z
|
||||
john,,0001-01-01T00:00:00Z`,
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string `csv:"name"`
|
||||
Age int `csv:"age,omitempty"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
var users []User
|
||||
if err := csvutil.Unmarshal(csvInput, &users); err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
fmt.Printf("%+v\n", u)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {Name:jacek Age:26 CreatedAt:2012-04-01 15:00:00 +0000 UTC}
|
||||
// {Name:john Age:0 CreatedAt:0001-01-01 00:00:00 +0000 UTC}
|
||||
```
|
||||
|
||||
### Marshal <a name="examples_marshal"></a>
|
||||
|
||||
Marshal is using the Go std [csv.Writer](https://golang.org/pkg/encoding/csv/#Writer) with its default options. Use [Encoder](https://godoc.org/github.com/jszwec/csvutil#Encoder) for streaming or to use a different Writer.
|
||||
|
||||
```go
|
||||
type Address struct {
|
||||
City string
|
||||
Country string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Address
|
||||
Age int `csv:"age,omitempty"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
users := []User{
|
||||
{
|
||||
Name: "John",
|
||||
Address: Address{"Boston", "USA"},
|
||||
Age: 26,
|
||||
CreatedAt: time.Date(2010, 6, 2, 12, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Name: "Alice",
|
||||
Address: Address{"SF", "USA"},
|
||||
},
|
||||
}
|
||||
|
||||
b, err := csvutil.Marshal(users)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
|
||||
// Output:
|
||||
// Name,City,Country,age,CreatedAt
|
||||
// John,Boston,USA,26,2010-06-02T12:00:00Z
|
||||
// Alice,SF,USA,,0001-01-01T00:00:00Z
|
||||
```
|
||||
|
||||
### Unmarshal and metadata <a name="examples_unmarshal_and_metadata"></a>
|
||||
|
||||
It may happen that your CSV input will not always have the same header. In addition
|
||||
to your base fields you may get extra metadata that you would still like to store.
|
||||
[Decoder](https://godoc.org/github.com/jszwec/csvutil#Decoder) provides
|
||||
[Unused](https://godoc.org/github.com/jszwec/csvutil#Decoder.Unused) method, which after each call to
|
||||
[Decode](https://godoc.org/github.com/jszwec/csvutil#Decoder.Decode) can report which header indexes
|
||||
were not used during decoding. Based on that, it is possible to handle and store all these extra values.
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Name string `csv:"name"`
|
||||
City string `csv:"city"`
|
||||
Age int `csv:"age"`
|
||||
OtherData map[string]string `csv:"-"`
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(strings.NewReader(`
|
||||
name,age,city,zip
|
||||
alice,25,la,90005
|
||||
bob,30,ny,10005`))
|
||||
|
||||
dec, err := csvutil.NewDecoder(csvReader)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
header := dec.Header()
|
||||
var users []User
|
||||
for {
|
||||
u := User{OtherData: make(map[string]string)}
|
||||
|
||||
if err := dec.Decode(&u); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, i := range dec.Unused() {
|
||||
u.OtherData[header[i]] = dec.Record()[i]
|
||||
}
|
||||
users = append(users, u)
|
||||
}
|
||||
|
||||
fmt.Println(users)
|
||||
|
||||
// Output:
|
||||
// [{alice la 25 map[zip:90005]} {bob ny 30 map[zip:10005]}]
|
||||
```
|
||||
|
||||
### But my CSV file has no header... <a name="examples_but_my_csv_has_no_header"></a>
|
||||
|
||||
Some CSV files have no header, but if you know how it should look like, it is
|
||||
possible to define a struct and generate it. All that is left to do, is to pass
|
||||
it to a decoder.
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ID int
|
||||
Name string
|
||||
Age int `csv:",omitempty"`
|
||||
City string
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(strings.NewReader(`
|
||||
1,John,27,la
|
||||
2,Bob,,ny`))
|
||||
|
||||
// in real application this should be done once in init function.
|
||||
userHeader, err := csvutil.Header(User{}, "csv")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
dec, err := csvutil.NewDecoder(csvReader, userHeader...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var users []User
|
||||
for {
|
||||
var u User
|
||||
if err := dec.Decode(&u); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
users = append(users, u)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v", users)
|
||||
|
||||
// Output:
|
||||
// [{ID:1 Name:John Age:27 City:la} {ID:2 Name:Bob Age:0 City:ny}]
|
||||
```
|
||||
|
||||
### Decoder.Map - data normalization <a name="examples_decoder_map"></a>
|
||||
|
||||
The Decoder's [Map](https://godoc.org/github.com/jszwec/csvutil#Decoder.Map) function is a powerful tool that can help clean up or normalize
|
||||
the incoming data before the actual decoding takes place.
|
||||
|
||||
Lets say we want to decode some floats and the csv input contains some NaN values, but these values are represented by the 'n/a' string. An attempt to decode 'n/a' into float will end up with error, because strconv.ParseFloat expects 'NaN'. Knowing that, we can implement a Map function that will normalize our 'n/a' string and turn it to 'NaN' only for float types.
|
||||
|
||||
```go
|
||||
dec, err := NewDecoder(r)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
dec.Map = func(field, column string, v interface{}) string {
|
||||
if _, ok := v.(float64); ok && field == "n/a" {
|
||||
return "NaN"
|
||||
}
|
||||
return field
|
||||
}
|
||||
```
|
||||
|
||||
Now our float64 fields will be decoded properly into NaN. What about float32, float type aliases and other NaN formats? Look at the full example [here](https://gist.github.com/jszwec/2bb94f8f3612e0162eb16003701f727e).
|
||||
|
||||
### Different separator/delimiter <a name="examples_different_separator"></a>
|
||||
|
||||
Some files may use different value separators, for example TSV files would use `\t`. The following examples show how to set up a Decoder and Encoder for such use case.
|
||||
|
||||
#### Decoder:
|
||||
```go
|
||||
csvReader := csv.NewReader(r)
|
||||
csvReader.Comma = '\t'
|
||||
|
||||
dec, err := NewDecoder(csvReader)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var users []User
|
||||
for {
|
||||
var u User
|
||||
if err := dec.Decode(&u); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
users = append(users, u)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Encoder:
|
||||
```go
|
||||
var buf bytes.Buffer
|
||||
|
||||
w := csv.NewWriter(&buf)
|
||||
w.Comma = '\t'
|
||||
enc := csvutil.NewEncoder(w)
|
||||
|
||||
for _, u := range users {
|
||||
if err := enc.Encode(u); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
if err := w.Error(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Types and Overrides <a name="examples_custom_types"></a>
|
||||
|
||||
There are multiple ways to customize or override your type's behavior.
|
||||
|
||||
1. a type implements [csvutil.Marshaler](https://pkg.go.dev/github.com/jszwec/csvutil#Marshaler) and/or [csvutil.Unmarshaler](https://pkg.go.dev/github.com/jszwec/csvutil#Unmarshaler)
|
||||
```go
|
||||
type Foo int64
|
||||
|
||||
func (f Foo) MarshalCSV() ([]byte, error) {
|
||||
return strconv.AppendInt(nil, int64(f), 16), nil
|
||||
}
|
||||
|
||||
func (f *Foo) UnmarshalCSV(data []byte) error {
|
||||
i, err := strconv.ParseInt(string(data), 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = Foo(i)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
2. a type implements [encoding.TextUnmarshaler](https://golang.org/pkg/encoding/#TextUnmarshaler) and/or [encoding.TextMarshaler](https://golang.org/pkg/encoding/#TextMarshaler)
|
||||
```go
|
||||
type Foo int64
|
||||
|
||||
func (f Foo) MarshalText() ([]byte, error) {
|
||||
return strconv.AppendInt(nil, int64(f), 16), nil
|
||||
}
|
||||
|
||||
func (f *Foo) UnmarshalText(data []byte) error {
|
||||
i, err := strconv.ParseInt(string(data), 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = Foo(i)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
3. a type is registered using [Encoder.Register](https://pkg.go.dev/github.com/jszwec/csvutil#Encoder.Register) and/or [Decoder.Register](https://pkg.go.dev/github.com/jszwec/csvutil#Decoder.Register)
|
||||
```go
|
||||
type Foo int64
|
||||
|
||||
enc.Register(func(f Foo) ([]byte, error) {
|
||||
return strconv.AppendInt(nil, int64(f), 16), nil
|
||||
})
|
||||
|
||||
dec.Register(func(data []byte, f *Foo) error {
|
||||
v, err := strconv.ParseInt(string(data), 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = Foo(v)
|
||||
return nil
|
||||
})
|
||||
```
|
||||
4. a type implements an interface that was registered using [Encoder.Register](https://pkg.go.dev/github.com/jszwec/csvutil#Encoder.Register) and/or [Decoder.Register](https://pkg.go.dev/github.com/jszwec/csvutil#Decoder.Register)
|
||||
```go
|
||||
type Foo int64
|
||||
|
||||
func (f Foo) String() string {
|
||||
return strconv.FormatInt(int64(f), 16)
|
||||
}
|
||||
|
||||
func (f *Foo) Scan(state fmt.ScanState, verb rune) error {
|
||||
// too long; look here: https://github.com/jszwec/csvutil/blob/master/example_decoder_register_test.go#L19
|
||||
}
|
||||
|
||||
enc.Register(func(s fmt.Stringer) ([]byte, error) {
|
||||
return []byte(s.String()), nil
|
||||
})
|
||||
|
||||
dec.Register(func(data []byte, s fmt.Scanner) error {
|
||||
_, err := fmt.Sscan(string(data), s)
|
||||
return err
|
||||
})
|
||||
```
|
||||
|
||||
The order of precedence for both Encoder and Decoder is:
|
||||
1. type is registered
|
||||
2. type implements an interface that was registered
|
||||
3. csvutil.{Un,M}arshaler
|
||||
4. encoding.Text{Un,M}arshaler
|
||||
|
||||
For more examples look [here](https://pkg.go.dev/github.com/jszwec/csvutil?readme=expanded#pkg-examples)
|
||||
|
||||
### Custom time.Time format <a name="examples_time_format"></a>
|
||||
|
||||
Type [time.Time](https://golang.org/pkg/time/#Time) can be used as is in the struct fields by both Decoder and Encoder
|
||||
due to the fact that both have builtin support for [encoding.TextUnmarshaler](https://golang.org/pkg/encoding/#TextUnmarshaler) and [encoding.TextMarshaler](https://golang.org/pkg/encoding/#TextMarshaler). This means that by default
|
||||
Time has a specific format; look at [MarshalText](https://golang.org/pkg/time/#Time.MarshalText) and [UnmarshalText](https://golang.org/pkg/time/#Time.UnmarshalText). There are two ways to override it, which one you choose depends on your use case:
|
||||
|
||||
1. Via Register func (based on encoding/json)
|
||||
```go
|
||||
const format = "2006/01/02 15:04:05"
|
||||
|
||||
marshalTime := func(t time.Time) ([]byte, error) {
|
||||
return t.AppendFormat(nil, format), nil
|
||||
}
|
||||
|
||||
unmarshalTime := func(data []byte, t *time.Time) error {
|
||||
tt, err := time.Parse(format, string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = tt
|
||||
return nil
|
||||
}
|
||||
|
||||
enc := csvutil.NewEncoder(w)
|
||||
enc.Register(marshalTime)
|
||||
|
||||
dec, err := csvutil.NewDecoder(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.Register(unmarshalTime)
|
||||
```
|
||||
|
||||
2. With custom type:
|
||||
```go
|
||||
type Time struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
const format = "2006/01/02 15:04:05"
|
||||
|
||||
func (t Time) MarshalCSV() ([]byte, error) {
|
||||
var b [len(format)]byte
|
||||
return t.AppendFormat(b[:0], format), nil
|
||||
}
|
||||
|
||||
func (t *Time) UnmarshalCSV(data []byte) error {
|
||||
tt, err := time.Parse(format, string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = Time{Time: tt}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Custom struct tags <a name="examples_struct_tags"></a>
|
||||
|
||||
Like in other Go encoding packages struct field tags can be used to set
|
||||
custom names or options. By default encoders and decoders are looking at `csv` tag.
|
||||
However, this can be overriden by manually setting the Tag field.
|
||||
|
||||
```go
|
||||
type Foo struct {
|
||||
Bar int `custom:"bar"`
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
dec, err := csvutil.NewDecoder(r)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
dec.Tag = "custom"
|
||||
```
|
||||
|
||||
```go
|
||||
enc := csvutil.NewEncoder(w)
|
||||
enc.Tag = "custom"
|
||||
```
|
||||
|
||||
### Slice and Map fields <a name="examples_slice_and_map_field"></a>
|
||||
|
||||
There is no default encoding/decoding support for slice and map fields because there is no CSV spec for such values.
|
||||
In such case, it is recommended to create a custom type alias and implement Marshaler and Unmarshaler interfaces.
|
||||
Please note that slice and map aliases behave differently than aliases of other types - there is no need for type casting.
|
||||
|
||||
```go
|
||||
type Strings []string
|
||||
|
||||
func (s Strings) MarshalCSV() ([]byte, error) {
|
||||
return []byte(strings.Join(s, ",")), nil // strings.Join takes []string but it will also accept Strings
|
||||
}
|
||||
|
||||
type StringMap map[string]string
|
||||
|
||||
func (sm StringMap) MarshalCSV() ([]byte, error) {
|
||||
return []byte(fmt.Sprint(sm)), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
b, err := csvutil.Marshal([]struct {
|
||||
Strings Strings `csv:"strings"`
|
||||
Map StringMap `csv:"map"`
|
||||
}{
|
||||
{[]string{"a", "b"}, map[string]string{"a": "1"}}, // no type casting is required for slice and map aliases
|
||||
{Strings{"c", "d"}, StringMap{"b": "1"}},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", b)
|
||||
|
||||
// Output:
|
||||
// strings,map
|
||||
// "a,b",map[a:1]
|
||||
// "c,d",map[b:1]
|
||||
}
|
||||
```
|
||||
|
||||
### Nested/Embedded structs <a name="examples_nested_structs"></a>
|
||||
|
||||
Both Encoder and Decoder support nested or embedded structs.
|
||||
|
||||
Playground: https://play.golang.org/p/ZySjdVkovbf
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jszwec/csvutil"
|
||||
)
|
||||
|
||||
type Address struct {
|
||||
Street string `csv:"street"`
|
||||
City string `csv:"city"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `csv:"name"`
|
||||
Address
|
||||
}
|
||||
|
||||
func main() {
|
||||
users := []User{
|
||||
{
|
||||
Name: "John",
|
||||
Address: Address{
|
||||
Street: "Boylston",
|
||||
City: "Boston",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b, err := csvutil.Marshal(users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", b)
|
||||
|
||||
var out []User
|
||||
if err := csvutil.Unmarshal(b, &out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", out)
|
||||
|
||||
// Output:
|
||||
//
|
||||
// name,street,city
|
||||
// John,Boylston,Boston
|
||||
//
|
||||
// [{Name:John Address:{Street:Boylston City:Boston}}]
|
||||
}
|
||||
```
|
||||
|
||||
### Inline tag <a name="examples_inlined_structs"></a>
|
||||
|
||||
Fields with inline tag behave similarly to embedded struct fields. However,
|
||||
it gives a possibility to specify the prefix for all underlying fields. This
|
||||
can be useful when one structure can define multiple CSV columns because they
|
||||
are different from each other only by a certain prefix. Look at the example below.
|
||||
|
||||
Playground: https://play.golang.org/p/jyEzeskSnj7
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jszwec/csvutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type Address struct {
|
||||
Street string `csv:"street"`
|
||||
City string `csv:"city"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `csv:"name"`
|
||||
Address Address `csv:",inline"`
|
||||
HomeAddress Address `csv:"home_address_,inline"`
|
||||
WorkAddress Address `csv:"work_address_,inline"`
|
||||
Age int `csv:"age,omitempty"`
|
||||
}
|
||||
|
||||
users := []User{
|
||||
{
|
||||
Name: "John",
|
||||
Address: Address{"Washington", "Boston"},
|
||||
HomeAddress: Address{"Boylston", "Boston"},
|
||||
WorkAddress: Address{"River St", "Cambridge"},
|
||||
Age: 26,
|
||||
},
|
||||
}
|
||||
|
||||
b, err := csvutil.Marshal(users)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", b)
|
||||
|
||||
// Output:
|
||||
// name,street,city,home_address_street,home_address_city,work_address_street,work_address_city,age
|
||||
// John,Washington,Boston,Boylston,Boston,River St,Cambridge,26
|
||||
}
|
||||
```
|
||||
|
||||
Performance
|
||||
------------
|
||||
|
||||
csvutil provides the best encoding and decoding performance with small memory usage.
|
||||
|
||||
### Unmarshal <a name="performance_unmarshal"></a>
|
||||
|
||||
[benchmark code](https://gist.github.com/jszwec/e8515e741190454fa3494bcd3e1f100f)
|
||||
|
||||
#### csvutil:
|
||||
```
|
||||
BenchmarkUnmarshal/csvutil.Unmarshal/1_record-12 280696 4516 ns/op 7332 B/op 26 allocs/op
|
||||
BenchmarkUnmarshal/csvutil.Unmarshal/10_records-12 95750 11517 ns/op 8356 B/op 35 allocs/op
|
||||
BenchmarkUnmarshal/csvutil.Unmarshal/100_records-12 14997 83146 ns/op 18532 B/op 125 allocs/op
|
||||
BenchmarkUnmarshal/csvutil.Unmarshal/1000_records-12 1485 750143 ns/op 121094 B/op 1025 allocs/op
|
||||
BenchmarkUnmarshal/csvutil.Unmarshal/10000_records-12 154 7587205 ns/op 1136662 B/op 10025 allocs/op
|
||||
BenchmarkUnmarshal/csvutil.Unmarshal/100000_records-12 14 76126616 ns/op 11808744 B/op 100025 allocs/op
|
||||
```
|
||||
|
||||
#### gocsv:
|
||||
```
|
||||
BenchmarkUnmarshal/gocsv.Unmarshal/1_record-12 141330 7499 ns/op 7795 B/op 97 allocs/op
|
||||
BenchmarkUnmarshal/gocsv.Unmarshal/10_records-12 54252 21664 ns/op 13891 B/op 307 allocs/op
|
||||
BenchmarkUnmarshal/gocsv.Unmarshal/100_records-12 6920 159662 ns/op 72644 B/op 2380 allocs/op
|
||||
BenchmarkUnmarshal/gocsv.Unmarshal/1000_records-12 752 1556083 ns/op 650248 B/op 23083 allocs/op
|
||||
BenchmarkUnmarshal/gocsv.Unmarshal/10000_records-12 72 17086623 ns/op 7017469 B/op 230092 allocs/op
|
||||
BenchmarkUnmarshal/gocsv.Unmarshal/100000_records-12 7 163610749 ns/op 75004923 B/op 2300105 allocs/op
|
||||
```
|
||||
|
||||
#### easycsv:
|
||||
```
|
||||
BenchmarkUnmarshal/easycsv.ReadAll/1_record-12 101527 10662 ns/op 8855 B/op 81 allocs/op
|
||||
BenchmarkUnmarshal/easycsv.ReadAll/10_records-12 23325 51437 ns/op 24072 B/op 391 allocs/op
|
||||
BenchmarkUnmarshal/easycsv.ReadAll/100_records-12 2402 447296 ns/op 170538 B/op 3454 allocs/op
|
||||
BenchmarkUnmarshal/easycsv.ReadAll/1000_records-12 272 4370854 ns/op 1595683 B/op 34057 allocs/op
|
||||
BenchmarkUnmarshal/easycsv.ReadAll/10000_records-12 24 47502457 ns/op 18861808 B/op 340068 allocs/op
|
||||
BenchmarkUnmarshal/easycsv.ReadAll/100000_records-12 3 468974170 ns/op 189427066 B/op 3400082 allocs/op
|
||||
```
|
||||
|
||||
### Marshal <a name="performance_marshal"></a>
|
||||
|
||||
[benchmark code](https://gist.github.com/jszwec/31980321e1852ebb5615a44ccf374f17)
|
||||
|
||||
#### csvutil:
|
||||
```
|
||||
BenchmarkMarshal/csvutil.Marshal/1_record-12 279558 4390 ns/op 9952 B/op 12 allocs/op
|
||||
BenchmarkMarshal/csvutil.Marshal/10_records-12 82478 15608 ns/op 10800 B/op 21 allocs/op
|
||||
BenchmarkMarshal/csvutil.Marshal/100_records-12 10275 117288 ns/op 28208 B/op 112 allocs/op
|
||||
BenchmarkMarshal/csvutil.Marshal/1000_records-12 1075 1147473 ns/op 168508 B/op 1014 allocs/op
|
||||
BenchmarkMarshal/csvutil.Marshal/10000_records-12 100 11985382 ns/op 1525973 B/op 10017 allocs/op
|
||||
BenchmarkMarshal/csvutil.Marshal/100000_records-12 9 113640813 ns/op 22455873 B/op 100021 allocs/op
|
||||
```
|
||||
|
||||
#### gocsv:
|
||||
```
|
||||
BenchmarkMarshal/gocsv.Marshal/1_record-12 203052 6077 ns/op 5914 B/op 81 allocs/op
|
||||
BenchmarkMarshal/gocsv.Marshal/10_records-12 50132 24585 ns/op 9284 B/op 360 allocs/op
|
||||
BenchmarkMarshal/gocsv.Marshal/100_records-12 5480 212008 ns/op 51916 B/op 3151 allocs/op
|
||||
BenchmarkMarshal/gocsv.Marshal/1000_records-12 514 2053919 ns/op 444506 B/op 31053 allocs/op
|
||||
BenchmarkMarshal/gocsv.Marshal/10000_records-12 52 21066666 ns/op 4332377 B/op 310064 allocs/op
|
||||
BenchmarkMarshal/gocsv.Marshal/100000_records-12 5 207408929 ns/op 51169419 B/op 3100077 allocs/op
|
||||
```
|
||||
2
vendor/github.com/jszwec/csvutil/_config.yml
generated
vendored
Normal file
2
vendor/github.com/jszwec/csvutil/_config.yml
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
theme: jekyll-theme-cayman
|
||||
markdown: GFM
|
||||
178
vendor/github.com/jszwec/csvutil/cache.go
generated
vendored
Normal file
178
vendor/github.com/jszwec/csvutil/cache.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type field struct {
|
||||
name string
|
||||
baseType reflect.Type
|
||||
typ reflect.Type
|
||||
tag tag
|
||||
index []int
|
||||
}
|
||||
|
||||
type fields []field
|
||||
|
||||
func (fs fields) Len() int { return len(fs) }
|
||||
|
||||
func (fs fields) Swap(i, j int) { fs[i], fs[j] = fs[j], fs[i] }
|
||||
|
||||
func (fs fields) Less(i, j int) bool {
|
||||
for k, n := range fs[i].index {
|
||||
if n != fs[j].index[k] {
|
||||
return n < fs[j].index[k]
|
||||
}
|
||||
}
|
||||
return len(fs[i].index) < len(fs[j].index)
|
||||
}
|
||||
|
||||
type typeKey struct {
|
||||
tag string
|
||||
reflect.Type
|
||||
}
|
||||
|
||||
type fieldMap map[string]fields
|
||||
|
||||
func (m fieldMap) insert(f field) {
|
||||
fs, ok := m[f.name]
|
||||
if !ok {
|
||||
m[f.name] = append(fs, f)
|
||||
return
|
||||
}
|
||||
|
||||
// insert only fields with the shortest path.
|
||||
if len(fs[0].index) != len(f.index) {
|
||||
return
|
||||
}
|
||||
|
||||
// fields that are tagged have priority.
|
||||
if !f.tag.empty {
|
||||
m[f.name] = append([]field{f}, fs...)
|
||||
return
|
||||
}
|
||||
|
||||
m[f.name] = append(fs, f)
|
||||
}
|
||||
|
||||
func (m fieldMap) fields() fields {
|
||||
out := make(fields, 0, len(m))
|
||||
for _, v := range m {
|
||||
for i, f := range v {
|
||||
if f.tag.empty != v[0].tag.empty {
|
||||
v = v[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(v) > 1 {
|
||||
continue
|
||||
}
|
||||
out = append(out, v[0])
|
||||
}
|
||||
sort.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func buildFields(k typeKey) fields {
|
||||
type key struct {
|
||||
reflect.Type
|
||||
tag
|
||||
}
|
||||
|
||||
q := fields{{typ: k.Type}}
|
||||
visited := make(map[key]struct{})
|
||||
fm := make(fieldMap)
|
||||
|
||||
for len(q) > 0 {
|
||||
f := q[0]
|
||||
q = q[1:]
|
||||
|
||||
key := key{f.typ, f.tag}
|
||||
if _, ok := visited[key]; ok {
|
||||
continue
|
||||
}
|
||||
visited[key] = struct{}{}
|
||||
|
||||
depth := len(f.index)
|
||||
|
||||
numField := f.typ.NumField()
|
||||
for i := 0; i < numField; i++ {
|
||||
sf := f.typ.Field(i)
|
||||
|
||||
if sf.PkgPath != "" && !sf.Anonymous {
|
||||
// unexported field
|
||||
continue
|
||||
}
|
||||
|
||||
if sf.Anonymous {
|
||||
t := sf.Type
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if sf.PkgPath != "" && t.Kind() != reflect.Struct {
|
||||
// ignore embedded unexported non-struct fields.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
tag := parseTag(k.tag, sf)
|
||||
if tag.ignore {
|
||||
continue
|
||||
}
|
||||
if f.tag.prefix != "" {
|
||||
tag.prefix += f.tag.prefix
|
||||
}
|
||||
|
||||
ft := sf.Type
|
||||
if ft.Kind() == reflect.Ptr {
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
newf := field{
|
||||
name: tag.prefix + tag.name,
|
||||
baseType: sf.Type,
|
||||
typ: ft,
|
||||
tag: tag,
|
||||
index: makeIndex(f.index, i),
|
||||
}
|
||||
|
||||
if sf.Anonymous && ft.Kind() == reflect.Struct && tag.empty {
|
||||
q = append(q, newf)
|
||||
continue
|
||||
}
|
||||
|
||||
if tag.inline && ft.Kind() == reflect.Struct {
|
||||
q = append(q, newf)
|
||||
continue
|
||||
}
|
||||
|
||||
fm.insert(newf)
|
||||
|
||||
// look for duplicate nodes on the same level. Nodes won't be
|
||||
// revisited, so write all fields for the current type now.
|
||||
for _, v := range q {
|
||||
if len(v.index) != depth {
|
||||
break
|
||||
}
|
||||
if v.typ == f.typ && v.tag.prefix == tag.prefix {
|
||||
// other nodes can have different path.
|
||||
fm.insert(field{
|
||||
name: tag.prefix + tag.name,
|
||||
baseType: sf.Type,
|
||||
typ: ft,
|
||||
tag: tag,
|
||||
index: makeIndex(v.index, i),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fm.fields()
|
||||
}
|
||||
|
||||
func makeIndex(index []int, v int) []int {
|
||||
out := make([]int, len(index), len(index)+1)
|
||||
copy(out, index)
|
||||
return append(out, v)
|
||||
}
|
||||
30
vendor/github.com/jszwec/csvutil/cache_go17.go
generated
vendored
Normal file
30
vendor/github.com/jszwec/csvutil/cache_go17.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// +build !go1.9
|
||||
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var fieldCache = struct {
|
||||
mtx sync.RWMutex
|
||||
m map[typeKey][]field
|
||||
}{m: make(map[typeKey][]field)}
|
||||
|
||||
func cachedFields(k typeKey) fields {
|
||||
fieldCache.mtx.RLock()
|
||||
fields, ok := fieldCache.m[k]
|
||||
fieldCache.mtx.RUnlock()
|
||||
|
||||
if ok {
|
||||
return fields
|
||||
}
|
||||
|
||||
fields = buildFields(k)
|
||||
|
||||
fieldCache.mtx.Lock()
|
||||
fieldCache.m[k] = fields
|
||||
fieldCache.mtx.Unlock()
|
||||
|
||||
return fields
|
||||
}
|
||||
18
vendor/github.com/jszwec/csvutil/cache_go19.go
generated
vendored
Normal file
18
vendor/github.com/jszwec/csvutil/cache_go19.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// +build go1.9
|
||||
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var fieldCache sync.Map // map[typeKey][]field
|
||||
|
||||
func cachedFields(k typeKey) fields {
|
||||
if v, ok := fieldCache.Load(k); ok {
|
||||
return v.(fields)
|
||||
}
|
||||
|
||||
v, _ := fieldCache.LoadOrStore(k, buildFields(k))
|
||||
return v.(fields)
|
||||
}
|
||||
223
vendor/github.com/jszwec/csvutil/csvutil.go
generated
vendored
Normal file
223
vendor/github.com/jszwec/csvutil/csvutil.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const defaultTag = "csv"
|
||||
|
||||
var (
|
||||
_bytes = reflect.TypeOf(([]byte)(nil))
|
||||
_error = reflect.TypeOf((*error)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Unmarshal parses the CSV-encoded data and stores the result in the slice or
|
||||
// the array pointed to by v. If v is nil or not a pointer to a struct slice or
|
||||
// struct array, Unmarshal returns an InvalidUnmarshalError.
|
||||
//
|
||||
// Unmarshal uses the std encoding/csv.Reader for parsing and csvutil.Decoder
|
||||
// for populating the struct elements in the provided slice. For exact decoding
|
||||
// rules look at the Decoder's documentation.
|
||||
//
|
||||
// The first line in data is treated as a header. Decoder will use it to map
|
||||
// csv columns to struct's fields.
|
||||
//
|
||||
// In case of success the provided slice will be reinitialized and its content
|
||||
// fully replaced with decoded data.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
val := reflect.ValueOf(v)
|
||||
|
||||
if val.Kind() != reflect.Ptr || val.IsNil() {
|
||||
return &InvalidUnmarshalError{Type: reflect.TypeOf(v)}
|
||||
}
|
||||
|
||||
switch val.Type().Elem().Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
default:
|
||||
return &InvalidUnmarshalError{Type: val.Type()}
|
||||
}
|
||||
|
||||
typ := val.Type().Elem()
|
||||
|
||||
if walkType(typ.Elem()).Kind() != reflect.Struct {
|
||||
return &InvalidUnmarshalError{Type: val.Type()}
|
||||
}
|
||||
|
||||
dec, err := NewDecoder(newCSVReader(bytes.NewReader(data)))
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// for the array just call decodeArray directly; for slice values call the
|
||||
// optimized code for better performance.
|
||||
|
||||
if typ.Kind() == reflect.Array {
|
||||
return dec.decodeArray(val.Elem())
|
||||
}
|
||||
|
||||
c := countRecords(data)
|
||||
slice := reflect.MakeSlice(typ, c, c)
|
||||
|
||||
var i int
|
||||
for ; ; i++ {
|
||||
// just in case countRecords counts it wrong.
|
||||
if i >= c && i >= slice.Len() {
|
||||
slice = reflect.Append(slice, reflect.New(typ.Elem()).Elem())
|
||||
}
|
||||
|
||||
if err := dec.Decode(slice.Index(i).Addr().Interface()); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
val.Elem().Set(slice.Slice3(0, i, i))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal returns the CSV encoding of slice or array v. If v is not a slice or
|
||||
// elements are not structs then Marshal returns InvalidMarshalError.
|
||||
//
|
||||
// Marshal uses the std encoding/csv.Writer with its default settings for csv
|
||||
// encoding.
|
||||
//
|
||||
// Marshal will always encode the CSV header even for the empty slice.
|
||||
//
|
||||
// For the exact encoding rules look at Encoder.Encode method.
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
val := walkValue(reflect.ValueOf(v))
|
||||
|
||||
if !val.IsValid() {
|
||||
return nil, &InvalidMarshalError{}
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return nil, &InvalidMarshalError{Type: reflect.ValueOf(v).Type()}
|
||||
}
|
||||
|
||||
typ := walkType(val.Type().Elem())
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return nil, &InvalidMarshalError{Type: reflect.ValueOf(v).Type()}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := csv.NewWriter(&buf)
|
||||
enc := NewEncoder(w)
|
||||
|
||||
if err := enc.encodeHeader(typ); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := enc.encodeArray(val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
if err := w.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func countRecords(s []byte) (n int) {
|
||||
var prev byte
|
||||
inQuote := false
|
||||
for {
|
||||
if len(s) == 0 && prev != '"' {
|
||||
return n
|
||||
}
|
||||
|
||||
i := bytes.IndexAny(s, "\n\"")
|
||||
if i == -1 {
|
||||
return n + 1
|
||||
}
|
||||
|
||||
switch s[i] {
|
||||
case '\n':
|
||||
if !inQuote && (i > 0 || prev == '"') {
|
||||
n++
|
||||
}
|
||||
case '"':
|
||||
inQuote = !inQuote
|
||||
}
|
||||
|
||||
prev = s[i]
|
||||
s = s[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Header scans the provided struct type and generates a CSV header for it.
|
||||
//
|
||||
// Field names are written in the same order as struct fields are defined.
|
||||
// Embedded struct's fields are treated as if they were part of the outer struct.
|
||||
// Fields that are embedded types and that are tagged are treated like any
|
||||
// other field.
|
||||
//
|
||||
// Unexported fields and fields with tag "-" are ignored.
|
||||
//
|
||||
// Tagged fields have the priority over non tagged fields with the same name.
|
||||
//
|
||||
// Following the Go visibility rules if there are multiple fields with the same
|
||||
// name (tagged or not tagged) on the same level and choice between them is
|
||||
// ambiguous, then all these fields will be ignored.
|
||||
//
|
||||
// It is a good practice to call Header once for each type. The suitable place
|
||||
// for calling it is init function. Look at Decoder.DecodingDataWithNoHeader
|
||||
// example.
|
||||
//
|
||||
// If tag is left empty the default "csv" will be used.
|
||||
//
|
||||
// Header will return UnsupportedTypeError if the provided value is nil or is
|
||||
// not a struct.
|
||||
func Header(v interface{}, tag string) ([]string, error) {
|
||||
typ, err := valueType(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tag == "" {
|
||||
tag = defaultTag
|
||||
}
|
||||
|
||||
fields := cachedFields(typeKey{tag, typ})
|
||||
h := make([]string, len(fields))
|
||||
for i, f := range fields {
|
||||
h[i] = f.name
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func valueType(v interface{}) (reflect.Type, error) {
|
||||
val := reflect.ValueOf(v)
|
||||
if !val.IsValid() {
|
||||
return nil, &UnsupportedTypeError{}
|
||||
}
|
||||
|
||||
loop:
|
||||
for {
|
||||
switch val.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
el := val.Elem()
|
||||
if !el.IsValid() {
|
||||
break loop
|
||||
}
|
||||
val = el
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
typ := walkType(val.Type())
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return nil, &UnsupportedTypeError{Type: typ}
|
||||
}
|
||||
return typ, nil
|
||||
}
|
||||
12
vendor/github.com/jszwec/csvutil/csvutil_go17.go
generated
vendored
Normal file
12
vendor/github.com/jszwec/csvutil/csvutil_go17.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build !go1.9
|
||||
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
)
|
||||
|
||||
func newCSVReader(r io.Reader) *csv.Reader {
|
||||
return csv.NewReader(r)
|
||||
}
|
||||
14
vendor/github.com/jszwec/csvutil/csvutil_go19.go
generated
vendored
Normal file
14
vendor/github.com/jszwec/csvutil/csvutil_go19.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build go1.9
|
||||
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
)
|
||||
|
||||
func newCSVReader(r io.Reader) *csv.Reader {
|
||||
rr := csv.NewReader(r)
|
||||
rr.ReuseRecord = true
|
||||
return rr
|
||||
}
|
||||
247
vendor/github.com/jszwec/csvutil/decode.go
generated
vendored
Normal file
247
vendor/github.com/jszwec/csvutil/decode.go
generated
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
textUnmarshaler = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
csvUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
var intDecoders = map[int]decodeFunc{
|
||||
8: decodeIntN(8),
|
||||
16: decodeIntN(16),
|
||||
32: decodeIntN(32),
|
||||
64: decodeIntN(64),
|
||||
}
|
||||
|
||||
var uintDecoders = map[int]decodeFunc{
|
||||
8: decodeUintN(8),
|
||||
16: decodeUintN(16),
|
||||
32: decodeUintN(32),
|
||||
64: decodeUintN(64),
|
||||
}
|
||||
|
||||
var (
|
||||
decodeFloat32 = decodeFloatN(32)
|
||||
decodeFloat64 = decodeFloatN(64)
|
||||
)
|
||||
|
||||
type decodeFunc func(s string, v reflect.Value) error
|
||||
|
||||
func decodeFuncValue(f reflect.Value) decodeFunc {
|
||||
isIface := f.Type().In(1).Kind() == reflect.Interface
|
||||
|
||||
return func(s string, v reflect.Value) error {
|
||||
if isIface && v.Type().Kind() == reflect.Interface && v.IsNil() {
|
||||
return &UnmarshalTypeError{Value: s, Type: v.Type()}
|
||||
}
|
||||
|
||||
out := f.Call([]reflect.Value{
|
||||
reflect.ValueOf([]byte(s)),
|
||||
v,
|
||||
})
|
||||
err, _ := out[0].Interface().(error)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func decodeFuncValuePtr(f reflect.Value) decodeFunc {
|
||||
return func(s string, v reflect.Value) error {
|
||||
out := f.Call([]reflect.Value{
|
||||
reflect.ValueOf([]byte(s)),
|
||||
v.Addr(),
|
||||
})
|
||||
err, _ := out[0].Interface().(error)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func decodeString(s string, v reflect.Value) error {
|
||||
v.SetString(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeIntN(bits int) decodeFunc {
|
||||
return func(s string, v reflect.Value) error {
|
||||
n, err := strconv.ParseInt(s, 10, bits)
|
||||
if err != nil {
|
||||
return &UnmarshalTypeError{Value: s, Type: v.Type()}
|
||||
}
|
||||
v.SetInt(n)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func decodeUintN(bits int) decodeFunc {
|
||||
return func(s string, v reflect.Value) error {
|
||||
n, err := strconv.ParseUint(s, 10, bits)
|
||||
if err != nil {
|
||||
return &UnmarshalTypeError{Value: s, Type: v.Type()}
|
||||
}
|
||||
v.SetUint(n)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func decodeFloatN(bits int) decodeFunc {
|
||||
return func(s string, v reflect.Value) error {
|
||||
n, err := strconv.ParseFloat(s, bits)
|
||||
if err != nil {
|
||||
return &UnmarshalTypeError{Value: s, Type: v.Type()}
|
||||
}
|
||||
v.SetFloat(n)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func decodeBool(s string, v reflect.Value) error {
|
||||
b, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return &UnmarshalTypeError{Value: s, Type: v.Type()}
|
||||
}
|
||||
v.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodePtrTextUnmarshaler(s string, v reflect.Value) error {
|
||||
return decodeTextUnmarshaler(s, v.Addr())
|
||||
}
|
||||
|
||||
func decodeTextUnmarshaler(s string, v reflect.Value) error {
|
||||
return v.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(s))
|
||||
}
|
||||
|
||||
func decodePtrFieldUnmarshaler(s string, v reflect.Value) error {
|
||||
return decodeFieldUnmarshaler(s, v.Addr())
|
||||
}
|
||||
|
||||
func decodeFieldUnmarshaler(s string, v reflect.Value) error {
|
||||
return v.Interface().(Unmarshaler).UnmarshalCSV([]byte(s))
|
||||
}
|
||||
|
||||
func decodePtr(typ reflect.Type, funcMap map[reflect.Type]reflect.Value, ifaceFuncs []reflect.Value) (decodeFunc, error) {
|
||||
next, err := decodeFn(typ.Elem(), funcMap, ifaceFuncs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(s string, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
return next(s, v.Elem())
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeInterface(funcMap map[reflect.Type]reflect.Value, ifaceFuncs []reflect.Value) decodeFunc {
|
||||
return func(s string, v reflect.Value) error {
|
||||
if v.NumMethod() != 0 {
|
||||
return &UnmarshalTypeError{
|
||||
Value: s,
|
||||
Type: v.Type(),
|
||||
}
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.ValueOf(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
el := walkValue(v)
|
||||
if !el.CanSet() {
|
||||
if el.IsValid() {
|
||||
// we may get a value receiver unmarshalers or registered funcs
|
||||
// underneath the interface in which case we should call
|
||||
// Unmarshal/Registered func.
|
||||
typ := el.Type()
|
||||
if f, ok := funcMap[typ]; ok {
|
||||
return decodeFuncValue(f)(s, el)
|
||||
}
|
||||
for _, f := range ifaceFuncs {
|
||||
if typ.AssignableTo(f.Type().In(1)) {
|
||||
return decodeFuncValue(f)(s, el)
|
||||
}
|
||||
}
|
||||
if typ.Implements(csvUnmarshaler) {
|
||||
return decodeFieldUnmarshaler(s, el)
|
||||
}
|
||||
if typ.Implements(textUnmarshaler) {
|
||||
return decodeTextUnmarshaler(s, el)
|
||||
}
|
||||
}
|
||||
v.Set(reflect.ValueOf(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
fn, err := decodeFn(el.Type(), funcMap, ifaceFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(s, el)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeBytes(s string, v reflect.Value) error {
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetBytes(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeFn(typ reflect.Type, funcMap map[reflect.Type]reflect.Value, ifaceFuncs []reflect.Value) (decodeFunc, error) {
|
||||
if f, ok := funcMap[typ]; ok {
|
||||
return decodeFuncValue(f), nil
|
||||
}
|
||||
if f, ok := funcMap[reflect.PtrTo(typ)]; ok {
|
||||
return decodeFuncValuePtr(f), nil
|
||||
}
|
||||
|
||||
for _, f := range ifaceFuncs {
|
||||
argType := f.Type().In(1)
|
||||
if typ.AssignableTo(argType) {
|
||||
return decodeFuncValue(f), nil
|
||||
}
|
||||
if reflect.PtrTo(typ).AssignableTo(argType) {
|
||||
return decodeFuncValuePtr(f), nil
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.PtrTo(typ).Implements(csvUnmarshaler) {
|
||||
return decodePtrFieldUnmarshaler, nil
|
||||
}
|
||||
if reflect.PtrTo(typ).Implements(textUnmarshaler) {
|
||||
return decodePtrTextUnmarshaler, nil
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Ptr:
|
||||
return decodePtr(typ, funcMap, ifaceFuncs)
|
||||
case reflect.Interface:
|
||||
return decodeInterface(funcMap, ifaceFuncs), nil
|
||||
case reflect.String:
|
||||
return decodeString, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return intDecoders[typ.Bits()], nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return uintDecoders[typ.Bits()], nil
|
||||
case reflect.Float32:
|
||||
return decodeFloat32, nil
|
||||
case reflect.Float64:
|
||||
return decodeFloat64, nil
|
||||
case reflect.Bool:
|
||||
return decodeBool, nil
|
||||
case reflect.Slice:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
return decodeBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, &UnsupportedTypeError{Type: typ}
|
||||
}
|
||||
487
vendor/github.com/jszwec/csvutil/decoder.go
generated
vendored
Normal file
487
vendor/github.com/jszwec/csvutil/decoder.go
generated
vendored
Normal file
@@ -0,0 +1,487 @@
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type decField struct {
|
||||
columnIndex int
|
||||
field
|
||||
decodeFunc
|
||||
zero interface{}
|
||||
}
|
||||
|
||||
// A Decoder reads and decodes string records into structs.
|
||||
type Decoder struct {
|
||||
// Tag defines which key in the struct field's tag to scan for names and
|
||||
// options (Default: 'csv').
|
||||
Tag string
|
||||
|
||||
// If true, Decoder will return a MissingColumnsError if it discovers
|
||||
// that any of the columns are missing. This means that a CSV input
|
||||
// will be required to contain all columns that were defined in the
|
||||
// provided struct.
|
||||
DisallowMissingColumns bool
|
||||
|
||||
// If not nil, Map is a function that is called for each field in the csv
|
||||
// record before decoding the data. It allows mapping certain string values
|
||||
// for specific columns or types to a known format. Decoder calls Map with
|
||||
// the current column name (taken from header) and a zero non-pointer value
|
||||
// of a type to which it is going to decode data into. Implementations
|
||||
// should use type assertions to recognize the type.
|
||||
//
|
||||
// The good example of use case for Map is if NaN values are represented by
|
||||
// eg 'n/a' string, implementing a specific Map function for all floats
|
||||
// could map 'n/a' back into 'NaN' to allow successful decoding.
|
||||
//
|
||||
// Use Map with caution. If the requirements of column or type are not met
|
||||
// Map should return 'field', since it is the original value that was
|
||||
// read from the csv input, this would indicate no change.
|
||||
//
|
||||
// If struct field is an interface v will be of type string, unless the
|
||||
// struct field contains a settable pointer value - then v will be a zero
|
||||
// value of that type.
|
||||
//
|
||||
// Map must be set before the first call to Decode and not changed after it.
|
||||
Map func(field, col string, v interface{}) string
|
||||
|
||||
r Reader
|
||||
typeKey typeKey
|
||||
hmap map[string]int
|
||||
header []string
|
||||
record []string
|
||||
cache []decField
|
||||
unused []int
|
||||
funcMap map[reflect.Type]reflect.Value
|
||||
ifaceFuncs []reflect.Value
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// Decoder will match struct fields according to the given header.
|
||||
//
|
||||
// If header is empty NewDecoder will read one line and treat it as a header.
|
||||
//
|
||||
// Records coming from r must be of the same length as the header.
|
||||
//
|
||||
// NewDecoder may return io.EOF if there is no data in r and no header was
|
||||
// provided by the caller.
|
||||
func NewDecoder(r Reader, header ...string) (dec *Decoder, err error) {
|
||||
if len(header) == 0 {
|
||||
header, err = r.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
h := make([]string, len(header))
|
||||
copy(h, header)
|
||||
header = h
|
||||
|
||||
m := make(map[string]int, len(header))
|
||||
for i, h := range header {
|
||||
m[h] = i
|
||||
}
|
||||
|
||||
return &Decoder{
|
||||
r: r,
|
||||
header: header,
|
||||
hmap: m,
|
||||
unused: make([]int, 0, len(header)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Decode reads the next string record or records from its input and stores it
|
||||
// in the value pointed to by v which must be a pointer to a struct, struct slice
|
||||
// or struct array.
|
||||
//
|
||||
// Decode matches all exported struct fields based on the header. Struct fields
|
||||
// can be adjusted by using tags.
|
||||
//
|
||||
// The "omitempty" option specifies that the field should be omitted from
|
||||
// the decoding if record's field is an empty string.
|
||||
//
|
||||
// Examples of struct field tags and their meanings:
|
||||
// // Decode matches this field with "myName" header column.
|
||||
// Field int `csv:"myName"`
|
||||
//
|
||||
// // Decode matches this field with "Field" header column.
|
||||
// Field int
|
||||
//
|
||||
// // Decode matches this field with "myName" header column and decoding is not
|
||||
// // called if record's field is an empty string.
|
||||
// Field int `csv:"myName,omitempty"`
|
||||
//
|
||||
// // Decode matches this field with "Field" header column and decoding is not
|
||||
// // called if record's field is an empty string.
|
||||
// Field int `csv:",omitempty"`
|
||||
//
|
||||
// // Decode ignores this field.
|
||||
// Field int `csv:"-"`
|
||||
//
|
||||
// // Decode treats this field exactly as if it was an embedded field and
|
||||
// // matches header columns that start with "my_prefix_" to all fields of this
|
||||
// // type.
|
||||
// Field Struct `csv:"my_prefix_,inline"`
|
||||
//
|
||||
// // Decode treats this field exactly as if it was an embedded field.
|
||||
// Field Struct `csv:",inline"`
|
||||
//
|
||||
// By default decode looks for "csv" tag, but this can be changed by setting
|
||||
// Decoder.Tag field.
|
||||
//
|
||||
// To Decode into a custom type v must implement csvutil.Unmarshaler or
|
||||
// encoding.TextUnmarshaler.
|
||||
//
|
||||
// Anonymous struct fields with tags are treated like normal fields and they
|
||||
// must implement csvutil.Unmarshaler or encoding.TextUnmarshaler unless inline
|
||||
// tag is specified.
|
||||
//
|
||||
// Anonymous struct fields without tags are populated just as if they were
|
||||
// part of the main struct. However, fields in the main struct have bigger
|
||||
// priority and they are populated first. If main struct and anonymous struct
|
||||
// field have the same fields, the main struct's fields will be populated.
|
||||
//
|
||||
// Fields of type []byte expect the data to be base64 encoded strings.
|
||||
//
|
||||
// Float fields are decoded to NaN if a string value is 'NaN'. This check
|
||||
// is case insensitive.
|
||||
//
|
||||
// Interface fields are decoded to strings unless they contain settable pointer
|
||||
// value.
|
||||
//
|
||||
// Pointer fields are decoded to nil if a string value is empty.
|
||||
//
|
||||
// If v is a slice, Decode resets it and reads the input until EOF, storing all
|
||||
// decoded values in the given slice. Decode returns nil on EOF.
|
||||
//
|
||||
// If v is an array, Decode reads the input until EOF or until it decodes all
|
||||
// corresponding array elements. If the input contains less elements than the
|
||||
// array, the additional Go array elements are set to zero values. Decode
|
||||
// returns nil on EOF unless there were no records decoded.
|
||||
//
|
||||
// Fields with inline tags that have a non-empty prefix must not be cyclic
|
||||
// structures. Passing such values to Decode will result in an infinite loop.
|
||||
func (d *Decoder) Decode(v interface{}) (err error) {
|
||||
val := reflect.ValueOf(v)
|
||||
if val.Kind() != reflect.Ptr || val.IsNil() {
|
||||
return &InvalidDecodeError{Type: reflect.TypeOf(v)}
|
||||
}
|
||||
|
||||
elem := indirect(val.Elem())
|
||||
switch elem.Kind() {
|
||||
case reflect.Struct:
|
||||
return d.decodeStruct(elem)
|
||||
case reflect.Slice:
|
||||
return d.decodeSlice(elem)
|
||||
case reflect.Array:
|
||||
return d.decodeArray(elem)
|
||||
case reflect.Interface, reflect.Invalid:
|
||||
elem = walkValue(elem)
|
||||
if elem.Kind() != reflect.Invalid {
|
||||
return &InvalidDecodeError{Type: elem.Type()}
|
||||
}
|
||||
return &InvalidDecodeError{Type: val.Type()}
|
||||
default:
|
||||
return &InvalidDecodeError{Type: reflect.PtrTo(elem.Type())}
|
||||
}
|
||||
}
|
||||
|
||||
// Record returns the most recently read record. The slice is valid until the
|
||||
// next call to Decode.
|
||||
func (d *Decoder) Record() []string {
|
||||
return d.record
|
||||
}
|
||||
|
||||
// Header returns the first line that came from the reader, or returns the
|
||||
// defined header by the caller.
|
||||
func (d *Decoder) Header() []string {
|
||||
header := make([]string, len(d.header))
|
||||
copy(header, d.header)
|
||||
return header
|
||||
}
|
||||
|
||||
// Unused returns a list of column indexes that were not used during decoding
|
||||
// due to lack of matching struct field.
|
||||
func (d *Decoder) Unused() []int {
|
||||
if len(d.unused) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
indices := make([]int, len(d.unused))
|
||||
copy(indices, d.unused)
|
||||
return indices
|
||||
}
|
||||
|
||||
// Register registers a custom decoding function for a concrete type or interface.
|
||||
// The argument f must be of type:
|
||||
// func([]byte, T) error
|
||||
//
|
||||
// T must be a concrete type such as *time.Time, or interface that has at least one
|
||||
// method.
|
||||
//
|
||||
// During decoding, fields are matched by the concrete type first. If match is not
|
||||
// found then Decoder looks if field implements any of the registered interfaces
|
||||
// in order they were registered.
|
||||
//
|
||||
// Register panics if:
|
||||
// - f does not match the right signature
|
||||
// - f is an empty interface
|
||||
// - f was already registered
|
||||
//
|
||||
// Register is based on the encoding/json proposal:
|
||||
// https://github.com/golang/go/issues/5901.
|
||||
func (d *Decoder) Register(f interface{}) {
|
||||
v := reflect.ValueOf(f)
|
||||
typ := v.Type()
|
||||
|
||||
if typ.Kind() != reflect.Func ||
|
||||
typ.NumIn() != 2 || typ.NumOut() != 1 ||
|
||||
typ.In(0) != _bytes || typ.Out(0) != _error {
|
||||
panic("csvutil: func must be of type func([]byte, T) error")
|
||||
}
|
||||
|
||||
argType := typ.In(1)
|
||||
|
||||
if argType.Kind() == reflect.Interface && argType.NumMethod() == 0 {
|
||||
panic("csvutil: func argument type must not be an empty interface")
|
||||
}
|
||||
|
||||
if d.funcMap == nil {
|
||||
d.funcMap = make(map[reflect.Type]reflect.Value)
|
||||
}
|
||||
|
||||
if _, ok := d.funcMap[argType]; ok {
|
||||
panic("csvutil: func " + typ.String() + " already registered")
|
||||
}
|
||||
|
||||
d.funcMap[argType] = v
|
||||
|
||||
if argType.Kind() == reflect.Interface {
|
||||
d.ifaceFuncs = append(d.ifaceFuncs, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeSlice(slice reflect.Value) error {
|
||||
typ := slice.Type().Elem()
|
||||
if walkType(typ).Kind() != reflect.Struct {
|
||||
return &InvalidDecodeError{Type: reflect.PtrTo(slice.Type())}
|
||||
}
|
||||
|
||||
slice.SetLen(0)
|
||||
|
||||
var c int
|
||||
for ; ; c++ {
|
||||
v := reflect.New(typ)
|
||||
|
||||
err := d.decodeStruct(indirect(v))
|
||||
if err == io.EOF {
|
||||
if c == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// we want to ensure that we append this element to the slice even if it
|
||||
// was partially decoded due to error. This is how JSON pkg does it.
|
||||
slice.Set(reflect.Append(slice, v.Elem()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
slice.Set(slice.Slice3(0, c, c))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeArray(v reflect.Value) error {
|
||||
if walkType(v.Type().Elem()).Kind() != reflect.Struct {
|
||||
return &InvalidDecodeError{Type: reflect.PtrTo(v.Type())}
|
||||
}
|
||||
|
||||
l := v.Len()
|
||||
|
||||
var i int
|
||||
for ; i < l; i++ {
|
||||
if err := d.decodeStruct(indirect(v.Index(i))); err == io.EOF {
|
||||
if i == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
zero := reflect.Zero(v.Type().Elem())
|
||||
for i := i; i < l; i++ {
|
||||
v.Index(i).Set(zero)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeStruct(v reflect.Value) (err error) {
|
||||
d.record, err = d.r.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(d.record) != len(d.header) {
|
||||
return ErrFieldCount
|
||||
}
|
||||
|
||||
return d.unmarshal(d.record, v)
|
||||
}
|
||||
|
||||
func (d *Decoder) unmarshal(record []string, v reflect.Value) error {
|
||||
fields, err := d.fields(typeKey{d.tag(), v.Type()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fieldLoop:
|
||||
for _, f := range fields {
|
||||
isBlank := record[f.columnIndex] == ""
|
||||
if f.tag.omitEmpty && isBlank {
|
||||
continue
|
||||
}
|
||||
|
||||
fv := v
|
||||
for n, i := range f.index {
|
||||
fv = fv.Field(i)
|
||||
if fv.Kind() == reflect.Ptr {
|
||||
if fv.IsNil() {
|
||||
if isBlank && n == len(f.index)-1 { // ensure we are on the leaf.
|
||||
continue fieldLoop
|
||||
}
|
||||
// this can happen if a field is an unexported embedded
|
||||
// pointer type. In Go prior to 1.10 it was possible to
|
||||
// set such value because of a bug in the reflect package
|
||||
// https://github.com/golang/go/issues/21353
|
||||
if !fv.CanSet() {
|
||||
return errPtrUnexportedStruct(fv.Type())
|
||||
}
|
||||
fv.Set(reflect.New(fv.Type().Elem()))
|
||||
}
|
||||
|
||||
if isBlank && n == len(f.index)-1 { // ensure we are on the leaf.
|
||||
fv.Set(reflect.Zero(fv.Type()))
|
||||
continue fieldLoop
|
||||
}
|
||||
|
||||
if n != len(f.index)-1 {
|
||||
fv = fv.Elem() // walk pointer until we are on the the leaf.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s := record[f.columnIndex]
|
||||
if d.Map != nil && f.zero != nil {
|
||||
zero := f.zero
|
||||
if fv := walkPtr(fv); fv.Kind() == reflect.Interface && !fv.IsNil() {
|
||||
if v := walkValue(fv); v.CanSet() {
|
||||
zero = reflect.Zero(v.Type()).Interface()
|
||||
}
|
||||
}
|
||||
s = d.Map(s, d.header[f.columnIndex], zero)
|
||||
}
|
||||
|
||||
if err := f.decodeFunc(s, fv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) fields(k typeKey) ([]decField, error) {
|
||||
if k == d.typeKey {
|
||||
return d.cache, nil
|
||||
}
|
||||
|
||||
var (
|
||||
fields = cachedFields(k)
|
||||
decFields = make([]decField, 0, len(fields))
|
||||
used = make([]bool, len(d.header))
|
||||
missingCols []string
|
||||
)
|
||||
for _, f := range fields {
|
||||
i, ok := d.hmap[f.name]
|
||||
if !ok {
|
||||
if d.DisallowMissingColumns {
|
||||
missingCols = append(missingCols, f.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fn, err := decodeFn(f.baseType, d.funcMap, d.ifaceFuncs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
df := decField{
|
||||
columnIndex: i,
|
||||
field: f,
|
||||
decodeFunc: fn,
|
||||
}
|
||||
|
||||
if d.Map != nil {
|
||||
switch f.typ.Kind() {
|
||||
case reflect.Interface:
|
||||
df.zero = "" // interface values are decoded to strings
|
||||
default:
|
||||
df.zero = reflect.Zero(walkType(f.typ)).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
decFields = append(decFields, df)
|
||||
used[i] = true
|
||||
}
|
||||
|
||||
if len(missingCols) > 0 {
|
||||
return nil, &MissingColumnsError{
|
||||
Columns: missingCols,
|
||||
}
|
||||
}
|
||||
|
||||
d.unused = d.unused[:0]
|
||||
for i, b := range used {
|
||||
if !b {
|
||||
d.unused = append(d.unused, i)
|
||||
}
|
||||
}
|
||||
|
||||
d.cache, d.typeKey = decFields, k
|
||||
return d.cache, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) tag() string {
|
||||
if d.Tag == "" {
|
||||
return defaultTag
|
||||
}
|
||||
return d.Tag
|
||||
}
|
||||
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
for {
|
||||
switch v.Kind() {
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
return v
|
||||
}
|
||||
e := v.Elem()
|
||||
if e.Kind() == reflect.Ptr && !e.IsNil() {
|
||||
v = e
|
||||
continue
|
||||
}
|
||||
return v
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
v = v.Elem()
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
6
vendor/github.com/jszwec/csvutil/doc.go
generated
vendored
Normal file
6
vendor/github.com/jszwec/csvutil/doc.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// Package csvutil provides fast and idiomatic mapping between CSV and Go values.
|
||||
//
|
||||
// This package does not provide a CSV parser itself, it is based on the Reader and Writer
|
||||
// interfaces which are implemented by eg. std csv package. This gives a possibility
|
||||
// of choosing any other CSV writer or reader which may be more performant.
|
||||
package csvutil
|
||||
245
vendor/github.com/jszwec/csvutil/encode.go
generated
vendored
Normal file
245
vendor/github.com/jszwec/csvutil/encode.go
generated
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
textMarshaler = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
csvMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
var (
|
||||
encodeFloat32 = encodeFloatN(32)
|
||||
encodeFloat64 = encodeFloatN(64)
|
||||
)
|
||||
|
||||
type encodeFunc func(buf []byte, v reflect.Value, omitempty bool) ([]byte, error)
|
||||
|
||||
func encodeFuncValue(fn reflect.Value) encodeFunc {
|
||||
return func(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
out := fn.Call([]reflect.Value{v})
|
||||
err, _ := out[1].Interface().(error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, out[0].Bytes()...), nil
|
||||
}
|
||||
}
|
||||
|
||||
func encodeFuncValuePtr(fn reflect.Value) encodeFunc {
|
||||
return func(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
if !v.CanAddr() {
|
||||
fallback, err := encodeFn(v.Type(), false, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fallback(buf, v, omitempty)
|
||||
}
|
||||
|
||||
out := fn.Call([]reflect.Value{v.Addr()})
|
||||
err, _ := out[1].Interface().(error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, out[0].Bytes()...), nil
|
||||
}
|
||||
}
|
||||
|
||||
func encodeString(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
return append(buf, v.String()...), nil
|
||||
}
|
||||
|
||||
func encodeInt(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
n := v.Int()
|
||||
if n == 0 && omitempty {
|
||||
return buf, nil
|
||||
}
|
||||
return strconv.AppendInt(buf, n, 10), nil
|
||||
}
|
||||
|
||||
func encodeUint(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
n := v.Uint()
|
||||
if n == 0 && omitempty {
|
||||
return buf, nil
|
||||
}
|
||||
return strconv.AppendUint(buf, n, 10), nil
|
||||
}
|
||||
|
||||
func encodeFloatN(bits int) encodeFunc {
|
||||
return func(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
f := v.Float()
|
||||
if f == 0 && omitempty {
|
||||
return buf, nil
|
||||
}
|
||||
return strconv.AppendFloat(buf, f, 'G', -1, bits), nil
|
||||
}
|
||||
}
|
||||
|
||||
func encodeBool(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
t := v.Bool()
|
||||
if !t && omitempty {
|
||||
return buf, nil
|
||||
}
|
||||
return strconv.AppendBool(buf, t), nil
|
||||
}
|
||||
|
||||
func encodeInterface(funcMap map[reflect.Type]reflect.Value, funcs []reflect.Value) encodeFunc {
|
||||
return func(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
if !v.IsValid() || v.IsNil() || !v.Elem().IsValid() {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
v = v.Elem()
|
||||
canAddr := v.Kind() == reflect.Ptr
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
if v.IsNil() {
|
||||
return buf, nil
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
enc, err := encodeFn(v.Type(), canAddr, funcMap, funcs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enc(buf, v, omitempty)
|
||||
}
|
||||
}
|
||||
|
||||
func encodePtrMarshaler(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
if v.CanAddr() {
|
||||
return encodeMarshaler(buf, v.Addr(), omitempty)
|
||||
}
|
||||
|
||||
fallback, err := encodeFn(v.Type(), false, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fallback(buf, v, omitempty)
|
||||
}
|
||||
|
||||
func encodeTextMarshaler(buf []byte, v reflect.Value, _ bool) ([]byte, error) {
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
b, err := v.Interface().(encoding.TextMarshaler).MarshalText()
|
||||
if err != nil {
|
||||
return nil, &MarshalerError{Type: v.Type(), MarshalerType: "MarshalText", Err: err}
|
||||
}
|
||||
return append(buf, b...), nil
|
||||
}
|
||||
|
||||
func encodePtrTextMarshaler(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
if v.CanAddr() {
|
||||
return encodeTextMarshaler(buf, v.Addr(), omitempty)
|
||||
}
|
||||
|
||||
fallback, err := encodeFn(v.Type(), false, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fallback(buf, v, omitempty)
|
||||
}
|
||||
|
||||
func encodeMarshaler(buf []byte, v reflect.Value, _ bool) ([]byte, error) {
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
b, err := v.Interface().(Marshaler).MarshalCSV()
|
||||
if err != nil {
|
||||
return nil, &MarshalerError{Type: v.Type(), MarshalerType: "MarshalCSV", Err: err}
|
||||
}
|
||||
return append(buf, b...), nil
|
||||
}
|
||||
|
||||
func encodePtr(typ reflect.Type, canAddr bool, funcMap map[reflect.Type]reflect.Value, funcs []reflect.Value) (encodeFunc, error) {
|
||||
next, err := encodeFn(typ.Elem(), canAddr, funcMap, funcs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(buf []byte, v reflect.Value, omitempty bool) ([]byte, error) {
|
||||
if v.IsNil() {
|
||||
return buf, nil
|
||||
}
|
||||
return next(buf, v.Elem(), omitempty)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeBytes(buf []byte, v reflect.Value, _ bool) ([]byte, error) {
|
||||
data := v.Bytes()
|
||||
|
||||
l := len(buf)
|
||||
buf = append(buf, make([]byte, base64.StdEncoding.EncodedLen(len(data)))...)
|
||||
base64.StdEncoding.Encode(buf[l:], data)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func encodeFn(typ reflect.Type, canAddr bool, funcMap map[reflect.Type]reflect.Value, funcs []reflect.Value) (encodeFunc, error) {
|
||||
if v, ok := funcMap[typ]; ok {
|
||||
return encodeFuncValue(v), nil
|
||||
}
|
||||
|
||||
if v, ok := funcMap[reflect.PtrTo(typ)]; ok && canAddr {
|
||||
return encodeFuncValuePtr(v), nil
|
||||
}
|
||||
|
||||
for _, v := range funcs {
|
||||
argType := v.Type().In(0)
|
||||
if typ.AssignableTo(argType) {
|
||||
return encodeFuncValue(v), nil
|
||||
}
|
||||
|
||||
if canAddr && reflect.PtrTo(typ).AssignableTo(argType) {
|
||||
return encodeFuncValuePtr(v), nil
|
||||
}
|
||||
}
|
||||
|
||||
if typ.Implements(csvMarshaler) {
|
||||
return encodeMarshaler, nil
|
||||
}
|
||||
|
||||
if canAddr && reflect.PtrTo(typ).Implements(csvMarshaler) {
|
||||
return encodePtrMarshaler, nil
|
||||
}
|
||||
|
||||
if typ.Implements(textMarshaler) {
|
||||
return encodeTextMarshaler, nil
|
||||
}
|
||||
|
||||
if canAddr && reflect.PtrTo(typ).Implements(textMarshaler) {
|
||||
return encodePtrTextMarshaler, nil
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.String:
|
||||
return encodeString, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return encodeInt, nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return encodeUint, nil
|
||||
case reflect.Float32:
|
||||
return encodeFloat32, nil
|
||||
case reflect.Float64:
|
||||
return encodeFloat64, nil
|
||||
case reflect.Bool:
|
||||
return encodeBool, nil
|
||||
case reflect.Interface:
|
||||
return encodeInterface(funcMap, funcs), nil
|
||||
case reflect.Ptr:
|
||||
return encodePtr(typ, canAddr, funcMap, funcs)
|
||||
case reflect.Slice:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
return encodeBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, &UnsupportedTypeError{Type: typ}
|
||||
}
|
||||
356
vendor/github.com/jszwec/csvutil/encoder.go
generated
vendored
Normal file
356
vendor/github.com/jszwec/csvutil/encoder.go
generated
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const defaultBufSize = 4096
|
||||
|
||||
type encField struct {
|
||||
field
|
||||
encodeFunc
|
||||
}
|
||||
|
||||
type encCache struct {
|
||||
fields []encField
|
||||
buf []byte
|
||||
index []int
|
||||
record []string
|
||||
}
|
||||
|
||||
func newEncCache(k typeKey, funcMap map[reflect.Type]reflect.Value, funcs []reflect.Value) (_ *encCache, err error) {
|
||||
fields := cachedFields(k)
|
||||
encFields := make([]encField, len(fields))
|
||||
|
||||
for i, f := range fields {
|
||||
fn, err := encodeFn(f.baseType, true, funcMap, funcs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encFields[i] = encField{
|
||||
field: f,
|
||||
encodeFunc: fn,
|
||||
}
|
||||
}
|
||||
return &encCache{
|
||||
fields: encFields,
|
||||
buf: make([]byte, 0, defaultBufSize),
|
||||
index: make([]int, len(encFields)),
|
||||
record: make([]string, len(encFields)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Encoder writes structs CSV representations to the output stream.
|
||||
type Encoder struct {
|
||||
// Tag defines which key in the struct field's tag to scan for names and
|
||||
// options (Default: 'csv').
|
||||
Tag string
|
||||
|
||||
// If AutoHeader is true, a struct header is encoded during the first call
|
||||
// to Encode automatically (Default: true).
|
||||
AutoHeader bool
|
||||
|
||||
w Writer
|
||||
c *encCache
|
||||
noHeader bool
|
||||
typeKey typeKey
|
||||
funcMap map[reflect.Type]reflect.Value
|
||||
ifaceFuncs []reflect.Value
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: w,
|
||||
noHeader: true,
|
||||
AutoHeader: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Register registers a custom encoding function for a concrete type or interface.
|
||||
// The argument f must be of type:
|
||||
// func(T) ([]byte, error)
|
||||
//
|
||||
// T must be a concrete type such as Foo or *Foo, or interface that has at
|
||||
// least one method.
|
||||
//
|
||||
// During encoding, fields are matched by the concrete type first. If match is not
|
||||
// found then Encoder looks if field implements any of the registered interfaces
|
||||
// in order they were registered.
|
||||
//
|
||||
// Register panics if:
|
||||
// - f does not match the right signature
|
||||
// - f is an empty interface
|
||||
// - f was already registered
|
||||
//
|
||||
// Register is based on the encoding/json proposal:
|
||||
// https://github.com/golang/go/issues/5901.
|
||||
func (e *Encoder) Register(f interface{}) {
|
||||
v := reflect.ValueOf(f)
|
||||
typ := v.Type()
|
||||
|
||||
if typ.Kind() != reflect.Func ||
|
||||
typ.NumIn() != 1 || typ.NumOut() != 2 ||
|
||||
typ.Out(0) != _bytes || typ.Out(1) != _error {
|
||||
panic("csvutil: func must be of type func(T) ([]byte, error)")
|
||||
}
|
||||
|
||||
argType := typ.In(0)
|
||||
|
||||
if argType.Kind() == reflect.Interface && argType.NumMethod() == 0 {
|
||||
panic("csvutil: func argument type must not be an empty interface")
|
||||
}
|
||||
|
||||
if e.funcMap == nil {
|
||||
e.funcMap = make(map[reflect.Type]reflect.Value)
|
||||
}
|
||||
|
||||
if _, ok := e.funcMap[argType]; ok {
|
||||
panic("csvutil: func " + typ.String() + " already registered")
|
||||
}
|
||||
|
||||
e.funcMap[argType] = v
|
||||
|
||||
if argType.Kind() == reflect.Interface {
|
||||
e.ifaceFuncs = append(e.ifaceFuncs, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes the CSV encoding of v to the output stream. The provided
|
||||
// argument v must be a struct, struct slice or struct array.
|
||||
//
|
||||
// Only the exported fields will be encoded.
|
||||
//
|
||||
// First call to Encode will write a header unless EncodeHeader was called first
|
||||
// or AutoHeader is false. Header names can be customized by using tags
|
||||
// ('csv' by default), otherwise original Field names are used.
|
||||
//
|
||||
// Header and fields are written in the same order as struct fields are defined.
|
||||
// Embedded struct's fields are treated as if they were part of the outer struct.
|
||||
// Fields that are embedded types and that are tagged are treated like any
|
||||
// other field, but they have to implement Marshaler or encoding.TextMarshaler
|
||||
// interfaces.
|
||||
//
|
||||
// Marshaler interface has the priority over encoding.TextMarshaler.
|
||||
//
|
||||
// Tagged fields have the priority over non tagged fields with the same name.
|
||||
//
|
||||
// Following the Go visibility rules if there are multiple fields with the same
|
||||
// name (tagged or not tagged) on the same level and choice between them is
|
||||
// ambiguous, then all these fields will be ignored.
|
||||
//
|
||||
// Nil values will be encoded as empty strings. Same will happen if 'omitempty'
|
||||
// tag is set, and the value is a default value like 0, false or nil interface.
|
||||
//
|
||||
// Bool types are encoded as 'true' or 'false'.
|
||||
//
|
||||
// Float types are encoded using strconv.FormatFloat with precision -1 and 'G'
|
||||
// format. NaN values are encoded as 'NaN' string.
|
||||
//
|
||||
// Fields of type []byte are being encoded as base64-encoded strings.
|
||||
//
|
||||
// Fields can be excluded from encoding by using '-' tag option.
|
||||
//
|
||||
// Examples of struct tags:
|
||||
//
|
||||
// // Field appears as 'myName' header in CSV encoding.
|
||||
// Field int `csv:"myName"`
|
||||
//
|
||||
// // Field appears as 'Field' header in CSV encoding.
|
||||
// Field int
|
||||
//
|
||||
// // Field appears as 'myName' header in CSV encoding and is an empty string
|
||||
// // if Field is 0.
|
||||
// Field int `csv:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears as 'Field' header in CSV encoding and is an empty string
|
||||
// // if Field is 0.
|
||||
// Field int `csv:",omitempty"`
|
||||
//
|
||||
// // Encode ignores this field.
|
||||
// Field int `csv:"-"`
|
||||
//
|
||||
// // Encode treats this field exactly as if it was an embedded field and adds
|
||||
// // "my_prefix_" to each field's name.
|
||||
// Field Struct `csv:"my_prefix_,inline"`
|
||||
//
|
||||
// // Encode treats this field exactly as if it was an embedded field.
|
||||
// Field Struct `csv:",inline"`
|
||||
//
|
||||
// Fields with inline tags that have a non-empty prefix must not be cyclic
|
||||
// structures. Passing such values to Encode will result in an infinite loop.
|
||||
//
|
||||
// Encode doesn't flush data. The caller is responsible for calling Flush() if
|
||||
// the used Writer supports it.
|
||||
func (e *Encoder) Encode(v interface{}) error {
|
||||
return e.encode(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// EncodeHeader writes the CSV header of the provided struct value to the output
|
||||
// stream. The provided argument v must be a struct value.
|
||||
//
|
||||
// The first Encode method call will not write header if EncodeHeader was called
|
||||
// before it. This method can be called in cases when a data set could be
|
||||
// empty, but header is desired.
|
||||
//
|
||||
// EncodeHeader is like Header function, but it works with the Encoder and writes
|
||||
// directly to the output stream. Look at Header documentation for the exact
|
||||
// header encoding rules.
|
||||
func (e *Encoder) EncodeHeader(v interface{}) error {
|
||||
typ, err := valueType(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.encodeHeader(typ)
|
||||
}
|
||||
|
||||
func (e *Encoder) encode(v reflect.Value) error {
|
||||
val := walkValue(v)
|
||||
|
||||
if !val.IsValid() {
|
||||
return &InvalidEncodeError{}
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Struct:
|
||||
return e.encodeStruct(val)
|
||||
case reflect.Array, reflect.Slice:
|
||||
if walkType(val.Type().Elem()).Kind() != reflect.Struct {
|
||||
return &InvalidEncodeError{v.Type()}
|
||||
}
|
||||
return e.encodeArray(val)
|
||||
default:
|
||||
return &InvalidEncodeError{v.Type()}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeStruct(v reflect.Value) error {
|
||||
if e.AutoHeader && e.noHeader {
|
||||
if err := e.encodeHeader(v.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return e.marshal(v)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeArray(v reflect.Value) error {
|
||||
l := v.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
if err := e.encodeStruct(walkValue(v.Index(i))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeHeader(typ reflect.Type) error {
|
||||
fields, _, _, record, err := e.cache(typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, f := range fields {
|
||||
record[i] = f.name
|
||||
}
|
||||
|
||||
if err := e.w.Write(record); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.noHeader = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) marshal(v reflect.Value) error {
|
||||
fields, buf, index, record, err := e.cache(v.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, f := range fields {
|
||||
v := walkIndex(v, f.index)
|
||||
|
||||
omitempty := f.tag.omitEmpty
|
||||
if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||
// We should disable omitempty for pointer and interface values,
|
||||
// because if it's nil we will automatically encode it as an empty
|
||||
// string. However, the initialized pointer should not be affected,
|
||||
// even if it's a default value.
|
||||
omitempty = false
|
||||
}
|
||||
|
||||
if !v.IsValid() {
|
||||
index[i] = 0
|
||||
continue
|
||||
}
|
||||
|
||||
b, err := f.encodeFunc(buf, v, omitempty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index[i], buf = len(b)-len(buf), b
|
||||
}
|
||||
|
||||
out := string(buf)
|
||||
for i, n := range index {
|
||||
record[i], out = out[:n], out[n:]
|
||||
}
|
||||
e.c.buf = buf[:0]
|
||||
|
||||
return e.w.Write(record)
|
||||
}
|
||||
|
||||
func (e *Encoder) tag() string {
|
||||
if e.Tag == "" {
|
||||
return defaultTag
|
||||
}
|
||||
return e.Tag
|
||||
}
|
||||
|
||||
func (e *Encoder) cache(typ reflect.Type) ([]encField, []byte, []int, []string, error) {
|
||||
if k := (typeKey{e.tag(), typ}); k != e.typeKey {
|
||||
c, err := newEncCache(k, e.funcMap, e.ifaceFuncs)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
e.c, e.typeKey = c, k
|
||||
}
|
||||
return e.c.fields, e.c.buf[:0], e.c.index, e.c.record, nil
|
||||
}
|
||||
|
||||
func walkIndex(v reflect.Value, index []int) reflect.Value {
|
||||
for _, i := range index {
|
||||
v = walkPtr(v)
|
||||
if !v.IsValid() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
v = v.Field(i)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func walkPtr(v reflect.Value) reflect.Value {
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func walkValue(v reflect.Value) reflect.Value {
|
||||
for {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
v = v.Elem()
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkType(typ reflect.Type) reflect.Type {
|
||||
for typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
156
vendor/github.com/jszwec/csvutil/error.go
generated
vendored
Normal file
156
vendor/github.com/jszwec/csvutil/error.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ErrFieldCount is returned when header's length doesn't match the length of
|
||||
// the read record.
|
||||
var ErrFieldCount = errors.New("wrong number of fields in record")
|
||||
|
||||
// An UnmarshalTypeError describes a string value that was not appropriate for
|
||||
// a value of a specific Go type.
|
||||
type UnmarshalTypeError struct {
|
||||
Value string // string value
|
||||
Type reflect.Type // type of Go value it could not be assigned to
|
||||
}
|
||||
|
||||
func (e *UnmarshalTypeError) Error() string {
|
||||
return "csvutil: cannot unmarshal " + strconv.Quote(e.Value) + " into Go value of type " + e.Type.String()
|
||||
}
|
||||
|
||||
// An UnsupportedTypeError is returned when attempting to encode or decode
|
||||
// a value of an unsupported type.
|
||||
type UnsupportedTypeError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *UnsupportedTypeError) Error() string {
|
||||
if e.Type == nil {
|
||||
return "csvutil: unsupported type: nil"
|
||||
}
|
||||
return "csvutil: unsupported type: " + e.Type.String()
|
||||
}
|
||||
|
||||
// An InvalidDecodeError describes an invalid argument passed to Decode.
|
||||
// (The argument to Decode must be a non-nil struct pointer)
|
||||
type InvalidDecodeError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *InvalidDecodeError) Error() string {
|
||||
if e.Type == nil {
|
||||
return "csvutil: Decode(nil)"
|
||||
}
|
||||
|
||||
if e.Type.Kind() != reflect.Ptr {
|
||||
return "csvutil: Decode(non-pointer " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
typ := walkType(e.Type)
|
||||
switch typ.Kind() {
|
||||
case reflect.Struct:
|
||||
case reflect.Slice, reflect.Array:
|
||||
if typ.Elem().Kind() != reflect.Struct {
|
||||
return "csvutil: Decode(invalid type " + e.Type.String() + ")"
|
||||
}
|
||||
default:
|
||||
return "csvutil: Decode(invalid type " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
return "csvutil: Decode(nil " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
|
||||
// (The argument to Unmarshal must be a non-nil slice of structs pointer)
|
||||
type InvalidUnmarshalError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *InvalidUnmarshalError) Error() string {
|
||||
if e.Type == nil {
|
||||
return "csvutil: Unmarshal(nil)"
|
||||
}
|
||||
|
||||
if e.Type.Kind() != reflect.Ptr {
|
||||
return "csvutil: Unmarshal(non-pointer " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
return "csvutil: Unmarshal(invalid type " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
// InvalidEncodeError is returned by Encode when the provided value was invalid.
|
||||
type InvalidEncodeError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *InvalidEncodeError) Error() string {
|
||||
if e.Type == nil {
|
||||
return "csvutil: Encode(nil)"
|
||||
}
|
||||
return "csvutil: Encode(" + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
// InvalidMarshalError is returned by Marshal when the provided value was invalid.
|
||||
type InvalidMarshalError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *InvalidMarshalError) Error() string {
|
||||
if e.Type == nil {
|
||||
return "csvutil: Marshal(nil)"
|
||||
}
|
||||
|
||||
if walkType(e.Type).Kind() == reflect.Slice {
|
||||
return "csvutil: Marshal(non struct slice " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
if walkType(e.Type).Kind() == reflect.Array {
|
||||
return "csvutil: Marshal(non struct array " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
return "csvutil: Marshal(invalid type " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
// MarshalerError is returned by Encoder when MarshalCSV or MarshalText returned
|
||||
// an error.
|
||||
type MarshalerError struct {
|
||||
Type reflect.Type
|
||||
MarshalerType string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *MarshalerError) Error() string {
|
||||
return "csvutil: error calling " + e.MarshalerType + " for type " + e.Type.String() + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// Unwrap implements Unwrap interface for errors package in Go1.13+.
|
||||
func (e *MarshalerError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func errPtrUnexportedStruct(typ reflect.Type) error {
|
||||
return fmt.Errorf("csvutil: cannot decode into a pointer to unexported struct: %s", typ)
|
||||
}
|
||||
|
||||
// MissingColumnsError is returned by Decoder only when DisallowMissingColumns
|
||||
// option was set to true. It contains a list of all missing columns.
|
||||
type MissingColumnsError struct {
|
||||
Columns []string
|
||||
}
|
||||
|
||||
func (e *MissingColumnsError) Error() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("csvutil: missing columns: ")
|
||||
for i, c := range e.Columns {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&b, "%q", c)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
3
vendor/github.com/jszwec/csvutil/go.mod
generated
vendored
Normal file
3
vendor/github.com/jszwec/csvutil/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/jszwec/csvutil
|
||||
|
||||
go 1.13
|
||||
29
vendor/github.com/jszwec/csvutil/interface.go
generated
vendored
Normal file
29
vendor/github.com/jszwec/csvutil/interface.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package csvutil
|
||||
|
||||
// Reader provides the interface for reading a single CSV record.
|
||||
//
|
||||
// If there is no data left to be read, Read returns (nil, io.EOF).
|
||||
//
|
||||
// It is implemented by csv.Reader.
|
||||
type Reader interface {
|
||||
Read() ([]string, error)
|
||||
}
|
||||
|
||||
// Writer provides the interface for writing a single CSV record.
|
||||
//
|
||||
// It is implemented by csv.Writer.
|
||||
type Writer interface {
|
||||
Write([]string) error
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface implemented by types that can unmarshal
|
||||
// a single record's field description of themselves.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalCSV([]byte) error
|
||||
}
|
||||
|
||||
// Marshaler is the interface implemented by types that can marshal themselves
|
||||
// into valid string.
|
||||
type Marshaler interface {
|
||||
MarshalCSV() ([]byte, error)
|
||||
}
|
||||
47
vendor/github.com/jszwec/csvutil/tag.go
generated
vendored
Normal file
47
vendor/github.com/jszwec/csvutil/tag.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package csvutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type tag struct {
|
||||
name string
|
||||
prefix string
|
||||
empty bool
|
||||
omitEmpty bool
|
||||
ignore bool
|
||||
inline bool
|
||||
}
|
||||
|
||||
func parseTag(tagname string, field reflect.StructField) (t tag) {
|
||||
tags := strings.Split(field.Tag.Get(tagname), ",")
|
||||
if len(tags) == 1 && tags[0] == "" {
|
||||
t.name = field.Name
|
||||
t.empty = true
|
||||
return
|
||||
}
|
||||
|
||||
switch tags[0] {
|
||||
case "-":
|
||||
t.ignore = true
|
||||
return
|
||||
case "":
|
||||
t.name = field.Name
|
||||
default:
|
||||
t.name = tags[0]
|
||||
}
|
||||
|
||||
for _, tagOpt := range tags[1:] {
|
||||
switch tagOpt {
|
||||
case "omitempty":
|
||||
t.omitEmpty = true
|
||||
case "inline":
|
||||
if walkType(field.Type).Kind() == reflect.Struct {
|
||||
t.inline = true
|
||||
t.prefix = tags[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user