diff --git a/Makefile b/Makefile index 0ba69163..dc99eecc 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ GO_FILES=$(shell find . -type f -name '*.go') # Standard dependencies are installed via go get DEPEND=\ golang.org/x/tools/cmd/goimports@latest \ - honnef.co/go/tools/cmd/staticcheck@latest \ + github.com/golangci/golangci-lint/cmd/golangci-lint@latest \ github.com/mjibson/esc@latest all: lint check-generated test @@ -41,8 +41,8 @@ ifneq ($(GOOS),windows) @if [ "`goimports -l $(GO_FILES) | tee /dev/stderr`" ]; then \ echo "^ - Repo contains improperly formatted go files" && echo && exit 1; \ fi - @if [ "`staticcheck ./... | tee /dev/stderr`" ]; then \ - echo "^ - staticcheck errors!" && echo && exit 1; \ + @if [ "`golangci-lint run ./... | tee /dev/stderr`" ]; then \ + echo "^ - golangci-lint errors!" && echo && exit 1; \ fi endif diff --git a/cmd/mdl/main.go b/cmd/mdl/main.go index 21fd9e1e..49369b7a 100644 --- a/cmd/mdl/main.go +++ b/cmd/mdl/main.go @@ -62,13 +62,19 @@ func main() { switch cmd { case "gen": addGlobals(genset) - genset.Parse(os.Args[idx:]) + if err := genset.Parse(os.Args[idx:]); err != nil { + fail(err.Error()) + } case "serve": addGlobals(svrset) - svrset.Parse(os.Args[idx:]) + if err := svrset.Parse(os.Args[idx:]); err != nil { + fail(err.Error()) + } default: addGlobals(gset) - gset.Parse(os.Args[idx:]) + if err := gset.Parse(os.Args[idx:]); err != nil { + fail(err.Error()) + } } if *h || *help { @@ -154,7 +160,7 @@ func printUsage(fss ...*flag.FlagSet) { } } -func fail(format string, args ...interface{}) { +func fail(format string, args ...any) { fmt.Fprintf(os.Stderr, format+"\n", args...) os.Exit(1) } diff --git a/cmd/mdl/serve.go b/cmd/mdl/serve.go index ac36eeae..b0bab2ac 100644 --- a/cmd/mdl/serve.go +++ b/cmd/mdl/serve.go @@ -11,6 +11,7 @@ import ( "strings" "sync" + "goa.design/model/codegen" "goa.design/model/mdl" ) @@ -31,7 +32,7 @@ type ( } // Layout is position info saved for one view (diagram) - Layout = map[string]interface{} + Layout = map[string]any // Layouts is a map from view key to the view Layout Layouts = map[string]Layout @@ -81,7 +82,7 @@ func (s *Server) Serve(outDir string, devmode bool, port int) error { http.HandleFunc("/data/save", func(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") if id == "" { - http.Error(w, "Param id is missing", http.StatusBadRequest) + handleError(w, fmt.Errorf("missing id")) return } @@ -91,9 +92,7 @@ func (s *Server) Serve(outDir string, devmode bool, port int) error { svgFile := path.Join(outDir, id+".svg") f, err := os.Create(svgFile) if err != nil { - msg := fmt.Sprintf("Saving failed, can't write to %s: %s!\n", svgFile, err) - fmt.Println(msg) - http.Error(w, msg, http.StatusInternalServerError) + handleError(w, err) return } defer func() { _ = f.Close() }() @@ -102,6 +101,32 @@ func (s *Server) Serve(outDir string, devmode bool, port int) error { w.WriteHeader(http.StatusAccepted) }) + http.HandleFunc("/data/gen", func(w http.ResponseWriter, r *http.Request) { + var design mdl.Design + + s.lock.Lock() + defer s.lock.Unlock() + + if err := json.NewDecoder(r.Body).Decode(&design); err != nil { + handleError(w, err) + return + } + + s.SetDesign(&design) + + dsl, err := codegen.Model(&design, outDir) + if err != nil { + handleError(w, err) + return + } + if err := os.WriteFile(path.Join(outDir, "model.go"), dsl, 0644); err != nil { + handleError(w, err) + return + } + + w.WriteHeader(http.StatusNoContent) + }) + // start the server fmt.Printf("Editor started. Open http://localhost:%d in your browser.\n", port) return http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", port), nil) @@ -123,6 +148,12 @@ func (s *Server) SetDesign(d *mdl.Design) { s.design = b } +// handleError writes the given error to stderr and http.Error. +func handleError(w http.ResponseWriter, err error) { + fmt.Fprintln(os.Stderr, err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) +} + // loadLayouts lists out directory and reads layout info from SVG files // for backwards compatibility, fallback to layout.json func loadLayouts(dir string) ([]byte, error) { @@ -164,7 +195,7 @@ func loadLayouts(dir string) ([]byte, error) { end := bytes.Index(b, endMark) b = b[begin:end] - var l Layout = make(map[string]interface{}) + var l Layout = make(map[string]any) err = json.Unmarshal(b, &l) if err != nil { return nil, err diff --git a/cmd/mdl/webapp.go b/cmd/mdl/webapp.go index da3bf787..22de2aa9 100644 --- a/cmd/mdl/webapp.go +++ b/cmd/mdl/webapp.go @@ -145,7 +145,7 @@ func (f *_escFile) IsDir() bool { return f.isDir } -func (f *_escFile) Sys() interface{} { +func (f *_escFile) Sys() any { return f } diff --git a/cmd/stz/main.go b/cmd/stz/main.go index 66504ee6..ef05a10a 100644 --- a/cmd/stz/main.go +++ b/cmd/stz/main.go @@ -46,7 +46,9 @@ func main() { } } done: - fs.Parse(os.Args[idx:]) + if err := fs.Parse(os.Args[idx:]); err != nil { + fail(err.Error()) + } pathOrDefault := func(p string) string { if p == "" { @@ -208,7 +210,7 @@ func put(path, wid, key, secret string, debug bool) error { return c.Put(wid, local) } -func fail(format string, args ...interface{}) { +func fail(format string, args ...any) { fmt.Fprintf(os.Stderr, format+"\n", args...) os.Exit(1) } diff --git a/codegen/model.go b/codegen/model.go new file mode 100644 index 00000000..9f6bc24e --- /dev/null +++ b/codegen/model.go @@ -0,0 +1,748 @@ +package codegen + +import ( + "bytes" + "fmt" + "strings" + + "goa.design/goa/v3/codegen" + "golang.org/x/tools/imports" + + "goa.design/model/dsl" + "goa.design/model/expr" + "goa.design/model/mdl" + model "goa.design/model/pkg" +) + +// Tags that should not be generated in DSL. +var builtInTags []string + +func init() { + for _, tags := range [][]string{ + expr.PersonTags, + expr.SoftwareSystemTags, + expr.ContainerTags, + expr.ComponentTags, + expr.DeploymentNodeTags, + expr.InfrastructureNodeTags, + expr.ContainerInstanceTags, + expr.RelationshipTags, + } { + builtInTags = append(builtInTags, tags...) + } +} + +// Model generates the model DSL from the given design package. +// pkg is the name of the generated package (e.g. "model"). +func Model(d *mdl.Design, pkg string) ([]byte, error) { + if d.Model == nil { + return nil, fmt.Errorf("model is nil") + } + var template string + for name, tmpl := range templates { + template += fmt.Sprintf("{{define %q}}%s{{end}}", name, tmpl) + } + template += designT + section := &codegen.SectionTemplate{ + Name: pkg, + Source: template, + Data: map[string]any{ + "Design": d, + "Pkg": pkg, + "ToolVersion": model.Version(), + }, + FuncMap: map[string]any{ + "elementPath": elementPath, + "filterTags": filterTags, + "softwareSystemData": softwareSystemData, + "containerData": containerData, + "componentData": componentData, + "personData": personData, + "systemLandscapeViewData": systemLandscapeViewData, + "systemContextViewData": systemContextViewData, + "containerViewData": containerViewData, + "componentViewData": componentViewData, + "viewPropsData": viewPropsData, + "relData": relData, + "relDSLFunc": relDSLFunc, + "systemHasFunc": systemHasFunc, + "containerHasFunc": containerHasFunc, + "componentHasFunc": componentHasFunc, + "personHasFunc": personHasFunc, + "autoLayoutHasFunc": autoLayoutHasFunc, + "hasViews": hasViews, + "findRelationship": findRelationship, + "deref": func(i *int) int { return *i }, + }, + } + var buf bytes.Buffer + if err := section.Write(&buf); err != nil { + return nil, err + } + opt := imports.Options{Comments: true, FormatOnly: true} + res, err := imports.Process("", buf.Bytes(), &opt) + if err != nil { + // Print content for troubleshooting + fmt.Println(buf.String()) + } + return res, err +} + +// relDSLFunc is a function used by the DSL codegen to compute the name of the +// DSL function used to represent the corresponding relationship, one of "Uses", +// "Delivers" or "InteractsWith". +func relDSLFunc(mod *mdl.Model, rel *mdl.Relationship) string { + var sourceIsPerson, destIsPerson bool + for _, p := range mod.People { + if p.ID == rel.SourceID { + sourceIsPerson = true + } else if p.ID == rel.DestinationID { + destIsPerson = true + } + } + if sourceIsPerson && destIsPerson { + return "InteractsWith" + } + if destIsPerson { + return "Delivers" + } + return "Uses" +} + +// relData produces a data structure appropriate for running the useT template. +func relData(mod *mdl.Model, rel *mdl.Relationship, current string) map[string]any { + return map[string]any{ + "Model": mod, + "Relationship": rel, + "CurrentPath": current, + } +} + +// softwareSystemData produces a data structure appropriate for running the systemT template. +func softwareSystemData(mod *mdl.Model, s *mdl.SoftwareSystem) map[string]any { + return map[string]any{ + "Model": mod, + "SoftwareSystem": s, + } +} + +// containerData produces a data structure appropriate for running the containerT template. +func containerData(mod *mdl.Model, c *mdl.Container, current string) map[string]any { + return map[string]any{ + "Model": mod, + "Container": c, + "CurrentPath": current, + } +} + +// componentData produces a data structure appropriate for running the componentT template. +func componentData(mod *mdl.Model, cmp *mdl.Component, current string) map[string]any { + return map[string]any{ + "Model": mod, + "Component": cmp, + "CurrentPath": current, + } +} + +// personData produces a data structure appropriate for running the personT template. +func personData(mod *mdl.Model, p *mdl.Person) map[string]any { + return map[string]any{ + "Model": mod, + "Person": p, + } +} + +// systemLandscapeViewData produces a data structure appropriate for running the systemLandscapeViewT template. +func systemLandscapeViewData(mod *mdl.Model, v *mdl.LandscapeView) map[string]any { + return map[string]any{ + "Model": mod, + "View": v, + } +} + +// systemContextViewData produces a data structure appropriate for running the systemContextViewT template. +func systemContextViewData(mod *mdl.Model, v *mdl.ContextView) map[string]any { + return map[string]any{ + "Model": mod, + "View": v, + } +} + +// containerViewData produces a data structure appropriate for running the containerViewT template. +func containerViewData(mod *mdl.Model, v *mdl.ContainerView) map[string]any { + return map[string]any{ + "Model": mod, + "View": v, + } +} + +// componentViewData produces a data structure appropriate for running the componentViewT template. +func componentViewData(mod *mdl.Model, v *mdl.ComponentView) map[string]any { + return map[string]any{ + "Model": mod, + "View": v, + } +} + +// viewPropsData produces a data structure appropriate for running the viewPropsT template. +func viewPropsData(mod *mdl.Model, v *mdl.ViewProps) map[string]any { + return map[string]any{ + "Model": mod, + "Props": v, + "DefaultRankSeparation": dsl.DefaultRankSeparation, + "DefaultNodeSeparation": dsl.DefaultNodeSeparation, + "DefaultEdgeSeparation": dsl.DefaultEdgeSeparation, + } +} + +// elementPath is used by templates codegen to compute the path to the element with the given ID. +func elementPath(mod *mdl.Model, id string, roots ...string) string { + var root string + if len(roots) > 0 { + root = roots[0] + } + for _, p := range mod.People { + if p.ID == id { + return p.Name + } + } + for _, s := range mod.Systems { + if s.ID == id { + return s.Name + } + for _, c := range s.Containers { + if c.ID == id { + if root == s.Name { + return c.Name + } + return fmt.Sprintf("%s/%s", s.Name, c.Name) + } + for _, cmp := range c.Components { + if cmp.ID == id { + if root == s.Name { + return fmt.Sprintf("%s/%s", c.Name, cmp.Name) + } + if root == fmt.Sprintf("%s/%s", s.Name, c.Name) { + return cmp.Name + } + return fmt.Sprintf("%s/%s/%s", s.Name, c.Name, cmp.Name) + } + } + } + } + return "" +} + +// findRelatioship returns the relationship with the given id. +func findRelationship(mod *mdl.Model, id string) *mdl.Relationship { + for _, p := range mod.People { + for _, rel := range p.Relationships { + if rel.ID == id { + return rel + } + } + } + for _, s := range mod.Systems { + for _, rel := range s.Relationships { + if rel.ID == id { + return rel + } + for _, c := range s.Containers { + for _, rel := range c.Relationships { + if rel.ID == id { + return rel + } + } + for _, cmp := range c.Components { + for _, rel := range cmp.Relationships { + if rel.ID == id { + return rel + } + } + } + } + } + } + return nil +} + +func filterTags(s string) []string { + parts := strings.Split(s, ",") + var res []string +loop: + for _, p := range parts { + p = strings.TrimSpace(p) + for _, builtIn := range builtInTags { + if p == builtIn { + continue loop + } + } + res = append(res, p) + } + return res +} + +// personHasFunc returns true if an anonymous DSL function must be generated for p. +func personHasFunc(p *mdl.Person) bool { + return len(filterTags(p.Tags)) > 0 || + p.URL != "" || + len(p.Properties) > 0 || + len(p.Relationships) > 0 +} + +// systemHasFunc returns true if an anonymous DSL function must be generated for s. +func systemHasFunc(s *mdl.SoftwareSystem) bool { + return len(filterTags(s.Tags)) > 0 || + s.URL != "" || + s.Location == mdl.LocationExternal || + len(s.Properties) > 0 || + len(s.Relationships) > 0 || + len(s.Containers) > 0 +} + +// containerHasFunc returns true if an anonymous DSL function must be generated for c. +func containerHasFunc(c *mdl.Container) bool { + return len(filterTags(c.Tags)) > 0 || + c.URL != "" || + len(c.Properties) > 0 || + len(c.Relationships) > 0 || + len(c.Components) > 0 +} + +// componentHasFunc returns true if an anonymous DSL function must be generated for cmp. +func componentHasFunc(cmp *mdl.Component) bool { + return len(filterTags(cmp.Tags)) > 0 || + cmp.URL != "" || + len(cmp.Properties) > 0 || + len(cmp.Relationships) > 0 +} + +// autoLayoutHasFunc returns true if an anonymous DSL function must be generated for v. +func autoLayoutHasFunc(l *mdl.AutoLayout) bool { + return l.RankSep != nil && *l.RankSep != dsl.DefaultRankSeparation || + l.NodeSep != nil && *l.NodeSep != dsl.DefaultNodeSeparation || + l.EdgeSep != nil && *l.EdgeSep != dsl.DefaultEdgeSeparation || + l.Vertices != nil && *l.Vertices +} + +// hasViews returns true if the given views is not empty. +func hasViews(v *mdl.Views) bool { + return len(v.LandscapeViews) > 0 || + len(v.ContextViews) > 0 || + len(v.ContainerViews) > 0 || + len(v.ComponentViews) > 0 || + len(v.DeploymentViews) > 0 || + len(v.FilteredViews) > 0 +} + +var templates = map[string]string{ + "systemT": systemT, + "containerT": containerT, + "componentT": componentT, + "personT": personT, + "useT": useT, + "deploymentEnvironmentT": deploymentEnvironmentT, + "deploymentNodeT": deploymentNodeT, + "infrastructureNodeT": infrastructureNodeT, + "containerInstanceT": containerInstanceT, + "healthCheckT": healthCheckT, + "viewPropsT": viewPropsT, + "systemLandscapeViewT": systemLandscapeViewT, + "systemContextViewT": systemContextViewT, + "containerViewT": containerViewT, + "componentViewT": componentViewT, + "filteredViewT": filteredViewT, + "deploymentView": deploymentViewT, + "dynamicView": dynamicViewT, + "styleT": styleT, +} + +const designT = `// Code generated by mdl {{.ToolVersion}}. + +package {{ .Pkg }} + +import . "goa.design/model/dsl" + +{{- with .Design }} +var _ = Design("{{.Name}}", "{{.Description}}", func() { + {{- if .Model.Enterprise }} + Enterprise({{ printf "%q" .Model.Enterprise }}) + {{- end }} + {{- range .Model.People }} + {{ template "personT" (personData $.Model .) }} + {{- end }} + {{- range .Model.Systems }} + {{ template "systemT" (softwareSystemData $.Design.Model .) }} + {{- end }} + {{- range .Model.DeploymentNodes }} + {{ template "deploymentEnvironmentT" . }} + {{- end }} + {{- if hasViews .Views }} + Views(func() { + {{- range .Views.LandscapeViews }} + {{ template "systemLandscapeViewT" (systemLandscapeViewData $.Design.Model .) }} + {{- end }} + {{- range .Views.ContextViews }} + {{ template "systemContextViewT" (systemContextViewData $.Design.Model .) }} + {{- end }} + {{- range .Views.ContainerViews }} + {{ template "containerViewT" (containerViewData $.Design.Model .) }} + {{- end }} + {{- range .Views.ComponentViews }} + {{ template "componentViewT" (componentViewData $.Design.Model .) }} + {{- end }} + {{- range .Views.DeploymentViews }} + {{ template "deploymentViewT" . }} + {{- end }} + {{- range .Views.FilteredViews }} + {{ template "filteredViewT" . }} + {{- end }} + {{- if .Views.Styles }} + {{ template "styleT" .Views.Styles }} + {{- end }} + }) + {{- end }} +}) +{{- end }}` + +var systemT = `SoftwareSystem("{{ .SoftwareSystem.Name}}", "{{ .SoftwareSystem.Description }}"{{ if systemHasFunc .SoftwareSystem }}, func() { + {{- range filterTags .SoftwareSystem.Tags }} + Tag({{ printf "%q" . }}) + {{- end }} + {{- if .SoftwareSystem.URL }} + URL({{ printf "%q" .SoftwareSystem.URL }}) + {{- end }} + {{- if eq .SoftwareSystem.Location 2 }} + External() + {{- end }} + {{- range $k, $v := .SoftwareSystem.Properties }} + Prop({{ printf "%q" $k }}, {{ printf "%q" $v }}) + {{- end }} + {{- range .SoftwareSystem.Containers }} + {{ template "containerT" (containerData $.Model . $.SoftwareSystem.Name) }} + {{- end }} + {{- range .SoftwareSystem.Relationships }} + {{ template "useT" (relData $.Model . .SoftwareSystem.Name) }} + {{- end }} +}{{ end }})` + +var containerT = `Container("{{ .Container.Name }}"{{ if .Container.Description }}, {{ printf "%q" .Container.Description }}{{ end }}{{ if .Container.Technology }}, {{ printf "%q" .Container.Technology }}{{ end }}{{ if containerHasFunc .Container }}, func() { + {{- range filterTags .Container.Tags }} + Tag({{ printf "%q" . }}) + {{- end }} + {{- if .Container.URL }} + URL({{ printf "%q" .Container.URL }}) + {{- end }} + {{- range $k, $v := .Container.Properties }} + Prop({{ printf "%q" $k }}, {{ printf "%q" $v }}) + {{- end }} + {{- range .Container.Components }} + {{ template "componentT" (componentData $.Model . (printf "%s/%s" $.CurrentPath $.Container.Name)) }} + {{- end }} + {{- range .Container.Relationships }} + {{ template "useT" (relData $.Model . $.CurrentPath) }} + {{- end }} +}{{ end }})` + +var componentT = `Component("{{ .Component.Name }}"{{ if .Component.Description }}, {{ printf "%q" .Component.Description }}{{ end }}{{ if .Component.Technology }}, {{ printf "%q" .Component.Technology }}{{ end }}{{ if componentHasFunc .Component }}, func() { + {{- range filterTags .Component.Tags }} + Tag({{ printf "%q" . }}) + {{- end }} + {{- if .Component.URL }} + URL({{ printf "%q" .Component.URL }}) + {{- end }} + {{- range $k, $v := .Component.Properties }} + Prop({{ printf "%q" $k }}, {{ printf "%q" $v }}) + {{- end }} + {{- range .Component.Relationships }} + {{ template "useT" (relData $.Model . $.CurrentPath) }} + {{- end }} +}{{ end }})` + +var personT = `Person("{{ .Person.Name }}"{{ if .Person.Description }}{{ printf "%q" .Person.Description}}{{ end }}{{ if personHasFunc .Person }}, func() { + {{- range filterTags .Person.Tags }} + Tag({{ printf "%q" . }}) + {{- end }} + {{- if .Person.URL }} + URL({{ printf "%q" .Person.URL }}) + {{- end }} + {{- range $k, $v := .Person.Properties }} + Prop({{ printf "%q" $k }}, {{ printf "%q" $v }}) + {{- end }} + {{- range .Person.Relationships }} + {{ template "useT" (relData $.Model . .Person.Name) }} + {{- end }} +}{{ end }})` + +var useT = `{{ relDSLFunc .Model .Relationship }}{{ with .Relationship }}("{{ elementPath $.Model .DestinationID $.CurrentPath }}"{{ if .Description}}, {{ printf "%q" .Description }}{{ end }}{{ if .Technology}}, {{ printf "%q" .Technology }}{{ end }}{{ if eq .InteractionStyle 1 }}, Synchronous{{ end }}{{ if eq .InteractionStyle 2 }}, Asynchronous{{ end }}{{ if or (filterTags .Tags) .URL }}, func() { + {{- range filterTags .Tags }} + Tag({{ printf "%q" . }}) + {{- end }} + {{- if .URL }} + URL({{ printf "%q" .URL }}) + {{- end }} +}{{ end }}){{ end }}` + +var deploymentEnvironmentT = `DeploymentEnvironment({{ printf "%q" .Environment }}, func() { + {{ template "deploymentNodeT" . }} +})` + +var deploymentNodeT = `DeploymentNode("{{.Name}}"{{ if .Description}}, {{ printf "%q" .Description }}{{ end }}{{ if .Technology}}, {{ printf "%q" .Technology }}{{ end }}, func() { + {{- range filterTags .Tags }} + Tag({{ printf "%q" . }}) + {{- end }} + {{- if .URL }} + URL({{ printf "%q" .URL }}) + {{- end }} + {{- if .Instances }} + Instances({{ .Instances }}) + {{- end }} + {{- range $k, $v := .Properties }} + Prop({{ printf "%q" $k }}, {{ printf "%q" $v }}) + {{- end }} + {{- range .Relationships }} + {{ template "useT" (relData $.Model . "") }} + {{- end }} + {{- range .Children }} + {{ template "deploymentNodeT" . }} + {{- end }} + {{- range .InfrastructureNodes }} + {{ template "infrastructureNodeT" . }} + {{- end }} + {{- range .ContainerInstances }} + {{ template "containerInstanceT" . }} + {{- end }} +})` + +var infrastructureNodeT = `InfrastructureNode("{{.Name}}"{{ if .Description}}, {{ printf "%q" .Description }}{{ end }}{{ if .Technology}}, {{ printf "%q" .Technology }}{{ end }}{{ if or .Tags .URL .Properties .Relationships }}, func() { + {{- range filterTags .Tags }} + Tag({{ printf "%q" . }}) + {{- end }} + {{- if .URL }} + URL({{ printf "%q" .URL }}) + {{- end }} + {{- range $k, $v := .Properties }} + Prop({{ printf "%q" $k }}, {{ printf "%q" $v }}) + {{- end }} + {{- range .Relationships }} + {{ template "useT" (relData $.Model . "") }} + {{- end }} +}{{ end }})` + +var containerInstanceT = `ContainerInstance("{{ elementPath $.Model .ContainerID .CurrentPath }}", func() { + InstanceID({{ .InstanceID }}) + {{- range filterTags .Tags "ContainerInstance" }} + Tag({{ printf "%q" . }}) + {{- end }} + {{- if .URL }} + URL({{ printf "%q" .URL }}) + {{- end }} + {{- range $k, $v := .Properties }} + Prop({{ printf "%q" $k }}, {{ printf "%q" $v }}) + {{- end }} + {{- range .HealthChecks }} + {{ template "healthCheckT" . }} + {{- end }} +})` + +var healthCheckT = `HealthCheck({{ printf "%q" .Name }}, func() { + URL({{ printf "%q" .URL }}) + Interval({{ .Interval }}) + Timeout({{ .Timeout }}) + {{- range $k, $v := .Headers }} + Header({{ printf "%q" $k }}, {{ printf "%q" $v }}) + {{- end }} +})` + +var viewPropsT = `{{ with .Props }}Title({{ printf "%q" .Title }}) +{{- if .PaperSize }} +PaperSize({{ .PaperSize.Name }}) +{{- end }} +{{- if .AutoLayout }}{{ with .AutoLayout }} +AutoLayout({{ .RankDirection.Name }}{{ if autoLayoutHasFunc . }}, func () { + {{- if and .RankSep (ne (deref .RankSep) $.DefaultRankSeparation) }} + RankSeparation({{ .RankSep }}) + {{- end }} + {{- if and .NodeSep (ne (deref .NodeSep) $.DefaultNodeSeparation) }} + NodeSeparation({{ .NodeSep }}) + {{- end }} + {{- if and .EdgeSep (ne (deref .EdgeSep) $.DefaultEdgeSeparation) }} + EdgeSeparation({{ .EdgeSep }}) + {{- end }} + {{- if .Vertices }} + RenderVertices() + {{- end }} +}{{ end }}) +{{- end }}{{- end }} +{{- with .Settings }} +{{- if .AddAll }} + AddAll() +{{- end }} +{{- if .AddDefault }} + AddDefault() +{{- end }} +{{- range .AddNeighborIDs }} + AddNeighbors("{{ elementPath $.Model . }}") +{{- end }} +{{- range .RemoveElementIDs }} + Remove("{{ elementPath $.Model . }}") +{{- end }} +{{- range .RemoveTags }} + RemoveTagged({{ printf "%q" . }}) +{{- end }} +{{- range .RemoveRelationshipIDs }} + {{- $rel := findRelationship $ . }} + {{- if $rel }} + Unlink("{{ elementPath $.Model $rel.SourceID }}", "{{ elementPath $.Model $rel.DestinationID }}"{{ if $rel.Description }}, {{ printf "%q" $rel.Description }}{{ end }}) + {{- end }} +{{- end }} +{{- range .RemoveUnreachableIDs }} + RemoveUnreachable("{{ elementPath $.Model .ID }}") +{{- end }} +{{- if .RemoveUnrelated }} + RemoveUnrelated() +{{- end }} +{{- end }} +{{- range .ElementViews }} + Add("{{ elementPath $.Model .ID }}"{{ if .X }}, func() { + Coord({{ .X }}, {{ .Y }}) + }{{ end }}) +{{- end }} +{{- range .RelationshipViews }} + {{- if .Source }} + Link("{{ elementPath $.Model .Source.ID }}", "{{ elementPath $.Model .Destination.ID }}"{{ if .Description }}, {{ printf "%q" .Description }}{{ end }}{{ if .Order }}, {{ printf "%q" .Order }}{{ end }}{{ if .Vertices }}, func() { + {{- range .Vertices }} + Vertex({{ .X }}, {{ .Y }}) + {{- end }} + }{{ end }}{{ if .Routing }}, {{ .Routing.Name }}{{ end }}{{ if .Position }}, {{ .Position }}{{ end }}) + {{- else }} + Link("{{ elementPath $.Model .Destination.ID }}"{{ if .Description }}, {{ printf "%q" .Description }}{{ end }}{{ if .Order }}, {{ printf "%q" .Order }}{{ end }}{{ if .Vertices }}, func() { + {{- range .Vertices }} + Vertex({{ .X }}, {{ .Y }}) + {{- end }} + }{{ end }}{{ if .Routing }}, {{ .Routing.Name }}{{ end }}{{ if .Position }}, {{ .Position }}{{ end }}) + {{- end }} +{{- end }} +{{- range .Animations }} + AnimationStep({{ range .Elements }}"{{ elementPath .GetElement.ID }}", {{ end }}) +{{- end }} +{{- end }}` + +var systemLandscapeViewT = `SystemLandscapeView("{{.View.Key}}"{{ if .View.Description}}, {{ printf "%q" .View.Description }}{{ end }}, func() { + {{ template "viewPropsT" (viewPropsData $.Model .View.ViewProps) }} + {{- if .View.EnterpriseBoundaryVisible }} + EnterpriseBoundaryVisible() + {{- end }} +})` + +var systemContextViewT = `SystemContextView("{{ elementPath .View.SoftwareSystemID }}", "{{ .View.Key }}"{{ if .View.Description}}, {{ printf "%q" .View.Description }}{{ end }}, func() { + {{ template "viewPropsT" (viewPropsData .Model .View.ViewProps) }} + {{- if .View.EnterpriseBoundaryVisible }} + EnterpriseBoundaryVisible() + {{- end }} +})` + +var containerViewT = `ContainerView("{{ elementPath .View.SoftwareSystemID }}", "{{ .View.Key }}"{{ if .View.Description}}, {{ printf "%q" .View.Description }}{{ end }}, func() { + {{ template "viewPropsT" (viewPropsData .Model .View.ViewProps) }} + {{- if .View.SystemBoundaryVisible }} + SystemBoundaryVisible() + {{- end }} +})` + +var componentViewT = `ComponentView("{{ elementPath .View.SoftwareSystemID }}", "{{ .View.Key }}"{{ if .View.Description}}, {{ printf "%q" .View.Description }}{{ end }}, func() { + {{ template "viewPropsT" (viewPropsData .Model .View.ViewProps) }} + {{- if .View.ContainerBoundaryVisible }} + ContainerBoundaryVisible() + {{- end }} +})` + +var filteredViewT = `FilteredView("{{ .Key }}", func() { + {{- range .FilterTags }} + FilterTag({{ printf "%q" . }}) + {{- end }} + {{- if .Exclude }} + Exclude() + {{- end }} +})` + +var deploymentViewT = `DeploymentView("{{ elementPath .SoftwareSystemID }}", {{ printf "%q" .Environment }}, {{ printf "%q" .Key }}{{ if .Description}}, {{ printf "%q" .Description }}{{ end }}, func() { + {{ template "viewPropsT" . }} +})` + +var dynamicViewT = `DynamicView("{{ elementPath .ElementID }}", {{ printf "%q" .Key }}, func() { + {{ template "viewPropsT" . }} +})` + +var styleT = `Style(func() { + {{- range .Elements }} + ElementStyle({{ printf "%q" .Tag }}, func() { + {{- if gt .Shape 0 }} + Shape("{{ .Shape.Name }}") + {{- end }} + {{- if .Icon }} + Icon({{ printf "%q" .Icon }}) + {{- end }} + {{- if .Background }} + Background({{ printf "%q" .Background }}) + {{- end }} + {{- if .Color }} + Color({{ printf "%q" .Color }}) + {{- end }} + {{- if .Stroke }} + Stroke({{ printf "%q" .Stroke }}) + {{- end }} + {{- if .Width }} + Width({{ .Width }}) + {{- end }} + {{- if .Height }} + Height({{ .Height }}) + {{- end }} + {{- if .FontSize }} + FontSize({{ .FontSize }}) + {{- end }} + {{- if .Metadata }} + ShowMetadata() + {{- end }} + {{- if .Description }} + ShowDescription() + {{- end }} + {{- if .Opacity }} + Opacity({{ .Opacity }}) + {{- end }} + {{- if gt .Border 0 }} + Border("{{ .Border.Name }}") + {{- end }} + }) + {{- end }} + {{- range .Relationships }} + RelationshipStyle({{ printf "%q" .Tag }}, func() { + {{- if .Thickness }} + Thickness({{ .Thickness }}) + {{- end }} + {{- if .FontSize }} + FontSize({{ .FontSize }}) + {{- end }} + {{- if .Width }} + Width({{ .Width }}) + {{- end }} + {{- if .Position }} + Position({{ .Position }}) + {{- end }} + {{- if .Color }} + Color({{ .Color }}) + {{- end }} + {{- if .Stroke }} + Stroke({{ .Stroke }}) + {{- end }} + {{- if .Dashed }} + Dashed() + {{- end }} + {{- if ge .Routing 0 }} + Routing({{ .Routing.Name }}) + {{- end }} + {{- if .Opacity }} + Opacity({{ .Opacity }}) + {{- end }} + }) + {{- end }} +})` diff --git a/codegen/model_test.go b/codegen/model_test.go new file mode 100644 index 00000000..2b3d4909 --- /dev/null +++ b/codegen/model_test.go @@ -0,0 +1,59 @@ +package codegen + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "goa.design/model/mdl" + model "goa.design/model/pkg" +) + +// A test case has a name and a JSON representation of a design. +type testCase struct { + Name string + JSON []byte +} + +func TestModel(t *testing.T) { + // Change to testdata directory so that the JSON function can load the + // test model Go packages. + require.NoError(t, os.Chdir("testdata")) + + // Iterate through the test models and for each one generate the JSON + // representation. Then run the model DSL on the JSON and compare the + // generated code with the original code. + entries, err := os.ReadDir(".") + require.NoError(t, err) + var cases []testCase + for _, e := range entries { + if !e.IsDir() { + continue + } + if e.Name() == "." || e.Name() == ".." { + continue + } + js, err := JSON("test/"+e.Name(), false) + require.NoError(t, err) + cases = append(cases, testCase{Name: e.Name(), JSON: js}) + } + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + var design mdl.Design + require.NoError(t, json.Unmarshal(c.JSON, &design)) + + got, err := Model(&design, "model") + require.NoError(t, err) + + byts, err := os.ReadFile(filepath.Join(c.Name, "model.go")) + require.NoError(t, err) + // Avoid hard-coding version in test files. + want := "// Code generated by mdl " + model.Version() + ".\n\n" + string(byts) + assert.Equal(t, want, string(got), "generated code does not match original code, generated JSON is:\n%s", string(c.JSON)) + }) + } +} diff --git a/codegen/testdata/1_empty/model.go b/codegen/testdata/1_empty/model.go new file mode 100644 index 00000000..a4abbaee --- /dev/null +++ b/codegen/testdata/1_empty/model.go @@ -0,0 +1,7 @@ +package model + +import . "goa.design/model/dsl" + +var _ = Design("Empty", "This is a test model.", func() { + SoftwareSystem("System", "Description") +}) diff --git a/codegen/testdata/2_service/model.go b/codegen/testdata/2_service/model.go new file mode 100644 index 00000000..338e25dc --- /dev/null +++ b/codegen/testdata/2_service/model.go @@ -0,0 +1,25 @@ +package model + +import . "goa.design/model/dsl" + +var _ = Design("Service", "This is a test model.", func() { + SoftwareSystem("System", "Description", func() { + Container("1_Empty", "Empty description") + Container("2_WithTag", "WithTag description", func() { + Tag("Foo") + }) + Container("3_WithURL", "WithURL description", func() { + URL("https://goa.design/docs/mysystem") + }) + Container("4_WithProperties", "WithProperties description", func() { + Prop("1_foo", "bar") + Prop("2_baz", "qux") + }) + Container("5_WithAll", "WithAll description", func() { + Tag("Foo") + URL("https://goa.design/docs/mysystem") + Prop("1_foo", "bar") + Prop("2_baz", "qux") + }) + }) +}) diff --git a/codegen/testdata/3_endpoint/model.go b/codegen/testdata/3_endpoint/model.go new file mode 100644 index 00000000..56dbb14d --- /dev/null +++ b/codegen/testdata/3_endpoint/model.go @@ -0,0 +1,26 @@ +package model + +import . "goa.design/model/dsl" + +var _ = Design("Service", "This is a test model.", func() { + SoftwareSystem("System", "Description", func() { + Container("Service", "Service description", func() { + Component("01_Endpoint", "Endpoint description", func() { + Tag("Endpoint") + }) + Component("02_WithURL", "WithURL description", func() { + URL("https://goa.design/docs/mysystem") + }) + Component("03_WithProperties", "WithProperties description", func() { + Prop("1_foo", "bar") + Prop("2_baz", "qux") + }) + Component("04_WithAll", "WithAll description", func() { + Tag("Endpoint") + URL("https://goa.design/docs/mysystem") + Prop("1_foo", "bar") + Prop("2_baz", "qux") + }) + }) + }) +}) diff --git a/codegen/testdata/4_relationship/model.go b/codegen/testdata/4_relationship/model.go new file mode 100644 index 00000000..1c6c7c09 --- /dev/null +++ b/codegen/testdata/4_relationship/model.go @@ -0,0 +1,85 @@ +package model + +import . "goa.design/model/dsl" + +var _ = Design("Dependency", "This is a test model.", func() { + SoftwareSystem("1_External", "External system", func() { + External() + Container("Dependency", "External dependency", func() { + Component("Endpoint", "Endpoint description", func() { + Tag("Endpoint") + }) + }) + }) + SoftwareSystem("2_System", "Description", func() { + Container("01_Dependency", "Dependency description", func() { + Component("Endpoint", "Endpoint description", func() { + Tag("Endpoint") + }) + }) + Container("02_Basic", "Basic description", func() { + Uses("01_Dependency/Endpoint", "Uses endpoint description") + Uses("01_Dependency", "Uses description") + }) + Container("03_WithTag", "WithTag description", func() { + Uses("01_Dependency", "Uses description", func() { + Tag("Foo") + }) + Uses("01_Dependency/Endpoint", "Uses endpoint description", func() { + Tag("Foo2") + }) + }) + Container("04_WithTech", "WithTech description", func() { + Uses("01_Dependency", "Uses description", "Technology") + Uses("01_Dependency/Endpoint", "Uses endpoint description", "Technology") + }) + Container("05_WithTechAndTag", "WithTechAndTag description", func() { + Uses("01_Dependency", "Uses description", "Technology", func() { + Tag("Foo") + }) + Uses("01_Dependency/Endpoint", "Uses endpoint description", "Technology", func() { + Tag("Foo2") + }) + }) + Container("06_WithSynchronous", "WithSynchronous description", func() { + Uses("01_Dependency", "Uses description", "Technology", Synchronous) + Uses("01_Dependency/Endpoint", "Uses endpoint description", "Technology", Synchronous) + }) + Container("07_WithSynchronousAndTag", "WithSynchronousAndTag description", func() { + Uses("01_Dependency", "Uses description", "Technology", Synchronous, func() { + Tag("Foo") + }) + Uses("01_Dependency/Endpoint", "Uses endpoint description", "Technology", Synchronous, func() { + Tag("Foo2") + }) + }) + Container("08_WithAsync", "WithAsync description", func() { + Uses("01_Dependency/Endpoint", "Uses endpoint description", "Technology", Asynchronous) + Uses("01_Dependency", "Uses description", "Technology", Asynchronous) + }) + Container("09_WithAsyncAndTag", "WithAsyncAndTag description", func() { + Uses("01_Dependency/Endpoint", "Uses endpoint description", "Technology", Asynchronous, func() { + Tag("Foo2") + }) + Uses("01_Dependency", "Uses description", "Technology", Asynchronous, func() { + Tag("Foo") + }) + }) + Container("10_External", "External description", func() { + Uses("1_External/Dependency/Endpoint", "Uses endpoint description") + Uses("1_External", "Uses description") + Uses("1_External/Dependency", "Uses dependency description") + }) + Container("11_ExternalWithTag", "ExternalWithTag description", func() { + Uses("1_External", "Uses description", func() { + Tag("Foo") + }) + Uses("1_External/Dependency", "Uses dependency description", func() { + Tag("Foo2") + }) + Uses("1_External/Dependency/Endpoint", "Uses endpoint description", func() { + Tag("Foo3") + }) + }) + }) +}) diff --git a/codegen/testdata/5_static_views/model.go b/codegen/testdata/5_static_views/model.go new file mode 100644 index 00000000..3afa8549 --- /dev/null +++ b/codegen/testdata/5_static_views/model.go @@ -0,0 +1,50 @@ +package model + +import . "goa.design/model/dsl" + +var _ = Design("Service", "This is a test model.", func() { + SoftwareSystem("System", "Description", func() { + Container("Dependency", "Dependency description", func() { + Component("Endpoint", "Endpoint description", func() { + Tag("Endpoint") + }) + }) + Container("Service", "Service description", func() { + Component("Endpoint", "Endpoint description", func() { + Tag("Endpoint") + }) + Uses("Dependency/Endpoint", "Uses endpoint description", "Technology", Synchronous) + Uses("Dependency", "Uses description", "Technology", Asynchronous) + }) + }) + Views(func() { + SystemLandscapeView("01_SystemLandscape", "System landscape description", func() { + Title("SystemLandscape") + }) + SystemLandscapeView("02_SystemLandscape_WithAutoLayout", "System context description", func() { + Title("SystemLandscape_WithAutoLayout") + AutoLayout(RankLeftRight) + }) + SystemLandscapeView("03_SystemLandscape_WithAutoLayoutExtended", "System context description", func() { + Title("SystemLandscape_WithAutoLayoutExtended") + AutoLayout(RankTopBottom, func() { + RankSeparation(200) + NodeSeparation(300) + EdgeSeparation(400) + RenderVertices() + }) + }) + SystemLandscapeView("04_SystemLandscape_WithEnterpriseBoundary", "System context description", func() { + Title("SystemLandscape_WithEnterpriseBoundary") + EnterpriseBoundaryVisible() + }) + SystemLandscapeView("05_SystemLandscape_WithPaperSize", "System context description", func() { + Title("SystemLandscape_WithPaperSize") + PaperSize(SizeSlide4X3) + }) + SystemLandscapeView("06_SystemLandscape_WithAddAll", "System context description", func() { + Title("SystemLandscape_WithAddAll") + AddAll() + }) + }) +}) diff --git a/codegen/testdata/go.mod b/codegen/testdata/go.mod new file mode 100644 index 00000000..b8ba7079 --- /dev/null +++ b/codegen/testdata/go.mod @@ -0,0 +1,22 @@ +module test + +go 1.21.3 + +require goa.design/model v1.9.0 + +require ( + github.com/AnatolyRugalev/goregen v0.1.0 // indirect + github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect + github.com/go-chi/chi/v5 v5.0.10 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect + github.com/sergi/go-diff v1.3.1 // indirect + goa.design/goa/v3 v3.13.2 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect +) + +replace goa.design/model => ../.. diff --git a/codegen/testdata/go.sum b/codegen/testdata/go.sum new file mode 100644 index 00000000..519ffd11 --- /dev/null +++ b/codegen/testdata/go.sum @@ -0,0 +1,46 @@ +github.com/AnatolyRugalev/goregen v0.1.0 h1:xrdXkLaskMnbxW0x4FWNj2yoednv0X2bcTBWpuJGYfE= +github.com/AnatolyRugalev/goregen v0.1.0/go.mod h1:sVlY1tjcirqLBRZnCcIq1+7/Lwmqz5g7IK8AStjOVzI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= +github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqCDBcAhLXoi3TzC27Zad/Vn+gnVQ= +github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= +github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b h1:3E44bLeN8uKYdfQqVQycPnaVviZdBLbizFhU49mtbe4= +github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b/go.mod h1:Bj8LjjP0ReT1eKt5QlKjwgi5AFm5mI6O1A2G4ChI0Ag= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +goa.design/goa/v3 v3.13.2 h1:RclNIpo7891ZqGRVO4fpBjT7Fs7LjBNm78i8J41KHrI= +goa.design/goa/v3 v3.13.2/go.mod h1:VvZsuC8CSIUQOHVqk6Ep3MFSFz21OjOv87UPqCHiB94= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dsl/deployment.go b/dsl/deployment.go index 117b5f25..2c7d1d90 100644 --- a/dsl/deployment.go +++ b/dsl/deployment.go @@ -90,7 +90,7 @@ func DeploymentEnvironment(name string, dsl func()) { // }) // }) // }) -func DeploymentNode(name string, args ...interface{}) *expr.DeploymentNode { +func DeploymentNode(name string, args ...any) *expr.DeploymentNode { if strings.Contains(name, "/") { eval.ReportError("DeploymentNode: name cannot include slashes") } @@ -168,7 +168,7 @@ func DeploymentNode(name string, args ...interface{}) *expr.DeploymentNode { // }) // }) // }) -func InfrastructureNode(name string, args ...interface{}) *expr.InfrastructureNode { +func InfrastructureNode(name string, args ...any) *expr.InfrastructureNode { d, ok := eval.Current().(*expr.DeploymentNode) if !ok { eval.IncompatibleDSL() @@ -241,7 +241,7 @@ func InfrastructureNode(name string, args ...interface{}) *expr.InfrastructureNo // }) // }) // }) -func ContainerInstance(container interface{}, dsl ...func()) *expr.ContainerInstance { +func ContainerInstance(container any, dsl ...func()) *expr.ContainerInstance { d, ok := eval.Current().(*expr.DeploymentNode) if !ok { eval.IncompatibleDSL() diff --git a/dsl/design.go b/dsl/design.go index 2a074ef2..f5424a79 100644 --- a/dsl/design.go +++ b/dsl/design.go @@ -44,7 +44,7 @@ import ( // var _ = Design("My Design", "A great architecture.", func() { // SoftwareSystem("My Software System") // }) -func Design(args ...interface{}) *expr.Design { +func Design(args ...any) *expr.Design { _, ok := eval.Current().(eval.TopExpr) if !ok { eval.IncompatibleDSL() diff --git a/dsl/doc.go b/dsl/doc.go index 235e3eb3..4a677db1 100644 --- a/dsl/doc.go +++ b/dsl/doc.go @@ -34,7 +34,7 @@ general shape of the DSL is: │ ├── Prop │ ├── AutoLayout │ ├── Uses │ ├── AnimationStep │ ├── Delivers │ ├── PaperSize - │ └─── Container │ └── EnterpriseBoundaryVisible + │ └── Container │ └── EnterpriseBoundaryVisible │ ├── Tag ├── SystemContextView │ ├── URL │ └── ... (same as SystemLandsapeView) │ ├── Prop ├── ContainerView diff --git a/dsl/elements.go b/dsl/elements.go index 00e9480f..566a8d57 100644 --- a/dsl/elements.go +++ b/dsl/elements.go @@ -39,7 +39,7 @@ import ( // Delivers("Customer", "Delivers emails to", "SMTP", Synchronous) // }) // }) -func SoftwareSystem(name string, args ...interface{}) *expr.SoftwareSystem { +func SoftwareSystem(name string, args ...any) *expr.SoftwareSystem { w, ok := eval.Current().(*expr.Design) if !ok { eval.IncompatibleDSL() @@ -112,7 +112,7 @@ func SoftwareSystem(name string, args ...interface{}) *expr.SoftwareSystem { // }) // }) // }) -func Container(args ...interface{}) *expr.Container { +func Container(args ...any) *expr.Container { system, ok := eval.Current().(*expr.SoftwareSystem) if !ok { eval.IncompatibleDSL() @@ -209,7 +209,7 @@ func Container(args ...interface{}) *expr.Container { // }) // }) // }) -func Component(name string, args ...interface{}) *expr.Component { +func Component(name string, args ...any) *expr.Component { container, ok := eval.Current().(*expr.Container) if !ok { eval.IncompatibleDSL() @@ -243,7 +243,7 @@ func Component(name string, args ...interface{}) *expr.Component { // func() // "[description]", func() // "[description]", "[technology]", func() -func parseElementArgs(args ...interface{}) (description, technology string, dsl func(), err error) { +func parseElementArgs(args ...any) (description, technology string, dsl func(), err error) { if len(args) == 0 { return } diff --git a/dsl/person.go b/dsl/person.go index 12cb75a2..b0f6ef8a 100644 --- a/dsl/person.go +++ b/dsl/person.go @@ -37,7 +37,7 @@ import ( // InteractsWith(Employee) // }) // }) -func Person(name string, args ...interface{}) *expr.Person { +func Person(name string, args ...any) *expr.Person { w, ok := eval.Current().(*expr.Design) if !ok { eval.IncompatibleDSL() diff --git a/dsl/relationship.go b/dsl/relationship.go index 499f5aa2..1282e136 100644 --- a/dsl/relationship.go +++ b/dsl/relationship.go @@ -84,7 +84,7 @@ const ( // InteractsWith("Customer", "Sends invoices to", Synchronous) // }) // }) -func Uses(element interface{}, description string, args ...interface{}) { +func Uses(element any, description string, args ...any) { var src *expr.Element switch e := eval.Current().(type) { case *expr.Person: @@ -142,7 +142,7 @@ func Uses(element interface{}, description string, args ...interface{}) { // InteractsWith(Employee, "Sends requests to", "email") // }) // }) -func InteractsWith(person interface{}, description string, args ...interface{}) { +func InteractsWith(person any, description string, args ...any) { src, ok := eval.Current().(*expr.Person) if !ok { eval.IncompatibleDSL() @@ -205,7 +205,7 @@ func InteractsWith(person interface{}, description string, args ...interface{}) // Delivers(Customer, "Sends requests to", "email") // }) // }) -func Delivers(person interface{}, description string, args ...interface{}) { +func Delivers(person any, description string, args ...any) { var src *expr.Element switch e := eval.Current().(type) { case *expr.SoftwareSystem: @@ -258,7 +258,7 @@ func Description(desc string) { // uses adds a relationship between the given source and destination. The caller // must make sure that the relationship is valid. -func uses(src *expr.Element, dest interface{}, desc string, args ...interface{}) error { +func uses(src *expr.Element, dest any, desc string, args ...any) error { var ( technology string style InteractionStyleKind diff --git a/dsl/views.go b/dsl/views.go index a2fdc937..de283273 100644 --- a/dsl/views.go +++ b/dsl/views.go @@ -103,7 +103,7 @@ const ( // Views(func() { // SystemContext(System, "SystemContext", "An example of a System Context diagram.", func() { // AddAll() -// AutoLayout() +// AutoLayout(RankTopBottom) // }) // }) // }) @@ -145,14 +145,14 @@ func Views(dsl func()) { // Title("Overview of system") // AddAll() // Remove(OtherSystem) -// AutoLayout() +// AutoLayout(RankTopBottom) // AnimationStep(System) // PaperSize(SizeSlide4X3) // EnterpriseBoundaryVisible() // }) // }) // }) -func SystemLandscapeView(key string, args ...interface{}) { +func SystemLandscapeView(key string, args ...any) { vs, ok := eval.Current().(*expr.Views) if !ok { eval.IncompatibleDSL() @@ -205,14 +205,14 @@ func SystemLandscapeView(key string, args ...interface{}) { // Title("Overview of system") // AddAll() // Remove(OtherSystem) -// AutoLayout() +// AutoLayout(RankTopBottom) // AnimationStep(System) // PaperSize(SizeSlide4X3) // EnterpriseBoundaryVisible() // }) // }) // }) -func SystemContextView(system interface{}, key string, args ...interface{}) { +func SystemContextView(system any, key string, args ...any) { vs, ok := eval.Current().(*expr.Views) if !ok { eval.IncompatibleDSL() @@ -282,14 +282,14 @@ func SystemContextView(system interface{}, key string, args ...interface{}) { // AddAll() // Remove(OtherSystem) // // Alternatively to AddAll + Remove: Add -// AutoLayout() +// AutoLayout(RankTopBottom) // AnimationStep(System) // PaperSize(SizeSlide4X3) // SystemBoundariesVisible() // }) // }) // }) -func ContainerView(system interface{}, key string, args ...interface{}) { +func ContainerView(system any, key string, args ...any) { vs, ok := eval.Current().(*expr.Views) if !ok { eval.IncompatibleDSL() @@ -364,14 +364,14 @@ func ContainerView(system interface{}, key string, args ...interface{}) { // Title("Overview of container") // AddAll() // Remove("Other System") -// AutoLayout() +// AutoLayout(RankTopBottom) // AnimationStep("Software System") // PaperSize(SizeSlide4X3) // ContainerBoundariesVisible() // }) // }) // }) -func ComponentView(container interface{}, key string, args ...interface{}) { +func ComponentView(container any, key string, args ...any) { vs, ok := eval.Current().(*expr.Views) if !ok { eval.IncompatibleDSL() @@ -418,8 +418,8 @@ func ComponentView(container interface{}, key string, args ...interface{}) { // // FilteredView must appear in Views. // -// FilteredView accepts 2 arguments: the view being filtered and a function -// describing additional properties. +// FilteredView accepts 2 arguments: the view being filtered or its key and a +// function describing additional properties. // // Example: // @@ -428,7 +428,7 @@ func ComponentView(container interface{}, key string, args ...interface{}) { // Views(func() { // SystemContextView(SoftwareSystem, "context", "An overview diagram.", func() { // AddAll() -// AutoLayout() +// AutoLayout(RankTopBottom) // }) // FilteredView(SystemContextView, func() { // FilterTag("infra") @@ -436,24 +436,27 @@ func ComponentView(container interface{}, key string, args ...interface{}) { // }) // }) // }) -func FilteredView(view interface{}, dsl func()) { +func FilteredView(view any, dsl func()) { vs, ok := eval.Current().(*expr.Views) if !ok { eval.IncompatibleDSL() return } - var key string - if v, ok := view.(expr.View); ok { - key = v.Props().Key - } else { + var baseKey string + switch actual := view.(type) { + case string: + baseKey = actual + case expr.View: + baseKey = actual.Props().Key + default: eval.IncompatibleDSL() return } - if key == "" { + if baseKey == "" { eval.ReportError("Filtered view applied on a view with no key. Make sure the view given as argument defines a key.") return } - fv := &expr.FilteredView{BaseKey: key} + fv := &expr.FilteredView{BaseKey: baseKey} eval.Execute(dsl, fv) vs.FilteredViews = append(vs.FilteredViews, fv) } @@ -535,12 +538,12 @@ func FilterTag(tag string, tags ...string) { // DynamicView(Global, "dynamic", "A dynamic diagram.", func() { // Title("Overview of system") // Link(FirstSystem, SecondSystem) -// AutoLayout() +// AutoLayout(RankTopBottom) // PaperSize(SizeSlide4X3) // }) // }) // }) -func DynamicView(scope interface{}, key string, args ...interface{}) { +func DynamicView(scope any, key string, args ...any) { vs, ok := eval.Current().(*expr.Views) if !ok { eval.IncompatibleDSL() @@ -648,20 +651,20 @@ func DynamicView(scope interface{}, key string, args ...interface{}) { // Title("Overview of deployment") // AddAll() // Remove("System/OtherContainer") -// AutoLayout() +// AutoLayout(RankTopBottom) // AnimationStep("System/Container") // PaperSize(SizeSlide4X3) // }) // }) // }) -func DeploymentView(scope interface{}, env, key string, args ...interface{}) { +func DeploymentView(scope any, env, key string, args ...any) { vs, ok := eval.Current().(*expr.Views) if !ok { eval.IncompatibleDSL() return } missing := true - expr.Iterate(func(e interface{}) { + expr.Iterate(func(e any) { if dn, ok := e.(*expr.DeploymentNode); ok { if dn.Environment == env { missing = false @@ -840,7 +843,7 @@ func Title(t string) { // }) // }) // }) -func Add(element interface{}, dsl ...func()) { +func Add(element any, dsl ...func()) { var ( eh expr.ElementHolder err error @@ -972,7 +975,7 @@ func Add(element interface{}, dsl ...func()) { // }) // }) // }) -func Link(source, destination interface{}, args ...interface{}) { +func Link(source, destination any, args ...any) { v, ok := eval.Current().(expr.View) if !ok { eval.IncompatibleDSL() @@ -1074,7 +1077,7 @@ func AddAll() { // }) // }) // }) -func AddNeighbors(element interface{}) { +func AddNeighbors(element any) { v, ok := eval.Current().(expr.View) if !ok { eval.IncompatibleDSL() @@ -1134,10 +1137,10 @@ func AddDefault() { func AddContainers() { switch v := eval.Current().(type) { case *expr.ContainerView: - v.AddElements(expr.Registry[v.SoftwareSystemID].(*expr.SoftwareSystem).Containers.Elements()...) + v.AddElements(expr.Registry[v.SoftwareSystemID].(*expr.SoftwareSystem).Containers.Elements()...) // nolint: errcheck case *expr.ComponentView: c := expr.Registry[v.ContainerID].(*expr.Container) - v.AddElements(c.System.Containers.Elements()...) + v.AddElements(c.System.Containers.Elements()...) // nolint: errcheck default: eval.IncompatibleDSL() } @@ -1167,7 +1170,7 @@ func AddInfluencers() { // AddComponents takes no argument func AddComponents() { if cv, ok := eval.Current().(*expr.ComponentView); ok { - cv.AddElements(expr.Registry[cv.ContainerID].(*expr.Container).Components.Elements()...) + cv.AddElements(expr.Registry[cv.ContainerID].(*expr.Container).Components.Elements()...) // nolint: errcheck return } eval.IncompatibleDSL() @@ -1227,7 +1230,7 @@ func AddComponents() { // }) // }) // }) -func Remove(element interface{}) { +func Remove(element any) { v, ok := eval.Current().(expr.View) if !ok { eval.IncompatibleDSL() @@ -1334,14 +1337,14 @@ func RemoveTagged(tag string) { // }) // }) // }) -func Unlink(source, destination interface{}, description ...string) { +func Unlink(source, destination any, description ...string) { v, ok := eval.Current().(expr.View) if !ok { eval.IncompatibleDSL() } - var args []interface{} + var args []any if len(description) > 0 { - args = []interface{}{description[0]} + args = []any{description[0]} if len(description) > 1 { eval.ReportError("Unlink: too many arguments") } @@ -1394,7 +1397,7 @@ func Unlink(source, destination interface{}, description ...string) { // }) // }) // }) -func RemoveUnreachable(element interface{}) { +func RemoveUnreachable(element any) { v, ok := eval.Current().(expr.View) if !ok { eval.IncompatibleDSL() @@ -1439,6 +1442,15 @@ func RemoveUnrelated() { eval.IncompatibleDSL() } +// DefaultRankSeparation sets the default rank separation for auto layout. +var DefaultRankSeparation = 300 + +// DefaultNodeSeparation sets the default node separation for auto layout. +var DefaultNodeSeparation = 600 + +// DefaultEdgeSeparation sets the default edge separation for auto layout. +var DefaultEdgeSeparation = 200 + // AutoLayout enables automatic layout mode for the diagram. The // first argument indicates the rank direction, it must be one of // RankTopBottom, RankBottomTop, RankLeftRight or RankRightLeft @@ -1483,12 +1495,11 @@ func AutoLayout(rank RankDirectionKind, args ...func()) { eval.ReportError("AutoLayout: too many arguments") } } - r, n, e := 300, 600, 200 layout := &expr.AutoLayout{ RankDirection: expr.RankDirectionKind(rank), - RankSep: &r, - NodeSep: &n, - EdgeSep: &e, + RankSep: &DefaultRankSeparation, + NodeSep: &DefaultNodeSeparation, + EdgeSep: &DefaultEdgeSeparation, } if dsl != nil { eval.Execute(dsl, layout) @@ -1530,7 +1541,7 @@ func AutoLayout(rank RankDirectionKind, args ...func()) { // }) // }) // }) -func AnimationStep(elements ...interface{}) { +func AnimationStep(elements ...any) { v, ok := eval.Current().(expr.ViewAdder) if !ok { eval.IncompatibleDSL() @@ -1730,7 +1741,10 @@ func Vertices(args ...int) { eval.IncompatibleDSL() } for i := 0; i < len(args); i += 2 { - rv.Vertices = append(rv.Vertices, &expr.Vertex{args[i], args[i+1]}) + rv.Vertices = append(rv.Vertices, &expr.Vertex{ + X: args[i], + Y: args[i+1], + }) } } @@ -1932,7 +1946,7 @@ func RenderVertices() { // // func() // "[description]", func() -func parseView(args ...interface{}) (description string, dsl func(), err error) { +func parseView(args ...any) (description string, dsl func(), err error) { if len(args) == 0 { err = fmt.Errorf("missing argument") return @@ -1967,7 +1981,7 @@ func parseView(args ...interface{}) (description string, dsl func(), err error) // findViewElement returns the element identifed by element that // is in scope for the given view. See model.FindElement for details. -func findViewElement(view expr.View, element interface{}) (expr.ElementHolder, error) { +func findViewElement(view expr.View, element any) (expr.ElementHolder, error) { if eh, ok := element.(expr.ElementHolder); ok { return eh, nil } @@ -2044,7 +2058,7 @@ func findDeploymentViewElement(env, path string) (expr.ElementHolder, error) { return nil, fmt.Errorf("could not find %q in path %q", name, path) } -func parseLinkArgs(v expr.View, source interface{}, destination interface{}, args []interface{}) (src, dest expr.ElementHolder, desc string, dsl func(), err error) { +func parseLinkArgs(v expr.View, source any, destination any, args []any) (src, dest expr.ElementHolder, desc string, dsl func(), err error) { var ok bool if len(args) > 0 { if dsl, ok = args[len(args)-1].(func()); ok { diff --git a/expr/component.go b/expr/component.go index 2efa687b..81e914ea 100644 --- a/expr/component.go +++ b/expr/component.go @@ -16,6 +16,9 @@ type ( Components []*Component ) +// ComponentTags lists the tags that are added to all components. +var ComponentTags = []string{"Element", "Component"} + // EvalName returns the generic expression name used in error messages. func (c *Component) EvalName() string { if c.Name == "" { @@ -26,7 +29,7 @@ func (c *Component) EvalName() string { // Finalize adds the 'Component' tag ands finalizes relationships. func (c *Component) Finalize() { - c.PrefixTags("Element", "Component") + c.PrefixTags(ComponentTags...) c.Element.Finalize() } diff --git a/expr/container.go b/expr/container.go index 7419929c..9676391c 100644 --- a/expr/container.go +++ b/expr/container.go @@ -18,6 +18,9 @@ type ( Containers []*Container ) +// ContainerTags lists the tags that are added to all containers. +var ContainerTags = []string{"Element", "Container"} + // EvalName returns the generic expression name used in error messages. func (c *Container) EvalName() string { if c.Name == "" { @@ -28,7 +31,7 @@ func (c *Container) EvalName() string { // Finalize adds the 'Container' tag ands finalizes relationships. func (c *Container) Finalize() { - c.PrefixTags("Element", "Container") + c.PrefixTags(ContainerTags...) c.Element.Finalize() } diff --git a/expr/deployment.go b/expr/deployment.go index b601409b..50aff469 100644 --- a/expr/deployment.go +++ b/expr/deployment.go @@ -60,6 +60,18 @@ type ( } ) +// DeploymentNodeTags list the tags that are automatically added to all +// deployment nodes. +var DeploymentNodeTags = []string{"Element", "Deployment Node"} + +// InfrastructureNodeTags list the tags that are automatically added to all +// infrastructure nodes. +var InfrastructureNodeTags = []string{"Element", "Infrastructure Node"} + +// ContainerInstanceTags list the tags that are automatically added to all +// container instances. +var ContainerInstanceTags = []string{"Container Instance"} + // EvalName returns the generic expression name used in error messages. func (d *DeploymentEnvironment) EvalName() string { return fmt.Sprintf("deployment environment %q", d.Name) @@ -70,7 +82,7 @@ func (d *DeploymentNode) EvalName() string { return fmt.Sprintf("deployment node // Finalize adds the 'Deployment Node' tag ands finalizes relationships. func (d *DeploymentNode) Finalize() { - d.PrefixTags("Element", "Deployment Node") + d.PrefixTags(DeploymentNodeTags...) d.Element.Finalize() } @@ -218,7 +230,7 @@ func (i *InfrastructureNode) EvalName() string { // Finalize adds the 'Infrastructure Node' tag ands finalizes relationships. func (i *InfrastructureNode) Finalize() { - i.PrefixTags("Element", "Infrastructure Node") + i.PrefixTags(InfrastructureNodeTags...) i.Element.Finalize() } @@ -233,7 +245,7 @@ func (ci *ContainerInstance) EvalName() string { // Finalize adds the "Container Instance" tag if not present. func (ci *ContainerInstance) Finalize() { - ci.PrefixTags("Container Instance") + ci.PrefixTags(ContainerInstanceTags...) ci.Element.Finalize() } diff --git a/expr/design.go b/expr/design.go index 023d708f..1be1d0e8 100644 --- a/expr/design.go +++ b/expr/design.go @@ -24,7 +24,7 @@ var Root = &Design{Model: &Model{}, Views: &Views{}} // Register design root with eval engine. func init() { - eval.Register(Root) + eval.Register(Root) // nolint: errcheck } // WalkSets iterates over the elements and views. diff --git a/expr/element.go b/expr/element.go index 9eaec8b7..413b9501 100644 --- a/expr/element.go +++ b/expr/element.go @@ -85,6 +85,9 @@ func mergeTags(existing string, tags []string) string { } } for _, tag := range tags { + if tag == "" { + continue + } found := false for _, o := range merged { if tag == o { diff --git a/expr/model.go b/expr/model.go index b5b01cae..7e1e8f7a 100644 --- a/expr/model.go +++ b/expr/model.go @@ -92,7 +92,7 @@ func (m *Model) Validate() error { // Finalize adds all implied relationships if needed. func (m *Model) Finalize() { // Add relationships between container instances. - Iterate(func(e interface{}) { + Iterate(func(e any) { if ci, ok := e.(*ContainerInstance); ok { c := Registry[ci.ContainerID].(*Container) for _, r := range c.Relationships { @@ -100,7 +100,7 @@ func (m *Model) Finalize() { if !ok { continue } - Iterate(func(e interface{}) { + Iterate(func(e any) { eci, ok := e.(*ContainerInstance) if !ok { return @@ -118,7 +118,7 @@ func (m *Model) Finalize() { return } // Add relationship between element parents. - Iterate(func(e interface{}) { + Iterate(func(e any) { if r, ok := e.(*Relationship); ok { src := Registry[r.Source.ID].(ElementHolder) switch s := src.(type) { diff --git a/expr/model_test.go b/expr/model_test.go index 28808320..8fc85fc2 100644 --- a/expr/model_test.go +++ b/expr/model_test.go @@ -566,7 +566,7 @@ func Test_AddImpliedRelationship(t *testing.T) { } for i, tt := range tests { tt := tt - t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run(fmt.Sprint(i), func(_ *testing.T) { addImpliedRelationships(tt.src, tt.dst, tt.existing) }) } @@ -689,7 +689,7 @@ func TestModelFinalize(t *testing.T) { {}, } for i := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run(fmt.Sprint(i), func(_ *testing.T) { m.Finalize() }) } diff --git a/expr/person.go b/expr/person.go index 27b7c500..8444b46c 100644 --- a/expr/person.go +++ b/expr/person.go @@ -15,6 +15,9 @@ type ( People []*Person ) +// PersonTags list the tags that are automatically added to all people. +var PersonTags = []string{"Element", "Person"} + // EvalName returns the generic expression name used in error messages. func (p *Person) EvalName() string { if p.Name == "" { @@ -25,7 +28,7 @@ func (p *Person) EvalName() string { // Finalize adds the 'Person' tag ands finalizes relationships. func (p *Person) Finalize() { - p.PrefixTags("Element", "Person") + p.PrefixTags(PersonTags...) p.Element.Finalize() } diff --git a/expr/registry.go b/expr/registry.go index 17036af5..940f5fa5 100644 --- a/expr/registry.go +++ b/expr/registry.go @@ -8,11 +8,11 @@ import ( ) // Registry captures all the elements, people and relationships. -var Registry = make(map[string]interface{}) +var Registry = make(map[string]any) // Iterate iterates through all elements, people and relationships in the // registry in a consistent order. -func Iterate(visitor func(elem interface{})) { +func Iterate(visitor func(elem any)) { keys := make([]string, len(Registry)) i := 0 for k := range Registry { @@ -28,7 +28,7 @@ func Iterate(visitor func(elem interface{})) { // IterateRelationships iterates through all relationships in the registry in a // consistent order. func IterateRelationships(visitor func(r *Relationship)) { - Iterate(func(e interface{}) { + Iterate(func(e any) { if r, ok := e.(*Relationship); ok { visitor(r) } @@ -39,7 +39,7 @@ func IterateRelationships(visitor func(r *Relationship)) { // it with the global registery. The algorithm first compute a unique moniker // for the element or relatioship (based on names and parent scope ID) then // hashes and base36 encodes the result. -func Identify(element interface{}) { +func Identify(element any) { switch e := element.(type) { case *Person: e.ID = idify(e.Name) diff --git a/expr/relationship.go b/expr/relationship.go index 5105bb17..e2d995c2 100644 --- a/expr/relationship.go +++ b/expr/relationship.go @@ -2,6 +2,7 @@ package expr import ( "fmt" + "strings" ) type ( @@ -42,6 +43,9 @@ const ( InteractionAsynchronous ) +// RelationshipTags lists the tags that are added to all relationships. +var RelationshipTags = []string{"Relationship", "Asynchronous", "Synchronous"} + // EvalName is the qualified name of the expression. func (r *Relationship) EvalName() string { var src, dest = "", "" @@ -58,11 +62,12 @@ func (r *Relationship) EvalName() string { func (r *Relationship) Finalize() { // prefix tags if r.InteractionStyle == InteractionAsynchronous { - r.Tags = mergeTags("Asynchronous", []string{r.Tags}) + r.Tags = mergeTags("Asynchronous", strings.Split(r.Tags, ",")) } - r.Tags = mergeTags("Relationship", []string{r.Tags}) + r.Tags = mergeTags(RelationshipTags[0], strings.Split(r.Tags, ",")) } +// PrefixTags adds the given tags to the beginning of the comm // Dup creates a new relationship with identical description, tags, URL, // technology and interaction style as r. Dup also creates a new ID for the // result. diff --git a/expr/render.go b/expr/render.go index 987cae74..a8d66467 100644 --- a/expr/render.go +++ b/expr/render.go @@ -11,26 +11,27 @@ func addAllElements(view View) { m := Root.Model switch v := view.(type) { case *LandscapeView: - v.AddElements(m.People.Elements()...) - v.AddElements(m.Systems.Elements()...) + v.AddElements(m.People.Elements()...) // nolint: errcheck + v.AddElements(m.Systems.Elements()...) // nolint: errcheck case *ContextView: - v.AddElements(m.People.Elements()...) - v.AddElements(m.Systems.Elements()...) + v.AddElements(m.People.Elements()...) // nolint: errcheck + v.AddElements(m.Systems.Elements()...) // nolint: errcheck case *ContainerView: - v.AddElements(m.People.Elements()...) - v.AddElements(m.Systems.Elements()...) - v.AddElements(Registry[v.SoftwareSystemID].(*SoftwareSystem).Containers.Elements()...) - removeElements(v.Props(), Registry[v.SoftwareSystemID].(*SoftwareSystem).Element) + v.AddElements(m.People.Elements()...) // nolint: errcheck + v.AddElements(m.Systems.Elements()...) // nolint: errcheck + s := Registry[v.SoftwareSystemID].(*SoftwareSystem) + v.AddElements(s.Containers.Elements()...) // nolint: errcheck + removeElements(v.Props(), s.Element) // nolint: errcheck case *ComponentView: - v.AddElements(m.People.Elements()...) - v.AddElements(m.Systems.Elements()...) + v.AddElements(m.People.Elements()...) // nolint: errcheck + v.AddElements(m.Systems.Elements()...) // nolint: errcheck c := Registry[v.ContainerID].(*Container) - v.AddElements(c.System.Containers.Elements()...) - v.AddElements(c.Components.Elements()...) + v.AddElements(c.System.Containers.Elements()...) // nolint: errcheck + v.AddElements(c.Components.Elements()...) // nolint: errcheck case *DeploymentView: for _, n := range m.DeploymentNodes { if n.Environment == "" || n.Environment == v.Environment { - v.AddElements(n) + v.AddElements(n) // nolint: errcheck } } default: @@ -47,22 +48,22 @@ func addDefaultElements(view View) { addAllElements(v) case *ContextView: s := Registry[v.SoftwareSystemID].(*SoftwareSystem) - v.AddElements(s) + v.AddElements(s) // nolint: errcheck addNeighbors(s.Element, v) case *ContainerView: s := Registry[v.SoftwareSystemID].(*SoftwareSystem) - v.AddElements(s.Containers.Elements()...) + v.AddElements(s.Containers.Elements()...) // nolint: errcheck for _, c := range s.Containers { - v.AddElements(relatedSoftwareSystems(c.Element).Elements()...) - v.AddElements(relatedPeople(c.Element).Elements()...) + v.AddElements(relatedSoftwareSystems(c.Element).Elements()...) // nolint: errcheck + v.AddElements(relatedPeople(c.Element).Elements()...) // nolint: errcheck } case *ComponentView: c := Registry[v.ContainerID].(*Container) - v.AddElements(c.Components.Elements()...) + v.AddElements(c.Components.Elements()...) // nolint: errcheck for _, c := range c.Components { - v.AddElements(relatedContainers(c.Element).Elements()...) - v.AddElements(relatedSoftwareSystems(c.Element).Elements()...) - v.AddElements(relatedPeople(c.Element).Elements()...) + v.AddElements(relatedContainers(c.Element).Elements()...) // nolint: errcheck + v.AddElements(relatedSoftwareSystems(c.Element).Elements()...) // nolint: errcheck + v.AddElements(relatedPeople(c.Element).Elements()...) // nolint: errcheck } case *DeploymentView: addAllElements(v) @@ -146,23 +147,23 @@ func addMissingElementsAndRelationships(vp *ViewProps) { func addNeighbors(e *Element, view View) { switch v := view.(type) { case *LandscapeView: - v.AddElements(relatedPeople(e).Elements()...) - v.AddElements(relatedSoftwareSystems(e).Elements()...) + v.AddElements(relatedPeople(e).Elements()...) // nolint: errcheck + v.AddElements(relatedSoftwareSystems(e).Elements()...) // nolint: errcheck case *ContextView: - v.AddElements(relatedPeople(e).Elements()...) - v.AddElements(relatedSoftwareSystems(e).Elements()...) + v.AddElements(relatedPeople(e).Elements()...) // nolint: errcheck + v.AddElements(relatedSoftwareSystems(e).Elements()...) // nolint: errcheck case *ContainerView: - v.AddElements(relatedPeople(e).Elements()...) - v.AddElements(relatedSoftwareSystems(e).Elements()...) - v.AddElements(relatedContainers(e).Elements()...) + v.AddElements(relatedPeople(e).Elements()...) // nolint: errcheck + v.AddElements(relatedSoftwareSystems(e).Elements()...) // nolint: errcheck + v.AddElements(relatedContainers(e).Elements()...) // nolint: errcheck case *ComponentView: - v.AddElements(relatedPeople(e).Elements()...) - v.AddElements(relatedSoftwareSystems(e).Elements()...) - v.AddElements(relatedContainers(e).Elements()...) - v.AddElements(relatedComponents(e).Elements()...) + v.AddElements(relatedPeople(e).Elements()...) // nolint: errcheck + v.AddElements(relatedSoftwareSystems(e).Elements()...) // nolint: errcheck + v.AddElements(relatedContainers(e).Elements()...) // nolint: errcheck + v.AddElements(relatedComponents(e).Elements()...) // nolint: errcheck case *DeploymentView: - v.AddElements(relatedInfrastructureNodes(e).Elements()...) - v.AddElements(relatedContainerInstances(e).Elements()...) + v.AddElements(relatedInfrastructureNodes(e).Elements()...) // nolint: errcheck + v.AddElements(relatedContainerInstances(e).Elements()...) // nolint: errcheck } } @@ -173,12 +174,12 @@ func addInfluencers(cv *ContainerView) { for _, s := range m.Systems { for _, r := range s.Relationships { if r.Destination.ID == cv.SoftwareSystemID { - cv.AddElements(s) + cv.AddElements(s) // nolint: errcheck } } for _, r := range system.Relationships { if r.Destination.ID == s.ID { - cv.AddElements(s) + cv.AddElements(s) // nolint: errcheck } } } @@ -186,12 +187,12 @@ func addInfluencers(cv *ContainerView) { for _, p := range m.People { for _, r := range p.Relationships { if r.Destination.ID == cv.SoftwareSystemID { - cv.AddElements(p) + cv.AddElements(p) // nolint: errcheck } } for _, r := range system.Relationships { if r.Destination.ID == p.ID { - cv.AddElements(p) + cv.AddElements(p) // nolint: errcheck } } } diff --git a/expr/render_test.go b/expr/render_test.go index 27a7eb9d..64d6aa64 100644 --- a/expr/render_test.go +++ b/expr/render_test.go @@ -40,7 +40,7 @@ func Test_AddAllElements(t *testing.T) { {in: &mDeploymentView}, } for i, tt := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run(fmt.Sprint(i), func(_ *testing.T) { addAllElements(tt.in) }) } @@ -89,7 +89,7 @@ func Test_AddDefaultElements(t *testing.T) { {in: &mDeploymentView}, } for i, tt := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run(fmt.Sprint(i), func(_ *testing.T) { addDefaultElements(tt.in) }) } @@ -139,7 +139,7 @@ func Test_AddNeighbors(t *testing.T) { {el: mContainer.Element, in: &mDeploymentView}, } for i, tt := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run(fmt.Sprint(i), func(_ *testing.T) { addNeighbors(tt.el, tt.in) }) } @@ -290,7 +290,7 @@ func Test_AddInfluencers(t *testing.T) { {el: &mContainerView}, } for i, tt := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run(fmt.Sprint(i), func(_ *testing.T) { addInfluencers(tt.el) }) } diff --git a/expr/system.go b/expr/system.go index 62668ea5..bc34a874 100644 --- a/expr/system.go +++ b/expr/system.go @@ -18,6 +18,9 @@ type ( SoftwareSystems []*SoftwareSystem ) +// SoftwareSystemTags lists the tags that are added to all software systems. +var SoftwareSystemTags = []string{"Element", "Software System"} + // EvalName returns the generic expression name used in error messages. func (s *SoftwareSystem) EvalName() string { if s.Name == "" { @@ -28,7 +31,7 @@ func (s *SoftwareSystem) EvalName() string { // Finalize adds the 'SoftwareSystem' tag ands finalizes relationships. func (s *SoftwareSystem) Finalize() { - s.PrefixTags("Element", "Software System") + s.PrefixTags(SoftwareSystemTags...) s.Element.Finalize() } diff --git a/expr/views.go b/expr/views.go index e2a238f7..24058429 100644 --- a/expr/views.go +++ b/expr/views.go @@ -621,3 +621,53 @@ func validateElementInView(v *ViewProps, e *Element, title string, verr *eval.Va } verr.Add(v, "%T %q used in %s not added to the view %q", e, e.Name, title, v.Key) } + +// Name returns then name of the shape. +func (s ShapeKind) Name() string { + switch s { + case ShapeBox: + return "ShapeBox" + case ShapeCircle: + return "ShapeCircle" + case ShapeCylinder: + return "ShapeCylinder" + case ShapeEllipse: + return "ShapeEllipse" + case ShapeHexagon: + return "ShapeHexagon" + case ShapeRoundedBox: + return "ShapeRoundedBox" + case ShapeComponent: + return "ShapeComponent" + case ShapeFolder: + return "ShapeFolder" + case ShapeMobileDeviceLandscape: + return "ShapeMobileDeviceLandscape" + case ShapeMobileDevicePortrait: + return "ShapeMobileDevicePortrait" + case ShapePerson: + return "ShapePerson" + case ShapePipe: + return "ShapePipe" + case ShapeRobot: + return "ShapeRobot" + case ShapeWebBrowser: + return "ShapeWebBrowser" + default: + return "ShapeUndefined" + } +} + +// Name returns the name of the border kind. +func (b BorderKind) Name() string { + switch b { + case BorderSolid: + return "BorderSolid" + case BorderDashed: + return "BorderDashed" + case BorderDotted: + return "BorderDotted" + default: + return "BorderUndefined" + } +} diff --git a/go.mod b/go.mod index 0836724d..181cb75b 100644 --- a/go.mod +++ b/go.mod @@ -6,21 +6,25 @@ require ( github.com/fsnotify/fsnotify v1.6.0 github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71 github.com/kylelemons/godebug v1.1.0 + github.com/stretchr/testify v1.8.4 goa.design/goa/v3 v3.13.2 golang.org/x/tools v0.14.0 ) require ( github.com/AnatolyRugalev/goregen v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/go-chi/chi/v5 v5.0.10 // indirect github.com/google/uuid v1.3.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/smartystreets/goconvey v1.8.1 // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e8be78ab..dd154157 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,10 @@ github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71 h1:24NdJ5N6 github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71/go.mod h1:ozZLfjiLmXytkIUh200wMeuoQJ4ww06wN+KZtFP6j3g= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +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= +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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= @@ -42,25 +44,19 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= goa.design/goa/v3 v3.13.2 h1:RclNIpo7891ZqGRVO4fpBjT7Fs7LjBNm78i8J41KHrI= goa.design/goa/v3 v3.13.2/go.mod h1:VvZsuC8CSIUQOHVqk6Ep3MFSFz21OjOv87UPqCHiB94= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/mdl/elements.go b/mdl/elements.go index b79e67ea..2ce2e140 100644 --- a/mdl/elements.go +++ b/mdl/elements.go @@ -14,8 +14,6 @@ type ( Name string `json:"name,omitempty"` // Description of element if any. Description string `json:"description,omitempty"` - // Technology used by element if any - not applicable to Person. - Technology string `json:"technology,omitempty"` // Tags attached to element as comma separated list if any. Tags string `json:"tags,omitempty"` // URL where more information about this element can be found. @@ -37,8 +35,6 @@ type ( Name string `json:"name,omitempty"` // Description of element if any. Description string `json:"description,omitempty"` - // Technology used by element if any - not applicable to Person. - Technology string `json:"technology,omitempty"` // Tags attached to element as comma separated list if any. Tags string `json:"tags,omitempty"` // URL where more information about this element can be found. @@ -75,8 +71,6 @@ type ( Relationships []*Relationship `json:"relationships,omitempty"` // Components list the components within the container. Components []*Component `json:"components,omitempty"` - // Endpoints list the endpoints exposed by the container. - Endpoints []*Endpoint `json:"endpoints,omitempty"` } // Component represents a component. @@ -100,16 +94,6 @@ type ( Relationships []*Relationship `json:"relationships,omitempty"` } - // Endpoint describes a container endpoint. - // - // Note: Endpoint information is not used directly in diagrams instead - // it is serialized in the system JSON representation for other tools to - // consume. - Endpoint struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - } - // LocationKind is the enum for possible locations. LocationKind int ) diff --git a/mdl/eval.go b/mdl/eval.go index 07207f68..c32ac56c 100644 --- a/mdl/eval.go +++ b/mdl/eval.go @@ -124,7 +124,6 @@ func modelizePerson(p *expr.Person) *Person { ID: p.Element.ID, Name: p.Element.Name, Description: p.Element.Description, - Technology: p.Element.Technology, Tags: p.Element.Tags, URL: p.Element.URL, Properties: p.Element.Properties, @@ -156,7 +155,6 @@ func modelizeSystem(sys *expr.SoftwareSystem) *SoftwareSystem { ID: sys.ID, Name: sys.Name, Description: sys.Description, - Technology: sys.Technology, Tags: sys.Tags, URL: sys.URL, Properties: sys.Properties, @@ -273,6 +271,7 @@ func modelizeProps(prop *expr.ViewProps) *ViewProps { ElementViews: modelizeElementViews(prop.ElementViews), RelationshipViews: modelizeRelationshipViews(prop.RelationshipViews), Animations: modelizeAnimationSteps(prop.AnimationSteps), + Settings: modelizeSettings(prop), } if layout := prop.AutoLayout; layout != nil { props.AutoLayout = &AutoLayout{ @@ -333,6 +332,35 @@ func modelizeAnimationSteps(as []*expr.AnimationStep) []*AnimationStep { return res } +func modelizeSettings(p *expr.ViewProps) *ViewSettings { + var addNeighborsIDs []string + for _, e := range p.AddNeighbors { + addNeighborsIDs = append(addNeighborsIDs, e.GetElement().ID) + } + var removeElementsIDs []string + for _, e := range p.RemoveElements { + removeElementsIDs = append(removeElementsIDs, e.GetElement().ID) + } + var removeRelationshipsIDs []string + for _, e := range p.RemoveRelationships { + removeRelationshipsIDs = append(removeRelationshipsIDs, e.ID) + } + var removeUnreachableIDs []string + for _, e := range p.RemoveUnreachable { + removeUnreachableIDs = append(removeUnreachableIDs, e.GetElement().ID) + } + return &ViewSettings{ + AddAll: p.AddAll, + AddDefault: p.AddDefault, + AddNeighborIDs: addNeighborsIDs, + RemoveElementIDs: removeElementsIDs, + RemoveTags: p.RemoveTags, + RemoveRelationshipIDs: removeRelationshipsIDs, + RemoveUnreachableIDs: removeUnreachableIDs, + RemoveUnrelated: p.RemoveUnrelated, + } +} + func modelizeStyles(s *expr.Styles) *Styles { if s == nil { return nil diff --git a/mdl/views.go b/mdl/views.go index d562eb0c..715332d0 100644 --- a/mdl/views.go +++ b/mdl/views.go @@ -125,6 +125,9 @@ type ( RelationshipViews []*RelationshipView `json:"relationships,omitempty"` // Animations describes the animation steps if any. Animations []*AnimationStep `json:"animations,omitempty"` + // Settings records DSL settings used to compute the elements and + // relationships included in the view. + Settings *ViewSettings `json:"viewSettings,omitempty"` } // ElementView describes an instance of a model element (Person, @@ -173,6 +176,34 @@ type ( Relationships []string `json:"relationships,omitempty"` } + // ViewSettings describes the settings used to compute the elements and + // relationships included in the view. + ViewSettings struct { + // AddAll describes whether all elements/relationships should be + // included. + AddAll bool `json:"addAll"` + // AddDefault describes whether default elements/relationships should be + // included. + AddDefault bool `json:"addDefault"` + // AddNeighborIDs lists the elements that should be included in the view. + AddNeighborIDs []string `json:"addNeighbors,omitempty"` + // RemoveElementIDs lists the elements that should be removed from the + // view. + RemoveElementIDs []string `json:"removeElements,omitempty"` + // RemoveTags lists the tags that should be removed from the view. + RemoveTags []string `json:"removeTags,omitempty"` + // RemoveRelationshipIDs lists the relationship IDs that should be + // removed from the view. + RemoveRelationshipIDs []string `json:"removeRelationships,omitempty"` + // RemoveUnreachableIDs lists the element IDs that should be + // removed from the view if they are not reachable from a + // specified element. + RemoveUnreachableIDs []string `json:"removeUnreachable,omitempty"` + // RemoveUnrelated describes whether unrelated elements should be + // removed from the view. + RemoveUnrelated bool `json:"removeUnrelated"` + } + // AutoLayout describes an automatic layout. AutoLayout struct { // Algorithm rank direction. @@ -419,6 +450,56 @@ func (s *Styles) MarshalJSON() ([]byte, error) { return json.Marshal(&ss) } +// Name is the name of the paper size kind as it appears in the DSL. +func (p PaperSizeKind) Name() string { + switch p { + case SizeA0Landscape: + return "SizeA0Landscape" + case SizeA0Portrait: + return "SizeA0Portrait" + case SizeA1Landscape: + return "SizeA1Landscape" + case SizeA1Portrait: + return "SizeA1Portrait" + case SizeA2Landscape: + return "SizeA2Landscape" + case SizeA2Portrait: + return "SizeA2Portrait" + case SizeA3Landscape: + return "SizeA3Landscape" + case SizeA3Portrait: + return "SizeA3Portrait" + case SizeA4Landscape: + return "SizeA4Landscape" + case SizeA4Portrait: + return "SizeA4Portrait" + case SizeA5Landscape: + return "SizeA5Landscape" + case SizeA5Portrait: + return "SizeA5Portrait" + case SizeA6Landscape: + return "SizeA6Landscape" + case SizeA6Portrait: + return "SizeA6Portrait" + case SizeLegalLandscape: + return "SizeLegalLandscape" + case SizeLegalPortrait: + return "SizeLegalPortrait" + case SizeLetterLandscape: + return "SizeLetterLandscape" + case SizeLetterPortrait: + return "SizeLetterPortrait" + case SizeSlide16X10: + return "SizeSlide16X10" + case SizeSlide16X9: + return "SizeSlide16X9" + case SizeSlide4X3: + return "SizeSlide4X3" + default: + return "SizeUndefined" + } +} + // MarshalJSON replaces the constant value with the proper string value. func (p PaperSizeKind) MarshalJSON() ([]byte, error) { buf := bytes.NewBufferString(`"`) @@ -525,6 +606,22 @@ func (p *PaperSizeKind) UnmarshalJSON(data []byte) error { return nil } +// Name returns the name of the rank direction is specified in the DSL. +func (r RankDirectionKind) Name() string { + switch r { + case RankTopBottom: + return "RankTopBottom" + case RankBottomTop: + return "RankBottomTop" + case RankLeftRight: + return "RankLeftRight" + case RankRightLeft: + return "RankRightLeft" + default: + return "RankUndefined" + } +} + // MarshalJSON replaces the constant value with the proper string value. func (r RoutingKind) MarshalJSON() ([]byte, error) { buf := bytes.NewBufferString(`"`) diff --git a/plugin/generate.go b/plugin/generate.go index cbd58eb2..2a454796 100644 --- a/plugin/generate.go +++ b/plugin/generate.go @@ -46,7 +46,7 @@ func Generate(_ string, roots []eval.Root, files []*codegen.File) ([]*codegen.Fi return files, nil } -func toJSON(d interface{}) string { +func toJSON(d any) string { b, err := json.MarshalIndent(d, "", " ") if err != nil { panic("design: " + err.Error()) // bug diff --git a/stz/client.go b/stz/client.go index 1957e2d3..919e1895 100644 --- a/stz/client.go +++ b/stz/client.go @@ -143,7 +143,7 @@ func (c *Client) lockUnlock(id string, lock bool) error { if resp.StatusCode != http.StatusOK { var res Response - json.NewDecoder(resp.Body).Decode(&res) // ignore error, just trying + json.NewDecoder(resp.Body).Decode(&res) // nolint: errcheck err = fmt.Errorf("service error: %s", resp.Status) if res.Message != "" { err = errors.New(res.Message) diff --git a/stz/client_test.go b/stz/client_test.go index 2d1a725e..463b81a1 100644 --- a/stz/client_test.go +++ b/stz/client_test.go @@ -23,7 +23,9 @@ func TestGet(t *testing.T) { validateHeaders(t, req) rw.Header().Add("Content-Type", "application/json") rw.WriteHeader(http.StatusOK) - json.NewEncoder(rw).Encode(wkspc) + if err := json.NewEncoder(rw).Encode(wkspc); err != nil { + t.Fatalf("failed to encode response: %s", err) + } })) defer server.Close()