1 module uml; 2 3 import graph; 4 import std.range; 5 import std.stdio; 6 7 Dependency[] read(R)(R input) 8 { 9 import std.array : appender; 10 11 auto output = appender!(Dependency[]); 12 13 read(input, output); 14 return output.data; 15 } 16 17 private void read(Input, Output)(Input input, auto ref Output output) 18 { 19 import std.algorithm : endsWith, startsWith; 20 import std.conv : to; 21 import std.regex : matchFirst, regex; 22 23 enum arrow = `(?P<arrow><?\.+(left|right|up|down|le?|ri?|up?|do?|\[.*?\])*\.*>?)`; 24 enum pattern = regex(`(?P<lhs>\w+(.\w+)*)\s*` ~ arrow ~ `\s*(?P<rhs>\w+(.\w+)*)`); 25 26 foreach (line; input) 27 { 28 auto captures = line.matchFirst(pattern); 29 30 if (captures) 31 { 32 const lhs = captures["lhs"].to!string; 33 const rhs = captures["rhs"].to!string; 34 35 if (captures["arrow"].endsWith(">")) 36 output.put(Dependency(lhs, rhs)); 37 if (captures["arrow"].startsWith("<")) 38 output.put(Dependency(rhs, lhs)); 39 } 40 } 41 } 42 43 /// reads Plant-UML dependencies 44 unittest 45 { 46 assert(read(only("a .> b")) == [Dependency("a", "b")]); 47 assert(read(only("a <. b")) == [Dependency("b", "a")]); 48 assert(read(only("a <.> b")) == [Dependency("a", "b"), Dependency("b", "a")]); 49 assert(read(only("a.[#red]>b")) == [Dependency("a", "b")]); 50 assert(read(only("a.[#red]le>b")) == [Dependency("a", "b")]); 51 } 52 53 void write(Output)(auto ref Output output, const Dependency[] dependencies) 54 { 55 import std.algorithm : sort; 56 57 auto writer = writer(formatter(output)); 58 59 output.put("@startuml\n"); 60 foreach (element; dependencies.elements.sort) 61 writer.put(element.split('.')); 62 writer.close; 63 output.put('\n'); 64 foreach (dependency; dependencies) 65 { 66 output.put(dependency.client); 67 output.put(" ..> "); 68 output.put(dependency.supplier); 69 output.put('\n'); 70 } 71 output.put("@enduml\n"); 72 } 73 74 /// writes Plant-UML package diagram 75 unittest 76 { 77 import std.array : appender; 78 import std.string : outdent, stripLeft; 79 80 auto output = appender!string; 81 const dependencies = [Dependency("a", "b")]; 82 83 output.write(dependencies); 84 85 const expected = ` 86 @startuml 87 package a {} 88 package b {} 89 90 a ..> b 91 @enduml 92 `; 93 94 assert(output.data == outdent(expected).stripLeft, output.data); 95 } 96 97 auto writer(Formatter)(Formatter formatter) 98 { 99 return Writer!Formatter(formatter); 100 } 101 102 struct Writer(Formatter) 103 { 104 Formatter formatter; 105 106 string[] prev = null; 107 108 void put(string[] path) 109 in 110 { 111 assert(!path.empty); 112 } 113 do 114 { 115 import std.algorithm : commonPrefix; 116 117 auto prefix = commonPrefix(this.prev, path); 118 119 close(this.prev.length - prefix.length); 120 this.prev = path.dup; 121 path.popFrontN(prefix.length); 122 put(prefix, path); 123 } 124 125 void put(string[] prefix, string[] path) 126 in 127 { 128 assert(!path.empty); 129 } 130 do 131 { 132 import std.format : format; 133 import std.string : join; 134 135 const name = path.front; 136 137 prefix ~= name; 138 path.popFront; 139 if (prefix.length == 1) 140 this.formatter.open(format!"package %s {"(name)); 141 else 142 this.formatter.open(format!"package %s as %s {"(name, prefix.join('.'))); 143 if (!path.empty) 144 put(prefix, path); 145 } 146 147 void close() 148 { 149 close(this.prev.length); 150 this.prev = null; 151 } 152 153 void close(size_t n) 154 { 155 foreach (_; 0 .. n) 156 this.formatter.close("}"); 157 } 158 159 } 160 161 auto formatter(Output)(auto ref Output output) 162 { 163 return Formatter!Output(output); 164 } 165 166 struct Formatter(Output) 167 { 168 Output output; 169 170 size_t indent = 0; 171 172 bool pending = false; 173 174 void open(string s) 175 { 176 if (this.pending) 177 this.output.put("\n"); 178 indentation; 179 this.output.put(s); 180 ++this.indent; 181 this.pending = true; 182 } 183 184 void close(string s) 185 { 186 --this.indent; 187 if (!this.pending) 188 indentation; 189 this.output.put(s); 190 this.output.put("\n"); 191 this.pending = false; 192 } 193 194 void indentation() 195 { 196 foreach (_; 0 .. this.indent) 197 this.output.put(" "); 198 } 199 } 200 201 /// writes nested packages 202 unittest 203 { 204 import std.array : appender; 205 import std.string : outdent, stripLeft; 206 207 auto output = appender!string; 208 auto writer = writer(formatter(output)); 209 210 writer.put(["a", "b", "x"]); 211 writer.put(["a", "b", "y"]); 212 writer.put(["a", "z"]); 213 writer.close; 214 215 const expected = ` 216 package a { 217 package b as a.b { 218 package x as a.b.x {} 219 package y as a.b.y {} 220 } 221 package z as a.z {} 222 } 223 `; 224 225 assert(output.data == outdent(expected).stripLeft); 226 }