1 /*
2  * Copyright (c) 2017-2018 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: 2017-2018 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/selery/source/selery/plugin.d, selery/plugin.d)
28  */
29 module selery.plugin;
30 
31 import selery.about;
32 import selery.lang : Translatable;
33 import selery.server : Server;
34 import selery.util.tuple : Tuple;
35 
36 /**
37  * Informations about a plugin and registration-related
38  * utilities.
39  */
40 class Plugin {
41 
42 	protected string n_name;
43 	protected string[] n_authors;
44 	protected string n_version;
45 	protected bool n_api;
46 	public bool hasMain;
47 
48 	protected string n_languages, n_textures;
49 	
50 	public void delegate()[] onstart, onreload, onstop;
51 	
52 	/**
53 	 * Gets the plugin's name as indicated in the plugin's
54 	 * package.json file.
55 	 */
56 	public pure nothrow @property @safe @nogc string name() {
57 		return this.n_name;
58 	}
59 	
60 	/**
61 	 * Gets the plugin's authors as indicated in the plugin's
62 	 * package.json file.
63 	 */
64 	public pure nothrow @property @safe @nogc string[] authors() {
65 		return this.n_authors;
66 	}
67 	
68 	/**
69 	 * Gets the plugin's version as indicated in the plugin's
70 	 * package.json file.
71 	 * This should be in major.minor[.revision] [alpha|beta] format.
72 	 */
73 	public pure nothrow @property @safe @nogc string vers() {
74 		return this.n_version;
75 	}
76 	
77 	/**
78 	 * Indicates whether or not the plugin has APIs.
79 	 * The plugin's APIs are always in the api.d file in
80 	 * the plugin's directory.
81 	 * Example:
82 	 * ---
83 	 * static if(__traits(compile, { import example.api; })) {
84 	 *    assert(server.plugins.filter!(a => a.namespace == "example")[0].api);
85 	 * }
86 	 * ---
87 	 */
88 	public pure nothrow @property @safe @nogc bool api() {
89 		return this.n_api;
90 	}
91 
92 	/**
93 	 * Gets the absolute location of the plugin's language files.
94 	 * Returns: null if the plugin has no language files, a path otherwise
95 	 */
96 	public pure nothrow @property @safe @nogc string languages() {
97 		return this.n_languages;
98 	}
99 
100 	/**
101 	 * Gets the absolute location of the plugin's textures.
102 	 * Returns: null if the plugin has no textures, a path otherwise
103 	 */
104 	public pure nothrow @property @safe @nogc string textures() {
105 		return this.n_textures;
106 	}
107 	
108 	public abstract void load(shared Server server);
109 	
110 }
111 
112 // attributes for main classes
113 enum start;
114 enum reload;
115 enum stop;
116 
117 // attributes for events
118 enum event;
119 enum global;
120 enum inherit;
121 enum cancel;
122 
123 struct Description {
124 
125 	enum : ubyte {
126 
127 		EMPTY,
128 		TEXT,
129 		TRANSLATABLE
130 
131 	}
132 
133 	public ubyte type = EMPTY;
134 
135 	union {
136 
137 		string text;
138 		Translatable translatable;
139 
140 	}
141 
142 	this(string text) {
143 		this.type = TEXT;
144 		this.text = text;
145 	}
146 
147 	this(Translatable translatable) {
148 		this.type = TRANSLATABLE;
149 		this.translatable = translatable;
150 	}
151 
152 }
153 
154 // attributes for commands
155 struct command {
156 
157 	string command;
158 	string[] aliases;
159 	Description description;
160 
161 	public this(string command, string[] aliases=[], Description description=Description.init) {
162 		this.command = command;
163 		this.aliases = aliases;
164 		this.description = description;
165 	}
166 
167 	public this(string command, string[] aliases, string description) {
168 		this(command, aliases, Description(description));
169 	}
170 
171 	public this(string command, string[] aliases, Translatable description) {
172 		this(command, aliases, Description(description));
173 	}
174 
175 	public this(string command, string description) {
176 		this(command, [], description);
177 	}
178 
179 	public this(string command, Translatable description) {
180 		this(command, [], description);
181 	}
182 
183 }
184 
185 struct permissionLevel { ubyte permissionLevel; }
186 enum op = permissionLevel(1);
187 struct permission { string[] permissions; this(string[] permissions...){ this.permissions = permissions; } }
188 alias permissions = permission;
189 enum hidden;
190 enum unimplemented;
191 
192 void loadPluginAttributes(bool main, EventBase, GlobalEventBase, bool inheritance, CommandBase, bool tasks, T, S)(T class_, Plugin plugin, S storage) {
193 
194 	enum bool events = !is(typeof(EventBase) == bool);
195 	enum bool globals = !is(typeof(GlobalEventBase) == bool);
196 	enum bool commands = !is(typeof(CommandBase) == bool);
197 
198 	import std.traits : getSymbolsByUDA, hasUDA, getUDAs, Parameters;
199 
200 	foreach(member ; __traits(allMembers, T)) {
201 		static if(is(typeof(__traits(getMember, T, member)) == function)) { //TODO must be public and not a template
202 			mixin("alias F = T." ~ member ~ ";");
203 			enum del = "&class_." ~ member;
204 			// start/stop
205 			static if(main) {
206 				static if(hasUDA!(F, start) && Parameters!F.length == 0) {
207 					plugin.onstart ~= mixin(del);
208 				}
209 				static if(hasUDA!(F, reload) && Parameters!F.length == 0) {
210 					plugin.onreload ~= mixin(del);
211 				}
212 				static if(hasUDA!(F, stop) && Parameters!F.length == 0) {
213 					plugin.onstop ~= mixin(del);
214 				}
215 			}
216 			// events
217 			enum isValid(E) = is(Parameters!F[0] == interface) || is(Parameters!F[0] : E);
218 			static if(events && Parameters!F.length == 1 && ((events && hasUDA!(F, event) && isValid!EventBase) || (globals && hasUDA!(F, global) && isValid!GlobalEventBase))) {
219 				static if(hasUDA!(F, cancel)) {
220 					//TODO event must be cancellable
221 					auto ev = delegate(Parameters!F[0] e){ e.cancel(); };
222 				} else {
223 					auto ev = mixin(del);
224 				}
225 				static if(events && hasUDA!(F, event)) {
226 					storage.addEventListener(ev);
227 				}
228 				static if(globals && hasUDA!(F, global)) {
229 					(cast()storage.globalListener).addEventListener(ev);
230 				}
231 			}
232 			// commands
233 			static if(commands && hasUDA!(F, command) && Parameters!F.length >= 1 && is(Parameters!F[0] : CommandBase)) {
234 				enum c = getUDAs!(F, command)[0];
235 				static if(hasUDA!(F, permissionLevel)) enum pl = getUDAs!(F, permissionLevel)[0].permissionLevel;
236 				else enum ubyte pl = 0;
237 				static if(hasUDA!(F, permission)) enum p = getUDAs!(F, permission)[0].permissions;
238 				else enum string[] p = [];
239 				storage.registerCommand!F(mixin(del), c.command, c.description, c.aliases, pl, p, hasUDA!(F, hidden), !hasUDA!(F, unimplemented));
240 			}
241 		}
242 	}
243 
244 }