1 #!/usr/bin/env dub 2 /+ dub.sdl: 3 dependency "dsh" version="~>1.3.0" 4 dependency "fswatch" version="~>0.6.0" 5 +/ 6 7 /** 8 * A helper program that can be used to make, build, and manage dsh scripts. 9 */ 10 module dshutil; 11 12 import dsh; 13 14 const DSH_VERSION = "1.3.0"; 15 16 int main(string[] args) { 17 import std.string; 18 if (args.length < 2) { 19 stderr.writeln("Missing required command argument."); 20 return 1; 21 } 22 string command = args[1].strip; 23 if (command == "create") return createScript(args[2..$]); 24 if (command == "build") return buildScript(args[2..$]); 25 if (command == "compile") return compileScript(args[2..$]); 26 version(Linux) { 27 if (command == "install") return install(); 28 if (command == "uninstall") return uninstall(); 29 } 30 stderr.writefln!"Unsupported command: %s"(command); 31 return 1; 32 } 33 34 /** 35 * Creates a new, empty script. 36 * Params: 37 * args = The arguments to the command. Accepts one optional argument to 38 * specify a name for the script file. 39 * Returns: 0 if successful, or 1 otherwise. 40 */ 41 int createScript(string[] args) { 42 import std.conv : to; 43 import std.string : strip; 44 string filePath = "script.d"; 45 int tryCount = 1; 46 while (filePath.exists) { 47 filePath = "script" ~ tryCount.to!string ~ ".d"; 48 } 49 if (args.length > 0) { 50 filePath = args[0].strip; 51 if (filePath.exists) { 52 stderr.writefln!"Cannot create script because %s already exists."(filePath); 53 return 1; 54 } 55 } 56 auto f = new File(filePath, "w"); 57 // Only include the shebang on Linux. 58 version (linux) { 59 f.writeln("#!/usr/bin/env dub"); 60 } 61 f.writeln("/+ dub.sdl:"); 62 f.writeln(" dependency \"dsh\" version=\"~>" ~ DSH_VERSION ~ "\""); 63 f.writeln("+/"); 64 f.writeln("import dsh;"); 65 f.writeln(); 66 f.writeln("void main() {"); 67 f.writeln(" writeln(\"Edit this to start writing your script.\");"); 68 f.writeln("}"); 69 f.writeln(); 70 f.close(); 71 // If on linux, set the file to be executable. 72 version (linux) { 73 run("chmod +x " ~ filePath); 74 } 75 writefln!"Created script: %s. Call \"./%s\" to run your script."(filePath, filePath); 76 return 0; 77 } 78 79 /** 80 * Watches a file to build it using DUB single-file mode, any time a change 81 * is noticed. 82 * Params: 83 * args = The program arguments. Accepts a single required argument being 84 * the file to build/watch. 85 * Returns: 0 if successful, or 1 otherwise. 86 */ 87 int buildScript(string[] args) { 88 import std.string; 89 if (args.length < 1) { 90 stderr.writeln("Missing required file argument."); 91 return 1; 92 } 93 string filePath = args[0].strip; 94 if (!exists(filePath) || !isFile(filePath)) { 95 stderr.writefln!"%s is not a file."(filePath); 96 return 1; 97 } 98 import fswatch; 99 import core.thread; 100 auto watcher = FileWatch(filePath, false); 101 writefln!"Watching %s to build when file changes."(filePath); 102 ProcessBuilder pb = new ProcessBuilder(); 103 pb.run("dub build --single " ~ filePath); 104 while (true) { 105 foreach (event; watcher.getEvents()) { 106 if (event.type == FileChangeEventType.modify) handleFileUpdate(filePath, pb); 107 } 108 Thread.sleep(seconds(1)); 109 } 110 } 111 112 private void handleFileUpdate(string filePath, ProcessBuilder pb) { 113 import std.algorithm; 114 writeln("File changed. Rebuilding..."); 115 if (pb.run("dub build --single " ~ filePath) == 0) { 116 auto f = File(filePath, "r"); 117 foreach (string line; lines(f)) { 118 if (startsWith(line, "// DSHTEST:")) runScriptTest(filePath, line); 119 } 120 f.close(); 121 } 122 } 123 124 private void runScriptTest(string filePath, string line) { 125 import std.string; 126 import std.algorithm; 127 if (line.length < 12) return; 128 string args = line[11 .. $].strip; 129 if (args.length == 0) return; 130 string scriptName = filePath; 131 if (endsWith(filePath, ".d")) { 132 scriptName = filePath[0..$-2]; 133 } 134 version (linux) { 135 scriptName = "./" ~ scriptName; 136 } 137 version (Windows) { 138 scriptName = scriptName ~ ".exe"; 139 } 140 string command = scriptName ~ " " ~ args; 141 writefln!"Running \"%s\""(command); 142 int result = run(command); 143 writefln!"Script exited %d"(result); 144 } 145 146 int compileScript(string[] args) { 147 import std.string; 148 if (args.length < 1) { 149 stderr.writeln("Missing required file argument."); 150 return 1; 151 } 152 string filePath = args[0].strip; 153 if (!exists(filePath) || !isFile(filePath)) { 154 stderr.writefln!"%s is not a file."(filePath); 155 return 1; 156 } 157 writeln("Compiling " ~ filePath); 158 int r = run("dub build --single --build=release " ~ filePath); 159 if (r != 0) { 160 stderr.writefln!"Could not compile: %d"(r); 161 return 1; 162 } 163 return 0; 164 } 165 166 version(linux) { 167 int install() { 168 runOrQuit("dub build --single --build=release dshutil.d"); 169 runOrQuit("mv dshutil /usr/local/bin/dshutil"); 170 writeln("Installed dshutil to /usr/local/bin"); 171 return 0; 172 } 173 174 int uninstall() { 175 const filePath = "/usr/local/bin/dshutil"; 176 if (exists(filePath)) { 177 remove(filePath); 178 } 179 writeln("Uninstalled dshutil from /usr/local/bin"); 180 return 0; 181 } 182 }