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 }