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