1 /** 2 * DSH provides conveniences that make it easier to write simple scripts in D. 3 * 4 * Several phobos modules are preemptively imported so that you don't need to 5 * do so in your script. Additionally, helper functions are defined which are 6 * especially useful for scripts. 7 */ 8 module dsh; 9 10 // Public imports from the standard library. 11 12 public import std.stdio; 13 public import std.file; 14 15 public void print(string, Args...)(string s, Args args) { 16 writefln(s, args); 17 } 18 19 public void error(string, Args...)(string s, Args args) { 20 stderr.writefln(s, args); 21 } 22 23 /** 24 * Checks if a directory is empty. 25 * Params: 26 * dir = The directory to check. 27 * Returns: True if the given directory exists, is a directory, and is empty. 28 */ 29 public bool isDirEmpty(string dir) { 30 import std.range : empty; 31 import std.array : array; 32 if (!exists(dir) || !isDir(dir)) return false; 33 return empty(dirEntries(dir, SpanMode.shallow).array); 34 } 35 36 unittest { 37 assert(!isDirEmpty("source")); 38 assert(!isDirEmpty("source/dsh.d")); 39 assert(!isDirEmpty("source/some-non-existant-file.blah")); 40 auto d = ".test_empty_dir"; 41 d.mkdir; 42 scope(exit) d.rmdir; 43 assert(isDirEmpty(d)); 44 } 45 46 /** 47 * Helper for more easily creating processes. 48 */ 49 public class ProcessBuilder { 50 private File stdin; 51 private File stdout; 52 private File stderr; 53 private string[string] env; 54 private string dir; 55 56 public this() { 57 this.stdin = std.stdio.stdin; 58 this.stdout = std.stdio.stdout; 59 this.stderr = std.stdio.stderr; 60 this.dir = getcwd(); 61 } 62 63 public ProcessBuilder inputFrom(string filename) { 64 this.stdin = File(filename, "r"); 65 return this; 66 } 67 68 public ProcessBuilder outputTo(string filename) { 69 this.stdout = File(filename, "w"); 70 return this; 71 } 72 73 public ProcessBuilder errorTo(string filename) { 74 this.stderr = File(filename, "w"); 75 return this; 76 } 77 78 public ProcessBuilder withEnv(string key, string value) { 79 this.env[key] = value; 80 return this; 81 } 82 83 public ProcessBuilder workingDir(string dir) { 84 this.dir = dir; 85 return this; 86 } 87 88 public int run(string command) { 89 import std.process; 90 import std.regex; 91 try { 92 auto r = regex("\\s+"); 93 auto s = split(command, r); 94 auto pid = spawnProcess(s, this.stdin, this.stdout, this.stderr, this.env, Config.none, this.dir); 95 return wait(pid); 96 } catch (ProcessException e) { 97 error("Could not start process \"%s\": %s", command, e.msg); 98 return -1; 99 } 100 } 101 } 102 103 unittest { 104 auto p = new ProcessBuilder() 105 .outputTo(".test.txt") 106 .workingDir("source"); 107 p.run("ls"); 108 109 } 110 111 /** 112 * Runs the given command using the user's shell. 113 * Params: 114 * cmd = The command to execute. 115 * Returns: The exit code from the command. Generally, 0 indicates success. If 116 * the process could not be started, -1 is returned. 117 */ 118 public int run(string cmd) { 119 return new ProcessBuilder().run(cmd); 120 } 121 122 unittest { 123 version(Posix) { 124 assert(run("ls") == 0); 125 } 126 version(Windows) { 127 assert(run("dir") == 0); 128 } 129 assert(run("kjafhdflkuahlkefuhfahlfeuhaf") == -1); 130 } 131 132 /** 133 * Runs the given command, and exits the program if the return code is not 0. 134 * Params: 135 * cmd = The command to run. 136 */ 137 public void runOrQuit(string cmd) { 138 import core.stdc.stdlib : exit; 139 int r = run(cmd); 140 if (r != 0) { 141 error("Process \"%s\" exited with code %d", cmd, r); 142 exit(r); 143 } 144 } 145 146 /** 147 * Gets an environment variable. 148 * Params: 149 * key = The name of the environment variable. 150 * Returns: The value of the environment variable, or null. 151 */ 152 public string getEnv(string key) { 153 import std.process : environment; 154 try { 155 return environment[key]; 156 } catch (Exception e) { 157 return null; 158 } 159 } 160 161 unittest { 162 assert(getEnv("PATH") !is null); 163 assert(getEnv("flkahelkuhfalukfehlakuefhl") is null); 164 } 165 166 /** 167 * Sets an environment variable. 168 * Params: 169 * key = The name of the environment variable. 170 * value = The value to set. 171 */ 172 public void setEnv(string key, string value) { 173 import std.process : environment; 174 try { 175 environment[key] = value; 176 } catch (Exception e) { 177 error("Could not set environment variable \"%s\": %s", key, e.msg); 178 } 179 } 180 181 unittest { 182 assert(getEnv("dsh_test_env_1") is null); 183 setEnv("dsh_test_env_1", "yes"); 184 assert(getEnv("dsh_test_env_1") == "yes"); 185 } 186 187 /** 188 * Sleeps for a specified amount of milliseconds. 189 * Params: 190 * amount = The amount of milliseconds to sleep for. 191 */ 192 public void sleepMillis(long amount) { 193 import core.thread; 194 Thread.sleep(msecs(amount)); 195 } 196 197 /** 198 * Sleeps for a specified amount of seconds. 199 * Params: 200 * amount = The amount of seconds to sleep for. 201 */ 202 public void sleepSeconds(long amount) { 203 import core.thread; 204 Thread.sleep(seconds(amount)); 205 } 206 207 /** 208 * Convenience method to replace all occurrences of matching patterns with 209 * some other string. 210 * Params: 211 * s = The string to replace things in. 212 * pattern = The regular expression to use to match. 213 * r = The thing to replace each match with. 214 * Returns: The string with all matches replaced. 215 */ 216 public string replaceAll(string s, string pattern, string r) { 217 import std.regex; 218 auto re = regex(pattern); 219 return std.regex.replaceAll(s, re, r); 220 } 221 222 unittest { 223 assert(replaceAll("abc", "a", "d") == "dbc"); 224 assert(replaceAll("123abc123", "\\d+", "") == "abc"); 225 }