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 }