diff --git a/README.md b/README.md index 373537d..82641f9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ cli-kintone is a command line utility for kintone. ## Version -0.6 +0.7 ## How to Build diff --git a/export.go b/export.go index 33a4708..2356371 100644 --- a/export.go +++ b/export.go @@ -34,18 +34,18 @@ func getRecords(app *kintone.App, fields []string, offset int64) ([]*kintone.Rec } } -func getWriter() io.Writer { +func getWriter(writer io.Writer) io.Writer { encoding := getEncoding() if (encoding == nil) { - return os.Stdout + return writer } - return transform.NewWriter(os.Stdout, encoding.NewEncoder()) + return transform.NewWriter(writer, encoding.NewEncoder()) } -func writeJson(app *kintone.App) error { +func writeJson(app *kintone.App, _writer io.Writer) error { i := 0 offset := int64(0) - writer := getWriter() + writer := getWriter(_writer) fmt.Fprint(writer, "{\"records\": [\n") for ;;offset += EXPORT_ROW_LIMIT { @@ -148,10 +148,10 @@ func hasSubTable(columns []*Column) bool { return false } -func writeCsv(app *kintone.App) error { +func writeCsv(app *kintone.App, _writer io.Writer) error { i := uint64(0) offset := int64(0) - writer := getWriter() + writer := getWriter(_writer) var columns Columns // retrieve field list @@ -349,6 +349,10 @@ func getType(f interface{}) string { return kintone.FT_DATETIME case kintone.UserField: return kintone.FT_USER + case kintone.OrganizationField: + return kintone.FT_ORGANIZATION + case kintone.GroupField: + return kintone.FT_GROUP case kintone.CategoryField: return kintone.FT_CATEGORY case kintone.StatusField: @@ -451,6 +455,20 @@ func toString(f interface{}, delimiter string) string { users = append(users, user.Code) } return strings.Join(users, delimiter) + case kintone.OrganizationField: + organizationField := f.(kintone.OrganizationField) + organizations := make([]string, 0, len(organizationField)) + for _, organization := range organizationField { + organizations = append(organizations, organization.Code) + } + return strings.Join(organizations, delimiter) + case kintone.GroupField: + groupField := f.(kintone.GroupField) + groups := make([]string, 0, len(groupField)) + for _, group := range groupField { + groups = append(groups, group.Code) + } + return strings.Join(groups, delimiter) case kintone.AssigneeField: assigneeField := f.(kintone.AssigneeField) users := make([]string, 0, len(assigneeField)) diff --git a/export_test.go b/export_test.go new file mode 100644 index 0000000..7f17b0c --- /dev/null +++ b/export_test.go @@ -0,0 +1,231 @@ + +package main + +import ( + "bytes" + "io" + "fmt" + "testing" + "encoding/csv" + "github.com/ryokdy/go-kintone" +) + +func makeTestData(app *kintone.App) error { + err := deleteRecords(app, "") + if err != nil { + return err + } + records := make([]*kintone.Record, 0) + + record := make(map[string]interface{}) + record["single_line_text"] = kintone.SingleLineTextField("single line1") + record["multi_line_text"] = kintone.SingleLineTextField("multi line1\nmulti line") + record["number"] = kintone.DecimalField("12345") + table := make([]*kintone.Record, 0) + sub := make(map[string]interface{}) + sub["table_single_line_text"] = kintone.SingleLineTextField("table single line1") + sub["table_multi_line_text"] = kintone.SingleLineTextField("table multi line1\nmulti line") + table = append(table, kintone.NewRecord(sub)) + sub = make(map[string]interface{}) + sub["table_single_line_text"] = kintone.SingleLineTextField("table single line2") + sub["table_multi_line_text"] = kintone.SingleLineTextField("table multi line2\nmulti line") + table = append(table, kintone.NewRecord(sub)) + record["table"] = kintone.SubTableField(table) + + records = append(records, kintone.NewRecord(record)) + + record = make(map[string]interface{}) + record["single_line_text"] = kintone.SingleLineTextField("single line2") + record["multi_line_text"] = kintone.SingleLineTextField("multi line2\nmulti line") + record["number"] = kintone.DecimalField("12345") + records = append(records, kintone.NewRecord(record)) + + _, err = app.AddRecords(records) + + return err +} + +func TestExport1(t *testing.T) { + buf := &bytes.Buffer{} + + app := newApp() + makeTestData(app) + + config.fields = []string{"single_line_text", "multi_line_text", "number"} + config.query = "order by record_number asc" + err := writeCsv(app, buf) + if err != nil { + t.Error(err) + } + + //output := buf.String() + //fmt.Printf(output) + fmt.Printf("\n") + + reader := csv.NewReader(buf) + + row, err := reader.Read() + if err != nil { + t.Error(err) + } + //fmt.Printf(row[0]) + if row[0] != "single_line_text" { + t.Error("Invalid field code") + } + if row[1] != "multi_line_text" { + t.Error("Invalid field code") + } + if row[2] != "number" { + t.Error("Invalid field code") + } + + row, err = reader.Read() + if err != nil { + t.Error(err) + } + if row[0] != "single line1" { + t.Error("Invalid 1st field value of row 1") + } + if row[1] != "multi line1\nmulti line" { + t.Error("Invalid 2nd field value of row 1") + } + if row[2] != "12345" { + t.Error("Invalid 3rd field value of row 1") + } + + row, err = reader.Read() + if err != nil { + t.Error(err) + } + if row[0] != "single line2" { + t.Error("Invalid 1st field value of row 2") + } + if row[1] != "multi line2\nmulti line" { + t.Error("Invalid 2nd field value of row 2") + } + if row[2] != "12345" { + t.Error("Invalid 3rd field value of row 2") + } + + row, err = reader.Read() + if err != io.EOF { + t.Error("Invalid record count") + } +} + +func TestExport2(t *testing.T) { + buf := &bytes.Buffer{} + + app := newApp() + makeTestData(app) + + config.fields = []string{"single_line_text", "multi_line_text", "number", "table"} + config.query = "order by record_number asc" + err := writeCsv(app, buf) + if err != nil { + t.Error(err) + } + + //output := buf.String() + //fmt.Printf(output) + + reader := csv.NewReader(buf) + + row, err := reader.Read() + if err != nil { + t.Error(err) + } + //fmt.Printf(row[0]) + if row[0] != "*" { + t.Error("Invalid field code") + } + if row[1] != "single_line_text" { + t.Error("Invalid field code") + } + if row[2] != "multi_line_text" { + t.Error("Invalid field code") + } + if row[3] != "number" { + t.Error("Invalid field code") + } + if row[4] != "table_single_line_text" { + t.Error("Invalid field code") + } + if row[5] != "table_multi_line_text" { + t.Error("Invalid field code") + } + + row, err = reader.Read() + if err != nil { + t.Error(err) + } + if row[0] != "*" { + t.Error("Invalid 1st field value of row 1") + } + if row[1] != "single line1" { + t.Error("Invalid 2nd field value of row 1") + } + if row[2] != "multi line1\nmulti line" { + t.Error("Invalid 3rd field value of row 1") + } + if row[3] != "12345" { + t.Error("Invalid 4th field value of row 1") + } + if row[4] != "table single line1" { + t.Error("Invalid 5th field value of row 1") + } + if row[5] != "table multi line1\nmulti line" { + t.Error("Invalid 6th field value of row 1") + } + + row, err = reader.Read() + if err != nil { + t.Error(err) + } + if row[0] != "" { + t.Error("Invalid 1st field value of row 2") + } + if row[1] != "single line1" { + t.Error("Invalid 2nd field value of row 2") + } + if row[2] != "multi line1\nmulti line" { + t.Error("Invalid 3rd field value of row 2") + } + if row[3] != "12345" { + t.Error("Invalid 4th field value of row 2") + } + if row[4] != "table single line2" { + t.Error("Invalid 5th field value of row 2") + } + if row[5] != "table multi line2\nmulti line" { + t.Error("Invalid 6th field value of row 2") + } + + row, err = reader.Read() + if err != nil { + t.Error(err) + } + if row[0] != "*" { + t.Error("Invalid 1st field value of row 3") + } + if row[1] != "single line2" { + t.Error("Invalid 2nd field value of row 3") + } + if row[2] != "multi line2\nmulti line" { + t.Error("Invalid 3rd field value of row 3") + } + if row[3] != "12345" { + t.Error("Invalid 4th field value of row 3") + } + if row[4] != "" { + t.Error("Invalid 5th field value of row 3") + } + if row[5] != "" { + t.Error("Invalid 6th field value of row 3") + } + + row, err = reader.Read() + if err != io.EOF { + t.Error("Invalid record count") + } +} diff --git a/import.go b/import.go index c1eeb6e..dac0260 100644 --- a/import.go +++ b/import.go @@ -16,12 +16,12 @@ import ( "golang.org/x/text/transform" ) -func getReader(file *os.File) io.Reader { +func getReader(reader io.Reader) io.Reader { encoding := getEncoding() if (encoding == nil) { - return file + return reader } - return transform.NewReader(file, encoding.NewDecoder()) + return transform.NewReader(reader, encoding.NewDecoder()) } func addSubField(app *kintone.App, column *Column, col string, tables map[string]map[string]interface{}) error { @@ -52,14 +52,9 @@ func addSubField(app *kintone.App, column *Column, col string, tables map[string return nil } -func readCsv(app *kintone.App, filePath string) error { - file, err := os.Open(filePath) - if err != nil { - return err - } - defer file.Close() +func readCsv(app *kintone.App, _reader io.Reader) error { - reader := csv.NewReader(getReader(file)) + reader := csv.NewReader(getReader(_reader)) head := true recordsInsert := make([]*kintone.Record, 0, IMPORT_ROW_LIMIT) @@ -98,7 +93,7 @@ func readCsv(app *kintone.App, filePath string) error { row = *peeked peeked = nil } - //fmt.Printf("%#v", row) + //fmt.Printf("%#v\n", row) if head && columns == nil { columns = make([]*Column, 0) for _, col := range row { @@ -280,6 +275,7 @@ func uploadFile(app *kintone.App, filePath string) (string, error) { func insert(app *kintone.App, recs []*kintone.Record) error { var err error + _, err = app.AddRecords(recs) return err @@ -416,6 +412,24 @@ func getField(fieldType string, value string) interface{} { } } return ret + case kintone.FT_ORGANIZATION: + organizations := strings.Split(value, "\n") + var ret kintone.OrganizationField = []kintone.Organization{} + for _, organization := range organizations { + if len(strings.TrimSpace(organization)) > 0 { + ret = append(ret, kintone.Organization{Code: organization}) + } + } + return ret + case kintone.FT_GROUP: + groups := strings.Split(value, "\n") + var ret kintone.GroupField = []kintone.Group{} + for _, group := range groups { + if len(strings.TrimSpace(group)) > 0 { + ret = append(ret, kintone.Group{Code: group}) + } + } + return ret case kintone.FT_CATEGORY: return nil case kintone.FT_STATUS: diff --git a/import_test.go b/import_test.go new file mode 100644 index 0000000..b2191be --- /dev/null +++ b/import_test.go @@ -0,0 +1,106 @@ + +package main + +import ( + "bytes" + //"io" + //"fmt" + "testing" + "github.com/ryokdy/go-kintone" +) + +func TestImport1(t *testing.T) { + data := "\"single_line_text\",\"multi_line_text\",\"number\",\"date_and_time\"\n\"single line2\",\"multi line2\nmulti line\",\"12345\",\"2016-09-12T10:13:00Z\"\n\"single line1\",\"multi line1\nmulti line\",\"12345\",\"2016-09-12T10:13:00Z\"" + + app := newApp() + + config.deleteAll = true + err := readCsv(app, bytes.NewBufferString(data)) + if err != nil { + t.Error(err) + } + + recs, err := app.GetRecords(nil, "order by record_number desc") + if err != nil { + t.Error(err) + } + if len(recs) != 2 { + t.Error("Invalid record count") + } + + fields := recs[0].Fields + if _, ok := fields["single_line_text"].(kintone.SingleLineTextField); !ok { + t.Error("Not a SingleLineTextField") + } + if fields["single_line_text"] != kintone.SingleLineTextField("single line1") { + t.Error("single_line_text mismatch") + } + if _, ok := fields["multi_line_text"].(kintone.MultiLineTextField); !ok { + t.Error("Not a MultiLineTextField") + } + if fields["multi_line_text"] != kintone.MultiLineTextField("multi line1\nmulti line") { + t.Error("multi_line_text mismatch") + } + num, ok := fields["number"].(kintone.DecimalField) + if !ok { + t.Error("Not a DecimalField") + } + if num != kintone.DecimalField("12345") { + t.Error("number mismatch") + } +} + +func TestImport2(t *testing.T) { + data := "\"*\",\"single_line_text\",\"multi_line_text\",\"number\",\"date_and_time\",\"table_single_line_text\",\"table_multi_line_text\"\n\"*\",\"single line2\",\"multi line2\nmulti line\",\"12345\",\"2016-09-12T10:13:00Z\",\"single1\",\"multi1\"\n\"\",\"single line2\",\"multi line2\nmulti line\",\"12345\",\"2016-09-12T10:13:00Z\",\"single2\",\"multi2\"\n\"*\",\"single line1\",\"multi line1\nmulti line\",\"12345\",\"2016-09-12T10:13:00Z\",\"\",\"\"" + + app := newApp() + + config.deleteAll = true + err := readCsv(app, bytes.NewBufferString(data)) + if err != nil { + t.Error(err) + } + + recs, err := app.GetRecords(nil, "order by record_number asc") + if err != nil { + t.Error(err) + } + if len(recs) != 2 { + t.Error("Invalid record count") + } + + fields := recs[0].Fields + if _, ok := fields["single_line_text"].(kintone.SingleLineTextField); !ok { + t.Error("Not a SingleLineTextField") + } + if fields["single_line_text"] != kintone.SingleLineTextField("single line2") { + t.Error("single_line_text mismatch") + } + if _, ok := fields["multi_line_text"].(kintone.MultiLineTextField); !ok { + t.Error("Not a MultiLineTextField") + } + if fields["multi_line_text"] != kintone.MultiLineTextField("multi line2\nmulti line") { + t.Error("multi_line_text mismatch") + } + num, ok := fields["number"].(kintone.DecimalField) + if !ok { + t.Error("Not a DecimalField") + } + if num != kintone.DecimalField("12345") { + t.Error("number mismatch") + } + table, ok := fields["table"].(kintone.SubTableField) + if !ok { + t.Error("Not a SubTableField") + } + if len(table) != 2 { + t.Error("Invalid sub record count") + } + sub := table[0].Fields + if _, ok := sub["table_single_line_text"].(kintone.SingleLineTextField); !ok { + t.Error("Not a SingleLineTextField") + } + if sub["table_single_line_text"] != kintone.SingleLineTextField("single1") { + t.Error("single_line_text mismatch") + } +} diff --git a/main.go b/main.go index 967445f..ed33032 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,7 @@ import ( "fmt" "log" "strings" - + "os" "github.com/kintone/go-kintone" "github.com/howeyc/gopass" "golang.org/x/text/encoding" @@ -205,12 +205,17 @@ func main() { var err error if config.filePath == "" { if config.format == "json" { - err = writeJson(app) + err = writeJson(app, os.Stdout) } else { - err = writeCsv(app) + err = writeCsv(app, os.Stdout) } } else { - err = readCsv(app, config.filePath) + var file *os.File + file, err = os.Open(config.filePath) + if err == nil { + defer file.Close() + err = readCsv(app, file) + } } if err != nil { log.Fatal(err) diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..7ab4a8e --- /dev/null +++ b/main_test.go @@ -0,0 +1,18 @@ + +package main + +import ( + "os" + "strconv" + "github.com/ryokdy/go-kintone" +) + +func newApp() *kintone.App { + appId, _ := strconv.ParseUint(os.Getenv("KINTONE_APP_ID"), 10, 64) + + return &kintone.App{ + Domain: os.Getenv("KINTONE_DOMAIN"), + ApiToken: os.Getenv("KINTONE_API_TOKEN"), + AppId: appId, + } +}