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