1 /++
2     Functionality generic enough to be used in several places.
3 
4     Copyright: [JR](https://github.com/zorael)
5     License: [Boost Software License 1.0](https://www.boost.org/users/license.html)
6 
7     Authors:
8         [JR](https://github.com/zorael)
9  +/
10 module lu.common;
11 
12 private:
13 
14 public:
15 
16 
17 // Next
18 /++
19     Enum of flags carrying the meaning of "what to do next".
20  +/
21 enum Next
22 {
23     /++
24         Unset, invalid value.
25      +/
26     unset,
27 
28     /++
29         Do nothing.
30      +/
31     noop,
32 
33     /++
34         Keep doing whatever is being done, alternatively continue on to the next step.
35      +/
36     continue_,
37 
38     /++
39         Halt what's being done and give it another attempt.
40      +/
41     retry,
42 
43     /++
44         Exit or return with a positive return value.
45      +/
46     returnSuccess,
47 
48     /++
49         Exit or abort with a negative return value.
50      +/
51     returnFailure,
52 
53     /++
54         Fatally abort.
55      +/
56     crash,
57 }
58 
59 
60 // ReturnValueException
61 /++
62     Exception, to be thrown when an executed command returns an error value.
63 
64     It is a normal [object.Exception|Exception] but with an attached command
65     and return value.
66  +/
67 final class ReturnValueException : Exception
68 {
69     /++
70         The command run.
71      +/
72     string command;
73 
74     /++
75         The value returned.
76      +/
77     int retval;
78 
79     /++
80         Create a new [ReturnValueException], without attaching anything.
81      +/
82     this(const string message,
83         const string file = __FILE__,
84         const size_t line = __LINE__,
85         Throwable nextInChain = null) pure nothrow @nogc @safe
86     {
87         super(message, file, line, nextInChain);
88     }
89 
90     /++
91         Create a new [ReturnValueException], attaching a command.
92      +/
93     this(const string message,
94         const string command,
95         const string file = __FILE__,
96         const size_t line = __LINE__,
97         Throwable nextInChain = null) pure nothrow @nogc @safe
98     {
99         this.command = command;
100         super(message, file, line, nextInChain);
101     }
102 
103     /++
104         Create a new [ReturnValueException], attaching a command and a returned value.
105      +/
106     this(const string message,
107         const string command,
108         const int retval,
109         const string file = __FILE__,
110         const size_t line = __LINE__,
111         Throwable nextInChain = null) pure nothrow @nogc @safe
112     {
113         this.command = command;
114         this.retval = retval;
115         super(message, file, line, nextInChain);
116     }
117 }
118 
119 
120 // FileExistsException
121 /++
122     Exception, to be thrown when attempting to create a file or directory and
123     finding that one already exists with the same name.
124 
125     It is a normal [object.Exception|Exception] but with an attached filename string.
126  +/
127 final class FileExistsException : Exception
128 {
129     /++
130         The name of the file.
131      +/
132     string filename;
133 
134     /++
135         Create a new [FileExistsException], without attaching a filename.
136      +/
137     this(const string message,
138         const string file = __FILE__,
139         const size_t line = __LINE__,
140         Throwable nextInChain = null) pure nothrow @nogc @safe
141     {
142         super(message, file, line, nextInChain);
143     }
144 
145     /++
146         Create a new [FileExistsException], attaching a filename.
147      +/
148     this(const string message,
149         const string filename,
150         const string file = __FILE__,
151         const size_t line = __LINE__,
152         Throwable nextInChain = null) pure nothrow @nogc @safe
153     {
154         this.filename = filename;
155         super(message, file, line, nextInChain);
156     }
157 }
158 
159 
160 // FileTypeMismatchException
161 /++
162     Exception, to be thrown when attempting to access a file or directory and
163     finding that something with the that name exists, but is of an unexpected type.
164 
165     It is a normal [object.Exception|Exception] but with an embedded filename
166     string, and an uint representing the existing file's type (file, directory,
167     symlink, ...).
168  +/
169 final class FileTypeMismatchException : Exception
170 {
171     /++
172         The filename of the non-FIFO.
173      +/
174     string filename;
175 
176     /++
177         File attributes.
178      +/
179     ushort attrs;
180 
181     /++
182         Create a new [FileTypeMismatchException], without embedding a filename.
183      +/
184     this(const string message,
185         const string file = __FILE__,
186         const size_t line = __LINE__,
187         Throwable nextInChain = null) pure nothrow @nogc @safe
188     {
189         super(message, file, line, nextInChain);
190     }
191 
192     /++
193         Create a new [FileTypeMismatchException], embedding a filename.
194      +/
195     this(const string message,
196         const string filename,
197         const ushort attrs,
198         const string file = __FILE__,
199         const size_t line = __LINE__,
200         Throwable nextInChain = null) pure nothrow @nogc @safe
201     {
202         this.filename = filename;
203         this.attrs = attrs;
204         super(message, file, line, nextInChain);
205     }
206 }
207 
208 
209 // sharedDomains
210 /++
211     Calculates how many dot-separated suffixes two strings share.
212 
213     This is useful to see to what extent two addresses are similar.
214 
215     Example:
216     ---
217     int numDomains = sharedDomains("irc.freenode.net", "leguin.freenode.net");
218     assert(numDomains == 2);  // freenode.net
219 
220     int numDomains2 = sharedDomains("Portlane2.EU.GameSurge.net", "services.gamesurge.net", caseSensitive:false);
221     assert(numDomains2 == 2);  // gamesurge.net
222     ---
223 
224     Params:
225         one = First domain string.
226         other = Second domain string.
227         caseSensitive = Whether or not comparison should be done on a
228             case-sensitive basis.
229 
230     Returns:
231         The number of domains the two strings share.
232 
233     TODO:
234         Support partial globs.
235  +/
236 auto sharedDomains(
237     const string one,
238     const string other,
239     const bool caseSensitive = true) pure @safe @nogc nothrow
240 {
241     if (!one.length || !other.length) return 0;
242 
243     static uint numDomains(const char[] one, const char[] other, const bool caseSensitive)
244     {
245         uint dots;
246         double doubleDots;
247 
248         // If both strings are the same, act as if there's an extra dot.
249         // That gives (.)rizon.net and (.)rizon.net two suffixes.
250 
251         if (caseSensitive)
252         {
253             if (one == other) ++dots;
254         }
255         else
256         {
257             import std.algorithm.comparison : equal;
258             import std.uni : asLowerCase;
259             if (one.asLowerCase.equal(other.asLowerCase)) ++dots;
260         }
261 
262         foreach (immutable i; 0..one.length)
263         {
264             immutable c1 = one[$-i-1];
265 
266             if (i == other.length)
267             {
268                 if (c1 == '.') ++dots;
269                 break;
270             }
271 
272             immutable c2 = other[$-i-1];
273 
274             if (caseSensitive)
275             {
276                 if (c1 != c2) break;
277             }
278             else
279             {
280                 import std.ascii : toLower;
281                 if (c1.toLower != c2.toLower) break;
282             }
283 
284             if (c1 == '.')
285             {
286                 if (!doubleDots)
287                 {
288                     ++dots;
289                     doubleDots = true;
290                 }
291             }
292             else
293             {
294                 doubleDots = false;
295             }
296         }
297 
298         return dots;
299     }
300 
301     return (one.length > other.length) ?
302         numDomains(one, other, cast(bool)caseSensitive) :
303         numDomains(other, one, cast(bool)caseSensitive);
304 }
305 
306 ///
307 @safe
308 unittest
309 {
310     import std.conv : text;
311 
312     immutable n1 = sharedDomains("irc.freenode.net", "help.freenode.net");
313     assert((n1 == 2), n1.text);
314 
315     immutable n2 = sharedDomains("irc.rizon.net", "services.rizon.net");
316     assert((n2 == 2), n2.text);
317 
318     immutable n3 = sharedDomains("www.google.com", "www.yahoo.com");
319     assert((n3 == 1), n3.text);
320 
321     immutable n4 = sharedDomains("www.google.se", "www.google.co.uk");
322     assert((n4 == 0), n4.text);
323 
324     immutable n5 = sharedDomains("", string.init);
325     assert((n5 == 0), n5.text);
326 
327     immutable n6 = sharedDomains("irc.rizon.net", "rizon.net");
328     assert((n6 == 2), n6.text);
329 
330     immutable n7 = sharedDomains("rizon.net", "rizon.net");
331     assert((n7 == 2), n7.text);
332 
333     immutable n8 = sharedDomains("net", "net");
334     assert((n8 == 1), n8.text);
335 
336     immutable n9 = sharedDomains("forum.dlang.org", "...");
337     assert((n9 == 0), n8.text);
338 
339     immutable n10 = sharedDomains("blahrizon.net", "rizon.net");
340     assert((n10 == 1), n10.text);
341 
342     immutable n11 = sharedDomains("rizon.net", "blahrizon.net");
343     assert((n11 == 1), n11.text);
344 
345     immutable n12 = sharedDomains("rizon.net", "irc.rizon.net");
346     assert((n12 == 2), n12.text);
347 
348     immutable n13 = sharedDomains("irc.gamesurge.net", "Stuff.GameSurge.net", caseSensitive:false);
349     assert((n13 == 2), n13.text);
350 
351     immutable n14 = sharedDomains("irc.freenode.net", "irc.FREENODE.net", caseSensitive:false);
352     assert((n14 == 3), n14.text);
353 
354     immutable n15 = sharedDomains("irc.SpotChat.org", "irc.FREENODE.net", caseSensitive:false);
355     assert((n15 == 0), n15.text);
356 }