Skip to content

Commit

Permalink
Support component name aliases (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysztofreczek authored Nov 29, 2020
1 parent b1eea4b commit 8be330e
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 33 deletions.
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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:
Expand Down
32 changes: 26 additions & 6 deletions pkg/internal/test/structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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() {
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
11 changes: 8 additions & 3 deletions pkg/model/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
)

type Info struct {
Name string
Kind string
Description string
Technology string
Expand All @@ -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)
}
}
Expand Down
32 changes: 26 additions & 6 deletions pkg/scraper/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 4 additions & 5 deletions pkg/scraper/scraper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
56 changes: 51 additions & 5 deletions pkg/scraper/scraper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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{}
Expand Down Expand Up @@ -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) {
Expand Down
23 changes: 18 additions & 5 deletions pkg/scraper/yaml.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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++
Expand Down
Loading

0 comments on commit 8be330e

Please sign in to comment.