Skip to content

Commit e721735

Browse files
authored
Merge pull request #98 from jfeliu007/issue-97
issue-97 - Generate relation for private fields
2 parents 5a8aff1 + 07f5781 commit e721735

File tree

7 files changed

+92
-39
lines changed

7 files changed

+92
-39
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ goplantuml [-recursive] path/to/gofiles path/to/gofiles2 > diagram_file_name.pum
3333
```
3434
```
3535
Usage of goplantuml:
36+
-aggregate-private-members
37+
Show aggregations for private members. Ignored if -show-aggregations is not used.
3638
-hide-connections
3739
hides all connections in the diagram
3840
-hide-fields

cmd/goplantuml/main.go

+9-6
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ func main() {
4747
notes := flag.String("notes", "", "Comma separated list of notes to be added to the diagram")
4848
output := flag.String("output", "", "output file path. If omitted, then this will default to standard output")
4949
showOptionsAsNote := flag.Bool("show-options-as-note", false, "Show a note in the diagram with the none evident options ran with this CLI")
50-
50+
aggregatePrivateMembers := flag.Bool("aggregate-private-members", false, "Show aggregations for private members. Ignored if -show-aggregations is not used.")
5151
flag.Parse()
5252
renderingOptions := map[goplantuml.RenderingOption]interface{}{
53-
goplantuml.RenderConnectionLabels: *showConnectionLabels,
54-
goplantuml.RenderFields: !*hideFields,
55-
goplantuml.RenderMethods: !*hideMethods,
56-
goplantuml.RenderAggregations: *showAggregations,
57-
goplantuml.RenderTitle: *title,
53+
goplantuml.RenderConnectionLabels: *showConnectionLabels,
54+
goplantuml.RenderFields: !*hideFields,
55+
goplantuml.RenderMethods: !*hideMethods,
56+
goplantuml.RenderAggregations: *showAggregations,
57+
goplantuml.RenderTitle: *title,
58+
goplantuml.AggregatePrivateMembers: *aggregatePrivateMembers,
5859
}
5960
if *hideConnections {
6061
renderingOptions[goplantuml.RenderAliases] = *showAliases
@@ -179,6 +180,8 @@ func getLegend(ro map[goplantuml.RenderingOption]interface{}) (string, error) {
179180
result = fmt.Sprintf("%sRender Implementations: %t\n", result, val.(bool))
180181
case goplantuml.RenderMethods:
181182
result = fmt.Sprintf("%sRender Methods: %t\n", result, val.(bool))
183+
case goplantuml.AggregatePrivateMembers:
184+
result = fmt.Sprintf("%sPritave Aggregations: %t\n", result, val.(bool))
182185
}
183186
}
184187
return strings.TrimSpace(result), nil

parser/class_parser.go

+39-18
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,16 @@ type ClassDiagramOptions struct {
6060

6161
//RenderingOptions will allow the class parser to optionally enebale or disable the things to render.
6262
type RenderingOptions struct {
63-
Title string
64-
Notes string
65-
Aggregations bool
66-
Fields bool
67-
Methods bool
68-
Compositions bool
69-
Implementations bool
70-
Aliases bool
71-
ConnectionLabels bool
63+
Title string
64+
Notes string
65+
Aggregations bool
66+
Fields bool
67+
Methods bool
68+
Compositions bool
69+
Implementations bool
70+
Aliases bool
71+
ConnectionLabels bool
72+
AggregatePrivateMembers bool
7273
}
7374

7475
const aliasComplexNameComment = "'This class was created so that we can correctly have an alias pointing to this name. Since it contains dots that can break namespaces"
@@ -100,6 +101,9 @@ const RenderTitle = 7
100101
//RenderNotes contains a list of notes to be rendered in the class diagram
101102
const RenderNotes = 8
102103

104+
//AggregatePrivateMembers is to be used in the SetRenderingOptions argument as the key to the map, when value is true, it will connect aggregations with private members
105+
const AggregatePrivateMembers = 9
106+
103107
//RenderingOption is an alias for an it so it is easier to use it as options in a map (see SetRenderingOptions(map[RenderingOption]bool) error)
104108
type RenderingOption int
105109

@@ -530,9 +534,23 @@ func (p *ClassParser) renderCompositions(structure *Struct, name string, composi
530534

531535
func (p *ClassParser) renderAggregations(structure *Struct, name string, aggregations *LineStringBuilder) {
532536

533-
orderedAggregations := []string{}
537+
aggregationMap := structure.Aggregations
538+
if p.renderingOptions.AggregatePrivateMembers {
539+
p.updatePrivateAggregations(structure, aggregationMap)
540+
}
541+
p.renderAggregationMap(aggregationMap, structure, aggregations, name)
542+
}
543+
544+
func (p *ClassParser) updatePrivateAggregations(structure *Struct, aggregationsMap map[string]struct{}) {
545+
546+
for agg := range structure.PrivateAggregations {
547+
aggregationsMap[agg] = struct{}{}
548+
}
549+
}
534550

535-
for a := range structure.Aggregations {
551+
func (p *ClassParser) renderAggregationMap(aggregationMap map[string]struct{}, structure *Struct, aggregations *LineStringBuilder, name string) {
552+
var orderedAggregations []string
553+
for a := range aggregationMap {
536554
orderedAggregations = append(orderedAggregations, a)
537555
}
538556

@@ -626,13 +644,14 @@ func (p *ClassParser) getOrCreateStruct(name string) *Struct {
626644
result, ok := p.structure[p.currentPackageName][name]
627645
if !ok {
628646
result = &Struct{
629-
PackageName: p.currentPackageName,
630-
Functions: make([]*Function, 0),
631-
Fields: make([]*Field, 0),
632-
Type: "",
633-
Composition: make(map[string]struct{}, 0),
634-
Extends: make(map[string]struct{}, 0),
635-
Aggregations: make(map[string]struct{}, 0),
647+
PackageName: p.currentPackageName,
648+
Functions: make([]*Function, 0),
649+
Fields: make([]*Field, 0),
650+
Type: "",
651+
Composition: make(map[string]struct{}, 0),
652+
Extends: make(map[string]struct{}, 0),
653+
Aggregations: make(map[string]struct{}, 0),
654+
PrivateAggregations: make(map[string]struct{}, 0),
636655
}
637656
p.structure[p.currentPackageName][name] = result
638657
}
@@ -671,6 +690,8 @@ func (p *ClassParser) SetRenderingOptions(ro map[RenderingOption]interface{}) er
671690
p.renderingOptions.Title = val.(string)
672691
case RenderNotes:
673692
p.renderingOptions.Notes = val.(string)
693+
case AggregatePrivateMembers:
694+
p.renderingOptions.AggregatePrivateMembers = val.(bool)
674695
default:
675696
return fmt.Errorf("Invalid Rendering option %v", option)
676697
}

parser/class_parser_test.go

+22-7
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,14 @@ func TestGetOrCreateStruct(t *testing.T) {
9090
st := parser.getOrCreateStruct(tc.nameToLookFor)
9191
if tc.expectedEmpty {
9292
if !reflect.DeepEqual(st, &Struct{
93-
PackageName: parser.currentPackageName,
94-
Functions: make([]*Function, 0),
95-
Fields: make([]*Field, 0),
96-
Type: "",
97-
Composition: make(map[string]struct{}, 0),
98-
Extends: make(map[string]struct{}, 0),
99-
Aggregations: make(map[string]struct{}, 0),
93+
PackageName: parser.currentPackageName,
94+
Functions: make([]*Function, 0),
95+
Fields: make([]*Field, 0),
96+
Type: "",
97+
Composition: make(map[string]struct{}, 0),
98+
Extends: make(map[string]struct{}, 0),
99+
Aggregations: make(map[string]struct{}, 0),
100+
PrivateAggregations: make(map[string]struct{}, 0),
100101
}) {
101102
t.Errorf("Expected resulting structure to be equal to %v, got %v", tc.structure, st)
102103
}
@@ -193,6 +194,8 @@ func TestRenderStructures(t *testing.T) {
193194
}
194195
st := getTestStruct()
195196
st.Aggregations = map[string]struct{}{"File": {}}
197+
st.PrivateAggregations = map[string]struct{}{"File": {}}
198+
st.PrivateAggregations = map[string]struct{}{"File2": {}}
196199
structMap = map[string]*Struct{
197200
"MainClass": st,
198201
}
@@ -206,6 +209,18 @@ func TestRenderStructures(t *testing.T) {
206209
if lineB.String() != expectedResult {
207210
t.Errorf("TestRenderStructures: expected %s, got %s", expectedResult, lineB.String())
208211
}
212+
213+
lineB = &LineStringBuilder{}
214+
parser = getEmptyParser("main")
215+
parser.SetRenderingOptions(map[RenderingOption]interface{}{
216+
RenderAggregations: true,
217+
AggregatePrivateMembers: true,
218+
})
219+
parser.renderStructures("main", structMap, lineB)
220+
expectedResult = "namespace main {\n class MainClass << (S,Aquamarine) >> {\n - privateField int\n\n + PublicField error\n\n - foo( int, string) (error, int)\n\n + Boo( string, int) int\n\n }\n}\n\"foopack.AnotherClass\" *-- \"main.MainClass\"\n\n\"main.NewClass\" <|-- \"main.MainClass\"\n\n\"main.MainClass\" o-- \"main.File\"\n\"main.MainClass\" o-- \"main.File2\"\n\n"
221+
if lineB.String() != expectedResult {
222+
t.Errorf("TestRenderStructures: expected %s, got %s", expectedResult, lineB.String())
223+
}
209224
}
210225

211226
func TestRenderStructure(t *testing.T) {

parser/struct.go

+17-7
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import (
88
//Struct represent a struct in golang, it can be of Type "class" or "interface" and can be associated
99
//with other structs via Composition and Extends
1010
type Struct struct {
11-
PackageName string
12-
Functions []*Function
13-
Fields []*Field
14-
Type string
15-
Composition map[string]struct{}
16-
Extends map[string]struct{}
17-
Aggregations map[string]struct{}
11+
PackageName string
12+
Functions []*Function
13+
Fields []*Field
14+
Type string
15+
Composition map[string]struct{}
16+
Extends map[string]struct{}
17+
Aggregations map[string]struct{}
18+
PrivateAggregations map[string]struct{}
1819
}
1920

2021
// ImplementsInterface returns true if the struct st conforms ot the given interface
@@ -68,6 +69,11 @@ func (st *Struct) AddToAggregation(fType string) {
6869
st.Aggregations[fType] = struct{}{}
6970
}
7071

72+
//addToPrivateAggregation adds an aggregation type to the list of aggregations for private members
73+
func (st *Struct) addToPrivateAggregation(fType string) {
74+
st.PrivateAggregations[fType] = struct{}{}
75+
}
76+
7177
//AddField adds a field into this structure. It parses the ast.Field and extract all
7278
//needed information
7379
func (st *Struct) AddField(field *ast.Field, aliases map[string]string) {
@@ -84,6 +90,10 @@ func (st *Struct) AddField(field *ast.Field, aliases map[string]string) {
8490
for _, t := range fundamentalTypes {
8591
st.AddToAggregation(replacePackageConstant(t, st.PackageName))
8692
}
93+
} else {
94+
for _, t := range fundamentalTypes {
95+
st.addToPrivateAggregation(replacePackageConstant(t, st.PackageName))
96+
}
8797
}
8898
} else if field.Type != nil {
8999
if theType[0] == "*"[0] {

testingsupport/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ func (t *test) test() {
1010
}
1111

1212
type test struct {
13-
field int
13+
field int
14+
field2 TestComplicatedAlias
1415
}
1516

1617
type myInt int

testingsupport/testingsupport.puml

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ end legend
88
namespace testingsupport {
99
class test << (S,Aquamarine) >> {
1010
- field int
11+
- field2 TestComplicatedAlias
1112

1213
- test()
1314

0 commit comments

Comments
 (0)