1 /** 2 * D Documentation Generator 3 * Copyright: © 2014 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) 6 */ 7 module main; 8 9 import std.algorithm; 10 import std.array; 11 import std.conv; 12 import dparse.ast; 13 import dparse.lexer; 14 import dparse.parser; 15 import std.file; 16 import std.getopt; 17 import std.path; 18 import std.stdio; 19 import visitor; 20 import unittest_preprocessor; 21 import macros; 22 import tocbuilder; 23 import ddoc.comments; 24 25 int main(string[] args) 26 { 27 version(unittest) 28 return 0; 29 30 string[] macroFiles; 31 string[] excludes; 32 string outputDirectory; 33 bool help; 34 string indexContent; 35 36 getopt(args, "m|macros", ¯oFiles, "o|output-directory", &outputDirectory, 37 "h|help", &help, "i|index", &indexContent, "e|exclude", &excludes); 38 39 if (help) 40 { 41 writeln(helpString); 42 return 0; 43 } 44 45 string[string] macros; 46 try 47 macros = readMacros(macroFiles); 48 catch (Exception e) 49 { 50 stderr.writeln(e.msg); 51 return 1; 52 } 53 54 if (outputDirectory is null) 55 outputDirectory = "doc"; 56 57 generateDocumentation(outputDirectory, indexContent, macros, args[1 .. $]); 58 59 return 0; 60 } 61 62 string[string] readMacros(const string[] macroFiles) 63 { 64 import ddoc.macros : DEFAULT_MACROS; 65 string[string] rVal; 66 foreach (k, v; DEFAULT_MACROS) 67 rVal[k] = v; 68 foreach (mf; macroFiles) 69 readMacroFile(mf, rVal); 70 return rVal; 71 } 72 73 void generateDocumentation(string outputDirectory, string indexContent, 74 string[string] macros, string[] args) 75 { 76 string[] files = getFilesToProcess(args); 77 import std.stdio : stderr, File; 78 stderr.writeln("Writing documentation to ", outputDirectory); 79 80 mkdirRecurse(outputDirectory); 81 82 { 83 File css = File(buildPath(outputDirectory, "style.css"), "w"); 84 css.write(stylecss); 85 File js = File(buildPath(outputDirectory, "highlight.pack.js"), "w"); 86 js.write(hljs); 87 File index = File(buildPath(outputDirectory, "index.html"), "w"); 88 index.write(indexhtml); 89 if (indexContent !is null) 90 { 91 File frontPage = File(buildPath(outputDirectory, "index_content.html"), "w"); 92 writeHeader(frontPage, "Index", 0); 93 frontPage.writeln(`<div class="breadcrumbs"> 94 <table id="results"></table> 95 <input type="search" id="search" placeholder="Search" onkeyup="searchSubmit(this.value, event)"/> 96 Main Page</div>`); 97 File indexContentFile = File(indexContent); 98 ubyte[] indexContentBytes = new ubyte[cast(uint) indexContentFile.size]; 99 indexContentFile.rawRead(indexContentBytes); 100 Comment[] prev; 101 readAndWriteComment(frontPage, cast(string) indexContentBytes, macros, prev); 102 frontPage.writeln(` 103 </body> 104 </html>`); 105 } 106 107 } 108 109 File search = File(buildPath(outputDirectory, "search.js"), "w"); 110 search.writeln(`"use strict";`); 111 search.writeln(`var items = [`); 112 113 string[] modules; 114 string[string] moduleMap; 115 116 foreach (f; files) 117 { 118 writeln("Generating documentation for ", f); 119 string moduleName; 120 string location; 121 try 122 { 123 writeDocumentation(outputDirectory, f, macros, moduleName, location, search); 124 if (moduleName != "") 125 { 126 immutable string path = stripLeadingDirectory(location, outputDirectory); 127 modules ~= moduleName; 128 moduleMap[moduleName] = path; 129 } 130 } 131 catch (Exception e) 132 { 133 stderr.writeln("Could not generate documentation for ", f, ": ", e.msg); 134 } 135 } 136 search.writeln(`];`); 137 search.writeln(searchjs); 138 139 File toc = File(buildPath(outputDirectory, "toc.html"), "w"); 140 toc.writeln(`<!DOCTYPE html> 141 <html> 142 <head> 143 <meta charset="utf-8"/> 144 <style type="text/css"> 145 html { 146 background-color: #eee; 147 padding: 0; 148 margin: 0; 149 } 150 151 body { 152 padding: 0; 153 margin: 0; 154 } 155 156 ul { 157 font-family: sans; 158 list-style: none; 159 padding: 0 0 0 1.5em; 160 } 161 ul ul { 162 display: none; 163 } 164 span { 165 cursor: pointer; 166 font-weight: bold; 167 } 168 169 .expanded::before { 170 content: "▼ "; 171 } 172 173 span::before { 174 content: "▶ "; 175 } 176 </style> 177 <script type="text/javascript"> 178 "use strict"; 179 function toggleChildren(t) { 180 var c = t.nextElementSibling; 181 if (t.className != "expanded" && (c.style.display === "" || c.style.display === "none")) { 182 c.style.display = "list-item"; 183 t.className = "expanded"; 184 } else { 185 c.style.display = "none"; 186 t.className = ""; 187 } 188 } 189 </script> 190 </head> 191 <body>`); 192 toc.writeln(`<ul>`); 193 194 sort(modules); 195 buildTree(modules, moduleMap).each!(a => a.write(toc)); 196 toc.writeln(`</ul></body></html>`); 197 } 198 199 /// Creates documentation for the module at the given path 200 void writeDocumentation(string outputDirectory, string path, 201 string[string] macros, ref string moduleName, ref string location, 202 File search) 203 { 204 import dparse.rollback_allocator : RollbackAllocator; 205 206 LexerConfig config; 207 config.fileName = path; 208 config.stringBehavior = StringBehavior.source; 209 210 File f = File(path); 211 ubyte[] fileBytes = uninitializedArray!(ubyte[])(to!size_t(f.size)); 212 f.rawRead(fileBytes); 213 StringCache cache = StringCache(1024 * 4); 214 auto tokens = getTokensForParser(fileBytes, config, &cache).array; 215 RollbackAllocator rba; 216 void doNothing(string, size_t, size_t, string, bool) {} 217 Module m = parseModule(tokens, path, &rba, &doNothing); 218 const TestRange[][size_t] unitTestMapping = getUnittestMap(m); 219 DocVisitor visitor = new DocVisitor(outputDirectory, macros, search, 220 unitTestMapping, fileBytes); 221 visitor.visit(m); 222 moduleName = visitor.moduleName; 223 location = visitor.location; 224 } 225 226 string[] getFilesToProcess(string[] args) 227 { 228 auto files = appender!(string[])(); 229 foreach (arg; args) 230 { 231 if (isDir(arg)) foreach (string fileName; dirEntries(arg, "*.{d,di}", SpanMode.depth)) 232 files.put(expandTilde(fileName)); 233 else if (isFile(arg)) 234 files.put(expandTilde(arg)); 235 else 236 stderr.writeln("Could not open `", arg, "`"); 237 } 238 return files.data; 239 } 240 241 enum helpString = ` 242 Generates documentation for D source code. 243 244 Usage: 245 harbored [Options] file.d 246 harbored [Options] directory1/ directory2/ ... 247 248 Options: 249 --macros | -m MACRO_FILE 250 Specifies a macro definition file 251 252 --output-directory | -o DIR 253 Writes the generated documentation to the given directory. If this 254 option is not specified, documentation will be written to a folder 255 called "doc" in the current directory. 256 257 --exclude | -e MODULE_NAME 258 Exclude the given module or package from the generated documentation. 259 By default no modules or packages will be excluded unless they do not 260 contain a module declaration. 261 262 --index | -i DDOC_FILE 263 Use DDOC_FILE as the content of the index.html page. By default this 264 page will be blank. 265 266 --help | -h 267 Prints this message. 268 `; 269 270 immutable string hljs = import("highlight.pack.js"); 271 immutable string stylecss = import("style.css"); 272 immutable string indexhtml = import("index.html"); 273 immutable string searchjs = import("search.js");