1 module python.conv.python_to_d;
2 
3 
4 import python.raw: PyObject, pyListCheck, pyTupleCheck, PyTuple_Size, pyCallableCheck;
5 import python.type: isUserAggregate, isTuple;
6 import std.traits: Unqual, isIntegral, isFloatingPoint, isAggregateType, isArray,
7     isStaticArray, isAssociativeArray, isPointer, PointerTarget, isSomeChar, isSomeString,
8     isDelegate, isFunctionPointer;
9 import std.range: isInputRange;
10 import std.datetime: DateTime, Date;
11 import core.time: Duration;
12 
13 
14 T to(T)(PyObject* value) @trusted if(isIntegral!T && !is(T == enum)) {
15     import python.raw: PyLong_AsLong;
16 
17     const ret = PyLong_AsLong(value);
18     if(ret > T.max || ret < T.min) throw new Exception("Overflow");
19 
20     return cast(T) ret;
21 }
22 
23 
24 T to(T)(PyObject* value) @trusted if(isFloatingPoint!T) {
25     import python.raw: PyFloat_AsDouble;
26     auto ret = PyFloat_AsDouble(value);
27     return cast(T) ret;
28 }
29 
30 
31 // Returns T if T is copyable, T* otherwise
32 auto to(T)(PyObject* value) @trusted if(isUserAggregate!T && is(T == struct)) {
33     import std.traits: Unqual, isCopyable;
34 
35     static if(isCopyable!T) {
36         alias RetType = T;
37         Unqual!T ret;
38         toStructImpl(value, &ret);
39     } else {
40         alias RetType = T*;
41         auto ret = new Unqual!T;
42         toStructImpl(value, ret);
43     }
44 
45     return maybeCast!RetType(ret);
46 }
47 
48 
49 private void toStructImpl(T)(PyObject* value, T* ret) {
50     import python.type: PythonClass;
51     import std.traits: fullyQualifiedName;
52 
53     auto pyclass = cast(PythonClass!T*) value;
54 
55     static foreach(i; 0 .. typeof(*ret).tupleof.length) {
56         static if(__traits(compiles, pyclass.getField!i.to!(typeof(T.tupleof[i]))))
57             (*ret).tupleof[i] = pyclass.getField!i.to!(typeof(T.tupleof[i]));
58         else
59             pragma(msg, "WARNING: cannot convert struct field #", i, " of ", fullyQualifiedName!T);
60     }
61 }
62 
63 
64 T to(T)(PyObject* value) @trusted if(isUserAggregate!T && !is(T == struct))
65 {
66     import python.type: PythonClass, userAggregateInit, gFactory;
67     import std.traits: Unqual;
68     import std.string: fromStringz;
69     import std.conv: text;
70 
71     assert(value !is null, "Cannot convert null PyObject to `" ~ T.stringof ~ "`");
72 
73     auto pyclass = cast(PythonClass!T*) value;
74     const runtimeType = value.ob_type.tp_name.fromStringz.text;
75     auto creator = runtimeType in gFactory;
76     return creator
77         ? cast(T) (*creator)(value)
78         : userAggregateInit!T;
79 }
80 
81 
82 T to(T)(PyObject* value) if(isPointer!T && isUserAggregate!(PointerTarget!T)) {
83     auto ret = new Unqual!(PointerTarget!T);
84     toStructImpl(value, ret);
85     return ret;
86 }
87 
88 
89 // FIXME - not sure why a separate implementation is needed for non user aggregates
90 T to(T)(PyObject* value) if(isPointer!T && !isUserAggregate!(PointerTarget!T) && !isFunctionPointer!T && !isDelegate!T) {
91     import std.traits: Unqual;
92     auto ret = new Unqual!(PointerTarget!T);
93     *ret = value.to!(Unqual!(PointerTarget!T));
94     return ret;
95 }
96 
97 
98 T to(T)(PyObject* value) if(is(Unqual!T == DateTime)) {
99     import python.raw;
100 
101     return DateTime(pyDateTimeYear(value),
102                     pyDateTimeMonth(value),
103                     pyDateTimeDay(value),
104                     pyDateTimeHour(value),
105                     pyDateTimeMinute(value),
106                     pyDateTimeSecond(value));
107 
108 }
109 
110 
111 T to(T)(PyObject* value) if(is(Unqual!T == Date)) {
112     import python.raw;
113 
114     return Date(pyDateTimeYear(value),
115                 pyDateTimeMonth(value),
116                 pyDateTimeDay(value));
117 }
118 
119 
120 T to(T)(PyObject* value) if(isArray!T && !isSomeString!T)
121     in(pyListCheck(value) || pyTupleCheck(value))
122 {
123     import python.raw: PyList_Size, PyList_GetItem, PyTuple_Size, PyTuple_GetItem;
124     import std.range: ElementEncodingType;
125     import std.traits: Unqual, isDynamicArray;
126     import std.exception: enforce;
127     import std.conv: text;
128 
129     alias ElementType = Unqual!(ElementEncodingType!T);
130 
131     // This is needed to deal with array of const or immutable
132     static if(isDynamicArray!T)
133         alias ArrayType = ElementType[];
134     else
135         alias ArrayType = Unqual!T;
136 
137     // deal with void[] here since otherwise we won't be able to iterate over it
138     static if(is(ElementType == void))
139         alias RetType = ubyte[];
140     else
141         alias RetType = ArrayType;
142 
143     RetType ret;
144 
145     static if(__traits(compiles, ret.length = 1)) {
146         const valueLength = {
147             if(pyListCheck(value))
148                 return PyList_Size(value);
149             else if(pyTupleCheck(value))
150                 return PyTuple_Size(value);
151             else
152                 assert(0);
153         }();
154         assert(valueLength >= 0, text("Invalid length ", valueLength));
155         ret.length = valueLength;
156     }
157 
158     foreach(i, ref elt; ret) {
159         auto pythonItem = {
160             if(pyListCheck(value))
161                 return PyList_GetItem(value, i);
162             else if(pyTupleCheck(value))
163                 return PyTuple_GetItem(value, i);
164             else
165                 assert(0);
166         }();
167         elt = pythonItem.to!(typeof(elt));
168     }
169 
170     return maybeCast!T(ret);
171 }
172 
173 
174 T to(T)(PyObject* value) if(isSomeString!T) {
175 
176     import python.raw: pyUnicodeGetSize, pyUnicodeCheck,
177         pyBytesAsString, pyObjectUnicode, pyUnicodeAsUtf8String, Py_ssize_t;
178     import std.conv: to;
179 
180     value = pyObjectUnicode(value);
181 
182     const length = pyUnicodeGetSize(value);
183     if(length == 0) return T.init;
184 
185     auto str = pyUnicodeAsUtf8String(value);
186     if(str is null) throw new Exception("Tried to convert a non-string Python value to D string");
187 
188     auto ptr = pyBytesAsString(str);
189     assert(length == 0 || ptr !is null);
190 
191     auto slice = ptr[0 .. length];
192 
193     return slice.to!T;
194 }
195 
196 
197 T to(T)(PyObject* value) if(is(Unqual!T == bool)) {
198     import python.raw: pyTrue;
199     return value is pyTrue;
200 }
201 
202 
203 T to(T)(PyObject* value) if(isAssociativeArray!T)
204 {
205     import python.raw: pyDictCheck, PyDict_Keys, PyList_Size, PyList_GetItem, PyDict_GetItem;
206 
207     assert(pyDictCheck(value));
208 
209     // this enum is to get K and V whilst avoiding auto-decoding, which is why we're not using
210     // std.traits
211     enum _ = is(T == V[K], V, K);
212     alias KeyType = Unqual!K;
213     alias ValueType = Unqual!V;
214 
215     ValueType[KeyType] ret;
216 
217     auto keys = PyDict_Keys(value);
218 
219     foreach(i; 0 .. PyList_Size(keys)) {
220         auto k = PyList_GetItem(keys, i);
221         auto v = PyDict_GetItem(value, k);
222         auto dk = k.to!KeyType;
223         auto dv = v.to!ValueType;
224 
225         ret[dk] = dv;
226     }
227 
228     return ret;
229 }
230 
231 
232 T to(T)(PyObject* value) if(isTuple!T)
233     in(pyTupleCheck(value))
234     in(PyTuple_Size(value) == T.length)
235     do
236 {
237     import python.raw: pyTupleCheck, PyTuple_Size, PyTuple_GetItem;
238 
239     T ret;
240 
241     static foreach(i; 0 .. T.length) {
242         ret[i] = PyTuple_GetItem(value, i).to!(typeof(ret[i]));
243     }
244 
245     return ret;
246 }
247 
248 
249 T to(T)(PyObject* value) if(is(Unqual!T == char) || is(Unqual!T == wchar) || is(Unqual!T == dchar)) {
250     auto str = value.to!string;
251     return str[0];
252 }
253 
254 
255 T to(T)(PyObject* value) if(isDelegate!T) {
256     return value.toDlangFunction!T;
257 }
258 
259 T to(T)(PyObject* value) if(isFunctionPointer!T) {
260     throw new Exception("Conversion of Python functions to D function pointers not yet implemented");
261 }
262 
263 
264 private T toDlangFunction(T)(PyObject* value)
265     in(pyCallableCheck(value))
266     do
267 {
268     import python.raw: PyObject_CallObject;
269     import python.conv.d_to_python: toPython;
270     import python.conv.python_to_d: to;
271     import std.traits: ReturnType, Parameters, Unqual;
272     import std.meta: staticMap;
273     import std.typecons: Tuple;
274 
275     alias UnqualParams = staticMap!(Unqual, Parameters!T);
276 
277     return (UnqualParams dArgs) {
278         Tuple!UnqualParams dArgsTuple;
279         static foreach(i; 0 .. UnqualParams.length) {
280             dArgsTuple[i] = dArgs[i];
281         }
282         auto pyArgs = dArgsTuple.toPython;
283         auto pyResult = PyObject_CallObject(value, pyArgs);
284         static if(!is(ReturnType!T == void))
285             return pyResult.to!(ReturnType!T);
286     };
287 }
288 
289 
290 T to(T)(PyObject* value) if(is(Unqual!T == Duration)) {
291     import python.raw: PyDateTime_Delta;
292     import core.time: days, seconds, usecs;
293 
294     const delta = cast(PyDateTime_Delta*) value;
295 
296     return delta.days.days + delta.seconds.seconds + delta.microseconds.usecs;
297 }
298 
299 
300 T to(T)(PyObject* value) if(is(T == enum)) {
301     import std.traits: OriginalType;
302     return cast(T) value.to!(OriginalType!T);
303 }
304 
305 
306 // Usually this is needed in the presence of `const` or `immutable`
307 private auto maybeCast(Wanted, Actual)(auto ref Actual value) {
308     static if(is(Wanted == Actual))
309         return value;
310     // the presence of `opCast` might mean this isn't
311     // always possible
312     else static if(__traits(compiles, cast(Wanted) value))
313         return cast(Wanted) value;
314     else {
315         Wanted ret = value;
316         return ret;
317     }
318 }