1 /**
2 	Serves documentation on through HTTP server.
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.htmlserver;
9 
10 import ddox.api;
11 import ddox.ddoc; // just so that rdmd picks it up
12 import ddox.entities;
13 import ddox.htmlgenerator;
14 import ddox.settings;
15 
16 import std.array;
17 import std..string;
18 import vibe.core.log;
19 import vibe.http.fileserver;
20 import vibe.http.router;
21 import vibe.templ.diet; // just so that rdmd picks it up
22 
23 
24 void registerApiDocs(URLRouter router, Package pack, GeneratorSettings settings = null)
25 {
26 	if( !settings ) settings = new GeneratorSettings;
27 
28 	string linkTo(Entity ent, size_t level)
29 	{
30 		auto dst = appender!string();
31 
32 		if( level ) foreach( i; 0 .. level ) dst.put("../");
33 		else dst.put("./");
34 
35 		if( ent !is null && ent.parent !is null ){
36 			auto dp = cast(VariableDeclaration)ent;
37 			auto dfn = cast(FunctionDeclaration)ent.parent;
38 			if( dp && dfn ) ent = ent.parent;
39 
40 			Entity[] nodes;
41 			size_t mod_idx = 0;
42 			while( ent ){
43 				if( cast(Module)ent ) mod_idx = nodes.length;
44 				nodes ~= ent;
45 				ent = ent.parent;
46 			}
47 			foreach_reverse(i, n; nodes[mod_idx .. $-1]){
48 				dst.put(n.name);
49 				if( i > 0 ) dst.put('.');
50 			}
51 			dst.put("/");
52 			foreach_reverse(i, n; nodes[0 .. mod_idx]){
53 				dst.put(n.name);
54 				if( i > 0 ) dst.put('.');
55 			}
56 
57 			if( dp && dfn ){
58 				dst.put('#');
59 				dst.put(dp.name);
60 			}
61 		}
62 
63 		return dst.data();
64 	}
65 
66 	void showApi(HTTPServerRequest req, HTTPServerResponse res)
67 	{
68 		res.contentType = "text/html; charset=UTF-8";
69 		generateApiIndex(res.bodyWriter, pack, settings, ent => linkTo(ent, 0), req);
70 	}
71 
72 	void showApiModule(HTTPServerRequest req, HTTPServerResponse res)
73 	{
74 		auto mod = pack.lookup!Module(req.params["modulename"]);
75 		if( !mod ) return;
76 
77 		res.contentType = "text/html; charset=UTF-8";
78 		generateModulePage(res.bodyWriter, pack, mod, settings, ent => linkTo(ent, 1), req);
79 	}
80 
81 	void showApiItem(HTTPServerRequest req, HTTPServerResponse res)
82 	{
83 		import std.algorithm;
84 
85 		auto mod = pack.lookup!Module(req.params["modulename"]);
86 		logDebug("mod: %s", mod !is null);
87 		if( !mod ) return;
88 		auto items = mod.lookupAll!Declaration(req.params["itemname"]);
89 		logDebug("items: %s", items.length);
90 		if( !items.length ) return;
91 
92 		auto docgroups = items.map!(i => i.docGroup).uniq.array;
93 
94 		res.contentType = "text/html; charset=UTF-8";
95 		generateDeclPage(res.bodyWriter, pack, mod, items[0].nestedName, docgroups, settings, ent => linkTo(ent, 1), req);
96 	}
97 
98 	void showSitemap(HTTPServerRequest req, HTTPServerResponse res)
99 	{
100 		res.contentType = "application/xml";
101 		generateSitemap(res.bodyWriter, pack, settings, ent => linkTo(ent, 0), req);
102 	}
103 
104 	string symbols_js;
105 	string symbols_js_md5;
106 
107 	void showSymbolJS(HTTPServerRequest req, HTTPServerResponse res)
108 	{
109 		if (!symbols_js.length) {
110 			import std.digest.md;
111 			import vibe.stream.memory;
112 			auto os = new MemoryOutputStream;
113 			generateSymbolsJS(os, pack, settings, ent => linkTo(ent, 0));
114 			symbols_js = cast(string)os.data;
115 			symbols_js_md5 = '"' ~ md5Of(symbols_js).toHexString().idup ~ '"';
116 		}
117 
118 		if (req.headers.get("If-None-Match", "") == symbols_js_md5) {
119 			res.statusCode = HTTPStatus.NotModified;
120 			res.writeVoidBody();
121 			return;
122 		}
123 
124 		res.headers["ETag"] = symbols_js_md5;
125 		res.writeBody(symbols_js, "application/javascript");
126 	}
127 
128 	auto path_prefix = settings.siteUrl.path.toString();
129 	if( path_prefix.endsWith("/") ) path_prefix = path_prefix[0 .. $-1];
130 
131 	router.get(path_prefix~"/", &showApi);
132 	router.get(path_prefix~"/:modulename/", &showApiModule);
133 	router.get(path_prefix~"/:modulename/:itemname", &showApiItem);
134 	router.get(path_prefix~"/sitemap.xml", &showSitemap);
135 	router.get(path_prefix~"/symbols.js", &showSymbolJS);
136 	router.get("*", serveStaticFiles("public"));
137 
138 	// convenience redirects (when leaving off the trailing slash)
139 	if( path_prefix.length ) router.get(path_prefix, staticRedirect(path_prefix~"/"));
140 	router.get(path_prefix~"/:modulename", delegate(req, res){ res.redirect(path_prefix~"/"~req.params["modulename"]~"/"); });
141 }