10203040506070809010011012013014015016017018019020021022023024025026027028029030031032033034035036903790381539040041042154304404504604715481549050051052053156954055156956156957058156959060061062063155464065066067068326769326770071326772326773074153607540297632647732647807908008132678208332648408508608708898018909009109232679309409509609732649809901000101010210103010401050106010719108910901100111101120113011401150116191170118011901200121312223621232362124012501260127012801290130013101322133013421352113601370138213901400141014201430144014501460147014811490150115111520153215401550156015732671580159196321601975216133071620163016401653267166016701680169655017065501715638172017301749121750176912177017891217991218091218191218201838799184202118520211869431870188018920211909431910192019329631949181950196019729631982419924200020102022021203942204020502060207912208232092321002110212912213021491221502160217021802190220022102220223222402250226022702282229023002310232023322340235023602370238223902400241024202432244024502460247024822490 module fluentasserts.core.operations.registry; import fluentasserts.core.results; import fluentasserts.core.evaluation; import std.functional; import std.string; import std.array; import std.algorithm; /// Delegate type that can handle asserts alias Operation = IResult[] delegate(ref Evaluation) @safe nothrow; /// ditto alias OperationFunc = IResult[] delegate(ref Evaluation) @safe nothrow; struct OperationPair { string valueType; string expectedValueType; } /// class Registry { /// Global instance for the assert operations static Registry instance; private { Operation[string] operations; OperationPair[][string] pairs; string[string] descriptions; } /// Register a new assert operation Registry register(T, U)(string name, Operation operation) { foreach(valueType; extractTypes!T) { foreach(expectedValueType; extractTypes!U) { register(valueType, expectedValueType, name, operation); } } return this; } /// ditto Registry register(T, U)(string name, IResult[] function(ref Evaluation) @safe nothrow operation) { const operationDelegate = operation.toDelegate; return this.register!(T, U)(name, operationDelegate); } /// ditto Registry register(string valueType, string expectedValueType, string name, Operation operation) { string key = valueType ~ "." ~ expectedValueType ~ "." ~ name; operations[key] = operation; pairs[name] ~= OperationPair(valueType, expectedValueType); return this; } /// ditto Registry register(string valueType, string expectedValueType, string name, IResult[] function(ref Evaluation) @safe nothrow operation) { return this.register(valueType, expectedValueType, name, operation.toDelegate); } /// Get an operation function Operation get(string valueType, string expectedValueType, string name) @safe nothrow { assert(valueType != "", "The value type is not set!"); assert(name != "", "The operation name is not set!"); auto genericKeys = [valueType ~ "." ~ expectedValueType ~ "." ~ name] ~ generalizeKey(valueType, expectedValueType, name); string matchedKey; foreach(key; genericKeys) { if(key in operations) { matchedKey = key; break; } } assert(matchedKey != "", "There are no matching assert operations. Register any of `" ~ genericKeys.join("`, `") ~ "` to perform this assert."); return operations[matchedKey]; } /// IResult[] handle(ref Evaluation evaluation) @safe nothrow { if(evaluation.operationName == "" || evaluation.operationName == "to" || evaluation.operationName == "should") { return []; } auto operation = this.get( evaluation.currentValue.typeName, evaluation.expectedValue.typeName, evaluation.operationName); return operation(evaluation); } /// void describe(string name, string text) { descriptions[name] = text; } /// string describe(string name) { if(name !in descriptions) { return ""; } return descriptions[name]; } /// OperationPair[] bindingsForName(string name) { return pairs[name]; } /// string[] registeredOperations() { return operations.keys .map!(a => a.split(".")) .map!(a => a[a.length - 1]) .array .sort .uniq .array; } /// string docs() { string result = ""; string[] operationNames = registeredOperations .map!(a => "- [" ~ a ~ "](api/" ~ a ~ ".md)") .array; return operationNames.join("\n"); } } /// It generates a list of md links for docs unittest { import std.datetime; import fluentasserts.core.operations.equal; import fluentasserts.core.operations.lessThan; auto instance = new Registry(); instance.register("*", "*", "equal", &equal); instance.register!(Duration, Duration)("lessThan", &lessThanDuration); instance.docs.should.equal("- [equal](api/equal.md)\n" ~ "- [lessThan](api/lessThan.md)"); } string[] generalizeKey(string valueType, string expectedValueType, string name) @safe nothrow { string[] results; foreach (string generalizedValueType; generalizeType(valueType)) { foreach (string generalizedExpectedValueType; generalizeType(expectedValueType)) { results ~= generalizedValueType ~ "." ~ generalizedExpectedValueType ~ "." ~ name; } } return results; } string[] generalizeType(string typeName) @safe nothrow { auto pos = typeName.indexOf("["); if(pos == -1) { return ["*"]; } string[] results = []; const pieces = typeName.split("["); string arrayType; bool isHashMap; int index = 0; int diff = 0; foreach (ch; typeName[pos..$]) { diff++; if(ch == '[') { index++; } if(ch == ']') { index--; } if(index == 0 && diff == 2) { arrayType ~= "[]"; } if(index == 0 && diff != 2) { arrayType ~= "[*]"; isHashMap = true; } if(index == 0) { diff = 0; } } if(isHashMap) { results ~= "*" ~ typeName[pos..$]; results ~= pieces[0] ~ arrayType; } results ~= "*" ~ arrayType; return results; } version(unittest) { import fluentasserts.core.base; } /// It can generalize an int unittest { generalizeType("int").should.equal(["*"]); } /// It can generalize a list unittest { generalizeType("int[]").should.equal(["*[]"]); } /// It can generalize a list of lists unittest { generalizeType("int[][]").should.equal(["*[][]"]); } /// It can generalize an assoc array unittest { generalizeType("int[int]").should.equal(["*[int]", "int[*]", "*[*]"]); } /// It can generalize a combination of assoc arrays and lists unittest { generalizeType("int[int][][string][]").should.equal(["*[int][][string][]", "int[*][][*][]", "*[*][][*][]"]); } /// It can generalize an assoc array with a key list unittest { generalizeType("int[int[]]").should.equal(["*[int[]]", "int[*]", "*[*]"]); }
module fluentasserts.core.operations.registry; import fluentasserts.core.results; import fluentasserts.core.evaluation; import std.functional; import std.string; import std.array; import std.algorithm; /// Delegate type that can handle asserts alias Operation = IResult[] delegate(ref Evaluation) @safe nothrow; /// ditto alias OperationFunc = IResult[] delegate(ref Evaluation) @safe nothrow; struct OperationPair { string valueType; string expectedValueType; } /// class Registry { /// Global instance for the assert operations static Registry instance; private { Operation[string] operations; OperationPair[][string] pairs; string[string] descriptions; } /// Register a new assert operation Registry register(T, U)(string name, Operation operation) { foreach(valueType; extractTypes!T) { foreach(expectedValueType; extractTypes!U) { register(valueType, expectedValueType, name, operation); } } return this; } /// ditto Registry register(T, U)(string name, IResult[] function(ref Evaluation) @safe nothrow operation) { const operationDelegate = operation.toDelegate; return this.register!(T, U)(name, operationDelegate); } /// ditto Registry register(string valueType, string expectedValueType, string name, Operation operation) { string key = valueType ~ "." ~ expectedValueType ~ "." ~ name; operations[key] = operation; pairs[name] ~= OperationPair(valueType, expectedValueType); return this; } /// ditto Registry register(string valueType, string expectedValueType, string name, IResult[] function(ref Evaluation) @safe nothrow operation) { return this.register(valueType, expectedValueType, name, operation.toDelegate); } /// Get an operation function Operation get(string valueType, string expectedValueType, string name) @safe nothrow { assert(valueType != "", "The value type is not set!"); assert(name != "", "The operation name is not set!"); auto genericKeys = [valueType ~ "." ~ expectedValueType ~ "." ~ name] ~ generalizeKey(valueType, expectedValueType, name); string matchedKey; foreach(key; genericKeys) { if(key in operations) { matchedKey = key; break; } } assert(matchedKey != "", "There are no matching assert operations. Register any of `" ~ genericKeys.join("`, `") ~ "` to perform this assert."); return operations[matchedKey]; } /// IResult[] handle(ref Evaluation evaluation) @safe nothrow { if(evaluation.operationName == "" || evaluation.operationName == "to" || evaluation.operationName == "should") { return []; } auto operation = this.get( evaluation.currentValue.typeName, evaluation.expectedValue.typeName, evaluation.operationName); return operation(evaluation); } /// void describe(string name, string text) { descriptions[name] = text; } /// string describe(string name) { if(name !in descriptions) { return ""; } return descriptions[name]; } /// OperationPair[] bindingsForName(string name) { return pairs[name]; } /// string[] registeredOperations() { return operations.keys .map!(a => a.split(".")) .map!(a => a[a.length - 1]) .array .sort .uniq .array; } /// string docs() { string result = ""; string[] operationNames = registeredOperations .map!(a => "- [" ~ a ~ "](api/" ~ a ~ ".md)") .array; return operationNames.join("\n"); } } /// It generates a list of md links for docs unittest { import std.datetime; import fluentasserts.core.operations.equal; import fluentasserts.core.operations.lessThan; auto instance = new Registry(); instance.register("*", "*", "equal", &equal); instance.register!(Duration, Duration)("lessThan", &lessThanDuration); instance.docs.should.equal("- [equal](api/equal.md)\n" ~ "- [lessThan](api/lessThan.md)"); } string[] generalizeKey(string valueType, string expectedValueType, string name) @safe nothrow { string[] results; foreach (string generalizedValueType; generalizeType(valueType)) { foreach (string generalizedExpectedValueType; generalizeType(expectedValueType)) { results ~= generalizedValueType ~ "." ~ generalizedExpectedValueType ~ "." ~ name; } } return results; } string[] generalizeType(string typeName) @safe nothrow { auto pos = typeName.indexOf("["); if(pos == -1) { return ["*"]; } string[] results = []; const pieces = typeName.split("["); string arrayType; bool isHashMap; int index = 0; int diff = 0; foreach (ch; typeName[pos..$]) { diff++; if(ch == '[') { index++; } if(ch == ']') { index--; } if(index == 0 && diff == 2) { arrayType ~= "[]"; } if(index == 0 && diff != 2) { arrayType ~= "[*]"; isHashMap = true; } if(index == 0) { diff = 0; } } if(isHashMap) { results ~= "*" ~ typeName[pos..$]; results ~= pieces[0] ~ arrayType; } results ~= "*" ~ arrayType; return results; } version(unittest) { import fluentasserts.core.base; } /// It can generalize an int unittest { generalizeType("int").should.equal(["*"]); } /// It can generalize a list unittest { generalizeType("int[]").should.equal(["*[]"]); } /// It can generalize a list of lists unittest { generalizeType("int[][]").should.equal(["*[][]"]); } /// It can generalize an assoc array unittest { generalizeType("int[int]").should.equal(["*[int]", "int[*]", "*[*]"]); } /// It can generalize a combination of assoc arrays and lists unittest { generalizeType("int[int][][string][]").should.equal(["*[int][][string][]", "int[*][][*][]", "*[*][][*][]"]); } /// It can generalize an assoc array with a key list unittest { generalizeType("int[int[]]").should.equal(["*[int[]]", "int[*]", "*[*]"]); }