1 // Written in the D programming language
2 
3 /++
4 	Ddoc generator
5 	
6 	This is a program for simplifying ddoc generation.
7 
8 	It ensures that the names of the generated .html files include the full
9 	module path (with underscores instead of dots) rather than simply being
10 	named after the modules (since just using the module names results in
11 	connflicts if any packages have modules with the same name).
12 
13 	It also provides an easy way to exclude files from ddoc generation. Any
14 	modules or packages with the name internal are excluded as well as any
15 	files that are passed on the command line. And package.d files have their
16 	corresponding .html files renamed to match the package name.
17 
18 	Also, the program generates a .ddoc file intended for use in a navigation
19 	bar on the side of the documentation (similar to what dlang.org has) uses
20 	it in the ddoc generation (it's deleted afterwards). The navigation bar
21 	contains the full module hierarchy to allow for easy navigation among the
22 	modules in the project. Of course, the other .ddoc files have to actually
23 	use the MODULE_MENU macro in the generated .ddoc file, or the documentation
24 	won't end up with a navigation bar.
25 
26 	The program assumes a layout similar to dub in that the source files are
27 	expected to be in a directory called "source", and the generated
28 	documentation goes in the "docs" directory (which is deleted before
29 	documentation generation to ensure a clean build).
30 
31 	It's expected that any .ddoc files being used will be in the "ddoc"
32 	directory, which isn't a dub thing, but they have to go somewhere.
33 
34 	In addition, the program expects there to be a "source_docs" directory. Any
35 	.dd files that are there will have corresponding .html files generated for
36 	them (e.g. for generating index.html), and any other files or directories
37 	(e.g. a "css" or "js" folder) will be copied over to the "docs" folder.
38 
39 	Note that this program does assume that all module names match their file
40 	names and that all package names match their folder names.
41 
42 	Copyright: Copyright 2017 - 2018
43 	License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
44 	Author:   Jonathan M Davis, SHOO
45   +/
46 module gendoc.main;
47 
48 
49 import gendoc.config;
50 import gendoc.generator;
51 import gendoc.modmgr;
52 import gendoc.cmdpipe;
53 alias Config = gendoc.config.Config;
54 
55 /*******************************************************************************
56  * Main routine of gendoc
57  */
58 int gendocMain(string[] args)
59 {
60 	import std.path, std.file;
61 	gendoc.config.Config cfg;
62 	DocumentGenerator generator;
63 	ModuleManager     modmgr;
64 	try
65 	{
66 		auto getoptInfo = cfg.setup(args);
67 		if (getoptInfo.helpWanted)
68 		{
69 			import std.getopt;
70 			defaultGetoptPrinter("Usage: gendoc [options]\nOptions:", getoptInfo.options);
71 			return 0;
72 		}
73 		
74 		initializeDocsDirecotry(cfg.gendocData.target);
75 		modmgr.setup(cfg);
76 		
77 		generator.setup(cfg, modmgr);
78 		
79 		generator.createTemporaryDir();
80 		scope(exit)
81 			generator.removeTemporaryDir();
82 		
83 		// generate JSON
84 		if (cfg.gendocData.enableGenerateJSON)
85 		{
86 			foreach (pkg; modmgr.dubPackages)
87 				generator.generateJson(pkg);
88 		}
89 		
90 		// set *.ddoc
91 		foreach (dir; cfg.gendocData.ddocs)
92 		{
93 			string[] ddocFiles;
94 			foreach (de; dir.dirEntries("*.ddoc", SpanMode.shallow))
95 				ddocFiles ~= de.name;
96 			foreach (de; dir.dirEntries("*.ddoc.mustache", SpanMode.shallow))
97 			{
98 				auto absDir = dir.absolutePath.buildNormalizedPath();
99 				auto absFile = de.name.absolutePath.buildNormalizedPath();
100 				ddocFiles ~= generator.generateFromMustache(
101 					modmgr.dubPackages, absDir, absFile.relativePath(absDir).stripExtension);
102 			}
103 			import std.algorithm: sort;
104 			ddocFiles.sort!((a, b) => filenameCmp(a, b) < 0);
105 			generator.ddocFiles ~= ddocFiles;
106 		}
107 		
108 		foreach (pkg; modmgr.dubPackages)
109 			generator.generate(pkg, cfg.singleFile);
110 		
111 		// set source_doc (*.dd|*.js|*.css|*.*)
112 		foreach (sdocs; cfg.gendocData.sourceDocs)
113 		{
114 			auto absSrcDir = sdocs.absolutePath.buildNormalizedPath;
115 			generator.rootDir   = absSrcDir;
116 			foreach (de; absSrcDir.dirEntries(SpanMode.depth))
117 			{
118 				import std.algorithm: endsWith;
119 				if (de.isDir)
120 					continue;
121 				auto targetFile = relativePath(de.name, absSrcDir);
122 				switch (de.name.endsWith(".mustache"))
123 				{
124 				case 1:
125 					auto absFile = de.name.absolutePath.buildNormalizedPath();
126 					auto mustachName = absFile.relativePath(absSrcDir).stripExtension;
127 					auto srcFile = generator.generateFromMustache(modmgr.dubPackages, absSrcDir, mustachName);
128 					generator.generate(targetFile, srcFile, cfg.packageData.options);
129 					break;
130 				default:
131 					generator.generate(targetFile, de.name, cfg.packageData.options);
132 					break;
133 				}
134 			}
135 		}
136 	}
137 	catch(Exception e)
138 	{
139 		import std.stdio : stderr, writeln;
140 		stderr.writeln(e.msg);
141 		return -1;
142 	}
143 
144 	return 0;
145 }
146 
147 /*******************************************************************************
148  * setup the module manager
149  */
150 void setup(ref ModuleManager modmgr, const ref Config cfg)
151 {
152 	setupModuleManagerImpl(modmgr, cfg);
153 }
154 /// ditto
155 void setup(ref ModuleManager modmgr, Config cfg)
156 {
157 	setupModuleManagerImpl(modmgr, cfg);
158 }
159 
160 private void setupModuleManagerImpl(ref ModuleManager modmgr, const ref Config cfg)
161 {
162 	modmgr.exclude(
163 		cfg.gendocData.excludePackages,
164 		cfg.gendocData.excludePackagePatterns,
165 		cfg.gendocData.excludePaths,
166 		cfg.gendocData.excludePatterns);
167 	
168 	modmgr.target = cfg.gendocData.target;
169 	
170 	void addPkg(in ref PackageConfig pkgcfg)
171 	{
172 		modmgr.addSources(
173 			pkgcfg.name,
174 			pkgcfg.packageVersion,
175 			pkgcfg.path,
176 			pkgcfg.files,
177 			pkgcfg.options);
178 		foreach (subpkg; pkgcfg.subPackages)
179 			addPkg(subpkg);
180 	}
181 	addPkg(cfg.packageData);
182 }
183 
184 /*******************************************************************************
185  * setup the module manager
186  */
187 void setup(ref DocumentGenerator generator, const ref Config cfg, const ref ModuleManager modmgr)
188 {
189 	setupDocumentGenerator(generator, cfg, modmgr);
190 }
191 /// ditto
192 void setup(ref DocumentGenerator generator, Config cfg, ModuleManager modmgr)
193 {
194 	setupDocumentGenerator(generator, cfg, modmgr);
195 }
196 
197 
198 private void setupDocumentGenerator(
199 	ref DocumentGenerator generator, const ref Config cfg, const ref ModuleManager modmgr)
200 {
201 	import std.stdio, std.path, std..string;
202 	
203 	generator.combinedDubPackagePatterns = cfg.gendocData.combinedDubPackagePatterns;
204 	
205 	if (!cfg.quiet)
206 	{
207 		generator.preGenerateCallback = (string pkgName, in ModInfo[] modInfo, in string[] args)
208 		{
209 			if (cfg.varbose)
210 			{
211 				writef("Generate %s ------------------\n", pkgName);
212 				foreach (m; modInfo)
213 				{
214 					writef("  |from: %s\n", generator.rootDir.buildPath(m.src));
215 					writef("  |  to: %s\n", generator.targetDir.buildPath(m.dst));
216 				}
217 				writef("    with args:\n%-(        %-s\n%)\n    ...", args);
218 			}
219 			else
220 			{
221 				writef("Generate %s ...", pkgName.length > 0
222 					? pkgName
223 					: modInfo.length > 0 ? modInfo[0].dst : "html");
224 			}
225 		};
226 		
227 		generator.postGenerateCallback = (string pkgName, in ModInfo[] modInfo, int status, string resMsg)
228 		{
229 			if (status == 0)
230 			{
231 				writeln(" done.");
232 			}
233 			else
234 			{
235 				if (cfg.varbose)
236 				{
237 					writefln(" Failed\n    with code = 0x%08x msg: %s", status, resMsg);
238 				}
239 				else
240 				{
241 					writeln(" Failed.");
242 				}
243 				throw new Exception(resMsg);
244 			}
245 		};
246 		
247 		generator.postCopyCallback = (string src, string dst)
248 		{
249 			if (cfg.varbose)
250 			{
251 				writefln("Copyfrom %s\n      to %s\n    ... done.",
252 					generator.targetDir.buildPath(src),
253 					generator.rootDir.buildPath(dst));
254 			}
255 			else
256 			{
257 				writefln("Copy     %s ... done.", dst);
258 			}
259 		};
260 	}
261 	
262 	generator.commandProcessor = (string args, string workDir, string[string] env)
263 	{
264 		auto pipe = CommandPipe(cfg, modmgr.dubPackages);
265 		pipe.run(args, workDir, env);
266 		return pipe.result.join("\n");
267 	};
268 	
269 	generator.executeProcessor = (string[] args, string workDir, string[string] env)
270 	{
271 		auto pipe = CommandPipe(cfg, modmgr.dubPackages);
272 		pipe.run(args, workDir, env);
273 		return pipe.result.join("\n");
274 	};
275 	generator.compiler  = cfg.compiler;
276 	generator.targetDir = cfg.gendocData.target.absolutePath.buildNormalizedPath;
277 	
278 }
279 
280 
281 /*******************************************************************************
282  * Setup docs directory
283  */
284 void initializeDocsDirecotry(string docsDir)
285 {
286 	import std.file, std.path, std.algorithm;
287 	if(!docsDir.exists)
288 		mkdir(docsDir);
289 	foreach (de; docsDir.dirEntries(SpanMode.shallow))
290 	{
291 		if (de.name.startsWith(docsDir.buildPath(".")))
292 			continue;
293 		if (de.isDir)
294 		{
295 			rmdirRecurse(de);
296 		}
297 		else
298 		{
299 			remove(de.name);
300 		}
301 	}
302 }