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 /** 16 * Checks if a directory is empty. 17 * Params: 18 * dir = The directory to check. 19 * Returns: True if the given directory exists, is a directory, and is empty. 20 */ 21 public bool isDirEmpty(string dir) { 22 import std.range : empty; 23 import std.array : array; 24 if (!exists(dir) || !isDir(dir)) return false; 25 return empty(dirEntries(dir, SpanMode.shallow).array); 26 } 27 28 unittest { 29 assert(!isDirEmpty("source")); 30 assert(!isDirEmpty("source/dsh.d")); 31 assert(!isDirEmpty("source/some-non-existant-file.blah")); 32 auto d = ".test_empty_dir"; 33 d.mkdir; 34 scope(exit) d.rmdir; 35 assert(isDirEmpty(d)); 36 } 37 38 /** 39 * Helper for more easily creating processes. 40 */ 41 public class ProcessBuilder { 42 private File stdin; 43 private File stdout; 44 private File stderr; 45 private string[string] env; 46 private string dir; 47 48 public this() { 49 this.stdin = std.stdio.stdin; 50 this.stdout = std.stdio.stdout; 51 this.stderr = std.stdio.stderr; 52 this.dir = getcwd(); 53 } 54 55 public ProcessBuilder inputFrom(string filename) { 56 this.stdin = File(filename, "r"); 57 return this; 58 } 59 60 public ProcessBuilder outputTo(string filename) { 61 this.stdout = File(filename, "w"); 62 return this; 63 } 64 65 public ProcessBuilder errorTo(string filename) { 66 this.stderr = File(filename, "w"); 67 return this; 68 } 69 70 public ProcessBuilder withEnv(string key, string value) { 71 this.env[key] = value; 72 return this; 73 } 74 75 public ProcessBuilder workingDir(string dir) { 76 this.dir = dir; 77 return this; 78 } 79 80 public int run(string command) { 81 import std.process; 82 import std.regex; 83 try { 84 auto r = regex("\\s+"); 85 auto s = split(command, r); 86 auto pid = spawnProcess(s, this.stdin, this.stdout, this.stderr, this.env, Config.none, this.dir); 87 return wait(pid); 88 } catch (ProcessException e) { 89 stderr.writefln!"Could not start process \"%s\": %s"(command, e.msg); 90 return -1; 91 } 92 } 93 } 94 95 unittest { 96 auto p = new ProcessBuilder() 97 .outputTo(".test.txt") 98 .workingDir("source"); 99 p.run("ls"); 100 101 } 102 103 /** 104 * Runs the given command using the user's shell. 105 * Params: 106 * cmd = The command to execute. 107 * Returns: The exit code from the command. Generally, 0 indicates success. If 108 * the process could not be started, -1 is returned. 109 */ 110 public int run(string cmd) { 111 return new ProcessBuilder().run(cmd); 112 } 113 114 unittest { 115 version(Posix) { 116 assert(run("ls") == 0); 117 } 118 version(Windows) { 119 assert(run("dir") == 0); 120 } 121 assert(run("kjafhdflkuahlkefuhfahlfeuhaf") == -1); 122 } 123 124 /** 125 * Gets an environment variable. 126 * Params: 127 * key = The name of the environment variable. 128 * Returns: The value of the environment variable, or null. 129 */ 130 public string getEnv(string key) { 131 import std.process : environment; 132 try { 133 return environment[key]; 134 } catch (Exception e) { 135 return null; 136 } 137 } 138 139 unittest { 140 assert(getEnv("PATH") !is null); 141 assert(getEnv("flkahelkuhfalukfehlakuefhl") is null); 142 } 143 144 /** 145 * Sets an environment variable. 146 * Params: 147 * key = The name of the environment variable. 148 * value = The value to set. 149 */ 150 public void setEnv(string key, string value) { 151 import std.process : environment; 152 try { 153 environment[key] = value; 154 } catch (Exception e) { 155 stderr.writefln!"Could not set environment variable \"%s\": %s"(key, e.msg); 156 } 157 } 158 159 unittest { 160 assert(getEnv("dsh_test_env_1") is null); 161 setEnv("dsh_test_env_1", "yes"); 162 assert(getEnv("dsh_test_env_1") == "yes"); 163 }