diff --git a/README.md b/README.md index c0e9d02a3..548b57860 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Usage: Available Commands: diff diff database and document doc document a database + dot generate dot file help Help about any command version print tbls version diff --git a/cmd/doc.go b/cmd/doc.go index d87ebbff1..d4273e15a 100644 --- a/cmd/doc.go +++ b/cmd/doc.go @@ -28,9 +28,6 @@ import ( "os" ) -// force is a flag on whether to force genarate -var force bool - // docCmd represents the doc command var docCmd = &cobra.Command{ Use: "doc [DSN] [DOCUMENT_PATH]", @@ -67,7 +64,13 @@ var docCmd = &cobra.Command{ } } - err = md.Output(s, outputPath, force) + switch outputFormat { + case "md": + err = md.Output(s, outputPath, force) + default: + err = fmt.Errorf("Error: %s", "unsupported output format") + } + if err != nil { fmt.Println(err) os.Exit(1) @@ -80,4 +83,5 @@ func init() { docCmd.Flags().BoolVarP(&force, "force", "f", false, "force") docCmd.Flags().BoolVarP(&sort, "sort", "", false, "sort") docCmd.Flags().StringVarP(&additionalDataPath, "add", "a", "", "additional schema data path") + docCmd.Flags().StringVarP(&outputFormat, "output", "o", "md", "output format") } diff --git a/cmd/dot.go b/cmd/dot.go new file mode 100644 index 000000000..79f57758f --- /dev/null +++ b/cmd/dot.go @@ -0,0 +1,80 @@ +// Copyright © 2018 Ken'ichiro Oyama +// +// 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. + +package cmd + +import ( + "fmt" + "github.com/k1LoW/tbls/db" + "github.com/k1LoW/tbls/output/dot" + "github.com/spf13/cobra" + "os" +) + +// dotCmd represents the doc command +var dotCmd = &cobra.Command{ + Use: "dot [DSN] [OUTPUT_PATH]", + Short: "generate dot file", + Long: `'tbls dot' analyzes a database and generate dot file.`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return fmt.Errorf("Error: %s", "requires two args") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + dsn := args[0] + outputPath := args[1] + s, err := db.Analyze(dsn) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if additionalDataPath != "" { + err = s.LoadAdditionalRelations(additionalDataPath) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + if sort { + err = s.Sort() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + err = dot.Output(s, outputPath, force) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + }, +} + +func init() { + rootCmd.AddCommand(dotCmd) + dotCmd.Flags().BoolVarP(&force, "force", "f", false, "force") + dotCmd.Flags().StringVarP(&additionalDataPath, "add", "a", "", "additional schema data path") +} diff --git a/cmd/root.go b/cmd/root.go index 9f312060a..03468beee 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -27,12 +27,18 @@ import ( "github.com/spf13/cobra" ) +// force is a flag on whether to force genarate +var force bool + // sort is a flag on whether to sort tables, columns, and more var sort bool // additionalDataPath is a additional data path var additionalDataPath string +// outputFormat is output format +var outputFormat string + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "tbls", diff --git a/main.go b/main.go index 3e22eabc3..4ebaa206c 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ import ( ) //go:generate go-assets-builder -p md -s="/output/md/templates" output/md/templates -o output/md/templates.go +//go:generate go-assets-builder -p dot -s="/output/dot/templates" output/dot/templates -o output/dot/templates.go func main() { cmd.Execute() diff --git a/output/dot/dot.go b/output/dot/dot.go new file mode 100644 index 000000000..4b1956e5c --- /dev/null +++ b/output/dot/dot.go @@ -0,0 +1,50 @@ +package dot + +import ( + "fmt" + "github.com/k1LoW/tbls/schema" + "io/ioutil" + "os" + "path/filepath" + "text/template" +) + +// Output generate dot file for full relation. +func Output(s *schema.Schema, path string, force bool) error { + fullPath, err := filepath.Abs(path) + if err != nil { + return err + } + + if !force && outputExists(s, fullPath) { + return fmt.Errorf("Error: %s", "output file already exists.") + } + + file, err := os.Create(filepath.Join(fullPath, "schema.dot")) + defer file.Close() + if err != nil { + return err + } + f, _ := Assets.Open(filepath.Join("/", "schema.dot.tmpl")) + bs, _ := ioutil.ReadAll(f) + tmpl, err := template.New("index").Parse(string(bs)) + if err != nil { + return err + } + err = tmpl.Execute(file, map[string]interface{}{ + "Schema": s, + }) + if err != nil { + return err + } + fmt.Printf("%s\n", filepath.Join(path, "schema.dot")) + + return nil +} + +func outputExists(s *schema.Schema, path string) bool { + if _, err := os.Lstat(filepath.Join(path, "schema.dot")); err == nil { + return true + } + return false +} diff --git a/output/dot/templates.go b/output/dot/templates.go new file mode 100644 index 000000000..9b3d007f2 --- /dev/null +++ b/output/dot/templates.go @@ -0,0 +1,23 @@ +package dot + +import ( + "time" + + "github.com/jessevdk/go-assets" +) + +var _Assets21532ae17ad95976ac467eeaeab81f2bb1d537e4 = "digraph {{ .Schema.Name }} {\n // Config\n graph [rankdir=TB, layout=dot, fontname=\"Arial\"];\n node [shape=record, fontsize=14, margin=0.6, fontname=\"Arial\"];\n edge [fontsize=10, labelfloat=false, splines=none, fontname=\"Arial\"];\n\n // Tables\n {{- range $i, $t := .Schema.Tables }}\n {{ $t.Name }} [shape=none, label=<\n \n {{- range $ii, $c := $t.Columns }}\n \n {{- end }}\n
{{ $t.Name }} [{{ $t.Type }}]
{{ $c.Name }} [{{ $c.Type }}]
>];\n {{- end }}\n\n // Relations\n {{- range $j, $r := .Schema.Relations }}\n {{ $r.Table.Name }}:{{ $c := index $r.Columns 0 }}{{ $c.Name }} -> {{ $r.ParentTable.Name }}:{{ $pc := index $r.ParentColumns 0 }}{{ $pc.Name }} [dir=back, arrowtail=crow, taillabel=<
{{ $r.Def }}
>];\n {{- end }}\n}\n" + +// Assets returns go-assets FileSystem +var Assets = assets.NewFileSystem(map[string][]string{"/": []string{"schema.dot.tmpl"}}, map[string]*assets.File{ + "/schema.dot.tmpl": &assets.File{ + Path: "/schema.dot.tmpl", + FileMode: 0x1a4, + Mtime: time.Unix(1527433545, 1527433545000000000), + Data: []byte(_Assets21532ae17ad95976ac467eeaeab81f2bb1d537e4), + }, "/": &assets.File{ + Path: "/", + FileMode: 0x800001ed, + Mtime: time.Unix(1527433545, 1527433545000000000), + Data: nil, + }}, "") diff --git a/output/dot/templates/schema.dot.tmpl b/output/dot/templates/schema.dot.tmpl new file mode 100644 index 000000000..b3238c4b0 --- /dev/null +++ b/output/dot/templates/schema.dot.tmpl @@ -0,0 +1,21 @@ +digraph {{ .Schema.Name }} { + // Config + graph [rankdir=TB, layout=dot, fontname="Arial"]; + node [shape=record, fontsize=14, margin=0.6, fontname="Arial"]; + edge [fontsize=10, labelfloat=false, splines=none, fontname="Arial"]; + + // Tables + {{- range $i, $t := .Schema.Tables }} + {{ $t.Name }} [shape=none, label=< + + {{- range $ii, $c := $t.Columns }} + + {{- end }} +
{{ $t.Name }} [{{ $t.Type }}]
{{ $c.Name }} [{{ $c.Type }}]
>]; + {{- end }} + + // Relations + {{- range $j, $r := .Schema.Relations }} + {{ $r.Table.Name }}:{{ $c := index $r.Columns 0 }}{{ $c.Name }} -> {{ $r.ParentTable.Name }}:{{ $pc := index $r.ParentColumns 0 }}{{ $pc.Name }} [dir=back, arrowtail=crow, taillabel=<
{{ $r.Def }}
>]; + {{- end }} +} diff --git a/output/md/templates.go b/output/md/templates.go index 0ccaa8710..a15a4eaeb 100644 --- a/output/md/templates.go +++ b/output/md/templates.go @@ -14,7 +14,7 @@ var Assets = assets.NewFileSystem(map[string][]string{"/": []string{"index.md.tm "/": &assets.File{ Path: "/", FileMode: 0x800001ed, - Mtime: time.Unix(1526904993, 1526904993000000000), + Mtime: time.Unix(1527296235, 1527296235000000000), Data: nil, }, "/index.md.tmpl": &assets.File{ Path: "/index.md.tmpl", @@ -24,6 +24,6 @@ var Assets = assets.NewFileSystem(map[string][]string{"/": []string{"index.md.tm }, "/table.md.tmpl": &assets.File{ Path: "/table.md.tmpl", FileMode: 0x1a4, - Mtime: time.Unix(1526904993, 1526904993000000000), + Mtime: time.Unix(1527296235, 1527296235000000000), Data: []byte(_Assetsac44302fb6150a621aa9d04a0350aac972bf7e18), }}, "")