1 /**
2    Functions to wrap entities in D modules for Python consumption.
3 
4    These functions are usually not called directly, but from the mixin generated by
5    autowrap.python.boilerplate.pydBoilerplate.
6  */
7 module autowrap.python.wrap;
8 
9 import autowrap.reflection: isUserAggregate, isModule;
10 import std.meta: allSatisfy;
11 import std.traits: isArray;
12 
13 
14 private alias I(alias T) = T;
15 private enum isString(alias T) = is(typeof(T) == string);
16 
17 ///  Wrap global functions from multiple modules
18 void wrapAllFunctions(Modules...)() if(allSatisfy!(isModule, Modules)) {
19     import autowrap.reflection: AllFunctions;
20     import pyd.pyd: def, PyName;
21 
22     static foreach(function_; AllFunctions!Modules)
23     {
24         static if(__traits(compiles,def!(function_.symbol,PyName!(toSnakeCase(function_.name)))()))
25 		def!(function_.symbol, PyName!(toSnakeCase(function_.name)))();
26 	else
27 		pragma(msg,"- " ~ function_.name);
28     }
29 }
30 
31 
32 /// Converts an identifier from camelCase or PascalCase to snake_case.
33 string toSnakeCase(in string str) @safe pure {
34 
35     import std.algorithm: all, map;
36     import std.ascii: isUpper;
37 
38     if(str.all!isUpper) return str;
39 
40     string ret;
41 
42     string convert(in size_t index, in char c) {
43         import std.ascii: isLower, toLower;
44 
45         const prefix = index == 0 ? "" : "_";
46         const isHump =
47             (index == 0 && c.isUpper) ||
48             (index > 0 && c.isUpper && str[index - 1].isLower);
49 
50         return isHump ? prefix ~ c.toLower : "" ~ c;
51     }
52 
53     foreach(i, c; str) {
54         ret ~= convert(i, c);
55     }
56 
57     return ret;
58 }
59 
60 
61 @("toSnakeCase empty")
62 @safe pure unittest {
63     static assert("".toSnakeCase == "");
64 }
65 
66 @("toSnakeCase no caps")
67 @safe pure unittest {
68     static assert("foo".toSnakeCase == "foo");
69 }
70 
71 @("toSnakeCase camelCase")
72 @safe pure unittest {
73     static assert("toSnakeCase".toSnakeCase == "to_snake_case");
74 }
75 
76 @("toSnakeCase PascalCase")
77 @safe pure unittest {
78     static assert("PascalCase".toSnakeCase == "pascal_case");
79 }
80 
81 @("toSnakeCase ALLCAPS")
82 @safe pure unittest {
83     static assert("ALLCAPS".toSnakeCase == "ALLCAPS");
84 }
85 
86 
87 /**
88    wrap all aggregates found in the given modules, specified by their name
89    (to avoid importing all of them first).
90 
91    This function wraps all struct and class definitions, and also all struct and class
92    types that are parameters or return types of any functions found.
93  */
94 void wrapAllAggregates(Modules...)() if(allSatisfy!(isModule, Modules)) {
95 
96     import autowrap.reflection: AllAggregates, Module;
97     import std.meta: staticMap;
98     import std.traits: fullyQualifiedName;
99 
100     static foreach(aggregate; AllAggregates!Modules) {
101         static if(__traits(compiles, wrapAggregate!aggregate))
102             wrapAggregate!aggregate;
103         else {
104             pragma(msg, "\nERROR! Autowrap could not wrap aggregate `", fullyQualifiedName!aggregate, "` for Python");
105             //wrapAggregate!aggregate; // uncomment to see the error messages from the compiler
106         }
107     }
108 }
109 
110 /**
111    Wrap aggregate of type T.
112  */
113 auto wrapAggregate(T)() if(isUserAggregate!T) {
114     import autowrap.reflection: Symbol;
115     import autowrap.python.pyd.class_wrap: MemberFunction,validMemberFunction;
116     import pyd.pyd: wrap_class, Member, Init;
117     import std.meta: staticMap, Filter, AliasSeq;
118     import std.traits: Parameters, FieldNameTuple, hasMember,isCopyable;
119     import std.typecons: Tuple;
120 
121     alias AggMember(string memberName) = Symbol!(T, memberName);
122     alias members = staticMap!(AggMember, __traits(allMembers, T));
123 
124     alias memberFunctions = Filter!(isMemberFunction, members);
125 
126     static if(hasMember!(T, "__ctor"))
127         alias constructors = AliasSeq!(__traits(getOverloads, T, "__ctor"));
128     else
129         alias constructors = AliasSeq!();
130 
131     // If we staticMap with std.traits.Parameters, we end up with a collapsed tuple
132     // i.e. with one constructor that takes int and another that takes int, string,
133     // we'd end up with 3 elements (int, int, string) instead of 2 ((int), (int, string))
134     // so we package them up in a std.typecons.Tuple to avoid flattening
135     // each being an AliasSeq of types for the constructor
136     alias ParametersTuple(alias F) = Tuple!(Parameters!F);
137 
138     // A tuple, with as many elements as constructors. Each element is a
139     // std.typecons.Tuple of the constructor parameter types.
140     alias constructorParamTuples = staticMap!(ParametersTuple, constructors);
141 
142     // Apply pyd's Init to the unpacked types of the parameter Tuple.
143     alias InitTuple(alias Tuple) = Init!(Tuple.Types);
144 
145     enum isPublic(string fieldName) =isPublicSymbol!(T,fieldName);
146     alias publicFields = Filter!(isPublic, FieldNameTuple!T);
147 
148     template validF(A...)
149     {
150 	    import std.string:startsWith;
151 	    enum notOpAssign = !(__traits(identifier,A[0]).startsWith("opAssign"));
152 	   static if (!isCopyable!T)
153 		   enum validF= validMemberFunction!(T,A);
154 		   //enum validF= notOpAssign && validMemberFunction!(T,A);
155 	   else
156 		   enum validF= validMemberFunction!(T,A);
157     }
158     alias validMemberFunctions = Filter!(validF,memberFunctions);
159     wrap_class!(
160         T,
161         staticMap!(Member, publicFields),
162         staticMap!(MemberFunction,validMemberFunctions),
163         staticMap!(InitTuple, constructorParamTuples),
164    );
165 }
166 
167 
168 
169 // must be a global template
170 private template isMemberFunction(A...) if(A.length == 1) {
171     alias T = A[0];
172     static if (__traits(compiles,isPublicFunction!T && !isInternalField!T))
173 	    enum isMemberFunction = isPublicFunction!T && !isInternalField!T;
174     else
175 	    enum isMemberFunction = false;
176 }
177 
178 private template isInternalFieldHelper(string fieldName)
179 {
180 	import std.string:startsWith;
181 	import std.algorithm:any;
182 	import std.meta:AliasSeq;
183 	enum isInternalFieldHelper = any!(field=>fieldName.startsWith(field))(["__ctor","__field","__aggr","__postblit","__dtor"]);
184 }
185 
186 private template isInternalField(alias T)
187 {
188 	import std.string:startsWith;
189 	import std.algorithm:any;
190 	import std.meta:AliasSeq;
191 	static if(__traits(compiles, __traits(identifier, T)))
192 	{
193 		enum field = __traits(identifier,T);
194 		enum isInternalField = isInternalFieldHelper!field;
195 	}
196 	else
197 	{
198 		enum isInternalField = false;
199 	}
200 }
201 
202 
203 private template isPublicSymbol(alias T, string fieldName)
204 {
205 	static if (__traits(compiles,isPublicSymbol!( __traits(getMember, T, fieldName))))
206 		enum isPublicSymbol = isPublicSymbol!(__traits(getMember,T,fieldName));
207 	else
208 		enum isPublicSymbol = false;
209 }
210 
211 private template isPublicFunction(alias F) {
212     import std.traits: isFunction;
213     enum isPublicFunction = isPublicSymbol!F && isFunction!F;
214 }
215 
216 private template isPublicSymbol(alias S) {
217     static if (__traits(compiles,__traits(getProtection,S)))
218     {
219 	    enum prot = __traits(getProtection, S);
220 	    enum isPublicSymbol =  (prot == "export" || prot == "public");
221     }
222     else
223 	    emum isPublicSymbol = false;
224 }