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