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 auto 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.join(" ");
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.specValue.length) {
384 			output.highlightDCode(" : ");
385 			output.highlightDCode(arg.specValue);
386 		}
387 		if (arg.defaultValue.length) {
388 			output.highlightDCode(" = ");
389 			output.highlightDCode(arg.defaultValue);
390 		}
391 	}
392 	output.put(')');
393 }
394 
395 CachedType getPropertyType(const(Entity)[] mems...)
396 {
397 	foreach (ov; mems) {
398 		auto ovf = cast(const(FunctionDeclaration))ov;
399 		if (!ovf) continue;
400 		auto rt = ovf.returnType;
401 		assert(!!rt);
402 		if (rt.typeName != "void") return rt;
403 		if (ovf.parameters.length == 0) continue;
404 		return ovf.parameters[0].type;
405 	}
406 	return CachedType.init;
407 }
408 
409 bool anyPropertyGetter(const(Entity)[] mems...)
410 {
411 	foreach (ov; mems) {
412 		auto ovf = cast(const(FunctionDeclaration))ov;
413 		if (!ovf) continue;
414 		// NOTE: functions with auto return have returnType set to null
415 		if (!ovf.returnType || ovf.returnType.typeName == "void") continue;
416 		if (ovf.parameters.length == 0) return true;
417 	}
418 	return false;
419 }
420 
421 bool anyPropertySetter(const(Entity)[] mems...)
422 {
423 	foreach (ov; mems) {
424 		auto ovf = cast(const(FunctionDeclaration))ov;
425 		if (!ovf) continue;
426 		if (ovf.parameters.length == 1) return true;
427 	}
428 	return false;
429 }