1 /** 
2  * Utilities for running and interacting with processes.
3  */
4 module dshutils.processutils;
5 
6 import dsh;
7 
8 /** 
9  * Helper for more easily creating processes.
10  */
11 public class ProcessBuilder {
12     private File stdin;
13     private File stdout;
14     private File stderr;
15     private string[string] env;
16     private string dir;
17 
18     public this() {
19         this.stdin = std.stdio.stdin;
20         this.stdout = std.stdio.stdout;
21         this.stderr = std.stdio.stderr;
22         this.dir = getcwd();
23     }
24 
25     public ProcessBuilder inputFrom(string filename) {
26         this.stdin = File(filename, "r");
27         return this;
28     }
29 
30     public ProcessBuilder outputTo(string filename) {
31         this.stdout = File(filename, "w");
32         return this;
33     }
34 
35     public ProcessBuilder errorTo(string filename) {
36         this.stderr = File(filename, "w");
37         return this;
38     }
39 
40     public ProcessBuilder withEnv(string key, string value) {
41         this.env[key] = value;
42         return this;
43     }
44 
45     public ProcessBuilder workingDir(string dir) {
46         this.dir = dir;
47         return this;
48     }
49 
50     public int run(string command) {
51         import std.process;
52         import std.regex;
53         try {
54             auto r = regex("\\s+");
55             auto s = split(command, r);
56             auto pid = spawnProcess(s, this.stdin, this.stdout, this.stderr, this.env, Config.none, this.dir);
57             return wait(pid);
58         } catch (ProcessException e) {
59             error("Could not start process \"%s\": %s", command, e.msg);
60             return -1;
61         }
62     }
63 }
64 
65 unittest {
66     auto p = new ProcessBuilder()
67         .outputTo(".test.txt")
68         .workingDir("source");
69     p.run("ls");
70 
71 }
72 
73 /**
74  * Runs the given command using the user's shell.
75  * Params:
76  *   cmd = The command to execute.
77  * Returns: The exit code from the command. Generally, 0 indicates success. If
78  * the process could not be started, -1 is returned.
79  */
80 public int run(string cmd) {
81     return new ProcessBuilder().run(cmd);
82 }
83 
84 unittest {
85     print("Running test of run(cmd). Expect some test output!");
86     version(Posix) {
87         assert(run("ls") == 0);
88     }
89     version(Windows) {
90         assert(run("dir") == 0);
91     }
92     print("Testing run(cmd) with a non-existent command. Expect an error message.");
93     assert(run("kjafhdflkuahlkefuhfahlfeuhaf") == -1);
94 }
95 
96 /** 
97  * Convenience method to run a command and pipe output to a file.
98  * Params:
99  *   cmd = The command to run.
100  *   outputFile = The file to send output to.
101  * Returns: The exit code from the command.
102  */
103 public int run(string cmd, string outputFile) {
104     return new ProcessBuilder()
105         .outputTo(outputFile)
106         .errorTo(outputFile)
107         .run(cmd);
108 }
109 
110 /** 
111  * Runs the given command, and exits the program if the return code is not 0.
112  * Params:
113  *   cmd = The command to run.
114  */
115 public void runOrQuit(string cmd) {
116     import core.stdc.stdlib : exit;
117     int r = run(cmd);
118     if (r != 0) {
119         error("Process \"%s\" exited with code %d", cmd, r);
120         exit(r);
121     }
122 }
123 
124 public void runOrQuit(string cmd, string outputFile) {
125     import core.stdc.stdlib : exit;
126     int r = run(cmd, outputFile);
127     if (r != 0) {
128         error("Process \"%s\" exited with code %d", cmd, r);
129         exit(r);
130     }
131 }
132 
133 /** 
134  * Gets an environment variable.
135  * Params:
136  *   key = The name of the environment variable.
137  * Returns: The value of the environment variable, or null.
138  */
139 public string getEnv(string key) {
140     import std.process : environment;
141     try {
142         return environment[key];
143     } catch (Exception e) {
144         return null;
145     }
146 }
147 
148 unittest {
149     assert(getEnv("PATH") !is null);
150     assert(getEnv("flkahelkuhfalukfehlakuefhl") is null);
151 }
152 
153 /** 
154  * Sets an environment variable.
155  * Params:
156  *   key = The name of the environment variable.
157  *   value = The value to set.
158  */
159 public void setEnv(string key, string value) {
160     import std.process : environment;
161     try {
162         environment[key] = value;
163     } catch (Exception e) {
164         error("Could not set environment variable \"%s\": %s", key, e.msg);
165     }
166 }
167 
168 unittest {
169     assert(getEnv("dsh_test_env_1") is null);
170     setEnv("dsh_test_env_1", "yes");
171     assert(getEnv("dsh_test_env_1") == "yes");
172 }