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 }