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 }