-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathclassgraph.d
124 lines (117 loc) · 3.96 KB
/
classgraph.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
module classgraph;
import tools.base: startsWith, or, rslice, Format, stuple, Stuple;
import tools.compat: replace, write;
// class graph gen
import std.moduleinit, tools.log;
void genGraph(string filename, bool drawModules = true, bool drawClasses = true, bool nested = true) {
nested &= drawClasses && drawModules;
ClassInfo[string] classfield;
string[][string] imports;
bool[string] modules;
bool ignore(string s) {
return !!s.startsWith("std."[] /or/ "object"[] /or/ "TypeInfo"[] /or/ "gcx"[]);
}
foreach (mod; ModuleInfo.modules()) {
modules[mod.name] = true;
foreach (mod2; mod.importedModules) {
if (!mod.name.ignore() && !mod2.name.ignore()) {
modules[mod2.name] = true;
imports[mod.name] ~= mod2.name;
}
}
foreach (cl; mod.localClasses) {
if (!ignore(cl.name))
classfield[cl.name] = cl;
foreach (intf; cl.interfaces)
if (!ignore(intf.classinfo.name))
classfield[intf.classinfo.name] = intf.classinfo;
}
}
auto classes = classfield.values;
string res = "Digraph G {
rankdir=LR; compound=true;
"/*concentrate=true; disabled because it crashes dot*/"
remincross=true;\n";
scope(success) filename.write(res);
scope(success) res ~= "}\n";
string[][string] mod2class;
string[string] name2label;
string filterName(string n) {
// ugly band-aid to filter invalid characters
return n.replace(".", "_").replace("!", "_").replace("(", "_").replace(")", "_");
}
foreach (cl; classes) {
auto classname = cl.name, mod = classname.rslice(".");
mod2class[mod] ~= filterName(cl.name);
name2label[filterName(cl.name)] = classname;
}
bool[string] import_relevant;
foreach (key, value; imports) {
import_relevant[key] = true;
foreach (p; value)
import_relevant[p] = true;
}
string marker(string mod) {
return "marker_"~mod.replace(".", "_");
}
string cluster(string mod) {
return "cluster_"~mod.replace(".", "_");
}
auto nestClasses = drawClasses && nested;
if (drawModules) {
if (nestClasses) {
foreach (key, value; modules) {
if (!key) continue;
res ~= "subgraph " ~ key.cluster() ~ " {\n";
res ~= "label=\"" ~ key ~ "\"; \n";
if (key in import_relevant)
res ~= key.marker() ~ " [style=invis, width=0, height=0, fontsize=0]; \n";
if (auto p = key in mod2class)
foreach (cl; *p) {
res ~= cl ~ " [label=\"" ~ name2label[cl]~"\", shape=box]; \n";
}
res ~= "}\n";
}
} else {
foreach (key, value; modules) {
if (!key) continue;
if (!(key in import_relevant)) continue;
res ~= key.marker() ~ " [label=\"" ~ key ~ "\"]; \n";
}
}
bool[string] linkAdded;
foreach (key, value; modules) {
if (auto p = key in imports)
foreach (mod2; *p) {
if (key.marker()~"!"~mod2.marker() in linkAdded) continue;
else linkAdded[key.marker()~"!"~mod2.marker()] = true;
res ~= key.marker() ~ " -> " ~ mod2.marker()
~ " [color=blue,penwidth=1.9"/*constraint=false,*/;
if (nestClasses)
res ~= ", style=dotted, ltail=" ~ key.cluster() ~ ", lhead=" ~ mod2.cluster();
res ~= "];\n";
}
}
}
if (drawClasses) {
if (!nestClasses) {
foreach (key, value; modules) {
if (!key) continue;
if (!(key in import_relevant)) continue;
if (auto p = key in mod2class) {
foreach (cl; *p)
res ~= cl ~ " [label=\"" ~ name2label[cl]~"\"]; \n";
}
}
}
foreach (cl; classes) {
auto name = cl.name;
if (cl.base && !cl.base.name.ignore())
res ~= filterName(name) ~ " -> " ~ filterName(cl.base.name) ~ " [color=red,penwidth=1.8]; \n";
foreach (i2; cl.interfaces) {
if (!i2.classinfo.name.ignore())
res ~= filterName(name) ~ " -> "~filterName(i2.classinfo.name)~" [color=red,style=dashed,penwidth=1.8]; \n";
}
}
}
}