1 /++
2     Postprocessor package module.
3 
4     A [Postprocessor] is a class that is passed an [dialect.defs.IRCEvent|IRCEvent]
5     after it has been parsed, and allowed to make last-minute modifications to it.
6 
7     See_Also:
8         [Postprocessor]
9  +/
10 module dialect.postprocessors;
11 
12 private:
13 
14 version(none)
15 version(TwitchSupport)
16 {
17     /+
18         This is needed for the module constructor mixed in with
19         [PostprocessorRegistration] to actually run. Without it, the Twitch
20         postprocessor is never instantiated.
21 
22         Currently the onus to do this is placed onto the importing project.
23         Version this back in if we ever change that stance.
24      +/
25     import dialect.postprocessors.twitch;
26 }
27 
28 
29 // PostprocessorRegistrationEntry
30 /++
31     An entry in [registeredPostprocessors] corresponding to a postprocessor
32     registered to be instantiated on library initialisation.
33  +/
34 struct PostprocessorRegistrationEntry
35 {
36     // priority
37     /++
38         Priority at which to instantiate the postprocessor. A lower priority
39         makes it get instantiated before other postprocessors.
40      +/
41     Priority priority;
42 
43     // ctor
44     /++
45         Function pointer to a "constructor"/builder that instantiates the relevant postprocessor.
46      +/
47     Postprocessor function() ctor;
48 
49     // this
50     /++
51         Constructor.
52 
53         Params:
54             priority = [Priority] at which to instantiate the postprocessor.
55                 A lower priority value makes it get instantiated before other postprocessors.
56             ctor = Function pointer to a "constructor"/builder that instantiates
57                 the relevant postprocessor.
58      +/
59     this(
60         const Priority priority,
61         typeof(this.ctor) ctor) pure @safe nothrow @nogc
62     {
63         this.priority = priority;
64         this.ctor = ctor;
65     }
66 }
67 
68 
69 // registeredPostprocessors
70 /++
71     Array of registered postprocessors, represented by [PostprocessorRegistrationEntry]/-ies,
72     to be instantiated on library initialisation.
73  +/
74 shared PostprocessorRegistrationEntry[] registeredPostprocessors;
75 
76 
77 // module constructor
78 /++
79     Module constructor that merely reserves space for [registeredPostprocessors]
80     to grow into.
81 
82     Only include this if the compiler is based on 2.095 or later, as the call to
83     [object.reserve|reserve] fails with those prior to that.
84 
85     This isn't really needed today as we only have one postprocessor.
86  +/
87 version(none)
88 static if (__VERSION__ >= 2095L)
89 shared static this()
90 {
91     enum initialSize = 4;
92     (cast()registeredPostprocessors).reserve(initialSize);
93 }
94 
95 
96 public:
97 
98 
99 // Postprocessor
100 /++
101     Postprocessor interface for concrete postprocessors to inherit from.
102 
103     Postprocessors modify [dialect.defs.IRCEvent|IRCEvent]s after they are parsed,
104     before returning the final object to the caller. This is used to provide support
105     for Twitch servers, where most information is carried in IRCv3 tags prepended
106     to the raw server strings. The normal parser routine just separates the tags
107     from the normal string, parses it as per usual, and lets postprocessors
108     interpret the tags. Or not, depending on what build configuration was compiled.
109  +/
110 interface Postprocessor
111 {
112 private:
113     import dialect.defs : IRCEvent;
114     import dialect.parsing : IRCParser;
115 
116 public:
117     /++
118         Postprocesses an [dialect.defs.IRCEvent|IRCEvent].
119      +/
120     void postprocess(ref IRCParser, ref IRCEvent) @system;
121 }
122 
123 
124 // registerPostprocessor
125 /++
126     Registers a postprocessor to be instantiated on library initialisation by creating
127     a [PostprocessorRegistrationEntry] and appending it to [registeredPostprocessors].
128 
129     Params:
130         priority = Priority at which to instantiate the postprocessor.
131             A lower priority makes it get instantiated before other postprocessors.
132         ctor = Function pointer to a "constructor"/builder that instantiates
133             the relevant postprocessor.
134  +/
135 void registerPostprocessor(
136     const Priority priority,
137     Postprocessor function() ctor)
138 {
139     registeredPostprocessors ~= PostprocessorRegistrationEntry(
140         priority,
141         ctor);
142 }
143 
144 
145 // instantiatePostprocessors
146 /++
147     Instantiates all postprocessors represented by a [PostprocessorRegistrationEntry]
148     in [registeredPostprocessors].
149 
150     Postprocessor modules may register their [Postprocessor] classes by mixing in
151     [PostprocessorRegistration].
152 
153     Returns:
154         An array of instantiated [Postprocessor]s.
155  +/
156 auto instantiatePostprocessors()
157 {
158     import std.algorithm.sorting : sort;
159 
160     Postprocessor[] postprocessors;
161     postprocessors.length = registeredPostprocessors.length;
162     uint i;
163 
164     auto sortedPostprocessorRegistrations = registeredPostprocessors
165         .sort!((a,b) => a.priority.value < b.priority.value);
166 
167     foreach (registration; sortedPostprocessorRegistrations)
168     {
169         postprocessors[i++] = registration.ctor();
170     }
171 
172     return postprocessors;
173 }
174 
175 
176 // PostprocessorRegistration
177 /++
178     Mixes in a module constructor that registers the supplied [Postprocessor]
179     class in the module to be instantiated on library initialisation.
180 
181     Params:
182         ThisPostprocessor = [Postprocessor] class of module.
183         priority = Priority at which to instantiate the postprocessor.
184             A lower priority makes it get instantiated before other postprocessors.
185             Defaults to `0.priority`.
186         module_ = String name of the module. Only used in case an error message is needed.
187  +/
188 mixin template PostprocessorRegistration(
189     ThisPostprocessor,
190     Priority priority = 0.priority,
191     string module_ = __MODULE__)
192 {
193     /++
194         Module constructor.
195      +/
196     shared static this()
197     {
198         static if (__traits(compiles, new ThisPostprocessor))
199         {
200             static Postprocessor ctor()
201             {
202                 return new ThisPostprocessor;
203             }
204 
205             registerPostprocessor(priority, &ctor);
206         }
207         else
208         {
209             import std.format : format;
210 
211             enum pattern = "`%s.%s` constructor does not compile";
212             enum message = pattern.format(module_, ThisPostprocessor.stringof);
213             static assert(0, message);
214         }
215     }
216 }
217 
218 
219 // Priority
220 /++
221     Embodies the notion of a priority at which a postprocessor should be instantiated,
222     and as such, the order in which they will be called to process events.
223  +/
224 struct Priority
225 {
226     /++
227         Numerical priority value. Lower is higher.
228      +/
229     int value;
230 
231     /++
232         Helper `opUnary` to allow for `-10.priority`, instead of having to do the
233         (more correct) `(-10).priority`.
234 
235         Example:
236         ---
237         mixin PostprocessorRegistration!(-10.priority);
238         ---
239 
240         Params:
241             op = Operator.
242 
243         Returns:
244             A new [Priority] with a [Priority.value|value] equal to the negative of this one's.
245      +/
246     auto opUnary(string op: "-")() const
247     {
248         return Priority(-value);
249     }
250 }
251 
252 
253 // priority
254 /++
255     Helper alias to use the proper style guide and still be able to instantiate
256     [Priority] instances with UFCS.
257 
258     Example:
259     ---
260     mixin PostprocessorRegistration!(50.priority);
261     ---
262  +/
263 alias priority = Priority;