1 /**
2  * Simple execution of shell commands,
3  * and wrappers for common utilities.
4  *
5  * License:
6  *   This Source Code Form is subject to the terms of
7  *   the Mozilla Public License, v. 2.0. If a copy of
8  *   the MPL was not distributed with this file, You
9  *   can obtain one at http://mozilla.org/MPL/2.0/.
10  *
11  * Authors:
12  *   Vladimir Panteleev <vladimir@thecybershadow.net>
13  */
14 
15 module ae.sys.cmd;
16 
17 import core.thread;
18 
19 import std.exception;
20 import std.process;
21 import std.stdio;
22 import std.string;
23 
24 import ae.sys.file;
25 
26 string getTempFileName(string extension)
27 {
28 	import std.random;
29 	import std.file;
30 	import std.path : buildPath;
31 
32 	static int counter;
33 	return buildPath(tempDir(), format("run-%d-%d-%d-%d.%s",
34 		getpid(),
35 		getCurrentThreadID(),
36 		uniform!uint(),
37 		counter++,
38 		extension
39 	));
40 }
41 
42 ulong getCurrentThreadID()
43 {
44 	version (Windows)
45 	{
46 		import core.sys.windows.windows;
47 		return GetCurrentThreadId();
48 	}
49 	else
50 	version (Posix)
51 	{
52 		import core.sys.posix.pthread;
53 		return cast(ulong)pthread_self();
54 	}
55 }
56 
57 // ************************************************************************
58 
59 private void invoke(alias runner)(string[] args)
60 {
61 	//debug scope(failure) std.stdio.writeln("[CWD] ", getcwd());
62 	debug(CMD) std.stdio.stderr.writeln("invoke: ", args);
63 	auto status = runner();
64 	enforce(status == 0,
65 		"Command %s failed with status %d".format(args, status));
66 }
67 
68 /// std.process helper.
69 /// Run a command, and throw if it exited with a non-zero status.
70 void run(string[] args)
71 {
72 	invoke!({ return spawnProcess(args).wait(); })(args);
73 }
74 
75 /// std.process helper.
76 /// Run a command and collect its output.
77 /// Throw if it exited with a non-zero status.
78 string query(string[] args)
79 {
80 	string output;
81 	invoke!({
82 		auto result = execute(args);
83 		output = result.output.stripRight();
84 		return result.status;
85 	})(args);
86 	return output;
87 }
88 
89 /// std.process helper.
90 /// Run a command, feed it the given input, and collect its output.
91 /// Throw if it exited with non-zero status. Return output.
92 T[] pipe(T)(string[] args, in T[] input)
93 {
94 	T[] output;
95 	invoke!({
96 		auto pipes = pipeProcess(args, Redirect.stdin | Redirect.stdout);
97 		auto f = pipes.stdin;
98 		auto writer = writeFileAsync(f, input);
99 		scope(exit) writer.join();
100 		output = cast(T[])readFile(pipes.stdout);
101 		return pipes.pid.wait();
102 	})(args);
103 	return output;
104 }
105 
106 // ************************************************************************
107 
108 ubyte[] iconv(in void[] data, string inputEncoding, string outputEncoding)
109 {
110 	auto args = ["iconv", "-f", inputEncoding, "-t", outputEncoding];
111 	auto result = pipe(args, data);
112 	return cast(ubyte[])result;
113 }
114 
115 string iconv(in void[] data, string inputEncoding)
116 {
117 	import std.utf;
118 	auto result = cast(string)iconv(data, inputEncoding, "UTF-8");
119 	validate(result);
120 	return result;
121 }
122 
123 version (HAVE_UNIX)
124 unittest
125 {
126 	assert(iconv("Hello"w, "UTF-16LE") == "Hello");
127 }
128 
129 string sha1sum(in void[] data)
130 {
131 	auto output = cast(string)pipe(["sha1sum", "-b", "-"], data);
132 	return output[0..40];
133 }
134 
135 version (HAVE_UNIX)
136 unittest
137 {
138 	assert(sha1sum("") == "da39a3ee5e6b4b0d3255bfef95601890afd80709");
139 	assert(sha1sum("a  b\nc\r\nd") == "667c71ffe2ac8a4fe500e3b96435417e4c5ec13b");
140 }
141 
142 // ************************************************************************
143 
144 import ae.utils.path;
145 deprecated alias NULL_FILE = nullFileName;
146 
147 // ************************************************************************
148 
149 /// Reverse of std.process.environment.toAA
150 void setEnvironment(string[string] env)
151 {
152 	foreach (k, v; env)
153 		if (k.length)
154 			environment[k] = v;
155 	foreach (k, v; environment.toAA())
156 		if (k.length && k !in env)
157 			environment.remove(k);
158 }
159 
160 import ae.utils.array;
161 import ae.utils.regex;
162 import std.regex;
163 
164 string expandEnvVars(alias RE)(string s, string[string] env = std.process.environment.toAA)
165 {
166 	string result;
167 	size_t last = 0;
168 	foreach (c; s.matchAll(RE))
169 	{
170 		result ~= s[last..s.sliceIndex(c[1])];
171 		auto var = c[2];
172 		string value = env.get(var, c[1]);
173 		result ~= value;
174 		last = s.sliceIndex(c[1]) + c[1].length;
175 	}
176 	result ~= s[last..$];
177 	return result;
178 }
179 
180 alias expandWindowsEnvVars = expandEnvVars!(re!`(%(.*?)%)`);
181 
182 unittest
183 {
184 	std.process.environment[`FOOTEST`] = `bar`;
185 	assert("a%FOOTEST%b".expandWindowsEnvVars() == "abarb");
186 }
187 
188 // ************************************************************************
189 
190 int waitTimeout(Pid pid, Duration time)
191 {
192 	bool ok = false;
193 	auto t = new Thread({
194 		Thread.sleep(time);
195 		if (!ok)
196 			try
197 				pid.kill();
198 			catch {} // Ignore race condition
199 	}).start();
200 	auto result = pid.wait();
201 	ok = true;
202 	return result;
203 }