diff --git a/cmd/out.go b/cmd/out.go index f7ba406dc..4f7545786 100644 --- a/cmd/out.go +++ b/cmd/out.go @@ -30,6 +30,7 @@ import ( "github.com/k1LoW/tbls/output/dot" "github.com/k1LoW/tbls/output/json" "github.com/k1LoW/tbls/output/md" + "github.com/k1LoW/tbls/output/xlsx" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -97,6 +98,8 @@ var outCmd = &cobra.Command{ o = new(dot.Dot) case "md": o = md.NewMd(false, false, "") + case "xlsx": + o = new(xlsx.Xlsx) default: printError(fmt.Errorf("unsupported format '%s'", format)) os.Exit(1) diff --git a/go.mod b/go.mod index 96f03a482..9de7ae90f 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,11 @@ module github.com/k1LoW/tbls require ( github.com/go-sql-driver/mysql v1.4.1 - github.com/gobuffalo/packr v1.24.1 + github.com/gobuffalo/packd v0.1.0 // indirect + github.com/gobuffalo/packr v1.25.0 github.com/labstack/gommon v0.2.8 - github.com/lib/pq v1.0.0 + github.com/lib/pq v1.1.0 + github.com/loadoff/excl v0.0.0-20171207172601-c6a9e4c4b4c4 github.com/mattn/go-colorable v0.1.1 // indirect github.com/mattn/go-isatty v0.0.7 // indirect github.com/mattn/go-runewidth v0.0.4 @@ -13,7 +15,8 @@ require ( github.com/pmezard/go-difflib v1.0.0 github.com/spf13/cobra v0.0.3 github.com/xo/dburl v0.0.0-20190203050942-98997a05b24f - golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 // indirect + golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0 // indirect + golang.org/x/text v0.3.1 // indirect google.golang.org/appengine v1.5.0 // indirect gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 575808c20..41908ca79 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTq github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= @@ -21,10 +23,12 @@ github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9h github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0 h1:P6naWPiHm/7R3eYx/ub3VhaW9G+1xAMJ6vzACePaGPI= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr v1.24.1 h1:OHj/GZSjbG5B96rcf0m32hrBKDibssSx/CUDiYUJe20= -github.com/gobuffalo/packr v1.24.1/go.mod h1:absPnW/XUUa4DmIh5ga7AipGXXg0DOcd5YWKk5RZs8Y= +github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr v1.25.0 h1:NtPK45yOKFdTKHTvRGKL+UIKAKmJVWIVJOZBDI/qEdY= +github.com/gobuffalo/packr v1.25.0/go.mod h1:NqsGg8CSB2ZD+6RBIRs18G7aZqdYDlYNNvsSqP6T4/U= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.0.10/go.mod h1:n90ZuXIc2KN2vFAOQascnPItp9A2g9QYSvYvS3AjQEM= +github.com/gobuffalo/packr/v2 v2.1.0/go.mod h1:n90ZuXIc2KN2vFAOQascnPItp9A2g9QYSvYvS3AjQEM= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -35,7 +39,6 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -43,8 +46,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= -github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/loadoff/excl v0.0.0-20171207172601-c6a9e4c4b4c4 h1:DNMe4L3ukcDtRt3Q/5Y4VcLnqs8iIo4hm6bPPt1OR1s= +github.com/loadoff/excl v0.0.0-20171207172601-c6a9e4c4b4c4/go.mod h1:nVZOIVwZIBGHgJj8QWr2IsA5E/hpqGObvtWfZHLkchM= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= @@ -88,18 +93,20 @@ github.com/xo/dburl v0.0.0-20190203050942-98997a05b24f/go.mod h1:g6rdekR8vgfVZrk golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190403202508-8e1b8d32e692/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998= -golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0 h1:V+O002es++Mnym06Rj/S6Fl7VCsgRBgVDGb/NoZVHUg= +golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1 h1:nsUiJHvm6yOoRozW9Tz0siNk9sHieLzR+w814Ihse3A= +golang.org/x/text v0.3.1/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190404132500-923d25813098/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= diff --git a/output/xlsx/xlsx.go b/output/xlsx/xlsx.go new file mode 100644 index 000000000..6aac36d96 --- /dev/null +++ b/output/xlsx/xlsx.go @@ -0,0 +1,217 @@ +package xlsx + +import ( + "fmt" + "io" + "io/ioutil" + "strings" + + "github.com/k1LoW/tbls/schema" + "github.com/loadoff/excl" + "github.com/pkg/errors" +) + +// Xlsx struct +type Xlsx struct{} + +// OutputSchema output Xlsx format for full relation. +func (x *Xlsx) OutputSchema(wr io.Writer, s *schema.Schema) error { + w, err := excl.Create() + if err != nil { + return err + } + err = createSchemaSheet(w, s) + if err != nil { + return err + } + for _, t := range s.Tables { + err = createTableSheet(w, t) + if err != nil { + return err + } + } + tf, _ := ioutil.TempFile("", "tbls.xlsx") + path := tf.Name() + defer tf.Close() + w.Save(path) + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + _, err = wr.Write(b) + if err != nil { + return err + } + return nil +} + +// OutputTable output Xlsx format for table. +func (x *Xlsx) OutputTable(wr io.Writer, t *schema.Table) error { + w, err := excl.Create() + if err != nil { + return err + } + err = createTableSheet(w, t) + if err != nil { + return err + } + tf, _ := ioutil.TempFile("", "tbls.xlsx") + path := tf.Name() + defer tf.Close() + w.Save(path) + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + _, err = wr.Write(b) + if err != nil { + return err + } + return nil +} + +func createSchemaSheet(w *excl.Workbook, s *schema.Schema) error { + sheetName := fmt.Sprintf("Tables of %s", s.Name) + sheet, err := w.OpenSheet(sheetName) + defer sheet.Close() + if err != nil { + return errors.WithStack(err) + } + setString(sheet, 1, 1, s.Name).SetFont(excl.Font{Bold: true}) + + setString(sheet, 3, 1, "Tables").SetFont(excl.Font{Bold: true}) + setHeader(sheet, 4, []string{"Name", "Columns", "Comment", "Type"}) + n := 5 + for i, t := range s.Tables { + setStringWithBorder(sheet, n+i, 1, t.Name) + setNumberWithBorder(sheet, n+i, 2, len(t.Columns)) + setStringWithBorder(sheet, n+i, 3, t.Comment) + setStringWithBorder(sheet, n+i, 4, t.Type) + } + + return nil +} + +func createTableSheet(w *excl.Workbook, t *schema.Table) error { + sheetName := t.Name + sheet, err := w.OpenSheet(sheetName) + defer sheet.Close() + if err != nil { + return errors.WithStack(err) + } + + setString(sheet, 1, 1, t.Name).SetFont(excl.Font{Bold: true}) + setString(sheet, 2, 1, t.Comment) + + setString(sheet, 4, 1, "Columns").SetFont(excl.Font{Bold: true}) + setHeader(sheet, 5, []string{"Name", "Type", "Default", "Nullable", "Children", "Parents", "Comment"}) + r := 6 + for i, c := range t.Columns { + setStringWithBorder(sheet, r+i, 1, c.Name) + setStringWithBorder(sheet, r+i, 2, c.Type) + setStringWithBorder(sheet, r+i, 3, c.Default.String) + setStringWithBorder(sheet, r+i, 4, fmt.Sprintf("%v", c.Nullable)) + children := []string{} + for _, child := range c.ChildRelations { + children = append(children, child.Table.Name) + } + setStringWithBorder(sheet, r+i, 5, strings.Join(children, "\n")) + parents := []string{} + for _, parent := range c.ParentRelations { + parents = append(parents, parent.ParentTable.Name) + } + setStringWithBorder(sheet, r+i, 6, strings.Join(parents, "\n")) + setStringWithBorder(sheet, r+i, 7, c.Comment) + } + r = r + len(t.Columns) + + if len(t.Constraints) > 0 { + r++ + setString(sheet, r, 1, "Constraints").SetFont(excl.Font{Bold: true}) + r++ + setHeader(sheet, r, []string{"Name", "Type", "Definition"}) + r++ + for i, c := range t.Constraints { + setStringWithBorder(sheet, r+i, 1, c.Name) + setStringWithBorder(sheet, r+i, 2, c.Type) + setStringWithBorder(sheet, r+i, 3, c.Def) + } + } + r = r + len(t.Constraints) + + if len(t.Indexes) > 0 { + r++ + setString(sheet, r, 1, "Indexes").SetFont(excl.Font{Bold: true}) + r++ + setHeader(sheet, r, []string{"Name", "Definition"}) + r++ + for i, idx := range t.Indexes { + setStringWithBorder(sheet, r+i, 1, idx.Name) + setStringWithBorder(sheet, r+i, 2, idx.Def) + } + } + r = r + len(t.Indexes) + + if len(t.Triggers) > 0 { + r++ + setString(sheet, r, 1, "Triggers").SetFont(excl.Font{Bold: true}) + r++ + setHeader(sheet, r, []string{"Name", "Definition"}) + r++ + for i, trg := range t.Triggers { + setStringWithBorder(sheet, r+i, 1, trg.Name) + setStringWithBorder(sheet, r+i, 2, trg.Def) + } + } + + return nil +} + +func setHeader(sheet *excl.Sheet, rowNo int, values []string) { + for i, v := range values { + sheet.SetColWidth(10, i+1) + setStringWithBorder(sheet, rowNo, i+1, v).SetFont(excl.Font{Bold: true}) + } +} + +func setNumber(sheet *excl.Sheet, rowNo int, colNo int, v int) *excl.Cell { + row := sheet.GetRow(rowNo) + return row.SetNumber(v, colNo) +} + +func setNumberWithBorder(sheet *excl.Sheet, rowNo int, colNo int, v int) *excl.Cell { + return setNumber(sheet, rowNo, colNo, v).SetBorder(excl.Border{ + Left: &excl.BorderSetting{Style: "thin"}, + Right: &excl.BorderSetting{Style: "thin"}, + Top: &excl.BorderSetting{Style: "thin"}, + Bottom: &excl.BorderSetting{Style: "thin"}, + }) +} + +func setString(sheet *excl.Sheet, rowNo int, colNo int, v string) *excl.Cell { + row := sheet.GetRow(rowNo) + return row.SetString(v, colNo) +} + +func setStringWithBorder(sheet *excl.Sheet, rowNo int, colNo int, v string) *excl.Cell { + return setString(sheet, rowNo, colNo, v).SetBorder(excl.Border{ + Left: &excl.BorderSetting{Style: "thin"}, + Right: &excl.BorderSetting{Style: "thin"}, + Top: &excl.BorderSetting{Style: "thin"}, + Bottom: &excl.BorderSetting{Style: "thin"}, + }) +} + +func setFormula(sheet *excl.Sheet, rowNo int, colNo int, v string) *excl.Cell { + row := sheet.GetRow(rowNo) + return row.SetFormula(v, colNo) +} + +func setFormulaWithBorder(sheet *excl.Sheet, rowNo int, colNo int, v string) *excl.Cell { + return setFormula(sheet, rowNo, colNo, v).SetBorder(excl.Border{ + Left: &excl.BorderSetting{Style: "thin"}, + Right: &excl.BorderSetting{Style: "thin"}, + Top: &excl.BorderSetting{Style: "thin"}, + Bottom: &excl.BorderSetting{Style: "thin"}, + }) +}