1 /** 2 Internal functions for use inside the HTML templates. 3 4 Copyright: © 2012-2016 RejectedSoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module ddox.api; 9 10 public import ddox.ddox; 11 public import ddox.ddoc; 12 13 import std.algorithm; 14 import std.array; 15 import std.conv; 16 import std.format; 17 import std.string; 18 import std.range : isOutputRange; 19 import vibe.core.log; 20 import vibe.data.json; 21 22 23 24 class DocGroupContext : DdocContext { 25 private { 26 DocGroup m_group; 27 string delegate(in Entity ent) m_linkTo; 28 string[string] m_inheritedMacros; 29 GeneratorSettings m_settings; 30 DdocRenderOptions m_renderOptions; 31 } 32 33 this(DocGroup grp, string delegate(in Entity ent) link_to, GeneratorSettings settings) 34 { 35 import std.typecons : Rebindable; 36 37 m_group = grp; 38 m_linkTo = link_to; 39 m_settings = settings; 40 41 m_renderOptions = DdocRenderOptions.defaults; 42 if (!m_settings.highlightInlineCode) 43 m_renderOptions &= ~DdocRenderOptions.highlightInlineCode; 44 45 // Path to the root of the generated docs (ends with a '/') 46 m_inheritedMacros["DDOX_ROOT_DIR"] = link_to(null); 47 48 // inherit macros of parent scopes 49 if (grp.members.length > 0) { 50 Entity e = grp.members[0]; 51 while (true) { 52 if (cast(Module)e) break; 53 e = e.parent; 54 if (!e) break; 55 56 //auto comment = e.docGroup.comment; // TODO: make this work! 57 if (e.docGroup) { 58 auto comment = new DdocComment(e.docGroup.text); 59 foreach (k, v; comment.macros) 60 if (k !in m_inheritedMacros) 61 m_inheritedMacros[k] = v; 62 } 63 } 64 } 65 } 66 67 @property DdocRenderOptions renderOptions() { return m_renderOptions; } 68 69 @property string docText() { return m_group.text; } 70 @property string[string] overrideMacroDefinitions() { return null; } 71 @property string[string] defaultMacroDefinitions() { return m_inheritedMacros; } 72 73 LinkInfo lookupScopeSymbolLink(string name) 74 { 75 import std.range : chain, walkLength; 76 LinkInfo ret; 77 78 if (auto n = lookupSymbol(name)) { 79 // don't return links to the declaration itself (or overloads of the 80 // declaration, because we don't know which one is meant), but 81 // the sepcial string # that will still print the identifier 82 // as code 83 if (m_group.members.canFind(n)) 84 ret.uri = "#"; 85 else 86 ret.uri = m_linkTo(n); 87 88 auto qname = n.module_.qualifiedName; 89 if (name.startsWith(qname.chain("."))) 90 ret.shortName = name[qname.walkLength+1 .. $]; 91 } 92 93 return ret; 94 } 95 96 Entity lookupSymbol(string name) 97 { 98 import std.typecons : Rebindable; 99 100 assert(name.length > 0, "Empty identifier!"); 101 if (name == "this") return null; 102 103 bool is_global = false; 104 if (name.startsWith(".")) { 105 is_global = true; 106 name = name[1 .. $]; 107 assert(name.length > 0, "Missing identifier after dot!"); 108 } 109 110 foreach( def; m_group.members ){ 111 Entity n, nmod; 112 if (is_global) { 113 n = def.module_.lookup(name); 114 nmod = def.module_.lookup!Module(name); 115 } else { 116 // if this is a function, first search the parameters 117 // TODO: maybe do the same for function/delegate variables/type aliases 118 if( auto fn = cast(FunctionDeclaration)def ){ 119 foreach( p; fn.parameters ) 120 if( p.name == name ) 121 return p; 122 } 123 124 // then look up the name in the outer scope 125 n = def.lookup(name); 126 nmod = def.lookup!Module(name); 127 } 128 129 // packages are not linked 130 if (cast(Package)n) { 131 if (nmod) n = nmod; 132 else continue; 133 } 134 135 // module names must be fully qualified 136 if (auto mod = cast(Module)n) 137 if (!mod.qualifiedName.equal(name)) 138 continue; 139 140 if (n) return n; 141 } 142 143 return null; 144 } 145 } 146 147 148 /// 149 string getFunctionName(Json proto) 150 { 151 auto n = proto["name"].get!string; 152 if( auto ptn = "templateName" in proto ){ 153 auto tn = ptn.get!string; 154 if( tn.startsWith(n~"(") ) 155 return tn; 156 return tn ~ "." ~ n; 157 } 158 return n; 159 } 160 161 /// 162 unittest { 163 assert(getFunctionName(Json(["name": Json("test")])) == "test"); 164 } 165 166 inout(DocGroup)[] docGroups(inout(Declaration)[] items) 167 { 168 inout(DocGroup)[] ret; 169 foreach( itm; items ){ 170 bool found = false; 171 foreach( g; ret ) 172 if( g is itm.docGroup ){ 173 found = true; 174 break; 175 } 176 if( !found ) ret ~= itm.docGroup; 177 } 178 return ret; 179 } 180 181 auto collectKinds(DocGroup grp) 182 { 183 return grp.members 184 .map!(e => e.kindCaption) 185 .array 186 .sort() 187 .uniq; 188 } 189 190 bool hasChild(T)(Module mod){ return hasChild!T(mod.members); } 191 bool hasChild(T)(CompositeTypeDeclaration decl){ return hasChild!T(decl.members); } 192 bool hasChild(T)(TemplateDeclaration mod){ return hasChild!T(mod.members); } 193 bool hasChild(T)(Declaration[] decls){ foreach( m; decls ) if( cast(T)m ) return true; return false; } 194 195 T[] getChildren(T)(Module mod){ return getChildren!T(mod.members); } 196 T[] getChildren(T)(CompositeTypeDeclaration decl){ return getChildren!T(decl.members); } 197 T[] getChildren(T)(TemplateDeclaration decl){ return getChildren!T(decl.members); } 198 T[] getChildren(T)(Declaration[] decls) 199 { 200 T[] ret; 201 foreach( ch; decls ) 202 if( auto ct = cast(T)ch ) 203 ret ~= ct; 204 return ret; 205 } 206 207 T[] getDocGroups(T)(Module mod){ return getDocGroups!T(mod.members); } 208 T[] getDocGroups(T)(CompositeTypeDeclaration decl){ return getDocGroups!T(decl.members); } 209 T[] getDocGroups(T)(TemplateDeclaration decl){ return getDocGroups!T(decl.members); } 210 T[] getDocGroups(T)(Declaration[] decls) 211 { 212 T[] ret; 213 DocGroup dg; 214 string name; 215 foreach( d; decls ){ 216 auto dt = cast(T)d; 217 if( !dt ) continue; 218 if( dt.docGroup !is dg || dt.name != name ){ 219 ret ~= dt; 220 dg = d.docGroup; 221 name = d.name; 222 } 223 } 224 return ret; 225 } 226 227 /// 228 string getAttributeString(S : string)(S[] attributes, AttributeStringKind kind) 229 { 230 enum backAttributes = ["const", "immutable", "shared", "nothrow", "@safe", "@trusted", "@system", "pure", "@property", "@nogc", "return", "scope"]; 231 auto ret = appender!string(); 232 foreach (a; attributes) { 233 bool back = backAttributes.canFind(a); 234 if (kind == AttributeStringKind.normal || back == (kind == AttributeStringKind.functionSuffix)) { 235 if (kind == AttributeStringKind.functionSuffix) ret.put(' '); 236 ret.put(a[]); 237 if (kind != AttributeStringKind.functionSuffix) ret.put(' '); 238 } 239 } 240 return ret.data; 241 } 242 /// ditto 243 string getAttributeString(Declaration decl, AttributeStringKind kind) 244 { 245 return getAttributeString(decl.attributes, kind); 246 } 247 248 enum AttributeStringKind { normal, functionPrefix, functionSuffix } 249 250 string[] declStyleClasses(Declaration decl) 251 { 252 string[] ret; 253 ret ~= decl.protection.to!string().toLower(); 254 if (decl.inheritingDecl) ret ~= "inherited"; 255 if (auto tdecl = cast(TypedDeclaration)decl) { 256 assert(tdecl.type != CachedType.init, typeid(tdecl).name~" declaration without type!?"); 257 if (tdecl.type.attributes.canFind("@property")) ret ~= "property"; 258 if (tdecl.type.attributes.canFind("static")) ret ~= "static"; 259 } 260 return ret; 261 } 262 263 string formatType()(CachedType type, scope string delegate(in Entity) link_to, bool include_code_tags = true) 264 { 265 if (!type) return "{null}"; 266 //logDebug("format type: %s", type); 267 auto ret = appender!string(); 268 formatType(ret, type, link_to, include_code_tags); 269 return ret.data(); 270 } 271 272 void formatType(R)(ref R dst, CachedType type, scope string delegate(in Entity) link_to, bool include_code_tags = true) 273 { 274 import ddox.highlight; 275 import std.range : chain, walkLength; 276 277 if (include_code_tags) dst.put("<code class=\"prettyprint lang-d\">"); 278 foreach( att; type.attributes){ 279 dst.highlightDCode(att); 280 dst.put(' '); 281 } 282 if( type.kind != TypeKind.Function && type.kind != TypeKind.Delegate ){ 283 foreach( att; type.modifiers ){ 284 dst.highlightDCode(att); 285 dst.highlightDCode("("); 286 } 287 } 288 switch (type.kind) { 289 default: 290 case TypeKind.Primitive: 291 if (type.typeDecl && !cast(const(TemplateParameterDeclaration))type.typeDecl) { 292 auto mn = type.typeDecl.module_.qualifiedName; 293 auto qn = type.typeDecl.nestedName; 294 if (qn.startsWith(chain(mn, "."))) qn = qn[mn.walkLength+1 .. $]; 295 formattedWrite(dst, "<a href=\"%s\">%s</a>", link_to(type.typeDecl), highlightDCode(qn).replace(".", ".<wbr/>")); // TODO: avoid allocating replace 296 } else { 297 dst.highlightDCode(type.typeName); 298 } 299 if( type.templateArgs.length ){ 300 dst.put('!'); 301 dst.put(type.templateArgs[]); 302 } 303 break; 304 case TypeKind.Function: 305 case TypeKind.Delegate: 306 formatType(dst, type.returnType, link_to, false); 307 dst.put(' '); 308 dst.highlightDCode(type.kind == TypeKind.Function ? "function" : "delegate"); 309 dst.highlightDCode("("); 310 foreach( size_t i, pt; type.parameterTypes ){ 311 if( i > 0 ) dst.highlightDCode(", "); 312 formatType(dst, pt, link_to, false); 313 if( type._parameterNames[i].length ){ 314 dst.put(' '); 315 dst.put(type._parameterNames[i][]); 316 } 317 if( type._parameterDefaultValues[i] ){ 318 dst.highlightDCode(" = "); 319 dst.put(type._parameterDefaultValues[i].valueString.str); 320 } 321 } 322 if (auto suffix = getVariadicSuffix(type)) 323 dst.highlightDCode(suffix); 324 dst.highlightDCode(")"); 325 foreach (att; type.modifiers) 326 dst.formattedWrite(" %s", att); 327 break; 328 case TypeKind.Pointer: 329 formatType(dst, type.elementType, link_to, false); 330 dst.highlightDCode("*"); 331 break; 332 case TypeKind.Array: 333 formatType(dst, type.elementType, link_to, false); 334 dst.highlightDCode("[]"); 335 break; 336 case TypeKind.StaticArray: 337 formatType(dst, type.elementType, link_to, false); 338 dst.highlightDCode("["); 339 dst.highlightDCode(type.arrayLength.to!string); 340 dst.highlightDCode("]"); 341 break; 342 case TypeKind.AssociativeArray: 343 formatType(dst, type.elementType, link_to, false); 344 dst.highlightDCode("["); 345 formatType(dst, type.keyType, link_to, false); 346 dst.highlightDCode("]"); 347 break; 348 } 349 if( type.kind != TypeKind.Function && type.kind != TypeKind.Delegate ){ 350 foreach( att; type.modifiers ) dst.highlightDCode(")"); 351 } 352 if (include_code_tags) dst.put("</code>"); 353 } 354 355 string getVariadicSuffix(Type type) 356 { 357 final switch (type.variadic) { 358 case Type.Variadic.no: 359 return null; 360 case Type.Variadic.c: 361 case Type.Variadic.d: 362 return type.parameterTypes.length ? ", ..." : "..."; 363 case Type.Variadic.typesafe: 364 return "..."; 365 } 366 } 367 368 void renderTemplateArgs(R)(ref R output, Declaration decl, scope string delegate(in Entity) link_to) 369 if (isOutputRange!(R, char)) 370 { 371 import ddox.highlight : highlightDCode; 372 373 if (!decl.templateArgs.length) return; 374 375 output.put('('); 376 foreach (i, arg; decl.templateArgs) { 377 if (i > 0) output.put(", "); 378 if (arg.type != CachedType.init) { 379 output.formatType(arg.type, link_to, false); 380 output.put(' '); 381 } 382 output.put(arg.name); 383 if (arg.defaultValue.length) { 384 output.highlightDCode(" = "); 385 output.highlightDCode(arg.defaultValue); 386 } 387 } 388 output.put(')'); 389 } 390 391 CachedType getPropertyType(const(Entity)[] mems...) 392 { 393 foreach (ov; mems) { 394 auto ovf = cast(const(FunctionDeclaration))ov; 395 if (!ovf) continue; 396 auto rt = ovf.returnType; 397 assert(!!rt); 398 if (rt.typeName != "void") return rt; 399 if (ovf.parameters.length == 0) continue; 400 return ovf.parameters[0].type; 401 } 402 return CachedType.init; 403 } 404 405 bool anyPropertyGetter(const(Entity)[] mems...) 406 { 407 foreach (ov; mems) { 408 auto ovf = cast(const(FunctionDeclaration))ov; 409 if (!ovf) continue; 410 // NOTE: functions with auto return have returnType set to null 411 if (!ovf.returnType || ovf.returnType.typeName == "void") continue; 412 if (ovf.parameters.length == 0) return true; 413 } 414 return false; 415 } 416 417 bool anyPropertySetter(const(Entity)[] mems...) 418 { 419 foreach (ov; mems) { 420 auto ovf = cast(const(FunctionDeclaration))ov; 421 if (!ovf) continue; 422 if (ovf.parameters.length == 1) return true; 423 } 424 return false; 425 }