From a5ece792f8721a84c39dbdc44e4f88af58d9e76e Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 2 Oct 2026 15:53:20 +0200 Subject: [PATCH] Upstream gocarina/gocsv state of the touched files before c31659ffab --- README.md | 213 +++++++++++++++++++++++++++++++++++++ examples/full/main.go | 132 +++++++++++++++++++++++ examples/full/main_test.go | 18 ++++ 3 files changed, 363 insertions(+) create mode 100644 README.md create mode 100644 examples/full/main.go create mode 100644 examples/full/main_test.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..2efa117 --- /dev/null +++ b/README.md @@ -0,0 +1,213 @@ +Go CSV +===== + +The GoCSV package aims to provide easy serialization and deserialization functions to use CSV in Golang + +API and techniques inspired from https://godoc.org/gopkg.in/mgo.v2 + +[![GoDoc](https://godoc.org/github.com/gocarina/gocsv?status.png)](https://godoc.org/github.com/gocarina/gocsv) +[![Build Status](https://travis-ci.org/gocarina/gocsv.svg?branch=master)](https://travis-ci.org/gocarina/gocsv) + +Installation +===== + +```go get -u github.com/gocarina/gocsv``` + +Full example +===== + +Consider the following CSV file + +```csv + +client_id,client_name,client_age +1,Jose,42 +2,Daniel,26 +3,Vincent,32 + +``` + +Easy binding in Go! +--- + +```go + +package main + +import ( + "fmt" + "os" + + "github.com/gocarina/gocsv" +) + +type NotUsed struct { + Name string +} + +type Client struct { // Our example struct, you can use "-" to ignore a field + Id string `csv:"client_id"` + Name string `csv:"client_name"` + Age string `csv:"client_age"` + NotUsedString string `csv:"-"` + NotUsedStruct NotUsed `csv:"-"` +} + +func main() { + clientsFile, err := os.OpenFile("clients.csv", os.O_RDWR|os.O_CREATE, os.ModePerm) + if err != nil { + panic(err) + } + defer clientsFile.Close() + + clients := []*Client{} + + if err := gocsv.UnmarshalFile(clientsFile, &clients); err != nil { // Load clients from file + panic(err) + } + for _, client := range clients { + fmt.Println("Hello", client.Name) + } + + if _, err := clientsFile.Seek(0, 0); err != nil { // Go to the start of the file + panic(err) + } + + clients = append(clients, &Client{Id: "12", Name: "John", Age: "21"}) // Add clients + clients = append(clients, &Client{Id: "13", Name: "Fred"}) + clients = append(clients, &Client{Id: "14", Name: "James", Age: "32"}) + clients = append(clients, &Client{Id: "15", Name: "Danny"}) + csvContent, err := gocsv.MarshalString(&clients) // Get all clients as CSV string + //err = gocsv.MarshalFile(&clients, clientsFile) // Use this to save the CSV back to the file + if err != nil { + panic(err) + } + fmt.Println(csvContent) // Display all clients as CSV string + +} + +``` + +Customizable Converters +--- + +```go + +type DateTime struct { + time.Time +} + +// Convert the internal date as CSV string +func (date *DateTime) MarshalCSV() (string, error) { + return date.Time.Format("20060201"), nil +} + +// You could also use the standard Stringer interface +func (date *DateTime) String() (string) { + return date.String() // Redundant, just for example +} + +// Convert the CSV string as internal date +func (date *DateTime) UnmarshalCSV(csv string) (err error) { + date.Time, err = time.Parse("20060201", csv) + return err +} + +type Client struct { // Our example struct with a custom type (DateTime) + Id string `csv:"id"` + Name string `csv:"name"` + Employed DateTime `csv:"employed"` +} + +``` + +Nested structs +--- + +When decoding nested pointer structs, GoCSV allocates the pointers needed to +reach each field. An `omitempty` field still omits an empty leaf pointer value; +it does not prevent allocation of the enclosing structs. + +By default, the fields of nested structs are prefixed with the parent field's +name. For example: + +```go +type Foo struct { + B Bar + X int `csv:"x"` +} + +type Bar struct { + A int `csv:"a"` + B int `csv:"b"` +} +``` + +produces columns `B.a, B.b, x`. + +Use the `csv:"."` tag to inline the nested struct's fields without that prefix; for example: + +```go +type Foo struct { + B Bar `csv:"."` + X int `csv:"x"` +} + +type Bar struct { + A int `csv:"a"` + B int `csv:"b"` +} +``` + +produces columns `a, b, x`. + +Customizable CSV Reader / Writer +--- + +```go + +func main() { + ... + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.Comma = '|' + return r // Allows use pipe as delimiter + }) + + ... + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.LazyQuotes = true + r.Comma = '.' + return r // Allows use dot as delimiter and use quotes in CSV + }) + + ... + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + //return csv.NewReader(in) + return gocsv.LazyCSVReader(in) // Allows use of quotes in CSV + }) + + ... + + gocsv.UnmarshalFile(file, &clients) + + ... + + gocsv.SetCSVWriter(func(out io.Writer) *gocsv.SafeCSVWriter { + writer := csv.NewWriter(out) + writer.Comma = '|' + return gocsv.NewSafeCSVWriter(writer) + }) + + ... + + gocsv.MarshalFile(&clients, file) + + ... +} + +``` diff --git a/examples/full/main.go b/examples/full/main.go new file mode 100644 index 0000000..e9f1323 --- /dev/null +++ b/examples/full/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "encoding/csv" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/gocarina/gocsv" +) + +type NotUsed struct { + Name string +} + +type Client struct { // Our example struct, you can use "-" to ignore a field + ID string `csv:"client_id"` + Name string `csv:"client_name"` + Age string `csv:"client_age"` + NotUsedString string `csv:"-"` + NotUsedStruct NotUsed `csv:"-"` + Address1 Address `csv:"addr1"` + Address2 Address //`csv:"addr2"` will use Address2 in header + Employed DateTime `csv:"employed"` +} + +type Address struct { + Street string `csv:"street"` + City string `csv:"city"` +} + +var _ gocsv.TypeMarshaller = new(DateTime) +var _ gocsv.TypeUnmarshaller = new(DateTime) + +type DateTime struct { + time.Time +} + +// Convert the internal date as CSV string +func (date *DateTime) MarshalCSV() (string, error) { + return date.String(), nil +} + +// You could also use the standard Stringer interface +func (date DateTime) String() string { + return date.Time.Format("20060201") +} + +// Convert the CSV string as internal date +func (date *DateTime) UnmarshalCSV(csv string) (err error) { + date.Time, err = time.Parse("20060201", csv) + return err +} + +func main() { + // set the pipe as the delimiter for writing + gocsv.TagSeparator = "|" + // set the pipe as the delimiter for reading + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.Comma = '|' + return r + }) + + // Create an empty clients file + clientsFile, err := os.OpenFile("clients.csv", os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) + if err != nil { + panic(err) + } + defer clientsFile.Close() + + // Create clients + clients := []*Client{ + {ID: "12", Name: "John", Age: "21", + Address1: Address{"Street 1", "City1"}, + Address2: Address{"Street 2", "City2"}, + Employed: DateTime{time.Date(2022, 11, 04, 12, 0, 0, 0, time.UTC)}, + }, + {ID: "13", Name: "Fred", + Address1: Address{`Main "Street" 1`, "City1"}, // show quotes in value + Address2: Address{"Main Street 2", "City2"}, + Employed: DateTime{time.Date(2022, 11, 04, 13, 0, 0, 0, time.UTC)}, + }, + {ID: "14", Name: "James", Age: "32", + Address1: Address{"Center Street 1", "City1"}, + Address2: Address{"Center Street 2", "City2"}, + Employed: DateTime{time.Date(2022, 11, 04, 14, 0, 0, 0, time.UTC)}, + }, + {ID: "15", Name: "Danny", + Address1: Address{"State Street 1", "City1"}, + Address2: Address{"State Street 2", "City2"}, + Employed: DateTime{time.Date(2022, 11, 04, 15, 0, 0, 0, time.UTC)}, + }, + } + // Save clients to csv file + if err = gocsv.MarshalFile(&clients, clientsFile); err != nil { + panic(err) + } + + // Reset the file reader + if _, err := clientsFile.Seek(0, io.SeekStart); err != nil { + panic(err) + } + // Read file and print to console + b, err := ioutil.ReadAll(clientsFile) + if err != nil { + panic(err) + } + fmt.Println("clients.csv:") + fmt.Println(string(b)) + + // Reset the file reader + if _, err := clientsFile.Seek(0, io.SeekStart); err != nil { + panic(err) + } + // Load clients from file + var newClients []*Client + if err := gocsv.UnmarshalFile(clientsFile, &newClients); err != nil { + panic(err) + } + fmt.Println("clients:") + for _, c := range newClients { + fmt.Printf("%s:%s Adress1:%s, %s Address2:%s, %s Employed: %s\n", + c.ID, c.Name, + c.Address1.Street, c.Address1.City, + c.Address2.Street, c.Address2.City, + c.Employed, + ) + } +} diff --git a/examples/full/main_test.go b/examples/full/main_test.go new file mode 100644 index 0000000..d627e8b --- /dev/null +++ b/examples/full/main_test.go @@ -0,0 +1,18 @@ +package main + +func Example() { + main() + // Output: + // clients.csv: + // client_id|client_name|client_age|addr1.street|addr1.city|Address2.street|Address2.city|employed + // 12|John|21|Street 1|City1|Street 2|City2|20220411 + // 13|Fred||"Main ""Street"" 1"|City1|Main Street 2|City2|20220411 + // 14|James|32|Center Street 1|City1|Center Street 2|City2|20220411 + // 15|Danny||State Street 1|City1|State Street 2|City2|20220411 + // + // clients: + // 12:John Adress1:Street 1, City1 Address2:Street 2, City2 Employed: 20220411 + // 13:Fred Adress1:Main "Street" 1, City1 Address2:Main Street 2, City2 Employed: 20220411 + // 14:James Adress1:Center Street 1, City1 Address2:Center Street 2, City2 Employed: 20220411 + // 15:Danny Adress1:State Street 1, City1 Address2:State Street 2, City2 Employed: 20220411 +}