1 /** 2 Wrapping functionality for D to Python. 3 */ 4 module autowrap.pynih.wrap; 5 6 7 public import std.typecons: Yes, No; 8 public import autowrap.reflection: Modules, Module; 9 static import python.boilerplate; 10 import python.raw: isPython2, isPython3; 11 12 13 /** 14 The name of the dynamic library, i.e. the file name with the .so/.dll extension 15 */ 16 struct LibraryName { 17 string value; 18 } 19 20 /** 21 Code to be inserted before the call to module_init 22 */ 23 struct PreModuleInitCode { 24 string value; 25 } 26 27 /** 28 Code to be inserted after the call to module_init 29 */ 30 struct PostModuleInitCode { 31 string value; 32 } 33 34 35 /** 36 Returns a string to mixin that implements the necessary boilerplate 37 to create a Python library containing one Python module 38 wrapping all relevant D code and data structures. 39 */ 40 string wrapDlang( 41 LibraryName libraryName, 42 Modules modules, 43 PreModuleInitCode preModuleInitCode = PreModuleInitCode(), 44 PostModuleInitCode postModuleInitCode = PostModuleInitCode()) 45 () 46 { 47 return !__ctfe 48 ? null 49 : createPythonModuleMixin!(libraryName, modules); 50 } 51 52 53 string createPythonModuleMixin(LibraryName libraryName, Modules modules) 54 () 55 { 56 import autowrap.reflection: AllAggregates, AllFunctions; 57 import std.format: format; 58 import std.algorithm: map; 59 import std.array: join; 60 61 if(!__ctfe) return null; 62 63 alias aggregates = AllAggregates!modules; 64 alias functions = AllFunctions!modules; 65 66 return q{ 67 import python: ModuleInitRet; 68 import python.raw: PyDateTime_CAPI; 69 import python.type: PythonFunction; 70 71 // This is declared as an extern C variable in python.bindings. 72 // We declare it here to avoid linker errors. 73 export __gshared extern(C) PyDateTime_CAPI* PyDateTimeAPI; 74 75 extern(C) export ModuleInitRet %s%s() { 76 import python.boilerplate: Module, CFunctions, CFunction, Aggregates; 77 import python.type: PythonFunction; 78 import autowrap.pynih.wrap: createPythonModule; 79 %s 80 %s 81 82 mixin createPythonModule!( 83 Module("%s"), 84 CFunctions!(%s), 85 Aggregates!(%s), 86 ); 87 return _py_init_impl; 88 } 89 }.format( 90 pyInitFuncName, // init function name 91 libraryName.value, // after init 92 `import ` ~ modules.value.map!(a => a.name).join(", ") ~ `;`, // import all modules 93 aggregateModuleImports!aggregates, 94 libraryName.value, // Module 95 functionNames!functions, 96 aggregateNames!aggregates, 97 ); 98 } 99 100 private string pyInitFuncName() @safe pure nothrow { 101 static if(isPython2) 102 return "init"; 103 else static if(isPython3) 104 return "PyInit_"; 105 else 106 static assert(false); 107 } 108 109 110 private string aggregateModuleImports(aggregates...)() { 111 import std.meta: staticMap, NoDuplicates; 112 import std.array: join; 113 import std.traits: moduleName; 114 115 alias aggModules = NoDuplicates!(staticMap!(moduleName, aggregates)); 116 117 string[] ret; 118 static foreach(name; aggModules) { 119 ret ~= name; 120 } 121 122 return `import ` ~ ret.join(", ") ~ `;`; 123 } 124 125 private string aggregateNames(aggregates...)() { 126 import std.meta: staticMap; 127 import std.array: join; 128 import std.traits: fullyQualifiedName; 129 130 enum Name(T) = fullyQualifiedName!T; 131 alias names = staticMap!(Name, aggregates); 132 133 string[] ret; 134 static foreach(name; names) { 135 ret ~= name; 136 } 137 138 return ret.join(", "); 139 } 140 141 private string functionNames(functions...)() { 142 import std.meta: staticMap; 143 import std.array: join; 144 import std.traits: fullyQualifiedName, moduleName; 145 146 enum FQN(alias functionSymbol) = fullyQualifiedName!(functionSymbol.symbol); 147 enum ImplName(alias functionSymbol) = 148 `CFunction!(PythonFunction!(` ~ FQN!functionSymbol ~ `)._py_function_impl, "` ~ 149 functionSymbol.name.toSnakeCase ~ 150 `")`; 151 alias names = staticMap!(ImplName, functions); 152 153 string[] ret; 154 static foreach(name; names) { 155 ret ~= name; 156 } 157 158 return ret.join(", "); 159 } 160 161 162 mixin template createPythonModule(python.boilerplate.Module module_, alias cfunctions, alias aggregates) 163 if(isPython3) 164 { 165 import python: ModuleInitRet; 166 import std.format: format; 167 168 static extern(C) export ModuleInitRet _py_init_impl() { 169 import python.raw: pyDateTimeImport; 170 import python.cooked: createModule; 171 import python.boilerplate: Module, CFunctions, Aggregates; 172 import core.runtime: rt_init; 173 174 rt_init; 175 176 pyDateTimeImport; 177 return createModule!(module_, cfunctions, aggregates); 178 } 179 } 180 181 182 mixin template createPythonModule(python.boilerplate.Module module_, alias cfunctions, alias aggregates) 183 if(isPython2) 184 { 185 import python: ModuleInitRet; 186 import std.format: format; 187 188 static extern(C) export void _py_init_impl() { 189 import python.raw: pyDateTimeImport; 190 import python.cooked: initModule; 191 import python.boilerplate: Module, CFunctions, Aggregates; 192 import core.runtime: rt_init; 193 194 rt_init; 195 196 pyDateTimeImport; 197 initModule!(module_, cfunctions, aggregates); 198 } 199 } 200 201 202 // FIXME - put into common subpackage 203 string toSnakeCase(in string str) @safe pure { 204 205 import std.algorithm: all, map; 206 import std.ascii: isUpper; 207 208 if(str.all!isUpper) return str; 209 210 string ret; 211 212 string convert(in size_t index, in char c) { 213 import std.ascii: isLower, toLower; 214 215 const prefix = index == 0 ? "" : "_"; 216 const isHump = 217 (index == 0 && c.isUpper) || 218 (index > 0 && c.isUpper && str[index - 1].isLower); 219 220 return isHump ? prefix ~ c.toLower : "" ~ c; 221 } 222 223 foreach(i, c; str) { 224 ret ~= convert(i, c); 225 } 226 227 return ret; 228 }