1 /// Authors: Thibaut CHARLES (Crom) cromfr@gmail.com 2 /// License: GPL-3.0 3 /// Copyright: Copyright Thibaut CHARLES 2016 4 5 module tools.nwnsrv; 6 7 import std.stdio; 8 import std.conv: to, ConvException; 9 import std.traits; 10 import std.meta; 11 import std.string; 12 import std.file; 13 import std.typecons: Tuple, Nullable; 14 import std.exception: enforce; 15 import std.path; 16 import std.algorithm; 17 import core.thread; 18 alias writeFile = std.file.write; 19 version(unittest) import std.exception: assertThrown, assertNotThrown; 20 21 import tools.common.getopt; 22 import nwn.constants; 23 import nwn.nwnserver; 24 25 26 int main(string[] args){ 27 if(args.length >= 2 && (args[1] == "-h" || args[1] == "--help")){ 28 writeln("Send requests to NWN2 servers."); 29 writefln("Usage: %s (ping|bnes|bnds|bnxi|bnlm)", args[0].baseName); 30 return 0; 31 } 32 if(args.any!(a => a == "--version")){ 33 import nwn.ver: NWN_LIB_D_VERSION; 34 writeln(NWN_LIB_D_VERSION); 35 return 0; 36 } 37 38 enforce(args.length > 1, "No subcommand provided"); 39 immutable command = args[1]; 40 args = args[0] ~ args[2..$]; 41 42 cmdswitch: 43 switch(command){ 44 case "ping": 45 { 46 int count = 1; 47 auto res = getopt(args, 48 "n", "Number of pings to issue. 0 for infinite. Default 1", &count); 49 if(res.helpWanted){ 50 improvedGetoptPrinter( 51 "Ping server and measure latency\n" 52 ~"Usage: "~args[0].baseName~" ping [args] hostname[:port]\n" 53 ~"Example: "~args[0].baseName~" ping lcda-nwn2.fr", 54 res.options); 55 return 0; 56 } 57 enforce(args.length > 1, "No hostname provided"); 58 enforce(args.length <= 2, "Too many arguments provided"); 59 60 auto serv = connectServer(args[1]); 61 62 double avg = double.nan; 63 double min = double.max; 64 double max = 0.0; 65 for(auto i = 0 ; i < count || count == 0 ; i++){ 66 if(i > 0) 67 Thread.sleep(1.dur!"seconds"); 68 auto ping = serv.ping(); 69 writeln(i, ": ", ping, "ms"); 70 if(i == 0) avg = ping; 71 else avg = (avg * i + ping) / cast(double)(i + 1); 72 73 if(ping < min) min = ping; 74 if(ping > max) max = ping; 75 } 76 writeln("Average: ", avg, "ms"); 77 writeln("Min: ", min, "ms"); 78 writeln("Max: ", max, "ms"); 79 } 80 break; 81 82 foreach(Req ; AliasSeq!("bnes", "bnds", "bnxi", "bnlm")){ 83 case Req: 84 { 85 string format = "text"; 86 auto res = getopt(args, 87 "f|format", "Output format. 'text' or 'json'. Default: 'text'.", &format 88 ); 89 if(res.helpWanted){ 90 improvedGetoptPrinter( 91 "Send a "~Req~" request to the server\n" 92 ~"Usage: "~args[0].baseName~" "~Req~" [args] hostname[:port]\n" 93 ~"Example: "~args[0].baseName~" "~Req~" lcda-nwn2.fr", 94 res.options); 95 return 0; 96 } 97 enforce(args.length > 1, "No hostname provided"); 98 enforce(args.length <= 2, "Too many arguments provided"); 99 100 auto serv = connectServer(args[1]); 101 102 switch(format){ 103 case "text": writeln(mixin("serv.query"~Req.toUpper~"()").serializeStruct!"text"); break; 104 case "json": writeln(mixin("serv.query"~Req.toUpper~"()").serializeStruct!"json"); break; 105 default: assert(0, "Bad output format"); 106 } 107 } 108 break cmdswitch; 109 } 110 111 default: 112 writefln("Unknown command '%s'. Try %s --help", command, args[0].baseName); 113 return -1; 114 } 115 return 0; 116 } 117 118 auto connectServer(string url){ 119 auto urlSplit = url.split(":"); 120 auto address = urlSplit[0]; 121 ushort port = urlSplit.length > 1 ? urlSplit[1].to!ushort : 5121; 122 return new NWNServer(address, port); 123 } 124 125 string serializeStruct(string format, T)(in T s){ 126 static if(format == "json"){ 127 import std.json; 128 auto ret = JSONValue(); 129 ret.object = null; 130 } 131 else static if(format == "text"){ 132 string ret; 133 } 134 else static assert(0); 135 136 static foreach(Member ; FieldNameTuple!T){ 137 static if(format == "json"){ 138 ret[Member] = mixin("s."~Member).to!string; 139 } 140 else static if(format == "text"){ 141 ret ~= Member ~ ": " ~ mixin("s."~Member).to!string ~ "\n"; 142 } 143 } 144 static if(format == "json"){ 145 return ret.toJSON(); 146 } 147 else{ 148 return ret; 149 } 150 } 151 152 unittest { 153 auto stdout_ = stdout; 154 auto tmpOut = buildPath(tempDir, "unittest-nwn-lib-d-"~__MODULE__); 155 stdout = File(tmpOut, "w"); 156 scope(success) tmpOut.remove(); 157 scope(exit) stdout = stdout_; 158 159 assertThrown(main(["nwn-srv"])); 160 assert(main(["nwn-srv","--help"]) == 0); 161 assert(main(["nwn-srv","--version"]) == 0); 162 163 assertThrown(main(["nwn-srv","bnxi"])); 164 assert(main(["nwn-srv","bnxi", "--help"]) == 0); 165 }