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 def!(function_.symbol, PyName!(toSnakeCase(function_.name)))(); 24 } 25 26 27 /// Converts an identifier from camelCase or PascalCase to snake_case. 28 string toSnakeCase(in string str) @safe pure { 29 30 import std.algorithm: all, map; 31 import std.ascii: isUpper; 32 33 if(str.all!isUpper) return str; 34 35 string ret; 36 37 string convert(in size_t index, in char c) { 38 import std.ascii: isLower, toLower; 39 40 const prefix = index == 0 ? "" : "_"; 41 const isHump = 42 (index == 0 && c.isUpper) || 43 (index > 0 && c.isUpper && str[index - 1].isLower); 44 45 return isHump ? prefix ~ c.toLower : "" ~ c; 46 } 47 48 foreach(i, c; str) { 49 ret ~= convert(i, c); 50 } 51 52 return ret; 53 } 54 55 56 @("toSnakeCase empty") 57 @safe pure unittest { 58 static assert("".toSnakeCase == ""); 59 } 60 61 @("toSnakeCase no caps") 62 @safe pure unittest { 63 static assert("foo".toSnakeCase == "foo"); 64 } 65 66 @("toSnakeCase camelCase") 67 @safe pure unittest { 68 static assert("toSnakeCase".toSnakeCase == "to_snake_case"); 69 } 70 71 @("toSnakeCase PascalCase") 72 @safe pure unittest { 73 static assert("PascalCase".toSnakeCase == "pascal_case"); 74 } 75 76 @("toSnakeCase ALLCAPS") 77 @safe pure unittest { 78 static assert("ALLCAPS".toSnakeCase == "ALLCAPS"); 79 } 80 81 82 /** 83 wrap all aggregates found in the given modules, specified by their name 84 (to avoid importing all of them first). 85 86 This function wraps all struct and class definitions, and also all struct and class 87 types that are parameters or return types of any functions found. 88 */ 89 void wrapAllAggregates(Modules...)() if(allSatisfy!(isModule, Modules)) { 90 91 import autowrap.reflection: AllAggregates, Module; 92 import std.meta: staticMap; 93 import std.traits: fullyQualifiedName; 94 95 static foreach(aggregate; AllAggregates!Modules) { 96 static if(__traits(compiles, wrapAggregate!aggregate)) 97 wrapAggregate!aggregate; 98 else { 99 pragma(msg, "\nERROR! Autowrap could not wrap aggregate `", fullyQualifiedName!aggregate, "` for Python\n"); 100 //wrapAggregate!aggregate; // uncomment to see the error messages from the compiler 101 } 102 } 103 } 104 105 /** 106 Wrap aggregate of type T. 107 */ 108 auto wrapAggregate(T)() if(isUserAggregate!T) { 109 110 import autowrap.reflection: Symbol; 111 import autowrap.python.pyd.class_wrap: MemberFunction; 112 import pyd.pyd: wrap_class, Member, Init; 113 import std.meta: staticMap, Filter, AliasSeq; 114 import std.traits: Parameters, FieldNameTuple, hasMember; 115 import std.typecons: Tuple; 116 117 alias AggMember(string memberName) = Symbol!(T, memberName); 118 alias members = staticMap!(AggMember, __traits(allMembers, T)); 119 120 alias memberFunctions = Filter!(isMemberFunction, members); 121 122 static if(hasMember!(T, "__ctor")) 123 alias constructors = AliasSeq!(__traits(getOverloads, T, "__ctor")); 124 else 125 alias constructors = AliasSeq!(); 126 127 // If we staticMap with std.traits.Parameters, we end up with a collapsed tuple 128 // i.e. with one constructor that takes int and another that takes int, string, 129 // we'd end up with 3 elements (int, int, string) instead of 2 ((int), (int, string)) 130 // so we package them up in a std.typecons.Tuple to avoid flattening 131 // each being an AliasSeq of types for the constructor 132 alias ParametersTuple(alias F) = Tuple!(Parameters!F); 133 134 // A tuple, with as many elements as constructors. Each element is a 135 // std.typecons.Tuple of the constructor parameter types. 136 alias constructorParamTuples = staticMap!(ParametersTuple, constructors); 137 138 // Apply pyd's Init to the unpacked types of the parameter Tuple. 139 alias InitTuple(alias Tuple) = Init!(Tuple.Types); 140 141 enum isPublic(string fieldName) = __traits(getProtection, __traits(getMember, T, fieldName)) == "public"; 142 alias publicFields = Filter!(isPublic, FieldNameTuple!T); 143 144 wrap_class!( 145 T, 146 staticMap!(Member, publicFields), 147 staticMap!(MemberFunction, memberFunctions), 148 staticMap!(InitTuple, constructorParamTuples), 149 ); 150 } 151 152 153 // must be a global template 154 private template isMemberFunction(A...) if(A.length == 1) { 155 alias T = A[0]; 156 static if(__traits(compiles, __traits(identifier, T))) 157 enum isMemberFunction = isPublicFunction!T && __traits(identifier, T) != "__ctor"; 158 else 159 enum isMemberFunction = false; 160 } 161 162 163 private template isPublicFunction(alias F) { 164 import std.traits: isFunction; 165 enum prot = __traits(getProtection, F); 166 enum isPublicFunction = isFunction!F && (prot == "export" || prot == "public"); 167 }