1 module fuzzycopy;
2 
3 template canBeConvertedWithTo(F,T) {
4 	import std.traits : isIntegral, isFloatingPoint, isArray;
5 	import std.range : ElementEncodingType;
6 
7 	static if(isIntegral!F && isIntegral!T) {
8 		enum canBeConvertedWithTo = true;
9 	} else static if(isIntegral!F && isFloatingPoint!T) {
10 		enum canBeConvertedWithTo = true;
11 	} else static if(isFloatingPoint!F && isIntegral!T) {
12 		enum canBeConvertedWithTo = true;
13 	} else static if(isArray!F && isArray!T) {
14 		alias FType = ElementEncodingType!F;
15 		alias TType = ElementEncodingType!T;
16 		enum canBeConvertedWithTo = canBeConvertedWithTo!(FType,TType);
17 	} else {
18 		enum canBeConvertedWithTo = false;
19 	}
20 }
21 
22 unittest {
23 	static assert(canBeConvertedWithTo!(int,byte));
24 	static assert(canBeConvertedWithTo!(byte,float));
25 	static assert(!canBeConvertedWithTo!(string,float));
26 	static assert(!canBeConvertedWithTo!(int[],float));
27 	static assert(canBeConvertedWithTo!(int[],float[]));
28 	static assert(canBeConvertedWithTo!(byte[],double[]));
29 	static assert(canBeConvertedWithTo!(double[],ubyte[]));
30 }
31 
32 enum FuzzyCP {
33 	Fuzzy,
34 	AllFrom, // Does not work currently
35 	AllTo,
36 	Both // Does not work currently
37 }
38 
39 string buildFromAllSwitchCase(T)() {
40 	import std.conv : to;
41 	import std.traits : isFunction;
42 	import std.format : format;
43 
44 	string ret = "switch(mem) {\n";
45 	size_t idx = 0;
46 	static foreach(mem; __traits(allMembers, T)) {
47 		static if(mem != "this" && !isFunction!(mixin(format("T.%s", mem)))) {
48 			ret ~= "case \"" ~ mem ~ "\":\n";
49 			ret ~= "fromMemberVisited[" ~ to!string(idx) ~ "] = true;\n";
50 			ret ~= "break;\n";
51 			++idx;
52 		}
53 	}
54 
55 	ret ~= "default: assert(false, mem);\n";
56 	ret ~= "}\n";
57 	return ret;
58 }
59 
60 private size_t numberOfMember(T)() {
61 	import std.traits : isFunction;
62 	import std.format : format;
63 
64 	size_t ret = 0;
65 	static foreach(mem; __traits(allMembers, T)) {
66 		static if(mem != "this" && !isFunction!(mixin(format("T.%s", mem)))) {
67 			++ret;
68 		}
69 	}
70 
71 	return ret;
72 }
73 
74 void fuzzyCP(F,T, FuzzyCP FC = FuzzyCP.Fuzzy)(auto ref F from, auto ref T target) {
75 	import std.traits : hasMember, isImplicitlyConvertible, isFunction;
76 	import std.format : format;
77 
78 	static if(FC == FuzzyCP.Both || FC == FuzzyCP.AllTo) {
79 		bool[numberOfMember!T()] fromMemberVisited;
80 	}
81 	foreach(mem; __traits(allMembers, typeof(from))) {
82 		// we can not copy function so don't try
83 		static if(mem != "this" && !isFunction!(mixin(format("F.%s", mem)))) {
84 			alias FromType = typeof(__traits(getMember, from, mem));
85 			//pragma(msg, mem ~ " " ~ FromType.stringof);
86 
87 			// check if target has a value of the same name
88 			static if(hasMember!(T, mem)) {
89 				static import std.conv;
90 				alias ToType = typeof(__traits(getMember, target, mem));
91 
92 				// recursive if it is a struct
93 				static if(is(FromType == struct) && is(ToType == struct)) {
94 					fuzzyCP!(FromType,ToType,FC)(
95 							__traits(getMember, from, mem), 
96 							__traits(getMember, target, mem)
97 						);
98 					//pragma(msg, buildFromAllSwitchCase!F());
99 					//mixin(buildFromAllSwitchCase!F());
100 				// assign if it is assignable
101 				} else  static if(isImplicitlyConvertible!(FromType,ToType)) {
102 					__traits(getMember, target, mem) = std.conv.to!(ToType)(
103 							__traits(getMember, from, mem)
104 						);
105 				} else  static if(canBeConvertedWithTo!(FromType,ToType)) {
106 					__traits(getMember, target, mem) = std.conv.to!(ToType)(
107 							__traits(getMember, from, mem)
108 						);
109 				} else static if(FC == FuzzyCP.Both || FC == FuzzyCP.AllFrom) {
110 					import std.format : format;
111 					static assert(false, format(
112 						"fuzzyCP is using '%s' Mode and From '%s' and To '%s' have" 
113 						~ " a member '%s' but From.%s can not be converted target "
114 						~ "To.%s",
115 						FC, F.stringof, T.stringof, mem, FromType.stringof, 
116 						ToType.stringof));
117 				}
118 			// for FromAll and Both To needs a member
119 			} else static if(FC == FuzzyCP.Both || FC == FuzzyCP.AllFrom) {
120 				import std.format : format;
121 				static assert(false, format(
122 					"fuzzyCP is using %s Mode and %s has no member named %s",
123 					FC, T.stringof, mem));
124 			}
125 		}
126 	}
127 }
128 
129 unittest {
130 	struct Foo {
131 		int a = 10;
132 		int b = 20;
133 	}
134 
135 	struct Bar {
136 		int a;
137 		int b;
138 	}
139 
140 	Foo foo;
141 	assert(foo.a == 10);
142 	assert(foo.b == 20);
143 
144 	Bar bar;
145 
146 	fuzzyCP(foo, bar);
147 
148 	assert(bar.a == 10);
149 	assert(bar.b == 20);
150 }
151 
152 unittest {
153 	struct Foo {
154 		int a = 99;
155 		int b = 77;
156 	}
157 
158 	struct Bar {
159 		Foo foo;
160 		string s = "hello";
161 	}
162 
163 	struct NotFoo {
164 		long a;
165 		long b;
166 	}
167 
168 	struct NotBar {
169 		NotFoo foo;
170 		string s;
171 	}
172 
173 	Bar bar;
174 	NotBar nBar;
175 
176 	fuzzyCP(bar, nBar);
177 
178 	assert(nBar.foo.a == 99);
179 	assert(nBar.foo.b == 77);
180 	assert(nBar.s == "hello");
181 }
182 
183 unittest {
184 	struct Foo {
185 		int a = 99;
186 		int b = 77;
187 
188 		int fun2() {
189 			return b;
190 		}
191 	}
192 
193 	struct Bar {
194 		Foo foo;
195 		string s = "hello";
196 		string fun() {
197 			return s;
198 		}
199 	}
200 
201 	struct NotFoo {
202 		byte a;
203 		ushort b;
204 	}
205 
206 	struct NotBar {
207 		NotFoo foo;
208 		string s;
209 	}
210 
211 	Bar bar;
212 	NotBar nBar;
213 
214 	fuzzyCP!(Bar,NotBar,FuzzyCP.Both)(bar, nBar);
215 
216 	assert(nBar.foo.a == 99);
217 	assert(nBar.foo.b == 77);
218 	assert(nBar.s == "hello");
219 }
220 
221 unittest {
222 	import std.exception : assertThrown;
223 	struct Foo {
224 		long l = 126;
225 	}
226 
227 	struct Bar {
228 		byte l;
229 	}
230 
231 	Foo f;
232 	Bar b;
233 
234 	fuzzyCP(f, b);
235 	assert(b.l == 126);
236 	
237 	f.l = 128;
238 	assertThrown(fuzzyCP(f, b));
239 
240 	static assert(!__traits(compiles, fuccyCP!(Foo,Bar,FuzzyCP.FromAll)));
241 	static assert(!__traits(compiles, fuccyCP!(Foo,Bar,FuzzyCP.ToAll)));
242 }