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 }