1 module commandr.utils; 2 3 import commandr; 4 import std.array : array; 5 import std.algorithm : find, map, levenshteinDistance; 6 import std.typecons : Tuple, Nullable; 7 import std.range : isInputRange, ElementType; 8 9 10 // helpers 11 12 private Nullable!T wrapIntoNullable(T)(T[] data) pure nothrow @safe @nogc { 13 Nullable!T result; 14 if (data.length > 0) { 15 result = data[0]; 16 } 17 return result; 18 } 19 20 unittest { 21 assert(wrapIntoNullable(cast(string[])[]).isNull); 22 23 auto wrapped = wrapIntoNullable(["test"]); 24 assert(!wrapped.isNull); 25 assert(wrapped.get() == "test"); 26 27 wrapped = wrapIntoNullable(["test", "bar"]); 28 assert(!wrapped.isNull); 29 assert(wrapped.get() == "test"); 30 } 31 32 Nullable!Option getOptionByFull(T)(T aggregate, string name, bool useDefault = false) nothrow pure @safe { 33 auto ret = aggregate.options.find!(o => o.full == name).wrapIntoNullable; 34 if (ret.isNull && aggregate.defaultCommand !is null && useDefault) 35 ret = aggregate.commands[aggregate.defaultCommand].getOptionByFull(name, useDefault); 36 return ret; 37 } 38 39 Nullable!Flag getFlagByFull(T)(T aggregate, string name, bool useDefault = false) nothrow pure @safe { 40 auto ret = aggregate.flags.find!(o => o.full == name).wrapIntoNullable; 41 if (ret.isNull && aggregate.defaultCommand !is null && useDefault) 42 ret = aggregate.commands[aggregate.defaultCommand].getFlagByFull(name, useDefault); 43 return ret; 44 } 45 46 Nullable!Option getOptionByShort(T)(T aggregate, string name, bool useDefault = false) nothrow pure @safe { 47 auto ret = aggregate.options.find!(o => o.abbrev == name).wrapIntoNullable; 48 if (ret.isNull && aggregate.defaultCommand !is null && useDefault) 49 ret = aggregate.commands[aggregate.defaultCommand].getOptionByShort(name, useDefault); 50 return ret; 51 } 52 53 Nullable!Flag getFlagByShort(T)(T aggregate, string name, bool useDefault = false) nothrow pure @safe { 54 auto ret = aggregate.flags.find!(o => o.abbrev == name).wrapIntoNullable; 55 if (ret.isNull && aggregate.defaultCommand !is null && useDefault) 56 ret = aggregate.commands[aggregate.defaultCommand].getFlagByShort(name, useDefault); 57 return ret; 58 } 59 60 string getEntryKindName(IEntry entry) nothrow pure @safe { 61 if (cast(Option)entry) { 62 return "option"; 63 } 64 65 else if (cast(Flag)entry) { 66 return "flag"; 67 } 68 69 else if (cast(Argument)entry) { 70 return "argument"; 71 } 72 else { 73 return null; 74 } 75 } 76 77 string matchingCandidate(string[] values, string current) @safe { 78 auto distances = values.map!(v => levenshteinDistance(v, current)); 79 80 immutable long index = distances.minIndex; 81 if (index < 0) { 82 return null; 83 } 84 85 return values[index]; 86 } 87 88 unittest { 89 assert (matchingCandidate(["test", "bar"], "tst") == "test"); 90 assert (matchingCandidate(["test", "bar"], "barr") == "bar"); 91 assert (matchingCandidate([], "barr") == null); 92 } 93 94 95 // minIndex is not in GDC (ugh) 96 ptrdiff_t minIndex(T)(T range) if(isInputRange!T) { 97 ptrdiff_t index, minIndex; 98 ElementType!T min = ElementType!T.max; 99 100 foreach(el; range) { 101 if (el < min) { 102 min = el; 103 minIndex = index; 104 } 105 index += 1; 106 } 107 108 if (min == ElementType!T.max) { 109 return -1; 110 } 111 112 return minIndex; 113 } 114 115 unittest { 116 assert([1, 0].minIndex == 1); 117 assert([0, 1, 2].minIndex == 0); 118 assert([2, 1, 2].minIndex == 1); 119 assert([2, 1, 0].minIndex == 2); 120 assert([1, 1, 0].minIndex == 2); 121 assert([0, 1, 0].minIndex == 0); 122 assert((cast(int[])[]).minIndex == -1); 123 }