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 }