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 }