diff --git a/cmd/entc/entc.go b/cmd/entc/entc.go index 0d65572c9..d6e556cd5 100644 --- a/cmd/entc/entc.go +++ b/cmd/entc/entc.go @@ -72,7 +72,8 @@ func main() { Run: func(cmd *cobra.Command, path []string) { graph, err := entc.LoadGraph(path[0], &gen.Config{}) failOnErr(err) - graph.Describe(os.Stdout) + p := printer{os.Stdout} + p.Print(graph) }, }, func() *cobra.Command { diff --git a/cmd/entc/print.go b/cmd/entc/print.go new file mode 100644 index 000000000..e87069539 --- /dev/null +++ b/cmd/entc/print.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "io" + "reflect" + "strconv" + "strings" + + "github.com/facebookincubator/ent/entc/gen" + + "github.com/olekukonko/tablewriter" +) + +// printer is a table printer for ent graphs. +type printer struct { + io.Writer +} + +// Print prints a table description of the graph to the given writer. +func (p *printer) Print(g *gen.Graph) { + for _, n := range g.Nodes { + p.node(n) + } +} + +// node returns description of a type. The format of the description is: +// +// Type: +// +// +// +// +func (p *printer) node(t *gen.Type) { + var ( + b strings.Builder + table = tablewriter.NewWriter(&b) + header = []string{"Field", "Type", "Unique", "Optional", "Nillable", "Default", "UpdateDefault", "Immutable", "StructTag", "Validators"} + ) + b.WriteString(t.Name + ":\n") + table.SetAutoFormatHeaders(false) + table.SetHeader(header) + for _, f := range append([]*gen.Field{t.ID}, t.Fields...) { + v := reflect.ValueOf(*f) + row := make([]string, len(header)) + for i := range row { + field := v.FieldByNameFunc(func(name string) bool { + // The first field is mapped from "Name" to "Field". + return name == "Name" && i == 0 || name == header[i] + }) + row[i] = fmt.Sprint(field.Interface()) + } + table.Append(row) + } + table.Render() + table = tablewriter.NewWriter(&b) + table.SetAutoFormatHeaders(false) + table.SetHeader([]string{"Edge", "Type", "Inverse", "BackRef", "Relation", "Unique", "Optional"}) + for _, e := range t.Edges { + table.Append([]string{ + e.Name, + e.Type.Name, + strconv.FormatBool(e.IsInverse()), + e.Inverse, + e.Rel.Type.String(), + strconv.FormatBool(e.Unique), + strconv.FormatBool(e.Optional), + }) + } + if table.NumLines() > 0 { + table.Render() + } + io.WriteString(p, strings.ReplaceAll(b.String(), "\n", "\n\t")+"\n") +} diff --git a/cmd/entc/print_test.go b/cmd/entc/print_test.go new file mode 100644 index 000000000..815ef9ae2 --- /dev/null +++ b/cmd/entc/print_test.go @@ -0,0 +1,174 @@ +package main + +import ( + "strings" + "testing" + + "github.com/facebookincubator/ent/entc/gen" + "github.com/facebookincubator/ent/schema/field" + + "github.com/stretchr/testify/assert" +) + +func TestPrinter_Print(t *testing.T) { + tests := []struct { + input *gen.Graph + out string + }{ + { + input: &gen.Graph{ + Nodes: []*gen.Type{ + { + Name: "User", + ID: &gen.Field{Name: "id", Type: &field.TypeInfo{Type: field.TypeInt}}, + Fields: []*gen.Field{ + {Name: "name", Type: &field.TypeInfo{Type: field.TypeString}, Validators: 1}, + {Name: "age", Type: &field.TypeInfo{Type: field.TypeInt}, Nillable: true}, + {Name: "created_at", Type: &field.TypeInfo{Type: field.TypeTime}, Nillable: true, Immutable: true}, + }, + }, + }, + }, + out: ` +User: + +------------+-----------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | Field | Type | Unique | Optional | Nillable | Default | UpdateDefault | Immutable | StructTag | Validators | + +------------+-----------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | id | int | false | false | false | false | false | false | | 0 | + | name | string | false | false | false | false | false | false | | 1 | + | age | int | false | false | true | false | false | false | | 0 | + | created_at | time.Time | false | false | true | false | false | true | | 0 | + +------------+-----------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + +`, + }, + { + input: &gen.Graph{ + Nodes: []*gen.Type{ + { + Name: "User", + ID: &gen.Field{Name: "id", Type: &field.TypeInfo{Type: field.TypeInt}}, + Edges: []*gen.Edge{ + {Name: "groups", Type: &gen.Type{Name: "Group"}, Rel: gen.Relation{Type: gen.M2M}, Optional: true}, + {Name: "spouse", Type: &gen.Type{Name: "User"}, Unique: true, Rel: gen.Relation{Type: gen.O2O}}, + }, + }, + }, + }, + out: ` +User: + +-------+------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | Field | Type | Unique | Optional | Nillable | Default | UpdateDefault | Immutable | StructTag | Validators | + +-------+------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | id | int | false | false | false | false | false | false | | 0 | + +-------+------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + +--------+-------+---------+---------+----------+--------+----------+ + | Edge | Type | Inverse | BackRef | Relation | Unique | Optional | + +--------+-------+---------+---------+----------+--------+----------+ + | groups | Group | false | | M2M | false | true | + | spouse | User | false | | O2O | true | false | + +--------+-------+---------+---------+----------+--------+----------+ + +`, + }, + { + input: &gen.Graph{ + Nodes: []*gen.Type{ + { + Name: "User", + ID: &gen.Field{Name: "id", Type: &field.TypeInfo{Type: field.TypeInt}}, + Fields: []*gen.Field{ + {Name: "name", Type: &field.TypeInfo{Type: field.TypeString}, Validators: 1}, + {Name: "age", Type: &field.TypeInfo{Type: field.TypeInt}, Nillable: true}, + }, + Edges: []*gen.Edge{ + {Name: "groups", Type: &gen.Type{Name: "Group"}, Rel: gen.Relation{Type: gen.M2M}, Optional: true}, + {Name: "spouse", Type: &gen.Type{Name: "User"}, Unique: true, Rel: gen.Relation{Type: gen.O2O}}, + }, + }, + }, + }, + out: ` +User: + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | Field | Type | Unique | Optional | Nillable | Default | UpdateDefault | Immutable | StructTag | Validators | + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | id | int | false | false | false | false | false | false | | 0 | + | name | string | false | false | false | false | false | false | | 1 | + | age | int | false | false | true | false | false | false | | 0 | + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + +--------+-------+---------+---------+----------+--------+----------+ + | Edge | Type | Inverse | BackRef | Relation | Unique | Optional | + +--------+-------+---------+---------+----------+--------+----------+ + | groups | Group | false | | M2M | false | true | + | spouse | User | false | | O2O | true | false | + +--------+-------+---------+---------+----------+--------+----------+ + +`, + }, + { + input: &gen.Graph{ + Nodes: []*gen.Type{ + { + Name: "User", + ID: &gen.Field{Name: "id", Type: &field.TypeInfo{Type: field.TypeInt}}, + Fields: []*gen.Field{ + {Name: "name", Type: &field.TypeInfo{Type: field.TypeString}, Validators: 1}, + {Name: "age", Type: &field.TypeInfo{Type: field.TypeInt}, Nillable: true}, + }, + Edges: []*gen.Edge{ + {Name: "groups", Type: &gen.Type{Name: "Group"}, Rel: gen.Relation{Type: gen.M2M}, Optional: true}, + {Name: "spouse", Type: &gen.Type{Name: "User"}, Unique: true, Rel: gen.Relation{Type: gen.O2O}}, + }, + }, + { + Name: "Group", + ID: &gen.Field{Name: "id", Type: &field.TypeInfo{Type: field.TypeInt}}, + Fields: []*gen.Field{ + {Name: "name", Type: &field.TypeInfo{Type: field.TypeString}}, + }, + Edges: []*gen.Edge{ + {Name: "users", Type: &gen.Type{Name: "User"}, Rel: gen.Relation{Type: gen.M2M}, Optional: true}, + }, + }, + }, + }, + out: ` +User: + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | Field | Type | Unique | Optional | Nillable | Default | UpdateDefault | Immutable | StructTag | Validators | + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | id | int | false | false | false | false | false | false | | 0 | + | name | string | false | false | false | false | false | false | | 1 | + | age | int | false | false | true | false | false | false | | 0 | + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + +--------+-------+---------+---------+----------+--------+----------+ + | Edge | Type | Inverse | BackRef | Relation | Unique | Optional | + +--------+-------+---------+---------+----------+--------+----------+ + | groups | Group | false | | M2M | false | true | + | spouse | User | false | | O2O | true | false | + +--------+-------+---------+---------+----------+--------+----------+ + +Group: + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | Field | Type | Unique | Optional | Nillable | Default | UpdateDefault | Immutable | StructTag | Validators | + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + | id | int | false | false | false | false | false | false | | 0 | + | name | string | false | false | false | false | false | false | | 0 | + +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ + +-------+------+---------+---------+----------+--------+----------+ + | Edge | Type | Inverse | BackRef | Relation | Unique | Optional | + +-------+------+---------+---------+----------+--------+----------+ + | users | User | false | | M2M | false | true | + +-------+------+---------+---------+----------+--------+----------+ + +`, + }, + } + for _, tt := range tests { + b := &strings.Builder{} + p := printer{b} + p.Print(tt.input) + assert.Equal(t, tt.out, "\n"+b.String()) + } +} diff --git a/entc/gen/graph.go b/entc/gen/graph.go index bd4fac275..c30ecece6 100644 --- a/entc/gen/graph.go +++ b/entc/gen/graph.go @@ -8,7 +8,6 @@ package gen import ( "bytes" "fmt" - "io" "io/ioutil" "os" "path/filepath" @@ -114,13 +113,6 @@ func (g *Graph) Gen() (err error) { return formatFiles(written) } -// Describe writes a description of the graph to the given writer. -func (g *Graph) Describe(w io.Writer) { - for _, n := range g.Nodes { - n.Describe(w) - } -} - // addNode creates a new Type/Node/Ent to the graph. func (g *Graph) addNode(schema *load.Schema) { t, err := NewType(g.Config, schema) diff --git a/entc/gen/type.go b/entc/gen/type.go index 83e4b1751..235d73c03 100644 --- a/entc/gen/type.go +++ b/entc/gen/type.go @@ -7,18 +7,14 @@ package gen import ( "fmt" "go/token" - "io" "reflect" "sort" - "strconv" "strings" "unicode" "github.com/facebookincubator/ent/dialect/sql/schema" "github.com/facebookincubator/ent/entc/load" "github.com/facebookincubator/ent/schema/field" - - "github.com/olekukonko/tablewriter" ) type ( @@ -331,48 +327,6 @@ func (t Type) TagTypes() []string { return r } -// Describe returns description of a type. The format of the description is: -// -// Type: -// -// -// -// -func (t Type) Describe(w io.Writer) { - b := &strings.Builder{} - b.WriteString(t.Name + ":\n") - table := tablewriter.NewWriter(b) - table.SetAutoFormatHeaders(false) - table.SetHeader([]string{"Field", "Type", "Unique", "Optional", "Nillable", "Default", "UpdateDefault", "Immutable", "StructTag", "Validators"}) - for _, f := range append([]*Field{t.ID}, t.Fields...) { - v := reflect.ValueOf(*f) - row := make([]string, v.NumField()-2) - for i := range row { - row[i] = fmt.Sprint(v.Field(i + 1).Interface()) - } - table.Append(row) - } - table.Render() - table = tablewriter.NewWriter(b) - table.SetAutoFormatHeaders(false) - table.SetHeader([]string{"Edge", "Type", "Inverse", "BackRef", "Relation", "Unique", "Optional"}) - for _, e := range t.Edges { - table.Append([]string{ - e.Name, - e.Type.Name, - strconv.FormatBool(e.IsInverse()), - e.Inverse, - e.Rel.Type.String(), - strconv.FormatBool(e.Unique), - strconv.FormatBool(e.Optional), - }) - } - if table.NumLines() > 0 { - table.Render() - } - io.WriteString(w, strings.ReplaceAll(b.String(), "\n", "\n\t")+"\n") -} - // AddIndex adds a new index for the type. // It fails if the schema index is invalid. func (t *Type) AddIndex(idx *load.Index) error { diff --git a/entc/gen/type_test.go b/entc/gen/type_test.go index 6ead1c737..19903a5bc 100644 --- a/entc/gen/type_test.go +++ b/entc/gen/type_test.go @@ -5,13 +5,11 @@ package gen import ( - "strings" "testing" "github.com/facebookincubator/ent/entc/load" "github.com/facebookincubator/ent/schema/field" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -265,95 +263,3 @@ func TestEdge(t *testing.T) { require.Equal(t, "user_groups", users.Label()) require.Equal(t, "user_groups", groups.Label()) } - -func TestType_Describe(t *testing.T) { - tests := []struct { - typ *Type - out string - }{ - { - typ: &Type{ - Name: "User", - ID: &Field{Name: "id", Type: &field.TypeInfo{Type: field.TypeInt}}, - Fields: []*Field{ - {Name: "name", Type: &field.TypeInfo{Type: field.TypeString}, Validators: 1}, - {Name: "age", Type: &field.TypeInfo{Type: field.TypeInt}, Nillable: true}, - {Name: "created_at", Type: &field.TypeInfo{Type: field.TypeTime}, Nillable: true, Immutable: true}, - }, - }, - out: ` -User: - +------------+-----------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - | Field | Type | Unique | Optional | Nillable | Default | UpdateDefault | Immutable | StructTag | Validators | - +------------+-----------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - | id | int | false | false | false | false | false | false | | 0 | - | name | string | false | false | false | false | false | false | | 1 | - | age | int | false | false | true | false | false | false | | 0 | - | created_at | time.Time | false | false | true | false | false | true | | 0 | - +------------+-----------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - -`, - }, - { - typ: &Type{ - Name: "User", - ID: &Field{Name: "id", Type: &field.TypeInfo{Type: field.TypeInt}}, - Edges: []*Edge{ - {Name: "groups", Type: &Type{Name: "Group"}, Rel: Relation{Type: M2M}, Optional: true}, - {Name: "spouse", Type: &Type{Name: "User"}, Unique: true, Rel: Relation{Type: O2O}}, - }, - }, - out: ` -User: - +-------+------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - | Field | Type | Unique | Optional | Nillable | Default | UpdateDefault | Immutable | StructTag | Validators | - +-------+------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - | id | int | false | false | false | false | false | false | | 0 | - +-------+------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - +--------+-------+---------+---------+----------+--------+----------+ - | Edge | Type | Inverse | BackRef | Relation | Unique | Optional | - +--------+-------+---------+---------+----------+--------+----------+ - | groups | Group | false | | M2M | false | true | - | spouse | User | false | | O2O | true | false | - +--------+-------+---------+---------+----------+--------+----------+ - -`, - }, - { - typ: &Type{ - Name: "User", - ID: &Field{Name: "id", Type: &field.TypeInfo{Type: field.TypeInt}}, - Fields: []*Field{ - {Name: "name", Type: &field.TypeInfo{Type: field.TypeString}, Validators: 1}, - {Name: "age", Type: &field.TypeInfo{Type: field.TypeInt}, Nillable: true}, - }, - Edges: []*Edge{ - {Name: "groups", Type: &Type{Name: "Group"}, Rel: Relation{Type: M2M}, Optional: true}, - {Name: "spouse", Type: &Type{Name: "User"}, Unique: true, Rel: Relation{Type: O2O}}, - }, - }, - out: ` -User: - +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - | Field | Type | Unique | Optional | Nillable | Default | UpdateDefault | Immutable | StructTag | Validators | - +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - | id | int | false | false | false | false | false | false | | 0 | - | name | string | false | false | false | false | false | false | | 1 | - | age | int | false | false | true | false | false | false | | 0 | - +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------+------------+ - +--------+-------+---------+---------+----------+--------+----------+ - | Edge | Type | Inverse | BackRef | Relation | Unique | Optional | - +--------+-------+---------+---------+----------+--------+----------+ - | groups | Group | false | | M2M | false | true | - | spouse | User | false | | O2O | true | false | - +--------+-------+---------+---------+----------+--------+----------+ - -`, - }, - } - for _, tt := range tests { - b := &strings.Builder{} - tt.typ.Describe(b) - assert.Equal(t, tt.out, "\n"+b.String()) - } -}