1 /** Wrappers around commands such `git`, `patch`, etc. 2 * 3 * Original version https://gist.github.com/PetarKirov/b4c8b64e7fc9bb7391901bcb541ddf3a 4 * 5 * See_Also: https://github.com/CyberShadow/ae/blob/master/sys/git.d 6 * See_Also: https://forum.dlang.org/post/ziqgqpkdjolplyfztulp@forum.dlang.org 7 * 8 * TODO: integrate @CyberShadow’s ae.sys.git at ae/sys/git.d 9 */ 10 module nxt.cmd; 11 12 import std.exception : enforce; 13 import std.format : format; 14 import std.file : exists, isDir, isFile; 15 import std.path : absolutePath, buildNormalizedPath, dirName, relativePath; 16 import std.stdio : write, writeln, writefln, File, stdin, stdout, stderr; 17 import std.algorithm.mutation : move; 18 import nxt.logging : LogLevel, defaultLogLevel, trace, info, warning, error; 19 20 @safe: 21 22 ExitStatus exitMessage(ExitStatus code) => typeof(return)(code); 23 24 /** Process exit status (code). 25 * 26 * See: https://en.wikipedia.org/wiki/Exit_status 27 */ 28 struct ExitStatus 29 { 30 int value; 31 alias value this; 32 } 33 34 /** Process spawn state. 35 Action accessing predeclared file system resources. 36 */ 37 struct Spawn 38 { 39 import std.process : Pid; 40 41 this(Pid pid, File input_, File out_, File err_, in LogLevel logLevel = defaultLogLevel) 42 in(input_.isOpen) 43 in(out_.isOpen) 44 in(err_.isOpen) { 45 this.pid = pid; 46 this.input = input_; 47 this.output = out_; 48 this.error = err_; 49 this.logLevel = logLevel; 50 } 51 52 this(this) @disable; // avoid copying `File`s for now 53 54 auto ref setLogLevel(in LogLevel logLevel) scope pure nothrow @nogc { 55 this.logLevel = logLevel; 56 return this; 57 } 58 59 ExitStatus wait() { 60 if (logLevel <= LogLevel.trace) .trace("Waiting"); 61 62 import std.process : wait; 63 auto result = typeof(return)(pid.wait()); 64 65 static void echo(ref File src, ref File dst, in string name) 66 in(src.isOpen) 67 in(dst.isOpen) { 68 if (src.eof) 69 return; // skip if empty 70 writeln(" - ", name, ":"); 71 src.flush(); 72 import std.algorithm.mutation: copy; 73 writeln("src:",src, "dst:",dst, "stdout:",stdout, "stderr:",stderr); 74 () @trusted { src.byLine().copy(dst.lockingBinaryWriter); } (); /+ TODO: writeIndented +/ 75 } 76 77 if (result == 0) { 78 if (logLevel <= LogLevel.trace) 79 .trace("Process exited successfully with exitStatus:", result); 80 } else { 81 if (logLevel <= LogLevel.error) 82 .error("Process exited unsuccessfully with exitStatus:", result); 83 } 84 85 import std.stdio : stdout, stderr; 86 if (result != 0 && logLevel <= LogLevel.info) { 87 () @trusted { if (output != stdout) echo(output, stdout, "OUT"); } (); 88 } 89 if (result != 0 && logLevel <= LogLevel.warning) { 90 () @trusted { if (error != stderr) echo(error, stderr, "ERR"); } (); 91 } 92 93 return result; 94 } 95 96 package: 97 Pid pid; 98 File input; 99 File output; 100 File error; 101 LogLevel logLevel; 102 } 103 104 Spawn spawn(scope const(char[])[] args, 105 in LogLevel logLevel = defaultLogLevel, 106 File input_ = stdin, 107 File out_ = stdout, 108 File err_ = stderr) { 109 import std.process : spawnProcess; 110 if (logLevel <= LogLevel.trace) .trace("Spawning ", args); 111 auto pid = spawnProcess(args, input_, out_, err_); 112 return typeof(return)(pid, input_, out_, err_, logLevel); 113 } 114 115 /++ Variant of `std.stdio.write` that flushes `stdout` afterwards. 116 +/ 117 void writeFlushed(Args...)(scope Args args) { 118 import std.stdio : stdout; 119 () @trusted { write(args, " ... "); stdout.flush(); } (); 120 } 121 122 /++ Variant of `std.stderr.write` that flushes `stderr` afterwards. 123 +/ 124 void ewriteFlushed(Args...)(scope Args args) { 125 import std.stdio : stderr; 126 () @trusted { stderr.write(args, " ... "); stderr.flush(); } (); 127 }