1 /** 2 Copyright: Copyright (c) 2015-2016 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.command.plugin; 7 8 import netlib; 9 import pluginlib; 10 public import netlib.connection : ClientId; 11 public import std.getopt; 12 import std.experimental.logger; 13 import std.string : format; 14 15 struct CommandParams 16 { 17 string rawArgs; // without command name 18 auto rawStrippedArgs() @property 19 { 20 import std.string : strip; 21 return rawArgs.strip; 22 } 23 string[] args; // first arg is command name. Use with getopt. 24 ClientId source; 25 } 26 27 // On client side source == 0 28 // On server if command is issued locally source == 0 29 // First argument is command name (useful for std.getopt) 30 alias CommandHandler = void delegate(CommandParams params); 31 32 enum ExecStatus 33 { 34 success, 35 notRegistered, 36 //invalidArgs, 37 error 38 } 39 40 struct ExecResult 41 { 42 string[] args; 43 ExecStatus status; 44 string error; 45 } 46 47 final class CommandPluginClient : IPlugin 48 { 49 mixin CommandPluginCommon; 50 } 51 52 final class CommandPluginServer : IPlugin 53 { 54 mixin CommandPluginCommon; 55 mixin CommandPluginServerImpl; 56 } 57 58 mixin template CommandPluginCommon() 59 { 60 // IPlugin stuff 61 mixin IdAndSemverFrom!(voxelman.command.plugininfo); 62 63 CommandHandler[string] handlers; 64 65 void registerCommand(string name, CommandHandler handler) 66 { 67 import std.algorithm : splitter; 68 foreach(comAlias; name.splitter('|')) 69 { 70 assert(comAlias !in handlers, comAlias ~ " command is already registered"); 71 handlers[comAlias] = handler; 72 } 73 } 74 75 ExecResult execute(const(char)[] input, ClientId source = ClientId(0)) 76 { 77 import std.regex : ctRegex, splitter; 78 import std.string : strip; 79 import std.array : array; 80 81 string stripped = cast(string)input.strip; 82 string[] args = splitter(stripped, ctRegex!`\s+`).array; 83 84 if (args.length == 0) 85 return ExecResult(args, ExecStatus.notRegistered); 86 87 string comName = args[0]; 88 string rawArgs = stripped[args[0].length..$]; 89 90 if (auto handler = handlers.get(cast(string)comName, null)) 91 { 92 try 93 { 94 handler(CommandParams(rawArgs, args, source)); 95 } 96 catch(Exception e) 97 { 98 return ExecResult(args, ExecStatus.error, e.msg); 99 } 100 } 101 else 102 { 103 return ExecResult(args, ExecStatus.notRegistered); 104 } 105 106 return ExecResult(args, ExecStatus.success); 107 } 108 } 109 110 mixin template CommandPluginServerImpl() 111 { 112 import voxelman.net.plugin : NetServerPlugin; 113 import voxelman.core.packets : CommandPacket; 114 import voxelman.net.packets : MessagePacket; 115 NetServerPlugin connection; 116 override void init(IPluginManager pluginman) 117 { 118 connection = pluginman.getPlugin!NetServerPlugin; 119 connection.registerPacketHandler!CommandPacket(&handleCommandPacket); 120 } 121 122 void handleCommandPacket(ubyte[] packetData, ClientId clientId) 123 { 124 auto packet = unpackPacket!CommandPacket(packetData); 125 126 ExecResult res = execute(packet.command, clientId); 127 128 if (res.status == ExecStatus.notRegistered) 129 connection.sendTo(clientId, MessagePacket(0, format("Unknown command '%s'", packet.command))); 130 else if (res.status == ExecStatus.error) 131 connection.sendTo(clientId, 132 MessagePacket(0, format("Error executing command '%s': %s", packet.command, res.error))); 133 } 134 }