1 #!/usr/bin/env rdmd
2 module Bin2D;
3 import std.file : exists, isFile, isDir, dirEntries, SpanMode;
4 import std.stdio : writeln, File, SEEK_CUR, write, stdout;
5 import std..string : indexOf, lastIndexOf, tr;
6 import std.path : baseName, dirName;
7 import std.math : ceil;
8 import std.conv : to;
9 import std.getopt : getopt, defaultGetoptPrinter, config;
10 import std.format : formattedWrite;
11 import std.array : Appender;
12 
13 static ubyte[4096] buffer;
14 
15 int main(string[] args) {
16 	string[] originalArgs = args;
17 	
18 	string outputFile;
19 	string modulename;
20 	
21 	if (args.length > 2) {
22 		outputFile = args[1];
23 		if (outputFile.indexOf("=") > 0) {
24 			modulename = outputFile[outputFile.indexOf("=") + 1 .. $];
25 			outputFile = outputFile[0 .. outputFile.indexOf("=")];
26 		}
27 		
28 		args = args[0] ~ args[2 .. $];
29 	} else
30 		args = [args[0]];
31 
32 	bool usePackage;
33 	bool useUnittest;
34 	bool useEnum;
35 
36 	auto procArgs = getopt(
37 		args,
38 		"onlyPackage", "Limits the embedded files using package modifier", &usePackage,
39 		"onlyUnittest", "Limits the embedded files using version(unittest)", &useUnittest,
40 		"useEnum", `Use enum instead of const(ubyte[]) to store the data.
41 			Allows for usage at compile time but memory increase for runtime.
42 			Will require usage of the enum __ctfeValues instead of values for CTFE access.`, &useEnum
43 	);
44 	args = args[1 .. $];
45 	
46 	if (args.length > 0 && !procArgs.helpWanted) {
47 		File output = File(outputFile, "w");
48 		scope(exit) output.close;
49 		
50 		output.write("/**\n * Generated_By $(WEB, github.com/rikkimax/bin2d, Bin2D)\n",
51 				" * Copyright: Richard (Rikki) Andrew Cattermole 2014 - 2015\n * \n",
52 				" * Using_Command: ");
53 		foreach(arg; originalArgs) {
54 			output.write(arg ~ " ");
55 		}
56 		
57 		output.seek(-1, SEEK_CUR);
58 		output.write("\n * \n * Do $(I not) modify this file directly.\n");
59 		
60 		output.write(" */\nmodule ", modulename, ";\n");
61 		
62 		if (usePackage && useUnittest)
63 			output.write("version(unittest) package:\n");
64 		else if (usePackage)
65 			output.write("package:\n");
66 		else if (useUnittest)
67 			output.write("version(unittest):\n");
68 
69 		// file name processing
70 			
71 		string[] files;
72 		string[] filesWithoutScanDir;
73 		foreach (file; args) {
74 			if (file[$-1] == '/' || file[$-1] == '\\') //Clean off paths with slash on end eg. folder\
75 				file.length--;
76 			file = file.tr("\\", "/"); //use forward slashes
77 		
78 			if (exists(file)) {
79 				if (isFile(file)) {
80 					files ~= file;
81 					filesWithoutScanDir ~= baseName(file);
82 				} else if (isDir(file)) {
83 					foreach (entry; dirEntries(file, SpanMode.breadth)) {
84 						if (isFile(entry)) {
85 							files ~= entry.tr("\\", "/");
86 							filesWithoutScanDir ~= entry[file.length + 1 .. $].tr("\\", "/");
87 						}
88 					}
89 				}
90 			}
91 		}
92 		
93 		ushort longestFileName;
94 		string[] filenames;
95 		foreach(file; files){
96 			// not ideal in terms of allocation
97 			// but atleast it is only the file names,
98 			// if we were duplicating or actually storing the file data 
99 			// then we might start having problems
100 			char[] t = cast(char[])file.dup;
101 			// has to be duplicated because of modification would override the values
102 			// of course if this was string, it would do something similar
103 			// with implicit allocations + dup.
104 			
105 			if (lastIndexOf(t, "/") > 0)
106 				t = t[lastIndexOf(t, "/") + 1 .. $];
107 				
108 			// sanitises file names
109 			foreach(i, c; t) {
110 				switch(c) {
111 					case 'a': .. case 'z':
112 					case 'A': .. case 'Z':
113 					case '0': .. case '9':
114 					case '_':
115 						break;
116 					default:
117 						t[i] = '_';
118 				}
119 			}
120 			filenames ~= cast(string)t;
121 			
122 			if (file.length > longestFileName)
123 				longestFileName = cast(ushort)file.length;
124 		}
125 		
126 		output.write(
127 /*BEGIN FILE HEADER P1*/   
128 `
129 import std.file : write, isDir, exists, mkdirRecurse, rmdirRecurse, tempDir, mkdir;
130 import std.path : buildPath, dirName;
131 import std.process : thisProcessID;
132 import std.conv : text;
133 
134 deprecated("Use outputFilesToFileSystem instead")
135 alias outputBin2D2FS = outputFilesToFileSystem;
136 
137 deprecated("Use names instead")
138 alias assetNames = names;
139 
140 deprecated("Use values instead")
141 alias assetValues = values;
142 
143 deprecated("Use originalNames instead")
144 alias assetOriginalNames = originalNames;
145 
146 string rootDirectory;
147 
148 void cleanup(){
149   rmdirRecurse(rootDirectory);
150 }
151 
152 string[string] outputFilesToFileSystem() {
153 	return outputFilesToFileSystem(buildPath(tempDir(), text(thisProcessID())));
154 }
155 
156 string[string] outputFilesToFileSystem(string dir)
157 in {
158   rootDirectory = dir;
159 	if (exists(dir)) {
160 		if (isDir(dir)) {
161 			rmdirRecurse(dir);
162 			mkdirRecurse(dir);
163 		} else {
164 			mkdirRecurse(dir);
165 		}
166 	} else {
167 		mkdirRecurse(dir);
168 	}
169 } body {
170 `
171 /*END FILE HEADER P1*/);
172 	if (useEnum) {
173 		output.write(/*BEGIN FILE HEADER P2*/`
174 	string[string] files;`);
175 	foreach(i, file; files) {
176 		output.write("
177 	if (!buildPath(dir, \"", file, "\").dirName().exists())
178 		mkdir(cast(string)buildPath(dir, \"", file, "\").dirName());
179 	files[\"", file, "\"] ~= cast(string)buildPath(dir, \"", file, "\");
180 	write(buildPath(dir, \"", file, "\"), ", filenames[i], ");");
181 	}
182 
183 	output.write(`
184 	return files;
185 }
186 `
187 /*END FILE HEADER p2*/);
188 	} else {
189 		output.write(/*BEGIN FILE HEADER P2*/`
190 	string[string] files;
191 	foreach(i, name; names) {
192 		string realname = originalNames[i];
193 		if (!buildPath(dir, realname).dirName().exists())
194 		  mkdir(cast(string)buildPath(dir, realname).dirName());
195 		files[cast(string)realname] ~= cast(string)buildPath(dir, realname);
196 		write(buildPath(dir, realname), *values[i]);
197 	}
198 	return files;
199 }
200 `
201 /*END FILE HEADER p2*/);
202 	}
203 		
204 		// write report heading
205 		ushort lenNames = cast(ushort)(longestFileName + 2);
206 		
207 		write("|");
208 		foreach(i; 0 .. (lenNames * 2) + 1)
209 			write("-");
210 		writeln("|");
211 		write("|");
212 		foreach(i; 0 .. lenNames-3)
213 			write(" ");
214 		write("REPORT");
215 		foreach(i; 0 .. lenNames-2)
216 			write(" ");
217 		writeln("|");
218 		
219 		write("|");
220 		foreach(i; 0 .. lenNames)
221 			write("-");
222 		write("|");
223 		foreach(i; 0 .. lenNames)
224 			write("-");
225 		writeln("|");
226 		stdout.flush;
227 		
228 		Appender!(char[]) dfout;
229 		// giant chunk of memory that should be able to hold exactly one chunk read
230 		// is reused, so memory use of program shouldn't be all that high
231 		dfout.reserve(buffer.length * 3);
232 		
233 		foreach(i, file; files) {	
234 			// output file contents
235 			if (useEnum)
236 				output.write("enum ubyte[] ", filenames[i], " = cast(ubyte[])x\"");
237 			else
238 				output.write("const(ubyte[]) ", filenames[i], " = cast(const(ubyte[]))x\"");
239 			
240 			File readFrom = File(file, "rb");
241 			bool readSome;
242 			foreach(bytes; readFrom.byChunk(buffer)) {
243 				foreach(b; bytes) {
244 					readSome = true;
245 					formattedWrite(dfout, "%02x ", b);
246 				}
247 				
248 				output.write(dfout.data());
249 				dfout.clear();
250 			}
251 			if (readSome)
252 				output.seek(-1, SEEK_CUR);
253 			output.write("\";\n");
254 			readFrom.close;
255 			
256 			// output report for file
257 			string replac = filenames[i];
258 			
259 			write('|');
260 			if (file.length > lenNames-2)
261 				write(' ', file[0 .. lenNames-2], ' ');
262 			else {
263 				foreach(j; 0 .. lenNames/2 - cast(uint)ceil(file.length / 2f) + 1)
264 					write(' ');
265 				write(file);
266 				foreach(j; 0 .. lenNames/2 - (file.length / 2))
267 					write(' ');
268 			}
269 			write('|');
270 			if (replac.length > lenNames - 2)
271 				write(' ', replac[0 .. lenNames-2], ' ');
272 			else {
273 				foreach(j; 0 .. lenNames/2 - cast(uint)ceil(replac.length / 2f) + 1)
274 					write(' ');
275 				write(replac);
276 				foreach(j; 0 .. lenNames/2 - (replac.length / 2))
277 					write(' ');
278 			}
279 			writeln('|');
280 			
281 			stdout.flush;
282 		}
283 		
284 		// close report table
285 		
286 		write("|");
287 		foreach(i; 0 .. lenNames)
288 			write("-");
289 		write("|");
290 		foreach(i; 0 .. lenNames)
291 			write("-");
292 		writeln("|");
293 		stdout.flush;
294 		
295 		// write names/originalNames/values out
296 		
297 		output.write("\n\n");
298 		
299 		if (useEnum)
300 			output.write("enum string[] names = [");
301 		else
302 			output.write("const(string[]) names = [");
303 		
304 		foreach(i, name; filenames) {
305 			output.write("\"", name , "\", ");
306 		}
307 		output.seek(-2, SEEK_CUR);
308 		output.write("];\n");
309 		
310 		if (useEnum)
311 			output.write("enum string[] originalNames = [");
312 		else
313 			output.write("const(string[]) originalNames = [");
314 		
315 		foreach(name; files) {
316 			output.write("`", name.tr("/","\\"), "`, ");
317 		}
318 		output.seek(-2, SEEK_CUR);
319 		output.write("];\n");
320 		
321 		if (useEnum) {
322 			output.write("enum ubyte[][] __ctfeValues = [");
323 			foreach(i, name; filenames) {
324 				output.write(name, ", ");
325 			}
326 			output.seek(-2, SEEK_CUR);
327 			output.write("];\n");
328 		}
329 		
330 		if (useEnum) {
331 			output.write(`
332 const(ubyte[]*[]) values;
333 shared static this() {
334 	ubyte[]*[] ret;
335 	ret.length = __ctfeValues.length;
336 	foreach(i, v; __ctfeValues)
337 		ret[i] = &v;
338 	values = cast(const)ret;
339 }`);
340 		} else {
341 			output.write("const(ubyte[]*[]) values = [");
342 			foreach(i, name; filenames) {
343 				if (!useEnum)
344 					output.write("&", name, ", ");
345 			}
346 			output.seek(-2, SEEK_CUR);
347 			output.write("];");
348 		}
349 		
350 		return 0;
351 	} else {
352 		defaultGetoptPrinter("
353 Usage: Bin2D <output file>[=<module name>] [switches] <files or directories...>
354 Bin2D is a resource compiler for the D programming language.
355 It compiles resources down to D source code.
356 For inclusion into a build. Later accessible given an optional module name.
357 
358 Switches:"[1 .. $], procArgs.options);
359 
360 		return -1;
361 	}
362 }