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)
91     if(isPointer!T && !isUserAggregate!(PointerTarget!T) &&
92        !isFunctionPointer!T && !isDelegate!T &&
93        !is(Unqual!(PointerTarget!T) == void))
94 {
95     import python.raw: pyUnicodeCheck;
96     import std.traits: Unqual, PointerTarget;
97     import std.string: toStringz;
98 
99     enum isStringz = is(PointerTarget!T == const(char)) || is(PointerTarget!T == immutable(char));
100 
101     if(isStringz && pyUnicodeCheck(value))
102         return value.to!string.toStringz.maybeCast!T;
103     else {
104         auto ret = new Unqual!(PointerTarget!T);
105         *ret = value.to!(Unqual!(PointerTarget!T));
106         return ret.maybeCast!T;
107     }
108 }
109 
110 
111 T to(T)(PyObject* value) if(isPointer!T && is(Unqual!(PointerTarget!T) == void))
112 {
113     import python.raw: pyBytesCheck, PyBytes_AsString;
114     import std.exception: enforce;
115 
116     enforce(pyBytesCheck(value), "Can only convert Python bytes object to void*");
117     return cast(void*) PyBytes_AsString(value);
118 }
119 
120 
121 T to(T)(PyObject* value) if(is(Unqual!T == DateTime)) {
122     import python.raw;
123 
124     return DateTime(pyDateTimeYear(value),
125                     pyDateTimeMonth(value),
126                     pyDateTimeDay(value),
127                     pyDateTimeHour(value),
128                     pyDateTimeMinute(value),
129                     pyDateTimeSecond(value));
130 
131 }
132 
133 
134 T to(T)(PyObject* value) if(is(Unqual!T == Date)) {
135     import python.raw;
136 
137     return Date(pyDateTimeYear(value),
138                 pyDateTimeMonth(value),
139                 pyDateTimeDay(value));
140 }
141 
142 
143 T to(T)(PyObject* value) if(isArray!T && !isSomeString!T)
144     in(pyListCheck(value) || pyTupleCheck(value))
145 {
146     import python.raw: PyList_Size, PyList_GetItem, PyTuple_Size, PyTuple_GetItem;
147     import std.range: ElementEncodingType;
148     import std.traits: Unqual, isDynamicArray;
149     import std.exception: enforce;
150     import std.conv: text;
151 
152     alias ElementType = Unqual!(ElementEncodingType!T);
153 
154     // This is needed to deal with array of const or immutable
155     static if(isDynamicArray!T)
156         alias ArrayType = ElementType[];
157     else
158         alias ArrayType = Unqual!T;
159 
160     // deal with void[] here since otherwise we won't be able to iterate over it
161     static if(is(ElementType == void))
162         alias RetType = ubyte[];
163     else
164         alias RetType = ArrayType;
165 
166     RetType ret;
167 
168     static if(__traits(compiles, ret.length = 1)) {
169         const valueLength = {
170             if(pyListCheck(value))
171                 return PyList_Size(value);
172             else if(pyTupleCheck(value))
173                 return PyTuple_Size(value);
174             else
175                 assert(0);
176         }();
177         assert(valueLength >= 0, text("Invalid length ", valueLength));
178         ret.length = valueLength;
179     }
180 
181     foreach(i, ref elt; ret) {
182         auto pythonItem = {
183             if(pyListCheck(value))
184                 return PyList_GetItem(value, i);
185             else if(pyTupleCheck(value))
186                 return PyTuple_GetItem(value, i);
187             else
188                 assert(0);
189         }();
190         elt = pythonItem.to!(typeof(elt));
191     }
192 
193     return maybeCast!T(ret);
194 }
195 
196 
197 T to(T)(PyObject* value) if(isSomeString!T) {
198 
199     import python.raw: pyUnicodeGetSize, pyUnicodeCheck,
200         pyBytesAsString, pyObjectUnicode, pyUnicodeAsUtf8String, Py_ssize_t;
201     import std.conv: to;
202 
203     value = pyObjectUnicode(value);
204 
205     const length = pyUnicodeGetSize(value);
206     if(length == 0) return T.init;
207 
208     auto str = pyUnicodeAsUtf8String(value);
209     if(str is null) throw new Exception("Tried to convert a non-string Python value to D string");
210 
211     auto ptr = pyBytesAsString(str);
212     assert(length == 0 || ptr !is null);
213 
214     auto slice = ptr[0 .. length];
215 
216     return slice.to!T;
217 }
218 
219 
220 T to(T)(PyObject* value) if(is(Unqual!T == bool)) {
221     import python.raw: pyTrue;
222     return value is pyTrue;
223 }
224 
225 
226 T to(T)(PyObject* value) if(isAssociativeArray!T)
227 {
228     import python.raw: pyDictCheck, PyDict_Keys, PyList_Size, PyList_GetItem, PyDict_GetItem;
229 
230     assert(pyDictCheck(value));
231 
232     // this enum is to get K and V whilst avoiding auto-decoding, which is why we're not using
233     // std.traits
234     enum _ = is(T == V[K], V, K);
235     alias KeyType = Unqual!K;
236     alias ValueType = Unqual!V;
237 
238     ValueType[KeyType] ret;
239 
240     auto keys = PyDict_Keys(value);
241 
242     foreach(i; 0 .. PyList_Size(keys)) {
243         auto k = PyList_GetItem(keys, i);
244         auto v = PyDict_GetItem(value, k);
245         auto dk = k.to!KeyType;
246         auto dv = v.to!ValueType;
247 
248         ret[dk] = dv;
249     }
250 
251     return ret;
252 }
253 
254 
255 T to(T)(PyObject* value) if(isTuple!T)
256     in(pyTupleCheck(value))
257     in(PyTuple_Size(value) == T.length)
258     do
259 {
260     import python.raw: pyTupleCheck, PyTuple_Size, PyTuple_GetItem;
261 
262     T ret;
263 
264     static foreach(i; 0 .. T.length) {
265         ret[i] = PyTuple_GetItem(value, i).to!(typeof(ret[i]));
266     }
267 
268     return ret;
269 }
270 
271 
272 T to(T)(PyObject* value) if(is(Unqual!T == char) || is(Unqual!T == wchar) || is(Unqual!T == dchar)) {
273     auto str = value.to!string;
274     return str[0];
275 }
276 
277 
278 T to(T)(PyObject* value) if(isDelegate!T) {
279     return value.toDlangFunction!T;
280 }
281 
282 T to(T)(PyObject* value) if(isFunctionPointer!T) {
283     throw new Exception("Conversion of Python functions to D function pointers not yet implemented");
284 }
285 
286 
287 private T toDlangFunction(T)(PyObject* value)
288     in(pyCallableCheck(value))
289     do
290 {
291     import python.raw: PyObject_CallObject;
292     import python.conv.d_to_python: toPython;
293     import python.conv.python_to_d: to;
294     import std.traits: ReturnType, Parameters, Unqual;
295     import std.meta: staticMap;
296     import std.typecons: Tuple;
297 
298     alias UnqualParams = staticMap!(Unqual, Parameters!T);
299 
300     return (UnqualParams dArgs) {
301         Tuple!UnqualParams dArgsTuple;
302         static foreach(i; 0 .. UnqualParams.length) {
303             dArgsTuple[i] = dArgs[i];
304         }
305         auto pyArgs = dArgsTuple.toPython;
306         auto pyResult = PyObject_CallObject(value, pyArgs);
307         static if(!is(ReturnType!T == void))
308             return pyResult.to!(ReturnType!T);
309     };
310 }
311 
312 
313 T to(T)(PyObject* value) if(is(Unqual!T == Duration)) {
314     import python.raw: PyDateTime_Delta;
315     import core.time: days, seconds, usecs;
316 
317     const delta = cast(PyDateTime_Delta*) value;
318 
319     return delta.days.days + delta.seconds.seconds + delta.microseconds.usecs;
320 }
321 
322 
323 T to(T)(PyObject* value) if(is(T == enum)) {
324     import std.traits: OriginalType;
325     return cast(T) value.to!(OriginalType!T);
326 }
327 
328 
329 // Usually this is needed in the presence of `const` or `immutable`
330 private auto maybeCast(Wanted, Actual)(auto ref Actual value) {
331     static if(is(Wanted == Actual))
332         return value;
333     // the presence of `opCast` might mean this isn't
334     // always possible
335     else static if(__traits(compiles, cast(Wanted) value))
336         return cast(Wanted) value;
337     else {
338         Wanted ret = value;
339         return ret;
340     }
341 }