Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathanPicques committed May 1, 2014
0 parents commit 99decde
Show file tree
Hide file tree
Showing 6 changed files with 699 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
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.
105 changes: 105 additions & 0 deletions README.md
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)

...
}

```
101 changes: 101 additions & 0 deletions csv.go
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))
}

104 changes: 104 additions & 0 deletions decode.go
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()
}
Loading

0 comments on commit 99decde

Please sign in to comment.