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) nothrow pure @safe @nogc {
33     return aggregate.options.find!(o => o.full == name).wrapIntoNullable;
34 }
35 
36 Nullable!Flag getFlagByFull(T)(T aggregate, string name) nothrow pure @safe @nogc {
37     return aggregate.flags.find!(o => o.full == name).wrapIntoNullable;
38 }
39 
40 Nullable!Option getOptionByShort(T)(T aggregate, string name) nothrow pure @safe @nogc {
41     return aggregate.options.find!(o => o.abbrev == name).wrapIntoNullable;
42 }
43 
44 Nullable!Flag getFlagByShort(T)(T aggregate, string name) nothrow pure @safe @nogc {
45     return aggregate.flags.find!(o => o.abbrev == name).wrapIntoNullable;
46 }
47 
48 string getEntryKindName(IEntry entry) nothrow pure @safe {
49     if (cast(Option)entry) {
50         return "option";
51     }
52 
53     else if (cast(Flag)entry) {
54         return "flag";
55     }
56 
57     else if (cast(Argument)entry) {
58         return "argument";
59     }
60     else {
61         return null;
62     }
63 }
64 
65 string matchingCandidate(string[] values, string current) @safe {
66     auto distances = values.map!(v => levenshteinDistance(v, current));
67 
68     immutable long index = distances.minIndex;
69     if (index < 0) {
70         return null;
71     }
72 
73     return values[index];
74 }
75 
76 unittest {
77     assert (matchingCandidate(["test", "bar"], "tst") == "test");
78     assert (matchingCandidate(["test", "bar"], "barr") == "bar");
79     assert (matchingCandidate([], "barr") == null);
80 }
81 
82 
83 // minIndex is not in GDC (ugh)
84 ptrdiff_t minIndex(T)(T range) if(isInputRange!T) {
85     ptrdiff_t index, minIndex;
86     ElementType!T min = ElementType!T.max;
87 
88     foreach(el; range) {
89         if (el < min) {
90             min = el;
91             minIndex = index;
92         }
93         index += 1;
94     }
95 
96     if (min == ElementType!T.max) {
97         return -1;
98     }
99 
100     return minIndex;
101 }
102 
103 unittest {
104     assert([1, 0].minIndex == 1);
105     assert([0, 1, 2].minIndex == 0);
106     assert([2, 1, 2].minIndex == 1);
107     assert([2, 1, 0].minIndex == 2);
108     assert([1, 1, 0].minIndex == 2);
109     assert([0, 1, 0].minIndex == 0);
110     assert((cast(int[])[]).minIndex == -1);
111 }