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