1 /++
2     Bits and bobs to register plugins to be instantiated on program startup/connect.
3 
4     This should really only have to be used internally.
5 
6     Example:
7     ---
8     import kameloso.plugins;
9     import kameloso.plugins.common.core;
10 
11     final class MyPlugin : IRCPlugin
12     {
13         mixin IRCPluginImpl;
14     }
15 
16     mixin ModuleRegistration;
17     ---
18 
19     Example:
20     ---
21     import kameloso.plugins;
22 
23     IRCPluginState state;
24     // state setup...
25 
26     IRCPlugin[] plugins = instantiatePlugins(state);
27     ---
28 
29     Copyright: [JR](https://github.com/zorael)
30     License: [Boost Software License 1.0](https://www.boost.org/users/license.html)
31 
32     Authors:
33         [JR](https://github.com/zorael)
34  +/
35 module kameloso.plugins;
36 
37 private:
38 
39 import kameloso.plugins.common.core : IRCPlugin, IRCPluginState;
40 
41 
42 // PluginRegistrationEntry
43 /++
44     An entry in [registeredPlugins] corresponding to a plugin registered to be
45     instantiated on program startup/connect.
46  +/
47 struct PluginRegistrationEntry
48 {
49     // priority
50     /++
51         Priority at which to instantiate the plugin. A lower priority makes it
52         get instantiated before other plugins.
53      +/
54     Priority priority;
55 
56     // ctor
57     /++
58         Function pointer to a "constructor"/builder that instantiates the relevant plugin.
59      +/
60     IRCPlugin function(IRCPluginState) ctor;
61 
62     // this
63     /++
64         Constructor.
65 
66         Params:
67             priority = [kameloso.plugins.Priority|Priority] at which
68                 to instantiate the plugin. A lower priority value makes it get
69                 instantiated before other plugins.
70             ctor = Function pointer to a "constructor"/builder that instantiates
71                 the relevant plugin.
72      +/
73     this(
74         const Priority priority,
75         typeof(this.ctor) ctor) pure @safe nothrow @nogc
76     {
77         this.priority = priority;
78         this.ctor = ctor;
79     }
80 }
81 
82 
83 // registeredPlugins
84 /++
85     Array of registered plugins, represented by [PluginRegistrationEntry]/-ies,
86     to be instantiated on program startup/connect.
87  +/
88 shared PluginRegistrationEntry[] registeredPlugins;
89 
90 
91 // module constructor
92 /++
93     Module constructor that merely reserves space for [registeredPlugins] to grow into.
94 
95     Only include this if the compiler is based on 2.095 or later, as the call to
96     [object.reserve|reserve] fails with those prior to that.
97  +/
98 static if (__VERSION__ >= 2095L)
99 shared static this()
100 {
101     enum initialSize = 64;
102     (cast()registeredPlugins).reserve(initialSize);
103 }
104 
105 
106 public:
107 
108 
109 // registerPlugin
110 /++
111     Registers a plugin to be instantiated on program startup/connect by creating
112     a [PluginRegistrationEntry] and appending it to [registeredPlugins].
113 
114     Params:
115         priority = Priority at which to instantiate the plugin. A lower priority
116             makes it get instantiated before other plugins.
117         ctor = Function pointer to a "constructor"/builder that instantiates
118             the relevant plugin.
119  +/
120 void registerPlugin(
121     const Priority priority,
122     IRCPlugin function(IRCPluginState) ctor)
123 {
124     registeredPlugins ~= PluginRegistrationEntry(
125         priority,
126         ctor);
127 }
128 
129 
130 // instantiatePlugins
131 /++
132     Instantiates all plugins represented by a [PluginRegistrationEntry] in
133     [registeredPlugins].
134 
135     Plugin modules may register their plugin classes by mixing in [PluginRegistration].
136 
137     Params:
138         state = The current plugin state on which to base new plugin instances.
139 
140     Returns:
141         An array of instantiated [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]s.
142  +/
143 auto instantiatePlugins(/*const*/ IRCPluginState state)
144 {
145     import std.algorithm.sorting : sort;
146 
147     IRCPlugin[] plugins;
148     plugins.length = registeredPlugins.length;
149     uint i;
150 
151     auto sortedPluginRegistrations = registeredPlugins
152         .sort!((a,b) => a.priority.value < b.priority.value);
153 
154     foreach (registration; sortedPluginRegistrations)
155     {
156         plugins[i++] = registration.ctor(state);
157     }
158 
159     return plugins;
160 }
161 
162 
163 // PluginRegistration
164 /++
165     Mixes in a module constructor that registers the supplied [IRCPlugin] subclass
166     to be instantiated on program startup/connect.
167 
168     Params:
169         Plugin = Plugin class of module.
170         priority = Priority at which to instantiate the plugin. A lower priority
171             makes it get instantiated before other plugins. Defaults to `0.priority`.
172         module_ = String name of the module. Only used in case an error message is needed.
173  +/
174 mixin template PluginRegistration(
175     Plugin,
176     Priority priority = 0.priority,
177     string module_ = __MODULE__)
178 {
179     // module constructor
180     /++
181         Mixed-in module constructor that registers the passed [Plugin] class
182         to be instantiated on program startup.
183      +/
184     shared static this()
185     {
186         import kameloso.plugins.common.core : IRCPluginState;
187 
188         static if (__traits(compiles, new Plugin(IRCPluginState.init)))
189         {
190             import kameloso.plugins : registerPlugin;
191 
192             static auto ctor(IRCPluginState state)
193             {
194                 return new Plugin(state);
195             }
196 
197             registerPlugin(priority, &ctor);
198         }
199         else
200         {
201             import std.format : format;
202 
203             enum pattern = "`%s.%s` constructor does not compile";
204             enum message = pattern.format(module_, Plugin.stringof);
205             static assert(0, message);
206         }
207     }
208 }
209 
210 
211 // Priority
212 /++
213     Embodies the notion of a priority at which a plugin should be instantiated,
214     and as such, the order in which they will be called to handle events.
215 
216     This also affects in what order they appear in the configuration file.
217  +/
218 struct Priority
219 {
220     /++
221         Numerical priority value. Lower is higher.
222      +/
223     int value;
224 
225     /++
226         Helper `opUnary` to allow for `-10.priority`, instead of having to do the
227         (more correct) `(-10).priority`.
228 
229         Example:
230         ---
231         mixin PluginRegistration!(MyPlugin, -10.priority);
232         ---
233 
234         Params:
235             op = Operator.
236 
237         Returns:
238             A new [Priority] with a [Priority.value|value] equal to the negative of this one's.
239      +/
240     auto opUnary(string op: "-")() const
241     {
242         return Priority(-value);
243     }
244 }
245 
246 
247 // priority
248 /++
249     Helper alias to use the proper style guide and still be able to instantiate
250     [Priority] instances with UFCS.
251 
252     Example:
253     ---
254     mixin PluginRegistration!(MyPlugin, 50.priority);
255     ---
256  +/
257 alias priority = Priority;