-
Notifications
You must be signed in to change notification settings - Fork 252
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 99decde
Showing
6 changed files
with
699 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2014 Jonathan Picques | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
Go CSV | ||
===== | ||
|
||
The GoCSV package aims to provide easy serialization and unserialization functions to use CSV in Go from ```os.File, string or []byte``` | ||
|
||
API and techniques inspired from http://labix.org/mgo | ||
|
||
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" | ||
"gocsv" | ||
"os" | ||
) | ||
|
||
type Client struct { // Our example struct, you can use "-" to ignore a field | ||
Id string `csv:id` | ||
Name string `csv:name` | ||
Age string `csv:age` | ||
NotUsed string `csv:-` | ||
} | ||
|
||
func main() { | ||
file, error := os.OpenFile("clients.csv", os.O_RDWR|os.O_CREATE, os.ModePerm) | ||
if error != nil { | ||
panic(error) | ||
} | ||
defer file.Close() | ||
clients := []Client{} | ||
|
||
if err := gocsv.UnmarshalFile(file, &clients); err != nil { // Load the first clients from file | ||
panic(err) | ||
} | ||
|
||
for _, client := range clients { | ||
fmt.Println("Hello", client.Name) | ||
} | ||
|
||
if _, err := file.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"}) | ||
|
||
if err := gocsv.MarshalFile(&clients, file); err != nil { // Save all clients back to the CSV file | ||
panic(err) | ||
} | ||
|
||
} | ||
|
||
``` | ||
|
||
Customizable CSV Reader / Writer | ||
--- | ||
|
||
```go | ||
|
||
func main() { | ||
... | ||
|
||
gocsv.SetCSVReader(func(in io.Reader) *csv.Reader { | ||
//return csv.NewReader(in) | ||
return gocsv.LazyCSVReader(in) // Allows use of quotes in CSV | ||
}) | ||
|
||
... | ||
|
||
gocsv.UnmarshalFile(file, &clients) | ||
|
||
... | ||
|
||
gocsv.SetCSVWriter(func(out io.Writer) *csv.Writer { | ||
return csv.NewWriter(out) | ||
}) | ||
|
||
... | ||
|
||
gocsv.MarshalFile(&clients, file) | ||
|
||
... | ||
} | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package gocsv | ||
|
||
import ( | ||
"bytes" | ||
"encoding/csv" | ||
"io" | ||
"os" | ||
"strings" | ||
) | ||
|
||
// -------------------------------------------------------------------------- | ||
// CSVReader used to format CSV | ||
|
||
type CSVWriter func(io.Writer) *csv.Writer | ||
|
||
var selfCSVWriter CSVWriter = DefaultCSVWriter | ||
|
||
func DefaultCSVWriter(out io.Writer) *csv.Writer { | ||
return csv.NewWriter(out) | ||
} | ||
|
||
func SetCSVWriter(csvWriter CSVWriter) { | ||
selfCSVWriter = csvWriter | ||
} | ||
|
||
func getCSVWriter(out io.Writer) *csv.Writer { | ||
return selfCSVWriter(out) | ||
} | ||
|
||
// -------------------------------------------------------------------------- | ||
// CSVReader used to parse CSV | ||
|
||
type CSVReader func(io.Reader) *csv.Reader | ||
|
||
var selfCSVReader CSVReader = DefaultCSVReader | ||
|
||
func DefaultCSVReader(in io.Reader) *csv.Reader { | ||
return csv.NewReader(in) | ||
} | ||
|
||
func LazyCSVReader(in io.Reader) *csv.Reader { | ||
csvReader := csv.NewReader(in) | ||
csvReader.LazyQuotes = true | ||
csvReader.TrimLeadingSpace = true | ||
return csvReader | ||
} | ||
|
||
func SetCSVReader(csvReader func(io.Reader) *csv.Reader) { | ||
selfCSVReader = csvReader | ||
} | ||
|
||
func getCSVReader(in io.Reader) *csv.Reader { | ||
return selfCSVReader(in) | ||
} | ||
|
||
// -------------------------------------------------------------------------- | ||
// Marshal functions | ||
|
||
func MarshalFile(in interface{}, file *os.File) (err error) { | ||
return Marshal(in, file) | ||
} | ||
|
||
func MarshalString(in interface{}) (out string, err error) { | ||
bufferString := bytes.NewBufferString(out) | ||
if err := Marshal(in, bufferString); err != nil { | ||
return "", err | ||
} | ||
return bufferString.String(), nil | ||
} | ||
|
||
func MarshalBytes(in interface{}) (out []byte, err error) { | ||
bufferString := bytes.NewBuffer(out) | ||
if err := Marshal(in, bufferString); err != nil { | ||
return nil, err | ||
} | ||
return bufferString.Bytes(), nil | ||
} | ||
|
||
func Marshal(in interface{}, out io.Writer) (err error) { | ||
return newEncoder(out).writeTo(getInterfaceType(in)) | ||
} | ||
|
||
// -------------------------------------------------------------------------- | ||
// Unmarshal functions | ||
|
||
func UnmarshalFile(in *os.File, out interface{}) (err error) { | ||
return Unmarshal(in, out) | ||
} | ||
|
||
func UnmarshalString(in string, out interface{}) (err error) { | ||
return Unmarshal(strings.NewReader(in), out) | ||
} | ||
|
||
func UnmarshalBytes(in []byte, out interface{}) (err error) { | ||
return Unmarshal(bytes.NewReader(in), out) | ||
} | ||
|
||
func Unmarshal(in io.Reader, out interface{}) (err error) { | ||
return newDecoder(in).readTo(getInterfaceType(out)) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package gocsv | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"reflect" | ||
) | ||
|
||
type decoder struct { | ||
in io.Reader | ||
} | ||
|
||
func newDecoder(in io.Reader) *decoder { | ||
return &decoder{in} | ||
} | ||
|
||
func (self *decoder) readTo(out reflect.Value) error { | ||
if err := self.ensureOutKind(&out); err != nil { // Check if interface is of type Slice or Array | ||
return err | ||
} | ||
outType := out.Type() | ||
csvContent, err := self.getCSVContent() // Get the CSV content | ||
if err != nil { | ||
return err | ||
} | ||
if err := self.ensureOutCapacity(&out, len(csvContent)); err != nil { // Check capacity and grows it if possible | ||
return err | ||
} | ||
outInnerType := outType.Elem() | ||
if err := self.ensureOutInnerKind(outInnerType); err != nil { // Check if internal data is a struct | ||
return err | ||
} | ||
outInnerStructInfo := getStructInfo(outInnerType) // Get struct info to get the columns tags | ||
csvColumnsNamesFieldsIndex := make(map[int]int) // Used to store column names and position in struct | ||
for i, csvRow := range csvContent { | ||
|
||
if i == 0 { | ||
for j, csvColumn := range csvRow { | ||
if num := self.ensureCSVFieldExists(csvColumn, outInnerStructInfo); num != -1 { | ||
csvColumnsNamesFieldsIndex[j] = num | ||
} | ||
} | ||
} else { | ||
outInner := reflect.New(outInnerType).Elem() | ||
for j, csvColumn := range csvRow { | ||
if pos, ok := csvColumnsNamesFieldsIndex[j]; ok { // Position found accordingly to column name position | ||
if err := setField(outInner.Field(pos), csvColumn); err != nil { // Set field of struct | ||
return err | ||
} | ||
} | ||
} | ||
out.Index(i - 1).Set(outInner) // Position if offset by one (0 is column names) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (self *decoder) ensureOutKind(out *reflect.Value) error { | ||
switch out.Kind() { | ||
case reflect.Array: | ||
fallthrough | ||
case reflect.Slice: | ||
return nil | ||
} | ||
return fmt.Errorf("Unsupported type " + out.Type().String() + ", only slice or array supported") | ||
} | ||
|
||
func (self *decoder) ensureOutInnerKind(outInnerType reflect.Type) error { | ||
switch outInnerType.Kind() { | ||
case reflect.Struct: | ||
return nil | ||
} | ||
return fmt.Errorf("Unsupported type " + outInnerType.String() + ", only struct supported") | ||
} | ||
|
||
func (self *decoder) ensureOutCapacity(out *reflect.Value, csvLen int) error { | ||
|
||
switch out.Kind() { | ||
case reflect.Array: | ||
if out.Cap() < csvLen-1 { // Array is not big enough to hold the CSV content | ||
return fmt.Errorf("Array capacity problem") | ||
} | ||
case reflect.Slice: | ||
if !out.CanAddr() && out.Len() < csvLen-1 { // Slice is not big enough tho hold the CSV content and is not addressable | ||
return fmt.Errorf("Slice not addressable capacity problem (did you forget &?)") | ||
} else if out.CanAddr() && out.Len() < csvLen-1 { | ||
out.Set(reflect.MakeSlice(out.Type(), csvLen-1, csvLen-1)) // Slice is not big enough, so grows it | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (self *decoder) ensureCSVFieldExists(key string, structInfo *structInfo) int { | ||
for i, field := range structInfo.Fields { | ||
if field.Key == key { | ||
return i | ||
} | ||
} | ||
return -1 | ||
} | ||
|
||
func (self *decoder) getCSVContent() ([][]string, error) { | ||
return getCSVReader(self.in).ReadAll() | ||
} |
Oops, something went wrong.