1 /++
2 	This module contains contains some misc. functions. (The name says that)
3 +/
4 module utils.misc;
5 
6 import std.stdio;
7 import std.file;
8 import std.path;
9 import utils.lists;
10 import std.datetime;
11 
12 public import utils.ds : ByteUnion;
13 ///`integer is a `long` on 64 bit systems, and `int` on 32 bit systems
14 alias integer = ptrdiff_t;
15 ///`uinteger` is a `ulong` on 64 bit systems, and `uint` on 32 bit systems
16 alias uinteger = size_t;
17 /// used to read data as array of bytes
18 alias ByteUnion = utils.ds.ByteUnion;
19 
20 /// Reads a file into array of string
21 ///
22 /// each element in the returned array is a separate line, excluding the trailing `\n` character
23 /// 
24 /// Returns: the lines read from file in array of string
25 /// 
26 /// Throws: Exception on failure
27 string[] fileToArray(string fname){
28 	File f = File(fname,"r");
29 	string[] r;
30 	string line;
31 	integer i=0;
32 	r.length=0;
33 	while (!f.eof()){
34 		if (i+1>=r.length){
35 			r.length+=5;
36 		}
37 		line=f.readln;
38 		if (line.length>0 && line[line.length-1]=='\n'){
39 			line.length--;
40 		}
41 		r[i]=line;
42 		i++;
43 	}
44 	f.close;
45 	r.length = i;
46 	return r;
47 }
48 
49 /// Writes an array of string to a file
50 /// 
51 /// If a file already exists, it will be overwritten, and `\n` is added at end of each string
52 /// 
53 /// Throws: exception on failure
54 void arrayToFile(string[] array, string fname){
55 	File f = File(fname,"w");
56 	uinteger i;
57 	for (i=0;i<array.length;i++){
58 		f.write(array[i],'\n');
59 	}
60 	f.close;
61 }
62 
63 /// uses `listdir` to list files/dirs in a dir, and filters the ones that were modified after a given time
64 /// 
65 /// if the provided dir has subdirs, those are also checked, and so on
66 ///
67 /// Arguments:
68 /// `filePath` is the path to the dir/file to check  
69 /// `lastTime` is the time to check against  
70 /// `exclude` is a list of files/dirs to not to include in the check  
71 /// 
72 /// Returns: the absolute paths of the files/dirs modified after the time
73 string[] filesModified(string filePath, SysTime lastTime, string[] exclude = []){
74 	import std.algorithm;
75 	import std.array;
76 	
77 	// make sure the filePath is not in exclude
78 	if (exclude.indexOf(filePath) >= 0){
79 		return [];
80 	}
81 	if (filePath.isDir){
82 		LinkedList!string modifiedList = new LinkedList!string;
83 		FIFOStack!string filesToCheck = new FIFOStack!string;
84 		filesToCheck.push(listDir(filePath));
85 		// go through the stack
86 		while (filesToCheck.count > 0){
87 			string file = filesToCheck.pop;
88 			if (!isAbsolute(file)){
89 				file = absolutePath(filePath~'/'~file);
90 			}
91 			if (exclude.indexOf(file) >= 0){
92 				continue;
93 			}
94 			// check if it's a dir, case yes, push it's files too
95 			if (file.isDir){
96 				filesToCheck.push(listDir(file));
97 			}else if (file.isFile){
98 				// is file, check if it was modified
99 				if (timeLastModified(file) > lastTime){
100 					modifiedList.append(absolutePath(file));
101 				}
102 			}
103 		}
104 		string[] r = modifiedList.toArray;
105 		.destroy (modifiedList);
106 		.destroy (filesToCheck);
107 		return r;
108 	}else{
109 		if (timeLastModified(filePath) > lastTime){
110 			return [filePath];
111 		}
112 	}
113 	return [];
114 }
115 
116 /// lists the files and dirs inside a dir
117 ///
118 /// only dirs and files are returned, symlinks are ignored
119 /// 
120 /// Returns: an array containing absolute paths of files/dirs
121 string[] listDir(string pathname){
122 	import std.algorithm;
123 	import std.array;
124 
125 	return std.file.dirEntries(pathname, SpanMode.shallow)
126 		.filter!(a => (a.isFile || a.isDir))
127 		.map!(a => std.path.absolutePath(a.name))
128 		.array;
129 }
130 
131 
132 /// Reads a hexadecimal number from string
133 /// 
134 /// Returns: the number in a uinteger
135 /// 
136 /// Throws: Exception in case string is not a hexadecimal number, or too big to store in uinteger, or empty string
137 uinteger readHexadecimal(string str){
138 	import std.range : iota, array;
139 	if (str.length == 0)
140 		throw new Exception("cannot read hexadecimal number from empty string");
141 	if (str.length > uinteger.sizeof * 2) // str.length / 2 = numberOfBytes 
142 		throw new Exception("hexadecimal number is too big to store in uinteger");
143 	static char[16] DIGITS = iota('0', '9'+1).array ~ iota('a', 'f'+1).array;
144 	str = str.lowercase;
145 	if (!(cast(char[])str).matchElements(DIGITS))
146 		throw new Exception("invalid character in hexadecimal number");
147 	uinteger r;
148 	immutable uinteger lastInd = str.length - 1;
149 	foreach (i, c; str)
150 		r |= DIGITS.indexOf(c) << 4 * (lastInd-i);
151 	return r;
152 }
153 /// 
154 unittest{
155 	assert("FF".readHexadecimal == 0xFF);
156 	assert("F0".readHexadecimal == 0xF0);
157 	assert("EF".readHexadecimal == 0xEF);
158 	assert("A12f".readHexadecimal == 0xA12F);
159 }
160 
161 /// Reads a binary number from string
162 /// 
163 /// Returns: the number in a uinteger
164 /// 
165 /// Throws: Exception in case string is not a binary number, or too big to store in uinteger, or empty string
166 uinteger readBinary(string str){
167 	if (str.length == 0)
168 		throw new Exception("cannot read binary number from empty string");
169 	if (str.length > uinteger.sizeof * 8)
170 		throw new Exception("binary number is too big to store in uinteger");
171 	if (!(cast(char[])str).matchElements(['0','1']))
172 		throw new Exception("invalid character in binary number");
173 	uinteger r;
174 	immutable uinteger lastInd = str.length-1;
175 	foreach (i, c; str)
176 		r |= (c == '1') << (lastInd - i);
177 	return r;
178 }
179 /// 
180 unittest{
181 	assert("01010101".readBinary == 0B01010101);
182 }
183 
184 /// Returns: true if an aray has an element, false if no
185 bool hasElement(T)(T[] array, T element){
186 	bool r = false;
187 	foreach(cur; array){
188 		if (cur == element){
189 			r = true;
190 			break;
191 		}
192 	}
193 	return r;
194 }
195 ///
196 unittest{
197 	assert([0, 1, 2].hasElement(2) == true);
198 	assert([0, 1, 2].hasElement(4) == false);
199 }
200 /// Returns: true if array contains all elements provided in an array, else, false
201 bool hasElement(T)(T[] array, T[] elements){
202 	bool r = true;
203 	elements = elements.dup;
204 	// go through the list and match as many elements as possible
205 	for (uinteger i = 0; i < elements.length; i ++){
206 		// check if it exists in array
207 		uinteger index = array.indexOf(elements[i]);
208 		if (index == -1){
209 			r = false;
210 			break;
211 		}
212 	}
213 	return r;
214 }
215 ///
216 unittest{
217 	assert([0, 1, 2].hasElement([2, 0, 1]) == true);
218 	assert([0, 1, 2].hasElement([2, 0, 1, 1, 0, 2]) == true); // it works different-ly from `LinkedList.hasElements`
219 	assert([0, 1, 2].hasElement([1, 2]) == true);
220 	assert([0, 1, 2].hasElement([2, 4]) == false);
221 }
222 /// Checks if all elements present in an array are also present in another array
223 /// 
224 /// Index, and the number of times the element is present in each array doesn't matter
225 /// 
226 /// 
227 /// Arguments:
228 /// `toMatch` is the array to perform the check on  
229 /// `elements` is the array containing the elements that will be compared against  
230 ///
231 /// Returns: true if all elements present in `toMatch` are also present in `elements`
232 bool matchElements(T)(T[] toMatch, T[] elements){
233 	bool r = true;
234 	foreach(currentToMatch; toMatch){
235 		if (!elements.hasElement(currentToMatch)){
236 			r = false;
237 			break;
238 		}
239 	}
240 	return r;
241 }
242 ///
243 unittest{
244 	assert("Hello".matchElements("aeloH") == true);
245 	assert("abcd".matchElements("cda") == false);
246 }
247 
248 /// Returns: the index of an element in an array, negative one if not found
249 integer indexOf(T)(T[] array, T element){
250 	integer i;
251 	for (i = 0; i < array.length; i++){
252 		if (array[i] == element){
253 			break;
254 		}
255 	}
256 	//check if it was not found, and the loop just ended
257 	if (i >= array.length || array[i] != element){
258 		i = -1;
259 	}
260 	return i;
261 }
262 ///
263 unittest{
264 	assert([0, 1, 2].indexOf(1) == 1);
265 	assert([0, 1, 2].indexOf(4) == -1);
266 }
267 
268 /// Returns index of closing/openinig bracket of the provided bracket  
269 /// 
270 /// `T` is data type of each element (usually char in case of searching in strings)
271 /// `forward` if true, then the search is in forward direction, i.e, the closing bracket is searched for
272 /// `opening` is the array of elements that are to be considered as opening brackets
273 /// `closing` is the array of elements that are to be considered as closing brackets. Must be in same order as `opening`
274 /// `s` is the array to search in
275 /// `index` is the index of the opposite bracket
276 /// 
277 /// Returns: index of closing/opening bracket
278 /// 
279 /// Throws: Exception if the bracket is not found
280 uinteger bracketPos(T, bool forward=true)
281 (T[] s, uinteger index, T[] opening=['[','{','('], T[] closing=[']','}',')']){
282 	Stack!T brackets = new Stack!T;
283 	uinteger i = index;
284 	for (immutable uinteger lastInd = (forward ? s.length : 0); i != lastInd; (forward ? i ++: i --)){
285 		if ((forward ? opening : closing).hasElement(s[i])){
286 			// push it to brackets
287 			brackets.push(s[i]);
288 		}else if ((forward ? closing : opening).hasElement(s[i])){
289 			// make sure the correct bracket was closed
290 			if ((forward ? opening : closing).indexOf(s[i]) !=
291 				(forward ? closing : opening).indexOf(brackets.pop)){
292 				throw new Exception("incorect brackets order - first opened must be last closed");
293 			}
294 		}
295 		if (brackets.count == 0){
296 			break;
297 		}
298 	}
299 	.destroy (brackets);
300 	return i;
301 }
302 ///
303 unittest{
304 	assert ((cast(char[])"hello(asdf[asdf])").bracketPos(5) == 16);
305 	assert ((cast(char[])"hello(asdf[asdf])").bracketPos(10) == 15);
306 }
307 
308 /// Removes a number of elements starting from an index
309 /// 
310 /// No range checks are done, so an IndexOutOfBounds may occur
311 ///
312 /// Returns: the modified array
313 T[] deleteElement(T)(T[] dat, uinteger pos, uinteger count=1){
314 	T[] ar1, ar2;
315 	ar1 = dat[0..pos];
316 	ar2 = dat[pos+count..dat.length];
317 	return ar1~ar2;
318 }
319 ///
320 unittest{
321 	assert([0, 1, 2].deleteElement(1) == [0, 2]);
322 	assert([0, 1, 2].deleteElement(0, 2) == [2]);
323 }
324 
325 /// Inserts an array into another array, at a provided index
326 /// 
327 /// No range checks are done, so an IndexOutOfBounds may occur
328 ///
329 /// Returns: the modified array
330 T[] insertElement(T)(T[] dat, T[] toInsert, uinteger pos){
331 	T[] ar1, ar2;
332 	ar1 = dat[0..pos];
333 	ar2 = dat[pos..dat.length];
334 	return ar1~toInsert~ar2;
335 }
336 ///
337 unittest{
338 	assert([0, 2].insertElement([1, 1], 1) == [0, 1, 1, 2]);
339 	assert([2].insertElement([0, 1], 0) == [0, 1, 2]);
340 }
341 /// Inserts an element into an array
342 /// 
343 /// No range checks are done, so an IndexOutOfBounds may occur
344 ///
345 /// Returns: the modified array
346 T[] insertElement(T)(T[] dat, T toInsert, uinteger pos){
347 	T[] ar1, ar2;
348 	ar1 = dat[0..pos];
349 	ar2 = dat[pos..dat.length];
350 	return ar1~[toInsert]~ar2;
351 }
352 ///
353 unittest{
354 	assert([0, 2].insertElement(1, 1) == [0, 1, 2]);
355 	assert([2].insertElement(1, 0) == [1, 2]);
356 }
357 
358 /// returns: the reverse of an array
359 T[] reverseArray(T)(T[] s){
360 	integer i, writePos = 0;
361 	T[] r;
362 	r.length = s.length;
363 
364 	for (i = s.length-1; writePos < r.length; i--){
365 		r[writePos] = s[i];
366 		writePos ++;
367 	}
368 	return r;
369 }
370 ///
371 unittest{
372 	assert([1, 2, 3, 4].reverseArray == [4, 3, 2, 1]);
373 }
374 
375 /*
376 /// divides an array into number of arrays while (trying to) keeping their length same
377 /// 
378 /// In case it's not possible to keep length same, the left-over elements from array will be added to the last array
379 ///
380 /// Returns: the divided arrays
381 T[][] divideArray(T)(T[] array, uinteger divBy){
382 	array = array.dup;
383 	T[][] r;
384 	r.length = divBy;
385 	uinteger elementCount = array.length / divBy;
386 	if (elementCount == 0){
387 		r[0] = array;
388 		return r;
389 	}
390 	foreach (i, element; r){
391 		if (elementCount > array.length){
392 			break;
393 		}
394 		r[i] = array[0 .. elementCount].dup;
395 		array = array[elementCount .. array.length];
396 	}
397 	// check if there's some elements left, append them to end of last array, to keep the order
398 	if (array.length > 0){
399 		r[divBy-1] = r[divBy-1] ~ array;
400 	}
401 	return r;
402 }
403 ///
404 unittest{
405 	import std.conv : to;
406 	assert ([0,1,2,3,4,5].divideArray(2) == [[0,1,2],[3,4,5]]);
407 	assert ([0,1,2,3,4,5,6,7].divideArray(3) == [[0,1],[2,3],[4,5,6,7]]);
408 	assert ([0,1].divideArray(3) == [[0,1],[],[]]);
409 }*/
410 
411 /// Divides an array into smaller arrays, where smaller arrays have a max size
412 /// 
413 /// Returns: array of the smaller arrays
414 T[][] divideArray(T)(T[] array, uinteger maxLength){
415 	if (maxLength == 0)
416 		throw new Exception("maxLength must be greater than 0");
417 	T[][] r;
418 	r.length = (array.length / maxLength) + (array.length % maxLength == 0 ? 0 : 1);
419 	for (uinteger readFrom = 0, i = 0; i < r.length; i ++){
420 		if (readFrom + maxLength > array.length){
421 			r[i] = array[readFrom .. array.length];
422 		}else{
423 			r[i] = array[readFrom .. readFrom + maxLength];
424 			readFrom += maxLength;
425 		}
426 	}
427 	return r;
428 }
429 ///
430 unittest{
431 	assert([0,1,2,3].divideArray(1) == [[0],[1],[2],[3]]);
432 	assert([0,1,2,3].divideArray(2) == [[0,1],[2,3]]);
433 	assert([0,1,2,3].divideArray(3) == [[0,1,2],[3]]);
434 	assert([0,1,2,3].divideArray(4) == [[0,1,2,3]]);
435 }
436 
437 /// Sorts an array, in ascending order, containing floating point, or integers
438 /// 
439 /// Returns: true if any sorting was done
440 bool sortAscending(T)(ref T[] array){
441 	if (array.length < 2)
442 		return false;
443 	bool notSorted;
444 	bool changed = false;
445 	immutable uinteger lastIndex = array.length-1;
446 	do{
447 		notSorted = false;
448 		for (uinteger i = 0; i < lastIndex; i ++){
449 			if (array[i] > array[i+1]){
450 				immutable T temp = array[i+1];
451 				array[i + 1] = array[i];
452 				array[i] = temp;
453 				notSorted = true;
454 			}
455 		}
456 		changed = changed || notSorted;
457 	}while (notSorted);
458 	return changed;
459 }
460 ///
461 unittest{
462 	int[] array = [0, 2,5,73,2,4,2];
463 	array.sortAscending;
464 	assert(array == [0, 2, 2, 2, 4, 5, 73]);
465 }
466 
467 /// Sorts an array in ascending order
468 /// 
469 /// Returns: array containing indexes of original array's elements in the order they are in now
470 uinteger[] sortAscendingIndex(T)(ref T[] array){
471 	if (array.length < 2)
472 		return [0];
473 	uinteger[] indexes;
474 	indexes.length = array.length;
475 	foreach (i; 0 .. indexes.length)
476 		indexes[i] = i;
477 	bool notSorted;
478 	immutable uinteger lastIndex = array.length-1;
479 	do{
480 		notSorted = false;
481 		for (uinteger i = 0; i < lastIndex; i ++){
482 			if (array[i] > array[i+1]){
483 				immutable T temp = array[i+1];
484 				immutable uinteger tempIndex = indexes[i+1];
485 				array[i+1] = array[i];
486 				indexes[i+1] = indexes[i];
487 				array[i] = temp;
488 				indexes[i] = tempIndex;
489 				notSorted = true;
490 			}
491 		}
492 	}while (notSorted);
493 	return indexes;
494 }
495 ///
496 unittest{
497 	int[] array = [5,4,9,3,6,2,1];
498 	uinteger[] indexes = array.sortAscendingIndex;
499 	assert(array == [1,2,3,4,5,6,9]);
500 	assert(indexes == [6,5,3,1,0,4,2]);
501 }
502 
503 /// Returns: true if a string is a number
504 bool isNum(string s, bool allowDecimalPoint=true){
505 	bool hasDecimalPoint = false;
506 	if (!allowDecimalPoint){
507 		hasDecimalPoint = true; // just a hack that makes it return false on "seeing" decimal point
508 	}
509 	s = s.dup;
510 	if (s.length > 0 && s[0] == '-')
511 		s = s[1 .. $];
512 	if (s.length == 0)
513 		return false;
514 	foreach (c; s){
515 		if (c == '.' && !hasDecimalPoint){
516 			hasDecimalPoint = true;
517 		}else if (!"0123456789".hasElement(c)){
518 			return false;
519 		}
520 	}
521 	return true;
522 }
523 ///
524 unittest{
525 	assert("32".isNum == true);
526 	assert("32.2".isNum == true);
527 	assert("32.2.4".isNum == false);
528 	assert("5.a".isNum == false);
529 	assert("thisIsAVar_1234".isNum == false);
530 	assert("5.3".isNum(false) == false);
531 	assert("53".isNum(false) == true);
532 	assert("-53".isNum(false) == true);
533 	assert("-53".isNum(true) == true);
534 	assert("-53.0".isNum(false) == false);
535 	assert("-53.8".isNum(true) == true);
536 	assert("-".isNum == false);
537 	assert("".isNum == false);
538 }
539 
540 /// Returns: a string with all uppercase alphabets converted into lowercase
541 string lowercase(string s){
542 	static const ubyte diff = 'a' - 'A';
543 	char[] r = (cast(char[])s).dup;
544 	foreach (i, c; r){
545 		if (c >= 'A' && c <= 'Z'){
546 			r[i] = cast(char)(c+diff);
547 		}
548 	}
549 	return cast(string)r;
550 }
551 ///
552 unittest{
553 	assert("ABcD".lowercase == "abcd");
554 	assert("abYZ".lowercase == "abyz");
555 }
556 
557 /// Returns: true if all characters in a string are alphabets, uppercase, lowercase, or both
558 bool isAlphabet(string s){
559 	uinteger i;
560 	bool r=true;
561 	foreach (c; s){
562 		if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')){
563 			return false;
564 		}
565 	}
566 	return true;
567 }
568 ///
569 unittest{
570 	assert("aBcDEf".isAlphabet == true);
571 	assert("ABCd_".isAlphabet == false);
572 	assert("ABC12".isAlphabet == false);
573 }
574 
575 /// generates a markdown table for some data.
576 /// 
577 /// Arguments:
578 /// `headings` is the headings for each column. Left-to-Right  
579 /// `data` contains each row's data. All rows must be same length  
580 /// 
581 /// Returns: the markdown, for the table, with each line of markdown as a separate element in the string[]
582 string[] makeTable(T)(string[] headings, T[][] data){
583 	assert(headings.length > 0, "cannot make table with no headings");
584 	assert(data.length > 0, "cannot make table with no data");
585 	assert(headings.length == data[0].length, "headings.length does not equal data.length "~to!string(headings.length)~"!="~
586 		to!string(data[0].length));
587 	import utils.lists;
588 	// stores the data in string
589 	string[][] sData;
590 	// convert it all to string
591 	static if (is (T == string)){
592 		sData = data;
593 	}else{
594 		sData.length = data.length;
595 		foreach (rowNum, row; data){
596 			sData[rowNum].length = row.length;
597 			foreach (cellNum, cell; row){
598 				sData[rowNum][cellNum] = to!string(cell);
599 			}
600 		}
601 	}
602 	// now make the table
603 	LinkedList!string table = new LinkedList!string;
604 	// add headings
605 	{
606 		string line;
607 		string alignment;
608 		line = headings[0];
609 		alignment = "---";
610 		for (uinteger i = 1; i < headings.length; i ++){
611 			line ~= " | "~headings[i];
612 			alignment ~= " | ---";
613 		}
614 		table.append([line, alignment]);
615 	}
616 	// now begin with the data
617 	foreach (row; sData){
618 		string line/* = row[0]*/;
619 		foreach (cell; row){
620 			line ~= cell~" | ";
621 		}
622 		line.length -= 3;
623 		table.append (line);
624 	}
625 	string[] r = table.toArray;
626 	.destroy(table);
627 	return r;
628 }