Skip to content

Commit

Permalink
Add markdown and ER diagram image generation for viewpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
k1LoW committed Jun 15, 2023
1 parent c6d33b2 commit 1bed38d
Show file tree
Hide file tree
Showing 14 changed files with 485 additions and 180 deletions.
64 changes: 1 addition & 63 deletions cmd/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ var docCmd = &cobra.Command{
}

if c.NeedToGenerateERImages() {
if err := withDot(s, c, force); err != nil {
if err := gviz.Output(s, c, force); err != nil {
return err
}
}
Expand All @@ -113,52 +113,6 @@ var docCmd = &cobra.Command{
},
}

func withDot(s *schema.Schema, c *config.Config, force bool) error {
erFormat := c.ER.Format
outputPath := c.DocPath
fullPath, err := filepath.Abs(outputPath)
if err != nil {
return errors.WithStack(err)
}

if !force && outputErExists(s, fullPath) {
return errors.New("output ER diagram files already exists")
}

err = os.MkdirAll(fullPath, 0755) // #nosec
if err != nil {
return errors.WithStack(err)
}

erFileName := fmt.Sprintf("schema.%s", erFormat)
fmt.Printf("%s\n", filepath.Join(outputPath, erFileName))

file, err := os.OpenFile(filepath.Join(fullPath, erFileName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) // #nosec
if err != nil {
return errors.WithStack(err)
}
g := gviz.New(c)
if err := g.OutputSchema(file, s); err != nil {
return errors.WithStack(err)
}

// tables
for _, t := range s.Tables {
erFileName := fmt.Sprintf("%s.%s", t.Name, erFormat)
fmt.Printf("%s\n", filepath.Join(outputPath, erFileName))

file, err := os.OpenFile(filepath.Join(fullPath, erFileName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) // #nosec
if err != nil {
return errors.WithStack(err)
}
if err := g.OutputTable(file, t); err != nil {
return errors.WithStack(err)
}
}

return nil
}

func withSchemaFile(s *schema.Schema, c *config.Config) (e error) {
sf, err := os.Create(c.SchemaFilePath())
if err != nil {
Expand Down Expand Up @@ -207,22 +161,6 @@ func loadDocArgs(args []string) ([]config.Option, error) {
return options, nil
}

func outputErExists(s *schema.Schema, path string) bool {
// schema.png
erFileName := fmt.Sprintf("schema.%s", erFormat)
if _, err := os.Lstat(filepath.Join(path, erFileName)); err == nil {
return true
}
// tables
for _, t := range s.Tables {
erFileName := fmt.Sprintf("%s.%s", t.Name, erFormat)
if _, err := os.Lstat(filepath.Join(path, erFileName)); err == nil {
return true
}
}
return false
}

func init() {
rootCmd.AddCommand(docCmd)
docCmd.Flags().StringVarP(&dsn, "dsn", "", "", "data source name")
Expand Down
119 changes: 41 additions & 78 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,27 +389,6 @@ func (c *Config) ModifySchema(s *schema.Schema) error {
for _, l := range c.Labels {
s.Labels = s.Labels.Merge(l)
}
// set Viewpoints
for _, v := range c.Viewpoints {
s.Viewpoints = s.Viewpoints.Merge(&schema.Viewpoint{
Name: v.Name,
Desc: v.Desc,
Labels: v.Labels,
Tables: v.Tables,
})
}
for _, v := range s.Viewpoints {
L:
for _, l := range v.Labels {
for _, t := range s.Tables {
if t.Labels.Contains(l) {
continue L
}
}
return fmt.Errorf("viewpoint '%s' has unknown label '%s'", v.Name, l)
}
}

if err := detectCardinality(s); err != nil {
return err
}
Expand Down Expand Up @@ -441,6 +420,41 @@ func (c *Config) ModifySchema(s *schema.Schema) error {
if err := c.detectShowColumnsForER(s); err != nil {
return err
}

// set Viewpoints
// viewpoints should be created using as complete a schema as possible
for _, v := range c.Viewpoints {
cs, err := s.Clone()
if err != nil {
return err
}
if err := cs.Filter(&schema.FilterOption{
Include: v.Tables,
IncludeLabels: v.Labels,
Distance: 0,
}); err != nil {
return err
}
s.Viewpoints = s.Viewpoints.Merge(&schema.Viewpoint{
Name: v.Name,
Desc: v.Desc,
Labels: v.Labels,
Tables: v.Tables,
Schema: cs,
})
}
for _, v := range s.Viewpoints {
L:
for _, l := range v.Labels {
for _, t := range s.Tables {
if t.Labels.Contains(l) {
continue L
}
}
return fmt.Errorf("viewpoint '%s' has unknown label '%s'", v.Name, l)
}
}

return nil
}

Expand All @@ -459,63 +473,12 @@ func (c *Config) MergeAdditionalData(s *schema.Schema) error {

// FilterTables filter tables from schema.Schema using include: and exclude: and includeLabels
func (c *Config) FilterTables(s *schema.Schema) error {
i := append(c.Include, s.NormalizeTableNames(c.Include)...)
e := append(c.Exclude, s.NormalizeTableNames(c.Exclude)...)

includes := []*schema.Table{}
excludes := []*schema.Table{}
for _, t := range s.Tables {
li, mi := matchLength(i, t.Name)
le, me := matchLength(e, t.Name)
ml := matchLabels(c.includeLabels, t.Labels)
switch {
case mi:
if me && li < le {
excludes = append(excludes, t)
continue
}
includes = append(includes, t)
case ml:
if me {
excludes = append(excludes, t)
continue
}
includes = append(includes, t)
case len(c.Include) == 0 && len(c.includeLabels) == 0:
if me {
excludes = append(excludes, t)
continue
}
includes = append(includes, t)
default:
excludes = append(excludes, t)
}
}

collects := []*schema.Table{}
for _, t := range includes {
ts, _, err := t.CollectTablesAndRelations(c.Distance, true)
if err != nil {
return err
}
for _, tt := range ts {
if !tt.Contains(includes) {
collects = append(collects, tt)
}
}
}

for _, t := range excludes {
if t.Contains(collects) {
continue
}
err := excludeTableFromSchema(t.Name, s)
if err != nil {
return errors.Wrap(errors.WithStack(err), fmt.Sprintf("failed to filter table '%s'", t.Name))
}
}

return nil
return s.Filter(&schema.FilterOption{
Include: c.Include,
Exclude: c.Exclude,
IncludeLabels: c.includeLabels,
Distance: c.Distance,
})
}

func (c *Config) mergeDictFromSchema(s *schema.Schema) {
Expand Down
5 changes: 3 additions & 2 deletions config/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ type Templates struct {
// MD holds the paths to the markdown template files.
// If populated the files are used to override the default ones.
type MD struct {
Index string `yaml:"index,omitempty"`
Table string `yaml:"table,omitempty"`
Index string `yaml:"index,omitempty"`
Table string `yaml:"table,omitempty"`
Viewpoint string `yaml:"viewpoint,omitempty"`
}

// Dot holds the paths to the dot template files.
Expand Down
7 changes: 0 additions & 7 deletions config/viewpoints.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
package config

import "github.com/k1LoW/tbls/schema"

type Viewpoint struct {
Name string `yaml:"name,omitempty"`
Desc string `yaml:"desc,omitempty"`
Labels []string `yaml:"labels,omitempty"`
Tables []string `yaml:"tables,omitempty"`
}

func (v *Viewpoint) FilterTables(s *schema.Schema) error {
c := &Config{Include: v.Tables, includeLabels: v.Labels}
return c.FilterTables(s)
}
100 changes: 96 additions & 4 deletions output/gviz/gviz.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gviz

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
Expand Down Expand Up @@ -33,7 +34,7 @@ func New(c *config.Config) *Gviz {
}
}

// OutputSchema output dot format for full relation.
// OutputSchema generage image for full relation.
func (g *Gviz) OutputSchema(wr io.Writer, s *schema.Schema) error {
buf := &bytes.Buffer{}
if err := g.dot.OutputSchema(buf, s); err != nil {
Expand All @@ -42,11 +43,19 @@ func (g *Gviz) OutputSchema(wr io.Writer, s *schema.Schema) error {
return g.render(wr, buf.Bytes())
}

// OutputTable output dot format for table.
// OutputTable generage image for table.
func (g *Gviz) OutputTable(wr io.Writer, t *schema.Table) error {
buf := &bytes.Buffer{}
err := g.dot.OutputTable(buf, t)
if err != nil {
if err := g.dot.OutputTable(buf, t); err != nil {
return errors.WithStack(err)
}
return g.render(wr, buf.Bytes())
}

// OutputViewpoint generage image for viewpoint.
func (g *Gviz) OutputViewpoint(wr io.Writer, v *schema.Viewpoint) error {
buf := &bytes.Buffer{}
if err := g.dot.OutputSchema(buf, v.Schema); err != nil {
return errors.WithStack(err)
}
return g.render(wr, buf.Bytes())
Expand Down Expand Up @@ -79,6 +88,66 @@ func (g *Gviz) render(wr io.Writer, b []byte) (e error) {
return nil
}

// Output generate images.
func Output(s *schema.Schema, c *config.Config, force bool) (e error) {
erFormat := c.ER.Format
outputPath := c.DocPath
fullPath, err := filepath.Abs(outputPath)
if err != nil {
return errors.WithStack(err)
}

if !force && outputErExists(s, c.ER.Format, fullPath) {
return errors.New("output ER diagram files already exists")
}

err = os.MkdirAll(fullPath, 0755) // #nosec
if err != nil {
return errors.WithStack(err)
}

fn := fmt.Sprintf("schema.%s", erFormat)
fmt.Printf("%s\n", filepath.Join(outputPath, fn))

f, err := os.OpenFile(filepath.Join(fullPath, fn), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) // #nosec
if err != nil {
return errors.WithStack(err)
}
g := New(c)
if err := g.OutputSchema(f, s); err != nil {
return errors.WithStack(err)
}

// tables
for _, t := range s.Tables {
fn := fmt.Sprintf("%s.%s", t.Name, erFormat)
fmt.Printf("%s\n", filepath.Join(outputPath, fn))

f, err := os.OpenFile(filepath.Join(fullPath, fn), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) // #nosec
if err != nil {
return errors.WithStack(err)
}
if err := g.OutputTable(f, t); err != nil {
return errors.WithStack(err)
}
}

// viewpoints
for i, v := range s.Viewpoints {
fn := fmt.Sprintf("viewpoint-%d.%s", i, erFormat)
fmt.Printf("%s\n", filepath.Join(outputPath, fn))
f, err := os.OpenFile(filepath.Join(fullPath, fn), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) // #nosec
if err != nil {
return errors.WithStack(err)
}
if err := g.OutputViewpoint(f, v); err != nil {
return errors.WithStack(err)
}
}

return nil
}

// getFaceFunc
func getFaceFunc(keyword string) (func(size float64) (font.Face, error), error) {
var (
Expand Down Expand Up @@ -135,3 +204,26 @@ func getFaceFunc(keyword string) (func(size float64) (font.Face, error), error)
}
return faceFunc, nil
}

func outputErExists(s *schema.Schema, erFormat, path string) bool {
// schema.png
fn := fmt.Sprintf("schema.%s", erFormat)
if _, err := os.Lstat(filepath.Join(path, fn)); err == nil {
return true
}
// tables
for _, t := range s.Tables {
fn := fmt.Sprintf("%s.%s", t.Name, erFormat)
if _, err := os.Lstat(filepath.Join(path, fn)); err == nil {
return true
}
}
// viewpoints
for i := range s.Viewpoints {
fn := fmt.Sprintf("viewpoint-%d.%s", i, erFormat)
if _, err := os.Lstat(filepath.Join(path, fn)); err == nil {
return true
}
}
return false
}
Loading

0 comments on commit 1bed38d

Please # to comment.