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 }