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 }