1 module python.conv.python_to_d;
2 
3 
4 import python.raw: PyObject, pyListCheck, pyTupleCheck, PyTuple_Size;
5 import python.type: isUserAggregate, isTuple;
6 import std.traits: Unqual, isIntegral, isFloatingPoint, isAggregateType, isArray,
7     isStaticArray, isAssociativeArray, isPointer, PointerTarget, isSomeChar, isSomeString, isSomeFunction;
8 import std.range: isInputRange;
9 import std.datetime: DateTime, Date;
10 
11 
12 T to(T)(PyObject* value) @trusted if(isIntegral!T) {
13     import python.raw: PyLong_AsLong;
14 
15     const ret = PyLong_AsLong(value);
16     if(ret > T.max || ret < T.min) throw new Exception("Overflow");
17 
18     return cast(T) ret;
19 }
20 
21 
22 T to(T)(PyObject* value) @trusted if(isFloatingPoint!T) {
23     import python.raw: PyFloat_AsDouble;
24     auto ret = PyFloat_AsDouble(value);
25     return cast(T) ret;
26 }
27 
28 
29 T to(T)(PyObject* value) @trusted if(isUserAggregate!T) {
30     import python.type: PythonClass, userAggregateInit;
31 
32     auto pyclass = cast(PythonClass!T*) value;
33     auto ret = userAggregateInit!(Unqual!T);
34 
35     static foreach(i; 0 .. T.tupleof.length) {
36         ret.tupleof[i] = pyclass.getField!i.to!(typeof(T.tupleof[i]));
37     }
38 
39     // might need to be cast to `const` or `immutable`
40     return cast(T) ret;
41 }
42 
43 
44 T to(T)(PyObject* value) if(isPointer!T && isUserAggregate!(PointerTarget!T)) {
45     auto ret = new Unqual!(PointerTarget!T);
46     *ret = to!(PointerTarget!T)(value);
47     return ret;
48 }
49 
50 
51 T to(T)(PyObject* value) if(is(Unqual!T == DateTime)) {
52     import python.raw;
53 
54     return DateTime(pyDateTimeYear(value),
55                     pyDateTimeMonth(value),
56                     pyDateTimeDay(value),
57                     pyDateTimeHour(value),
58                     pyDateTimeMinute(value),
59                     pyDateTimeSecond(value));
60 
61 }
62 
63 
64 T to(T)(PyObject* value) if(is(Unqual!T == Date)) {
65     import python.raw;
66 
67     return Date(pyDateTimeYear(value),
68                 pyDateTimeMonth(value),
69                 pyDateTimeDay(value));
70 }
71 
72 
73 T to(T)(PyObject* value) if(isArray!T && !isSomeString!T)
74     in(pyListCheck(value) || pyTupleCheck(value))
75 {
76     import python.raw: PyList_Size, PyList_GetItem, PyTuple_Size, PyTuple_GetItem;
77     import std.range: ElementEncodingType;
78     import std.traits: Unqual;
79     import std.exception: enforce;
80     import std.conv: text;
81 
82     Unqual!T ret;
83 
84     static if(__traits(compiles, ret.length = 1)) {
85         const valueLength = {
86             if(pyListCheck(value))
87                 return PyList_Size(value);
88             else if(pyTupleCheck(value))
89                 return PyTuple_Size(value);
90             else
91                 assert(0);
92         }();
93         assert(valueLength >= 0, text("Invalid length ", valueLength));
94         ret.length = valueLength;
95     }
96 
97     foreach(i, ref elt; ret) {
98         auto pythonItem = {
99             if(pyListCheck(value))
100                 return PyList_GetItem(value, i);
101             else if(pyTupleCheck(value))
102                 return PyTuple_GetItem(value, i);
103             else
104                 assert(0);
105         }();
106         elt = pythonItem.to!(ElementEncodingType!T);
107     }
108 
109     return ret;
110 }
111 
112 
113 T to(T)(PyObject* value) if(isSomeString!T) {
114     import python.raw: pyUnicodeGetSize, pyUnicodeCheck,
115         pyBytesAsString, pyObjectUnicode, pyUnicodeAsUtf8String, Py_ssize_t;
116     import std.range: ElementEncodingType;
117     import std.conv: to;
118 
119     value = pyObjectUnicode(value);
120 
121     const length = pyUnicodeGetSize(value);
122 
123     auto ptr = pyBytesAsString(pyUnicodeAsUtf8String(value));
124     assert(length == 0 || ptr !is null);
125 
126     auto slice = ptr[0 .. length];
127 
128     return slice.to!T;
129 }
130 
131 
132 T to(T)(PyObject* value) if(is(Unqual!T == bool)) {
133     import python.raw: pyTrue;
134     return value is pyTrue;
135 }
136 
137 
138 T to(T)(PyObject* value) if(isAssociativeArray!T)
139 {
140     import python.raw: pyDictCheck, PyDict_Keys, PyList_Size, PyList_GetItem, PyDict_GetItem;
141 
142     assert(pyDictCheck(value));
143 
144     // this enum is to get K and V whilst avoiding auto-decoding, which is why we're not using
145     // std.traits
146     enum _ = is(T == V[K], V, K);
147     alias KeyType = Unqual!K;
148     alias ValueType = Unqual!V;
149 
150     ValueType[KeyType] ret;
151 
152     auto keys = PyDict_Keys(value);
153 
154     foreach(i; 0 .. PyList_Size(keys)) {
155         auto k = PyList_GetItem(keys, i);
156         auto v = PyDict_GetItem(value, k);
157         auto dk = k.to!KeyType;
158         auto dv = v.to!ValueType;
159 
160         ret[dk] = dv;
161     }
162 
163     return ret;
164 }
165 
166 
167 T to(T)(PyObject* value) if(isTuple!T)
168     in(pyTupleCheck(value))
169     in(PyTuple_Size(value) == T.length)
170     do
171 {
172     import python.raw: pyTupleCheck, PyTuple_Size, PyTuple_GetItem;
173 
174     T ret;
175 
176     static foreach(i; 0 .. T.length) {
177         ret[i] = PyTuple_GetItem(value, i).to!(typeof(ret[i]));
178     }
179 
180     return ret;
181 }
182 
183 
184 T to(T)(PyObject* value) if(isSomeChar!T) {
185     auto str = value.to!string;
186     return str[0];
187 }
188 
189 
190 T to(T)(PyObject* value) if(isSomeFunction!T) {
191     return typeof(return).init;  // FIXME
192 }