1020304050607080901001101201301401501601701801902002102202302402502602702802903003103203303403503675637756381263904004104212643044045046047126481264905005105205314049540551410156140865705814077590600610620631395664065066067068556695567007155672556730742623756897655677556780790800815568208355684085086087088166889090091092556930940950960975569809901000101010290103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157556158015933361603336161556162016301640165556166016701680169111217011121711041172017301747117501767117701787117971180711817118201836391841421851421867118701880189142190711910192019321319471195019601972131980199020002010202142203712040205020602077120802090210021102127121302147121502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490 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[*]", "*[*]"]); }