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 }