From 8be330ef1d38605fc8df79d2c71ccd5edac129c3 Mon Sep 17 00:00:00 2001 From: Krzysztof Reczek Date: Sun, 29 Nov 2020 21:27:09 +0100 Subject: [PATCH] Support component name aliases (#12) --- README.md | 41 ++++++++++++++++++++++-- pkg/internal/test/structures.go | 32 +++++++++++++++---- pkg/model/info.go | 11 +++++-- pkg/scraper/rule.go | 32 +++++++++++++++---- pkg/scraper/scraper.go | 9 +++--- pkg/scraper/scraper_test.go | 56 ++++++++++++++++++++++++++++++--- pkg/scraper/yaml.go | 23 +++++++++++--- pkg/yaml/yaml.go | 1 + 8 files changed, 172 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 316328d..d19fbdd 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Structure `model.Info` is a basic structure that defines a component included in ```go type Info struct { Kind string // kind of scraped component + Name string // component name Description string // component description Technology string // technology used within the component Tags []string // tags are used to match view styles to component @@ -42,15 +43,34 @@ Having a scraper instantiated, you can register a set of rules that will allow t Each rule consists of: * Set of package regexp's - only types in a package matching at least one of the package regexp's will be processed * Name regexp - only type of name matching regexp will be processed -* Apply function - function that produces `model.Info` describing the component included in the scraped structure +* Apply function - function that produces `model.Info` describing the component included in the scraped structure. ```go r, err := scraper.NewRule(). WithPkgRegexps("github.com/krzysztofreczek/pkg/foo/.*"). WithNameRegexp("^(.*)Client$"). WithApplyFunc( - func() model.Info { - return model.ComponentInfo("foo client", "gRPC", "TAG") + func(name string, _ ...string) model.Info { + return model.ComponentInfo(name, "foo client", "gRPC", "TAG") + }). + Build() +err = s.RegisterRule(r) +``` + +The apply function has two arguments: name and groups matched from the name regular expression. + +Please note, groups' numbers start from index `1`. Group of index `0` contains the original type name in a format: `package.TypeName`. + +See the example: +```go +r, err := scraper.NewRule(). + WithPkgRegexps("github.com/krzysztofreczek/pkg/foo/.*"). + WithNameRegexp(`^(\w*)\.(\w*)Client$`). + WithApplyFunc( + func(_ string, groups ...string) model.Info { + // Do some groups sanity checks first, then: + n := fmt.Sprintf("Client of external %s service", groups[2]) + return model.ComponentInfo(n, "foo client", "gRPC", "TAG") }). Build() err = s.RegisterRule(r) @@ -68,6 +88,21 @@ rules: pkg_regexps: - "github.com/krzysztofreczek/pkg/foo/.*" component: + name: "FooClient" + description: "foo client" + technology: "gRPC" + tags: + - TAG +``` + +Regex groups may also be used within yaml rule definition. Here you can find an example: +```yaml +rules: + - name_regexp: "(\\w*)\\.(\\w*)Client$" + pkg_regexps: + - "github.com/krzysztofreczek/pkg/foo/.*" + component: + name: "Client of external {2} service" description: "foo client" technology: "gRPC" tags: diff --git a/pkg/internal/test/structures.go b/pkg/internal/test/structures.go index 7d8f569..0efd1bb 100644 --- a/pkg/internal/test/structures.go +++ b/pkg/internal/test/structures.go @@ -15,7 +15,10 @@ type PublicComponent struct{} type PublicComponentHasInfo struct{} func (r PublicComponentHasInfo) Info() model.Info { - return model.ComponentInfo("public") + return model.ComponentInfo( + "test.PublicComponentHasInfo", + "public", + ) } func (r PublicComponentHasInfo) DoSomethingPublic() { @@ -31,7 +34,10 @@ type privateComponent struct{} type privateComponentHasInfo struct{} func (r privateComponentHasInfo) Info() model.Info { - return model.ComponentInfo("private") + return model.ComponentInfo( + "test.privateComponentHasInfo", + "private", + ) } func (r privateComponentHasInfo) DoSomethingPublic() { @@ -60,6 +66,7 @@ func NewRootEmptyHasInfoPtr() *RootEmptyHasInfo { func (r RootEmptyHasInfo) Info() model.Info { return model.ComponentInfo( + "test.RootEmptyHasInfo", "root description", "root technology", "root tag 1", @@ -79,6 +86,7 @@ func NewRootEmptyPtrHasInfoPtr() *RootEmptyPtrHasInfo { func (r *RootEmptyPtrHasInfo) Info() model.Info { return model.ComponentInfo( + "test.RootEmptyPtrHasInfo", "root description", "root technology", "root tag 1", @@ -577,7 +585,10 @@ func NewRootHasInfoWithComponentHasInfoValue() RootHasInfoWithComponentHasInfoVa } func (r RootHasInfoWithComponentHasInfoValue) Info() model.Info { - return model.ComponentInfo("root has info") + return model.ComponentInfo( + "test.RootHasInfoWithComponentHasInfoValue", + "root has info", + ) } type RootHasInfoWithComponentHasInfoPointer struct { @@ -591,7 +602,10 @@ func NewRootHasInfoWithComponentHasInfoPointer() RootHasInfoWithComponentHasInfo } func (r RootHasInfoWithComponentHasInfoPointer) Info() model.Info { - return model.ComponentInfo("root has info") + return model.ComponentInfo( + "test.RootHasInfoWithComponentHasInfoPointer", + "root has info", + ) } type RootHasInfoWithNestedComponents struct { @@ -605,7 +619,10 @@ func NewRootHasInfoWithNestedComponents() RootHasInfoWithNestedComponents { } func (r RootHasInfoWithNestedComponents) Info() model.Info { - return model.ComponentInfo("root with nested public components") + return model.ComponentInfo( + "test.RootHasInfoWithNestedComponents", + "root with nested public components", + ) } type RootHasInfoWithNestedPrivateComponents struct { @@ -619,7 +636,10 @@ func NewRootHasInfoWithNestedPrivateComponents() RootHasInfoWithNestedPrivateCom } func (r RootHasInfoWithNestedPrivateComponents) Info() model.Info { - return model.ComponentInfo("root with nested private components") + return model.ComponentInfo( + "test.RootHasInfoWithNestedPrivateComponents", + "root with nested private components", + ) } type RootWithPublicMapOfHasInfoInterfaces struct { diff --git a/pkg/model/info.go b/pkg/model/info.go index 0c5ccc2..0888113 100644 --- a/pkg/model/info.go +++ b/pkg/model/info.go @@ -9,6 +9,7 @@ const ( ) type Info struct { + Name string Kind string Description string Technology string @@ -26,15 +27,19 @@ func info(kind string, s ...string) Info { } if len(s) > 0 { - info.Description = s[0] + info.Name = s[0] } if len(s) > 1 { - info.Technology = s[1] + info.Description = s[1] + } + + if len(s) > 2 { + info.Technology = s[2] } for i, tag := range s { - if i > 1 { + if i > 2 { info.Tags = append(info.Tags, tag) } } diff --git a/pkg/scraper/rule.go b/pkg/scraper/rule.go index a5a4e43..13aa68d 100644 --- a/pkg/scraper/rule.go +++ b/pkg/scraper/rule.go @@ -11,11 +11,19 @@ var ( matchAllRegexp = regexp.MustCompile("^.*$") ) -type RuleApplyFunc func() model.Info +type RuleApplyFunc func( + name string, + groups ...string, +) model.Info type Rule interface { - Applies(pkg string, name string) bool - Apply() model.Info + Applies( + pkg string, + name string, + ) bool + Apply( + name string, + ) model.Info } type rule struct { @@ -62,12 +70,24 @@ func newRule( }, nil } -func (r rule) Applies(pkg string, name string) bool { +func (r rule) Applies( + pkg string, + name string, +) bool { return r.nameApplies(name) && r.pkgApplies(pkg) } -func (r rule) Apply() model.Info { - return r.applyFunc() +func (r rule) Apply( + name string, +) model.Info { + regex := r.nameRegex + + groups := regex.FindAllStringSubmatch(name, -1) + if len(groups) != 0 && len(groups[0]) > 1 { + return r.applyFunc(groups[0][0], groups[0][1:]...) + } + + return r.applyFunc(name) } func (r rule) pkgApplies(pkg string) bool { diff --git a/pkg/scraper/scraper.go b/pkg/scraper/scraper.go index f19e594..86e20fe 100644 --- a/pkg/scraper/scraper.go +++ b/pkg/scraper/scraper.go @@ -132,7 +132,7 @@ func (s *Scraper) addComponent( c := model.Component{ ID: componentID(v), Kind: info.Kind, - Name: componentName(v), + Name: info.Name, Description: info.Description, Technology: info.Technology, Tags: info.Tags, @@ -177,13 +177,12 @@ func (s *Scraper) getInfoFromInterface(v reflect.Value) (model.Info, bool) { func (s *Scraper) getInfoFromRules(v reflect.Value) (model.Info, bool) { vPkg := valuePackage(v) - vType := v.Type().Name() + name := componentName(v) for _, r := range s.rules { - if !r.Applies(vPkg, vType) { + if !r.Applies(vPkg, name) { continue } - - return r.Apply(), true + return r.Apply(name), true } return model.Info{}, false diff --git a/pkg/scraper/scraper_test.go b/pkg/scraper/scraper_test.go index 0b9a60d..252bcf6 100644 --- a/pkg/scraper/scraper_test.go +++ b/pkg/scraper/scraper_test.go @@ -619,8 +619,9 @@ func TestScraper_Scrap_rules(t *testing.T) { ) ruleDefaultMatchAll, err := scraper.NewRule(). - WithApplyFunc(func() model.Info { + WithApplyFunc(func(name string, groups ...string) model.Info { return model.ComponentInfo( + name, "match-all description", "match-all technology", "match-all tag 1", @@ -631,9 +632,10 @@ func TestScraper_Scrap_rules(t *testing.T) { require.NoError(t, err) ruleMatchPublicComponent, err := scraper.NewRule(). - WithNameRegexp("^PublicComponent$"). - WithApplyFunc(func() model.Info { + WithNameRegexp("^test.PublicComponent$"). + WithApplyFunc(func(name string, groups ...string) model.Info { return model.ComponentInfo( + name, "match-pc description", "match-pc technology", "match-pc tag 1", @@ -645,13 +647,31 @@ func TestScraper_Scrap_rules(t *testing.T) { ruleMatchPublicComponentInAnotherPackage, err := scraper.NewRule(). WithPkgRegexps("^github.com/krzysztofreczek/go-structurizr/pkg/foo$"). - WithNameRegexp("^PublicComponent$"). - WithApplyFunc(func() model.Info { + WithNameRegexp("^test.PublicComponent$"). + WithApplyFunc(func(name string, groups ...string) model.Info { return model.ComponentInfo() }). Build() require.NoError(t, err) + ruleMatchPublicComponentWithNameAlias, err := scraper.NewRule(). + WithNameRegexp("^test.PublicComponent$"). + WithApplyFunc(func(name string, groups ...string) model.Info { + n := fmt.Sprintf("%sAlias", name) + return model.ComponentInfo(n) + }). + Build() + require.NoError(t, err) + + ruleMatchComponentWithNameGroups, err := scraper.NewRule(). + WithNameRegexp(`test\.(\w*)With(\w*)To(\w*)`). + WithApplyFunc(func(name string, groups ...string) model.Info { + n := fmt.Sprintf("test.%sWith%sTo%s", groups[0], groups[1], groups[2]) + return model.ComponentInfo(n) + }). + Build() + require.NoError(t, err) + var tests = []struct { name string structure interface{} @@ -708,6 +728,32 @@ func TestScraper_Scrap_rules(t *testing.T) { rules: []scraper.Rule{ruleMatchPublicComponentInAnotherPackage}, expectedComponents: map[string]model.Component{}, }, + { + name: "name with alias rule", + structure: test.NewRootWithPublicPointerToPublicComponent(), + rules: []scraper.Rule{ruleMatchPublicComponentWithNameAlias}, + expectedComponents: map[string]model.Component{ + componentID("PublicComponent"): { + ID: componentID("PublicComponent"), + Kind: "component", + Name: "test.PublicComponentAlias", + Tags: []string{}, + }, + }, + }, + { + name: "name recreated from groups rule", + structure: test.NewRootWithPublicPointerToPublicComponent(), + rules: []scraper.Rule{ruleMatchComponentWithNameGroups}, + expectedComponents: map[string]model.Component{ + componentID("RootWithPublicPointerToPublicComponent"): { + ID: componentID("RootWithPublicPointerToPublicComponent"), + Kind: "component", + Name: "test.RootWithPublicPointerToPublicComponent", + Tags: []string{}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/scraper/yaml.go b/pkg/scraper/yaml.go index 22d4814..7a6ccd2 100644 --- a/pkg/scraper/yaml.go +++ b/pkg/scraper/yaml.go @@ -1,8 +1,10 @@ package scraper import ( + "fmt" "github.com/krzysztofreczek/go-structurizr/pkg/model" "github.com/krzysztofreczek/go-structurizr/pkg/yaml" + "strings" ) func toScraperConfig(c yaml.Config) Configuration { @@ -17,12 +19,23 @@ func toScraperRules(c yaml.Config) ([]Rule, error) { WithNameRegexp(r.NameRegexp). WithPkgRegexps(r.PackageRegexps...). WithApplyFunc( - func() model.Info { - info := make([]string, len(r.Component.Tags)+2) - info[0] = r.Component.Description - info[1] = r.Component.Technology + func(name string, groups ...string) model.Info { + info := make([]string, len(r.Component.Tags)+3) - idx := 2 + if r.Component.Name != "" { + name = r.Component.Name + } + + for i, g := range groups { + placeholder := fmt.Sprintf("{%d}", i) + name = strings.Replace(name, placeholder, g, -1) + } + info[0] = name + + info[1] = r.Component.Description + info[2] = r.Component.Technology + + idx := 3 for _, t := range r.Component.Tags { info[idx] = t idx++ diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index b6a461f..fc18ed4 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -23,6 +23,7 @@ type ConfigRule struct { } type ConfigRuleComponent struct { + Name string `yaml:"name"` Description string `yaml:"description"` Technology string `yaml:"technology"` Tags []string `yaml:"tags"`