1 /**
2 	Internal functions for use inside the HTML templates.
3 
4 	Copyright: © 2012 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 vibe.core.log;
19 import vibe.data.json;
20 
21 
22 
23 class DocGroupContext : DdocContext {
24 	private {
25 		DocGroup m_group;
26 		string delegate(Entity ent) m_linkTo;
27 		string[string] m_inheritedMacros;
28 	}
29 
30 	this(DocGroup grp, string delegate(Entity ent) link_to)
31 	{
32 		m_group = grp;
33 		m_linkTo = link_to;
34 
35 		// Path to the root of the generated docs (ends with a '/')
36 		m_inheritedMacros["DDOX_ROOT_DIR"] = link_to(null);
37 
38 		// inherit macros of parent scopes
39 		if (grp.members.length > 0) {
40 			auto e = grp.members[0];
41 			while (true) {
42 				if (cast(Module)e) break;
43 				e = e.parent;
44 				if (!e) break;
45 
46 				//auto comment = e.docGroup.comment; // TODO: make this work!
47 				auto comment = new DdocComment(e.docGroup.text);
48 				foreach (k, v; comment.macros)
49 					if (k !in m_inheritedMacros)
50 						m_inheritedMacros[k] = v;
51 			}
52 		}
53 	}
54 
55 	@property string docText() { return m_group.text; }
56 	@property string[string] overrideMacroDefinitions() { return null; }
57 	@property string[string] defaultMacroDefinitions() { return m_inheritedMacros; }
58 
59 	string lookupScopeSymbolLink(string name)
60 	{
61 		assert(name.length > 0, "Empty identifier!");
62 		if (name == "this") return null;
63 
64 		bool is_global = false;
65 		if (name.startsWith(".")) {
66 			is_global = true;
67 			name = name[1 .. $];
68 			assert(name.length > 0, "Missing identifier after dot!");
69 		}
70 		
71 		foreach( def; m_group.members ){
72 			Entity n, nmod;
73 			if (is_global) {
74 				n = def.module_.lookup(name);
75 				nmod = def.module_.lookup!Module(name);
76 			} else {
77 				// if this is a function, first search the parameters
78 				// TODO: maybe do the same for function/delegate variables/type aliases
79 				if( auto fn = cast(FunctionDeclaration)def ){
80 					foreach( p; fn.parameters )
81 						if( p.name == name )
82 							return m_linkTo(p);
83 				}
84 
85 				// then look up the name in the outer scope
86 				n = def.lookup(name);
87 				nmod = def.lookup!Module(name);
88 			}
89 
90 			// packages are not linked
91 			if (cast(Package)n) {
92 				if (nmod) n = nmod;
93 				else continue;
94 			}
95 
96 			// module names must be fully qualified
97 			if( auto mod = cast(Module)n )
98 				if( mod.qualifiedName != name )
99 					continue;
100 
101 			// don't return links to the declaration itself, but
102 			// the sepcial string # that will still print the identifier
103 			// as code
104 			if( n is def ) return "#";
105 			
106 			if( n ) return m_linkTo(n);
107 		}
108 		return null;
109 	}
110 }
111 
112 
113 ///
114 string getFunctionName(Json proto)
115 {
116 	auto n = proto.name.get!string;
117 	if( auto ptn = "templateName" in proto ){
118 		auto tn = ptn.get!string;
119 		if( tn.startsWith(n~"(") )
120 			return tn;
121 		return tn ~ "." ~ n;
122 	}
123 	return n;
124 }
125 
126 ///
127 unittest {
128 	assert(getFunctionName(Json(["name": Json("test")])) == "test");
129 }
130 
131 DocGroup[] docGroups(Declaration[] items)
132 {
133 	DocGroup[] ret;
134 	foreach( itm; items ){
135 		bool found = false;
136 		foreach( g; ret )
137 			if( g is itm.docGroup ){
138 				found = true;
139 				break;
140 			}
141 		if( !found ) ret ~= itm.docGroup;
142 	}
143 	return ret;
144 }
145 
146 
147 bool hasChild(T)(Module mod){ return hasChild!T(mod.members); }
148 bool hasChild(T)(CompositeTypeDeclaration decl){ return hasChild!T(decl.members); }
149 bool hasChild(T)(TemplateDeclaration mod){ return hasChild!T(mod.members); }
150 bool hasChild(T)(Declaration[] decls){ foreach( m; decls ) if( cast(T)m ) return true; return false; }
151 
152 T[] getChildren(T)(Module mod){ return getChildren!T(mod.members); }
153 T[] getChildren(T)(CompositeTypeDeclaration decl){ return getChildren!T(decl.members); }
154 T[] getChildren(T)(TemplateDeclaration decl){ return getChildren!T(decl.members); }
155 T[] getChildren(T)(Declaration[] decls)
156 {
157 	T[] ret;
158 	foreach( ch; decls )
159 		if( auto ct = cast(T)ch )
160 			ret ~= ct;
161 	return ret;
162 }
163 
164 T[] getDocGroups(T)(Module mod){ return getDocGroups!T(mod.members); }
165 T[] getDocGroups(T)(CompositeTypeDeclaration decl){ return getDocGroups!T(decl.members); }
166 T[] getDocGroups(T)(TemplateDeclaration decl){ return getDocGroups!T(decl.members); }
167 T[] getDocGroups(T)(Declaration[] decls)
168 {
169 	T[] ret;
170 	DocGroup dg;
171 	string name;
172 	foreach( d; decls ){
173 		auto dt = cast(T)d;
174 		if( !dt ) continue;
175 		if( dt.docGroup !is dg || dt.name != name ){
176 			ret ~= dt;
177 			dg = d.docGroup;
178 			name = d.name;
179 		}
180 	}
181 	return ret;
182 }
183 
184 ///
185 string getAttributeString(string[] attributes, AttributeStringKind kind)
186 {
187 	enum backAttributes = ["const", "immutable", "shared", "nothrow", "@safe", "@trusted", "@system", "pure", "@property", "@nogc"];
188 	auto ret = appender!string();
189 	foreach (a; attributes) {
190 		bool back = backAttributes.canFind(a);
191 		if (kind == AttributeStringKind.normal || back == (kind == AttributeStringKind.functionSuffix)) {
192 			if (kind == AttributeStringKind.functionSuffix) ret.put(' ');
193 			ret.put(a);
194 			if (kind != AttributeStringKind.functionSuffix) ret.put(' ');
195 		}
196 	}
197 	return ret.data;
198 }
199 /// ditto
200 string getAttributeString(Declaration decl, AttributeStringKind kind)
201 {
202 	return getAttributeString(decl.attributes, kind);
203 }
204 
205 enum AttributeStringKind { normal, functionPrefix, functionSuffix }
206 
207 string[] declStyleClasses(Declaration decl)
208 {
209 	string[] ret;
210 	ret ~= decl.protection.to!string().toLower();
211 	if (decl.inheritingDecl) ret ~= "inherited";
212 	if (auto tdecl = cast(TypedDeclaration)decl) {
213 		assert(tdecl.type !is null, typeid(tdecl).name~" declaration without type!?");
214 		if (tdecl.type.attributes.canFind("@property")) ret ~= "property";
215 		if (tdecl.type.attributes.canFind("static")) ret ~= "static";
216 	}
217 	return ret;
218 }
219 
220 string formatType()(Type type, string delegate(Entity) link_to, bool include_code_tags = true)
221 {
222 	if( !type ) return "{null}";
223 	//logDebug("format type: %s", type);
224 	auto ret = appender!string();
225 	formatType(ret, type, link_to, include_code_tags);
226 	return ret.data();
227 }
228 
229 void formatType(R)(ref R dst, Type type, string delegate(Entity) link_to, bool include_code_tags = true)
230 {
231 	import ddox.highlight;
232 
233 	if (include_code_tags) dst.put("<code class=\"prettyprint lang-d\">");
234 	foreach( att; type.attributes){
235 		dst.highlightDCode(att);
236 		dst.put(' ');
237 	}
238 	if( type.kind != TypeKind.Function && type.kind != TypeKind.Delegate ){
239 		foreach( att; type.modifiers ){
240 			dst.highlightDCode(att);
241 			dst.highlightDCode("(");
242 		}
243 	}
244 	switch (type.kind) {
245 		default:
246 		case TypeKind.Primitive:
247 			if (type.typeDecl && !cast(TemplateParameterDeclaration)type.typeDecl) {
248 				auto mn = type.typeDecl.module_.qualifiedName;
249 				auto qn = type.typeDecl.nestedName;
250 				if( qn.startsWith(mn~".") ) qn = qn[mn.length+1 .. $];
251 				formattedWrite(dst, "<a href=\"%s\">%s</a>", link_to(type.typeDecl), highlightDCode(qn).replace(".", ".<wbr/>")); // TODO: avoid allocating replace
252 			} else {
253 				dst.highlightDCode(type.typeName);
254 			}
255 			if( type.templateArgs.length ){
256 				dst.put('!');
257 				dst.put(type.templateArgs);
258 			}
259 			break;
260 		case TypeKind.Function:
261 		case TypeKind.Delegate:
262 			formatType(dst, type.returnType, link_to, false);
263 			dst.put(' ');
264 			dst.highlightDCode(type.kind == TypeKind.Function ? "function" : "delegate");
265 			dst.highlightDCode("(");
266 			foreach( size_t i, pt; type.parameterTypes ){
267 				if( i > 0 ) dst.highlightDCode(", ");
268 				formatType(dst, pt, link_to, false);
269 				if( type._parameterNames[i].length ){
270 					dst.put(' ');
271 					dst.put(type._parameterNames[i]);
272 				}
273 				if( type._parameterDefaultValues[i] ){
274 					dst.highlightDCode(" = ");
275 					dst.put(type._parameterDefaultValues[i].valueString);
276 				}
277 			}
278 			dst.highlightDCode(")");
279 			foreach( att; type.modifiers ){
280 				dst.put(' ');
281 				dst.put(att);
282 			}
283 			break;
284 		case TypeKind.Pointer:
285 			formatType(dst, type.elementType, link_to, false);
286 			dst.highlightDCode("*");
287 			break;
288 		case TypeKind.Array:
289 			formatType(dst, type.elementType, link_to, false);
290 			dst.highlightDCode("[]");
291 			break;
292 		case TypeKind.StaticArray:
293 			formatType(dst, type.elementType, link_to, false);
294 			dst.highlightDCode("[");
295 			dst.highlightDCode(type.arrayLength.to!string);
296 			dst.highlightDCode("]");
297 			break;
298 		case TypeKind.AssociativeArray:
299 			formatType(dst, type.elementType, link_to, false);
300 			dst.highlightDCode("[");
301 			formatType(dst, type.keyType, link_to, false);
302 			dst.highlightDCode("]");
303 			break;
304 	}
305 	if( type.kind != TypeKind.Function && type.kind != TypeKind.Delegate ){
306 		foreach( att; type.modifiers ) dst.highlightDCode(")");
307 	}
308 	if (include_code_tags) dst.put("</code>");
309 }
310 
311 Type getPropertyType(Entity[] mems...)
312 {
313 	foreach (ov; mems) {
314 		auto ovf = cast(FunctionDeclaration)ov;
315 		if (!ovf) continue;
316 		auto rt = ovf.returnType;
317 		if (rt.typeName != "void") return rt;
318 		if (ovf.parameters.length == 0) continue;
319 		return ovf.parameters[0].type;
320 	}
321 	return null;
322 }
323 
324 bool anyPropertyGetter(Entity[] mems...)
325 {
326 	foreach (ov; mems) {
327 		auto ovf = cast(FunctionDeclaration)ov;
328 		if (!ovf) continue;
329 		if (ovf.returnType.typeName == "void") continue;
330 		if (ovf.parameters.length == 0) return true;
331 	}
332 	return false;
333 }
334 
335 bool anyPropertySetter(Entity[] mems...)
336 {
337 	foreach (ov; mems) {
338 		auto ovf = cast(FunctionDeclaration)ov;
339 		if (!ovf) continue;
340 		if (ovf.parameters.length == 1) return true;
341 	}
342 	return false;
343 }