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  * Runs the given command, and exits the program if the return code is not 0.
126  * Params:
127  *   cmd = The command to run.
128  */
129 public void runOrQuit(string cmd) {
130     import core.stdc.stdlib : exit;
131     int r = run(cmd);
132     if (r != 0) {
133         stderr.writefln!"Process \"%s\" exited with code %d"(cmd, r);
134         exit(r);
135     }
136 }
137 
138 /** 
139  * Gets an environment variable.
140  * Params:
141  *   key = The name of the environment variable.
142  * Returns: The value of the environment variable, or null.
143  */
144 public string getEnv(string key) {
145     import std.process : environment;
146     try {
147         return environment[key];
148     } catch (Exception e) {
149         return null;
150     }
151 }
152 
153 unittest {
154     assert(getEnv("PATH") !is null);
155     assert(getEnv("flkahelkuhfalukfehlakuefhl") is null);
156 }
157 
158 /** 
159  * Sets an environment variable.
160  * Params:
161  *   key = The name of the environment variable.
162  *   value = The value to set.
163  */
164 public void setEnv(string key, string value) {
165     import std.process : environment;
166     try {
167         environment[key] = value;
168     } catch (Exception e) {
169         stderr.writefln!"Could not set environment variable \"%s\": %s"(key, e.msg);
170     }
171 }
172 
173 unittest {
174     assert(getEnv("dsh_test_env_1") is null);
175     setEnv("dsh_test_env_1", "yes");
176     assert(getEnv("dsh_test_env_1") == "yes");
177 }
178 
179 /** 
180  * Sleeps for a specified amount of milliseconds.
181  * Params:
182  *   amount = The amount of milliseconds to sleep for.
183  */
184 public void sleepMillis(long amount) {
185     import core.thread;
186     Thread.sleep(msecs(amount));
187 }
188 
189 /** 
190  * Sleeps for a specified amount of seconds.
191  * Params:
192  *   amount = The amount of seconds to sleep for.
193  */
194 public void sleepSeconds(long amount) {
195     import core.thread;
196     Thread.sleep(seconds(amount));
197 }