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 
12 
13 T to(T)(PyObject* value) @trusted if(isIntegral!T) {
14     import python.raw: PyLong_AsLong;
15 
16     const ret = PyLong_AsLong(value);
17     if(ret > T.max || ret < T.min) throw new Exception("Overflow");
18 
19     return cast(T) ret;
20 }
21 
22 
23 T to(T)(PyObject* value) @trusted if(isFloatingPoint!T) {
24     import python.raw: PyFloat_AsDouble;
25     auto ret = PyFloat_AsDouble(value);
26     return cast(T) ret;
27 }
28 
29 
30 // Returns T if T is copyable, T* otherwise
31 auto to(T)(PyObject* value) @trusted if(isUserAggregate!T && is(T == struct)) {
32     import std.traits: Unqual, isCopyable;
33 
34     static if(isCopyable!T) {
35         alias RetType = T;
36         Unqual!T ret;
37         toStructImpl(value, &ret);
38     } else {
39         alias RetType = T*;
40         auto ret = new Unqual!T;
41         toStructImpl(value, ret);
42     }
43 
44     // might need to be cast to `const` or `immutable`
45     return cast(RetType) ret;
46 }
47 
48 
49 private void toStructImpl(T)(PyObject* value, T* ret) {
50     import python.type: PythonClass;
51 
52     auto pyclass = cast(PythonClass!T*) value;
53 
54     static foreach(i; 0 .. typeof(*ret).tupleof.length) {
55         (*ret).tupleof[i] = pyclass.getField!i.to!(typeof(T.tupleof[i]));
56     }
57 }
58 
59 
60 T to(T)(PyObject* value) @trusted if(isUserAggregate!T && !is(T == struct)) {
61     import python.type: PythonClass, userAggregateInit, gFactory;
62     import std.traits: Unqual;
63     import std.string: fromStringz;
64     import std.conv: text;
65 
66     auto pyclass = cast(PythonClass!T*) value;
67     const runtimeType = value.ob_type.tp_name.fromStringz.text;
68     auto creator = runtimeType in gFactory;
69     return creator
70         ? cast(T) (*creator)(value)
71         : userAggregateInit!T;
72 }
73 
74 
75 T to(T)(PyObject* value) if(isPointer!T && isUserAggregate!(PointerTarget!T)) {
76     import std.traits: ReturnType;
77     auto ret = new Unqual!(PointerTarget!T);
78     toStructImpl(value, ret);
79     return ret;
80 }
81 
82 
83 T to(T)(PyObject* value) if(is(Unqual!T == DateTime)) {
84     import python.raw;
85 
86     return DateTime(pyDateTimeYear(value),
87                     pyDateTimeMonth(value),
88                     pyDateTimeDay(value),
89                     pyDateTimeHour(value),
90                     pyDateTimeMinute(value),
91                     pyDateTimeSecond(value));
92 
93 }
94 
95 
96 T to(T)(PyObject* value) if(is(Unqual!T == Date)) {
97     import python.raw;
98 
99     return Date(pyDateTimeYear(value),
100                 pyDateTimeMonth(value),
101                 pyDateTimeDay(value));
102 }
103 
104 
105 T to(T)(PyObject* value) if(isArray!T && !isSomeString!T)
106     in(pyListCheck(value) || pyTupleCheck(value))
107 {
108     import python.raw: PyList_Size, PyList_GetItem, PyTuple_Size, PyTuple_GetItem;
109     import std.range: ElementEncodingType;
110     import std.traits: Unqual;
111     import std.exception: enforce;
112     import std.conv: text;
113 
114     Unqual!T ret;
115 
116     static if(__traits(compiles, ret.length = 1)) {
117         const valueLength = {
118             if(pyListCheck(value))
119                 return PyList_Size(value);
120             else if(pyTupleCheck(value))
121                 return PyTuple_Size(value);
122             else
123                 assert(0);
124         }();
125         assert(valueLength >= 0, text("Invalid length ", valueLength));
126         ret.length = valueLength;
127     }
128 
129     foreach(i, ref elt; ret) {
130         auto pythonItem = {
131             if(pyListCheck(value))
132                 return PyList_GetItem(value, i);
133             else if(pyTupleCheck(value))
134                 return PyTuple_GetItem(value, i);
135             else
136                 assert(0);
137         }();
138         elt = pythonItem.to!(ElementEncodingType!T);
139     }
140 
141     return ret;
142 }
143 
144 
145 T to(T)(PyObject* value) if(isSomeString!T) {
146 
147     import python.raw: pyUnicodeGetSize, pyUnicodeCheck,
148         pyBytesAsString, pyObjectUnicode, pyUnicodeAsUtf8String, Py_ssize_t;
149     import std.conv: to;
150 
151     value = pyObjectUnicode(value);
152 
153     const length = pyUnicodeGetSize(value);
154     if(length == 0) return T.init;
155 
156     auto str = pyUnicodeAsUtf8String(value);
157     if(str is null) throw new Exception("Tried to convert a non-string Python value to D string");
158 
159     auto ptr = pyBytesAsString(str);
160     assert(length == 0 || ptr !is null);
161 
162     auto slice = ptr[0 .. length];
163 
164     return slice.to!T;
165 }
166 
167 
168 T to(T)(PyObject* value) if(is(Unqual!T == bool)) {
169     import python.raw: pyTrue;
170     return value is pyTrue;
171 }
172 
173 
174 T to(T)(PyObject* value) if(isAssociativeArray!T)
175 {
176     import python.raw: pyDictCheck, PyDict_Keys, PyList_Size, PyList_GetItem, PyDict_GetItem;
177 
178     assert(pyDictCheck(value));
179 
180     // this enum is to get K and V whilst avoiding auto-decoding, which is why we're not using
181     // std.traits
182     enum _ = is(T == V[K], V, K);
183     alias KeyType = Unqual!K;
184     alias ValueType = Unqual!V;
185 
186     ValueType[KeyType] ret;
187 
188     auto keys = PyDict_Keys(value);
189 
190     foreach(i; 0 .. PyList_Size(keys)) {
191         auto k = PyList_GetItem(keys, i);
192         auto v = PyDict_GetItem(value, k);
193         auto dk = k.to!KeyType;
194         auto dv = v.to!ValueType;
195 
196         ret[dk] = dv;
197     }
198 
199     return ret;
200 }
201 
202 
203 T to(T)(PyObject* value) if(isTuple!T)
204     in(pyTupleCheck(value))
205     in(PyTuple_Size(value) == T.length)
206     do
207 {
208     import python.raw: pyTupleCheck, PyTuple_Size, PyTuple_GetItem;
209 
210     T ret;
211 
212     static foreach(i; 0 .. T.length) {
213         ret[i] = PyTuple_GetItem(value, i).to!(typeof(ret[i]));
214     }
215 
216     return ret;
217 }
218 
219 
220 T to(T)(PyObject* value) if(isSomeChar!T) {
221     auto str = value.to!string;
222     return str[0];
223 }
224 
225 
226 T to(T)(PyObject* value) if(isDelegate!T)
227     in(pyCallableCheck(value))
228 {
229     import python.raw: PyObject_CallObject;
230     import python.conv.d_to_python: toPython;
231     import python.conv.python_to_d: to;
232     import std.traits: ReturnType, Parameters, Unqual;
233     import std.meta: staticMap;
234     import std.typecons: Tuple;
235 
236     alias UnqualParams = staticMap!(Unqual, Parameters!T);
237 
238     return (UnqualParams dArgs) {
239         Tuple!UnqualParams dArgsTuple;
240         static foreach(i; 0 .. UnqualParams.length) {
241             dArgsTuple[i] = dArgs[i];
242         }
243         auto pyArgs = dArgsTuple.toPython;
244         auto pyResult = PyObject_CallObject(value, pyArgs);
245         return pyResult.to!(ReturnType!T);
246     };
247 }
248 
249 T to(T)(PyObject* value) if(isFunctionPointer!T)
250 {
251     throw new Exception("Can't handle function pointers yet");
252 }