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;