1 /*
2  * Copyright (c) 2017-2019 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: Copyright (c) 2017-2019 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 core.atomic : atomicOp;
32 
33 import selery.about;
34 import selery.lang : Translatable;
35 
36 private shared uint _id;
37 
38 /**
39  * Informations about a plugin and registration-related
40  * utilities.
41  */
42 class Plugin {
43 
44 	public immutable uint id;
45 
46 	/**
47 	 * Plugin's name as declared in plugin.toml.
48 	 */
49 	public immutable string name;
50 
51 	/**
52 	 * Plugin's absolute path, used to load plugin'a assets.
53 	 */
54 	public immutable string path;
55 
56 	/**
57 	 * Plugin's authors as declared in plugin.toml.
58 	 */
59 	public immutable string[] authors;
60 
61 	/**
62 	 * Plugin's version.
63 	 */
64 	public immutable string version_;
65 
66 	/**
67 	 * Indicates whether the plugin has a main class.
68 	 */
69 	public immutable bool main;
70 	
71 	public void delegate()[] onstart, onreload, onstop;
72 
73 	public this(string name, string path, string[] authors, string version_, bool main) {
74 		assert(version_.length);
75 		this.id = atomicOp!"+="(_id, 1);
76 		this.name = name;
77 		this.path = path;
78 		this.authors = authors.idup;
79 		this.version_ = version_;
80 		this.main = main;
81 	}
82 	
83 }
84 
85 // attributes for main classes
86 enum start;
87 enum reload;
88 enum stop;
89 
90 // attributes for events
91 enum event;
92 enum global;
93 enum inherit;
94 enum cancel;
95 
96 struct Description {
97 
98 	enum : ubyte {
99 
100 		EMPTY,
101 		TEXT,
102 		TRANSLATABLE
103 
104 	}
105 
106 	public ubyte type = EMPTY;
107 
108 	union {
109 
110 		string text;
111 		Translatable translatable;
112 
113 	}
114 
115 	this(string text) {
116 		this.type = TEXT;
117 		this.text = text;
118 	}
119 
120 	this(Translatable translatable) {
121 		this.type = TRANSLATABLE;
122 		this.translatable = translatable;
123 	}
124 
125 }
126 
127 // attributes for commands
128 struct command {
129 
130 	string command;
131 	string[] aliases;
132 	Description description;
133 
134 	public this(string command, string[] aliases=[], Description description=Description.init) {
135 		this.command = command;
136 		this.aliases = aliases;
137 		this.description = description;
138 	}
139 
140 	public this(string command, string[] aliases, string description) {
141 		this(command, aliases, Description(description));
142 	}
143 
144 	public this(string command, string[] aliases, Translatable description) {
145 		this(command, aliases, Description(description));
146 	}
147 
148 	public this(string command, string description) {
149 		this(command, [], description);
150 	}
151 
152 	public this(string command, Translatable description) {
153 		this(command, [], description);
154 	}
155 
156 }
157 
158 struct permissionLevel { ubyte permissionLevel; }
159 enum op = permissionLevel(1);
160 struct permission { string[] permissions; this(string[] permissions...){ this.permissions = permissions; } }
161 alias permissions = permission;
162 enum hidden;
163 enum unimplemented;
164 
165 void loadPluginAttributes(bool main, EventBase, GlobalEventBase, bool inheritance, CommandBase, bool tasks, T, S)(T class_, Plugin plugin, S storage) {
166 
167 	enum bool events = !is(typeof(EventBase) == bool);
168 	enum bool globals = !is(typeof(GlobalEventBase) == bool);
169 	enum bool commands = !is(typeof(CommandBase) == bool);
170 
171 	import std.traits : getSymbolsByUDA, hasUDA, getUDAs, Parameters;
172 
173 	foreach(member ; __traits(allMembers, T)) {
174 		static if(is(typeof(__traits(getMember, T, member)) == function)) { //TODO must be public and not a template
175 			mixin("alias F = T." ~ member ~ ";");
176 			enum del = "&class_." ~ member;
177 			// start/stop
178 			static if(main) {
179 				static if(hasUDA!(F, start) && Parameters!F.length == 0) {
180 					plugin.onstart ~= mixin(del);
181 				}
182 				static if(hasUDA!(F, reload) && Parameters!F.length == 0) {
183 					plugin.onreload ~= mixin(del);
184 				}
185 				static if(hasUDA!(F, stop) && Parameters!F.length == 0) {
186 					plugin.onstop ~= mixin(del);
187 				}
188 			}
189 			// events
190 			enum isValid(E) = is(Parameters!F[0] == interface) || is(Parameters!F[0] : E);
191 			static if(events && Parameters!F.length == 1 && ((events && hasUDA!(F, event) && isValid!EventBase) || (globals && hasUDA!(F, global) && isValid!GlobalEventBase))) {
192 				static if(hasUDA!(F, cancel)) {
193 					//TODO event must be cancellable
194 					auto ev = delegate(Parameters!F[0] e){ e.cancel(); };
195 				} else {
196 					auto ev = mixin(del);
197 				}
198 				static if(events && hasUDA!(F, event)) {
199 					storage.addEventListener(ev);
200 				}
201 				static if(globals && hasUDA!(F, global)) {
202 					(cast()storage.globalListener).addEventListener(ev);
203 				}
204 			}
205 			// commands
206 			static if(commands && hasUDA!(F, command) && Parameters!F.length >= 1 && is(Parameters!F[0] : CommandBase)) {
207 				enum c = getUDAs!(F, command)[0];
208 				static if(hasUDA!(F, permissionLevel)) enum pl = getUDAs!(F, permissionLevel)[0].permissionLevel;
209 				else enum ubyte pl = 0;
210 				static if(hasUDA!(F, permission)) enum p = getUDAs!(F, permission)[0].permissions;
211 				else enum string[] p = [];
212 				storage.registerCommand!F(mixin(del), c.command, c.description, c.aliases, pl, p, hasUDA!(F, hidden), !hasUDA!(F, unimplemented));
213 			}
214 		}
215 	}
216 
217 }