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 }