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.process : pipeProcess; 17 import std.stdio : write, writeln, writefln, File, stdin, stdout, stderr; 18 import std.algorithm.mutation : move; 19 import nxt.logging : LogLevel, defaultLogLevel, trace, info, warning, error; 20 21 @safe: 22 23 ExitStatus exitMessage(ExitStatus code) => typeof(return)(code); 24 25 /** Process exit status (code). 26 * 27 * See: https://en.wikipedia.org/wiki/Exit_status 28 */ 29 struct ExitStatus 30 { 31 int value; 32 alias value this; 33 } 34 35 /** Process spawn state. 36 Action accessing predeclared file system resources. 37 */ 38 struct Spawn 39 { 40 import std.process : Pid, ProcessPipes; 41 42 this(ProcessPipes processPipes, in LogLevel logLevel = defaultLogLevel) { 43 this.processPipes = processPipes; 44 this.logLevel = logLevel; 45 } 46 47 this(this) @disable; // avoid copying `File`s for now 48 49 auto ref setLogLevel(in LogLevel logLevel) scope pure nothrow @nogc { 50 this.logLevel = logLevel; 51 return this; 52 } 53 54 ExitStatus wait() { 55 if (logLevel <= LogLevel.trace) .trace("Waiting"); 56 57 import std.process : wait; 58 auto result = typeof(return)(processPipes.pid.wait()); 59 60 static void echo(File src, ref File dst, in string name) 61 in(src.isOpen) 62 in(dst.isOpen) { 63 if (src.eof) 64 return; // skip if empty 65 writeln(" - ", name, ":"); 66 src.flush(); 67 import std.algorithm.mutation: copy; 68 writeln("src:",src, "dst:",dst, "stdout:",stdout, "stderr:",stderr); 69 () @trusted { src.byLine().copy(dst.lockingBinaryWriter); } (); /+ TODO: writeIndented +/ 70 } 71 72 if (result == 0) { 73 if (logLevel <= LogLevel.trace) 74 .trace("Process exited successfully with exitStatus:", result); 75 } else { 76 if (logLevel <= LogLevel.error) 77 .error("Process exited unsuccessfully with exitStatus:", result); 78 } 79 80 import std.stdio : stdout, stderr; 81 if (result != 0 && logLevel <= LogLevel.info) { 82 () @trusted { if (processPipes.stdout != stdout) echo(processPipes.stdout, stdout, "OUT"); } (); 83 } 84 if (result != 0 && logLevel <= LogLevel.warning) { 85 () @trusted { if (processPipes.stderr != stderr) echo(processPipes.stderr, stderr, "ERR"); } (); 86 } 87 88 return result; 89 } 90 91 package: 92 ProcessPipes processPipes; 93 LogLevel logLevel; 94 } 95 96 Spawn spawn(scope const(char[])[] args, 97 in LogLevel logLevel = defaultLogLevel,) { 98 import std.process : spawnProcess; 99 if (logLevel <= LogLevel.trace) .trace("Spawning ", args); 100 return typeof(return)(pipeProcess(args), logLevel); 101 } 102 103 /++ Variant of `std.stdio.write` that flushes `stdout` afterwards. 104 +/ 105 void writeFlushed(Args...)(scope Args args) { 106 import std.stdio : stdout; 107 () @trusted { write(args, " ... "); stdout.flush(); } (); 108 } 109 110 /++ Variant of `std.stderr.write` that flushes `stderr` afterwards. 111 +/ 112 void ewriteFlushed(Args...)(scope Args args) { 113 import std.stdio : stderr; 114 () @trusted { stderr.write(args, " ... "); stderr.flush(); } (); 115 }