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/util/util.d, selery/util/util.d)
28  */
29 module selery.util.util;
30 
31 import std.conv : to, ConvException;
32 import std.datetime : Clock, UTC;
33 import std.string : toUpper;
34 import std.traits : isArray, isAssociativeArray, isSafe;
35 
36 /**
37  * Gets the seconds from January 1st, 1970.
38  */
39 public @property @safe uint seconds() {
40 	return Clock.currTime(UTC()).toUnixTime!int;
41 }
42 
43 /**
44  * Gets the milliseconds from January 1st, 1970.
45  */
46 public @property @safe ulong milliseconds() {
47 	auto t = Clock.currTime(UTC());
48 	return t.toUnixTime!long * 1000 + t.fracSecs.total!"msecs";
49 }
50 
51 /**
52  * Gets the microseconds from January 1st, 1970.
53  */
54 public @property @safe ulong microseconds() {
55 	auto t = Clock.currTime(UTC());
56 	return t.toUnixTime!long * 1000 + t.fracSecs.total!"usecs";
57 }
58 
59 /**
60  * Removes an element from an array.
61  * Params:
62  * 		value = the value to be removed from the array
63  * 		array = the array where the value should be removed from
64  * Returns: true if something has been removed, false otherwise
65  * Example:
66  * ---
67  * auto arr = [0, 1, 2, 3];
68  * assert(array_remove(2, [0, 1, 2, 3]) && arr = [0, 1, 3]);
69  * 
70  * string s = "test";
71  * assert(s.remove('t') && s == "es");
72  * ---
73  */
74 public @property @trusted bool array_remove(T, E)(T value, ref E[] array) /*if(__traits(compiles, T.init == E.init))*/ {
75 	foreach(uint i, E val; array) {
76 		if(val == value) {
77 			array = array[0..i] ~ array[i+1..$];
78 			return true;
79 		}
80 	}
81 	return false;
82 }
83 
84 /// ditto
85 alias remove = array_remove;
86 
87 unittest {
88 	
89 	auto array = [1, 2, 3, 4, 5];
90 	assert(array_remove(3, array));
91 	assert(array == [1, 2, 4, 5]);
92 	
93 }
94 
95 /**
96  * Finds a value in a array.
97  * Params:
98  * 		value = the value to be searched in the array
99  * 		array = the array to search into
100  * Returns: the index where the value has been found, -1 otherwise
101  * Example:
102  * ---
103  * assert(array_index(1, [1, 2, 3] == 0));
104  * assert(array_index(0, [1, 2, 3] == -1));
105  * assert("test".indexOf('e') == 1);
106  * ---
107  */
108 public @property @trusted ptrdiff_t array_index(T, E)(T value, E[] array) /*if(__traits(compiles, T.init == E.init))*/ {
109 	foreach(uint i, E avalue; array) {
110 		if(value == avalue) return i;
111 	}
112 	return -1;
113 }
114 
115 /** 
116  * Converts from roman number to an integer.
117  * Example:
118  * ---
119  * assert("I".roman == 1);
120  * assert("V".roman == 5);
121  * assert("XX".roman == 20);
122  * assert("XL".roman == 40);
123  * assert("MCMXCVI".roman == 1996);
124  * assert("MMXCI".roman == 2016);
125  * ---
126  */
127 public @property @safe uint roman(string str) {
128 	return str.toUpper.romanImpl;
129 }
130 
131 /// ditto
132 private @property @safe uint romanImpl(string str) {
133 	if(str == "") return 0;
134 	if(str[0..1] == "M") return 1000 + str[1..$].romanImpl;
135 	if(str.length > 1 && str[0..2] == "CM") return 900 + str[2..$].romanImpl;
136 	if(str[0..1] == "D") return 500 + str[1..$].romanImpl;
137 	if(str.length > 1 && str[0..2] == "CD") return 400 + str[2..$].romanImpl;
138 	if(str[0..1] == "C") return 100 + str[1..$].romanImpl;
139 	if(str.length > 1 && str[0..2] == "XC") return 90 + str[2..$].romanImpl;
140 	if(str[0..1] == "L") return 50 + str[1..$].romanImpl;
141 	if(str.length > 1 && str[0..2] == "XL") return 40 + str[2..$].romanImpl;
142 	if(str[0..1] == "X") return 10 + str[1..$].romanImpl;
143 	if(str.length > 1 && str[0..2] == "IX") return 9 + str[2..$].romanImpl;
144 	if(str[0..1] == "V") return 5 + str[1..$].romanImpl;
145 	if(str.length > 1 && str[0..2] == "IV") return 4 + str[2..$].romanImpl;
146 	if(str[0..1] == "I") return 1 + str[1..$].romanImpl;
147 	return 0;
148 }
149 
150 unittest {
151 	
152 	assert("I".roman == 1);
153 	assert("V".roman == 5);
154 	assert("L".roman == 50);
155 	assert("CCIV".roman == 204);
156 	
157 }
158 
159 /** 
160  * Calls a function on every element in the array.
161  * Example:
162  * ---
163  * Effect effect = new Effect(Effects.REGENERATION, 60, "V");
164  * 
165  * // classic method
166  * foreach(ref Player player ; players) {
167  *    player.addEffect(effect);
168  * }
169  * 
170  * // faster and easier method
171  * players.call!"addEffect"(effect);
172  * ---
173  */
174 public void call(string func, T, E...)(T array, E args) if((isArray!T || isAssociativeArray!T) && !isSafe!(__traits(getMember, typeof(T.init[0]), func))) {
175 	foreach(ref element ; array) {
176 		mixin("element." ~ func ~ "(args);");
177 	}
178 }
179 
180 /// ditto
181 public @safe void call(string func, T, E...)(T array, E args) if((isArray!T || isAssociativeArray!T) && isSafe!(__traits(getMember, typeof(T.init[0]), func))) {
182 	foreach(ref element ; array) {
183 		mixin("element." ~ func ~ "(args);");
184 	}
185 }
186 
187 /**
188  * Performs a safe conversion.
189  * Example:
190  * ---
191  * assert(90.safe!ubyte == 90);
192  * assert(256.safe!ubyte == 255);
193  * ---
194  */
195 public @property @safe T safe(T, E)(E value) {
196 	try {
197 		return value > T.max ? T.max : (value < T.min ? T.min : to!T(value));
198 	} catch(ConvException e) {
199 		return T.init;
200 	}
201 }
202 
203 /**
204  * Removes valid formatting codes from a message.
205  * Note that this function also removes uppercase formatting codes
206  * because they're supported by Minecraft (but not by Minecraft Pocket
207  * Edition).
208  * Example:
209  * ---
210  * assert(unformat("§agreen") == "green");
211  * assert(unformat("res§Ret") == "reset");
212  * assert(unformat("§xunsupported") == "§xunsupported");
213  * ---
214  */
215 string unformat(string message) {
216 	// regex should be ctRegex!("§[0-9a-fk-or]", "") but obviously doesn't work on DMD's release mode
217 	for(size_t i=0; i<message.length-2; i++) {
218 		if(message[i] == 194 && message[i+1] == 167) {
219 			char next = message[i+2];
220 			if(next >= '0' && next <= '9' ||
221 				next >= 'A' && next <= 'F' || next >= 'K' && next <= 'O' || next == 'R' ||
222 				next >= 'a' && next <= 'f' || next >= 'k' && next <= 'o' || next == 'r')
223 			{
224 				message = message[0..i] ~ message[i+3..$];
225 				i--;
226 			}
227 		}
228 	}
229 	return message;
230 }
231 
232 class UnloggedException : Exception {
233 	
234 	public @safe this(E...)(E args) {
235 		super(args);
236 	}
237 	
238 }