1 /*
2  * python-loader.c: Support for Python plugins.
3  *
4  * Author: Zbigniew Chyla (cyba@gnome.pl)
5  */
6 
7 #include <gnumeric-config.h>
8 #include <gnumeric.h>
9 #include "python-loader.h"
10 
11 #include "py-gnumeric.h"
12 #include "gnm-python.h"
13 
14 #include <gnm-plugin.h>
15 #include <application.h>
16 #include <workbook.h>
17 #include <sheet.h>
18 #include <workbook-view.h>
19 #include <wbc-gtk.h>
20 #include <gui-util.h>
21 #include <value.h>
22 #include <expr.h>
23 #include <expr-impl.h>
24 #include <func.h>
25 
26 #include <goffice/goffice.h>
27 #include <goffice/app/module-plugin-defs.h>
28 
29 #include <gsf/gsf-impl-utils.h>
30 
31 #include <glib/gi18n-lib.h>
32 
33 #include <glib/gstdio.h>
34 
35 #include <stdlib.h>
36 
37 #include <Python.h>
38 #include <pygobject.h>
39 
40 #define SERVICE_KEY "python-loader::service"
41 
42 typedef struct {
43 	GObject base;
44 
45 	gchar *module_name;
46 
47 	GnmPython *py_object;
48 	GnmPyInterpreter *py_interpreter_info;
49 	PyObject *main_module;
50 	PyObject *main_module_dict;
51 } GnmPythonPluginLoader;
52 typedef GObjectClass GnmPythonPluginLoaderClass;
53 
54 #define PLUGIN_GET_LOADER(plugin) \
55 	GNM_PYTHON_PLUGIN_LOADER (g_object_get_data (G_OBJECT (plugin), "python-loader"))
56 #define SERVICE_GET_LOADER(service) \
57 	PLUGIN_GET_LOADER (go_plugin_service_get_plugin (service))
58 #define SWITCH_TO_PLUGIN(plugin) \
59 	gnm_py_interpreter_switch_to (PLUGIN_GET_LOADER (plugin)->py_interpreter_info)
60 
61 static void
gplp_set_attributes(GOPluginLoader * loader,GHashTable * attrs,GOErrorInfo ** ret_error)62 gplp_set_attributes (GOPluginLoader *loader, GHashTable *attrs, GOErrorInfo **ret_error)
63 {
64 	GnmPythonPluginLoader *loader_python = GNM_PYTHON_PLUGIN_LOADER (loader);
65 	gchar *module_name = NULL;
66 
67 	GO_INIT_RET_ERROR_INFO (ret_error);
68 	module_name = g_hash_table_lookup (attrs, "module_name");
69 	if (module_name != NULL) {
70 		loader_python->module_name = g_strdup (module_name);
71 	} else {
72 		*ret_error = go_error_info_new_str (
73 		             _("Python module name not given."));
74 	}
75 }
76 
77 static FILE *
gnumeric_fopen_error_info(const char * file_name,const char * mode,GOErrorInfo ** ret_error)78 gnumeric_fopen_error_info (const char *file_name, const char *mode, GOErrorInfo **ret_error)
79 {
80 	FILE *f;
81 
82 	g_return_val_if_fail (file_name != NULL, NULL);
83 	g_return_val_if_fail (mode != NULL, NULL);
84 	g_return_val_if_fail (ret_error != NULL, NULL);
85 
86 	*ret_error = NULL;
87 	f = g_fopen (file_name, mode);
88 	if (f == NULL) {
89 		if (strchr (mode, 'w') != NULL && strchr (mode, 'r') == NULL) {
90 			*ret_error = go_error_info_new_printf (
91 			             _("Error while opening file \"%s\" for writing."),
92 			             file_name);
93 		} else {
94 			*ret_error = go_error_info_new_printf (
95 			             _("Error while opening file \"%s\" for reading."),
96 			             file_name);
97 		}
98 		go_error_info_add_details (*ret_error, go_error_info_new_from_errno ());
99 	}
100 
101 	return f;
102 }
103 static void
gplp_load_base(GOPluginLoader * loader,GOErrorInfo ** ret_error)104 gplp_load_base (GOPluginLoader *loader, GOErrorInfo **ret_error)
105 {
106 	static gchar const *python_file_extensions[] = {"py", "pyc", "pyo", NULL};
107 
108 	GnmPythonPluginLoader *loader_python = GNM_PYTHON_PLUGIN_LOADER (loader);
109 	gchar const **file_ext;
110 	GnmPython *py_object;
111 	GnmPyInterpreter *py_interpreter_info;
112 	gchar *full_module_file_name = NULL;
113 	FILE *f;
114 	GOPlugin *plugin = go_plugin_loader_get_plugin (loader);
115 	GOErrorInfo *open_error = NULL;
116 	PyObject *modules, *main_module, *main_module_dict;
117 
118 	GO_INIT_RET_ERROR_INFO (ret_error);
119 	g_object_set_data (G_OBJECT (plugin), "python-loader", loader);
120 
121 	py_object = gnm_python_object_get (ret_error);
122 	if (py_object == NULL)
123 		return;		/* gnm_python_object_get sets ret_error */
124 	py_interpreter_info = gnm_python_new_interpreter (py_object, plugin);
125 	if (py_interpreter_info == NULL) {
126 		*ret_error = go_error_info_new_str (_("Cannot create new Python interpreter."));
127 		gnm_python_clear_error_if_needed (py_object);
128 		g_object_unref (py_object);
129 		return;
130 	}
131 
132 	for (file_ext = python_file_extensions; *file_ext != NULL; file_ext++) {
133 		gchar *file_name = g_strconcat (
134 			loader_python->module_name, ".", *file_ext, NULL);
135 		gchar *path = g_build_filename (
136 			go_plugin_get_dir_name (plugin),
137 			file_name, NULL);
138 		g_free (file_name);
139 		if (g_file_test (path, G_FILE_TEST_EXISTS)) {
140 			full_module_file_name = path;
141 			break;
142 		} else
143 			g_free (path);
144 	}
145 	if (full_module_file_name == NULL) {
146 		*ret_error = go_error_info_new_printf (
147 		             _("Module \"%s\" doesn't exist."),
148 		             loader_python->module_name);
149 		gnm_python_destroy_interpreter (py_object, py_interpreter_info);
150 		g_object_unref (py_object);
151 		return;
152 	}
153 	f = gnumeric_fopen_error_info (full_module_file_name, "r", &open_error);
154 	g_free (full_module_file_name);
155 	if (f == NULL) {
156 		*ret_error = open_error;
157 		gnm_python_destroy_interpreter (py_object, py_interpreter_info);
158 		g_object_unref (py_object);
159 		return;
160 	}
161 
162 	if (PyRun_SimpleFile (f, loader_python->module_name) != 0) {
163 		(void) fclose (f);
164 		*ret_error = go_error_info_new_printf (
165 		             _("Execution of module \"%s\" failed."),
166 		             loader_python->module_name);
167 		gnm_python_destroy_interpreter (py_object, py_interpreter_info);
168 		g_object_unref (py_object);
169 		return;
170 	}
171 	(void) fclose (f);
172 
173 	modules = PyImport_GetModuleDict ();
174 	g_return_if_fail (modules != NULL);
175 	main_module = PyDict_GetItemString (modules, "__main__");
176 	g_return_if_fail (main_module != NULL);
177 	main_module_dict = PyModule_GetDict (main_module);
178 	g_return_if_fail (main_module_dict != NULL);
179 	loader_python->py_object = py_object;
180 	loader_python->py_interpreter_info = py_interpreter_info;
181 	loader_python->main_module = main_module;
182 	loader_python->main_module_dict = main_module_dict;
183 }
184 
185 static void
gplp_unload_base(GOPluginLoader * loader,GOErrorInfo ** ret_error)186 gplp_unload_base (GOPluginLoader *loader, GOErrorInfo **ret_error)
187 {
188 	GnmPythonPluginLoader *loader_python = GNM_PYTHON_PLUGIN_LOADER (loader);
189 	GOPlugin *plugin = go_plugin_loader_get_plugin (loader);
190 
191 	GO_INIT_RET_ERROR_INFO (ret_error);
192 	g_object_steal_data (G_OBJECT (plugin), "python-loader");
193 	gnm_python_destroy_interpreter (
194 		loader_python->py_object, loader_python->py_interpreter_info);
195 	g_object_unref (loader_python->py_object);
196 }
197 
198 /*
199  * Service - file_opener
200  */
201 
202 typedef struct {
203 	PyObject *python_func_file_probe;
204 	PyObject *python_func_file_open;
205 } ServiceLoaderDataFileOpener;
206 
207 static void
gplp_loader_data_opener_free(ServiceLoaderDataFileOpener * loader_data)208 gplp_loader_data_opener_free (ServiceLoaderDataFileOpener *loader_data)
209 {
210 	Py_DECREF (loader_data->python_func_file_probe);
211 	Py_DECREF (loader_data->python_func_file_open);
212 	g_free (loader_data);
213 }
214 
215 static gboolean
gplp_func_file_probe(G_GNUC_UNUSED GOFileOpener const * fo,GOPluginService * service,GsfInput * input,G_GNUC_UNUSED GOFileProbeLevel pl)216 gplp_func_file_probe (G_GNUC_UNUSED GOFileOpener const *fo, GOPluginService *service,
217 		      GsfInput *input, G_GNUC_UNUSED GOFileProbeLevel pl)
218 {
219 	ServiceLoaderDataFileOpener *loader_data;
220 	PyObject *probe_result = NULL;
221 	PyObject *input_wrapper;
222 	gboolean result;
223 
224 	g_return_val_if_fail (GO_IS_PLUGIN_SERVICE_FILE_OPENER (service), FALSE);
225 	g_return_val_if_fail (input != NULL, FALSE);
226 	if (_PyGObject_API == NULL) {
227 		pygobject_init (3, 0, 0);
228 		g_return_val_if_fail (_PyGObject_API != NULL, FALSE);
229 	}
230 
231 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
232 	SWITCH_TO_PLUGIN (go_plugin_service_get_plugin (service));
233 	input_wrapper = pygobject_new (G_OBJECT (input));
234 	if (input_wrapper == NULL) {
235 		g_warning ("%s", py_exc_to_string ());
236 		gnm_python_clear_error_if_needed (SERVICE_GET_LOADER (service)->py_object);
237 	}
238 	if (input_wrapper != NULL &&
239 	    loader_data->python_func_file_probe != NULL) {
240 		/* wrapping adds a reference */
241 		g_object_unref (input);
242 		probe_result = PyObject_CallFunction
243 			(loader_data->python_func_file_probe,
244 			 "O", input_wrapper);
245 	}
246 	Py_XDECREF (input_wrapper);
247 	if (probe_result != NULL) {
248 		result = PyObject_IsTrue (probe_result);
249 		Py_DECREF (probe_result);
250 	} else {
251 		PyErr_Clear ();
252 		result = FALSE;
253 	}
254 
255 	return result;
256 }
257 
258 static void
gplp_func_file_open(G_GNUC_UNUSED GOFileOpener const * fo,GOPluginService * service,GOIOContext * io_context,gpointer wb_view,GsfInput * input,G_GNUC_UNUSED char const * enc)259 gplp_func_file_open (G_GNUC_UNUSED GOFileOpener const *fo,
260 		     GOPluginService *service,
261 		     GOIOContext *io_context,
262 		     gpointer wb_view,
263 		     GsfInput *input, G_GNUC_UNUSED char const *enc)
264 {
265 	ServiceLoaderDataFileOpener *loader_data;
266 	Sheet *sheet, *old_sheet;
267 	PyObject *open_result = NULL;
268 	PyObject *input_wrapper;
269 
270 	g_return_if_fail (GO_IS_PLUGIN_SERVICE_FILE_OPENER (service));
271 	g_return_if_fail (input != NULL);
272 	if (_PyGObject_API == NULL) {
273 		pygobject_init (3, 0, 0);
274 		g_return_if_fail (_PyGObject_API != NULL);
275 	}
276 
277 	old_sheet = wb_view_cur_sheet (wb_view);
278 
279 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
280 	SWITCH_TO_PLUGIN (go_plugin_service_get_plugin (service));
281 	sheet = sheet_new (wb_view_get_workbook (wb_view), _("Some name"),
282 			   gnm_sheet_get_max_cols (old_sheet),
283 			   gnm_sheet_get_max_rows (old_sheet));
284 	input_wrapper = pygobject_new (G_OBJECT (input));
285 	if (input_wrapper != NULL) {
286 		 /* wrapping adds a reference */
287 		g_object_unref (input);
288 		open_result = PyObject_CallFunction
289 			(loader_data->python_func_file_open,
290 			 "NO",
291 			 pygobject_new (G_OBJECT (sheet)), input_wrapper);
292 		Py_DECREF (input_wrapper);
293 	}
294 	if (open_result != NULL) {
295 		Py_DECREF (open_result);
296 		workbook_sheet_attach (wb_view_get_workbook (wb_view), sheet);
297 	} else {
298 		go_io_error_string (io_context, py_exc_to_string ());
299 		gnm_python_clear_error_if_needed (SERVICE_GET_LOADER (service)->py_object);
300 		g_object_unref (sheet);
301 	}
302 }
303 
304 static void
gplp_load_service_file_opener(GOPluginLoader * loader,GOPluginService * service,GOErrorInfo ** ret_error)305 gplp_load_service_file_opener (GOPluginLoader *loader,
306 			       GOPluginService *service,
307 			       GOErrorInfo **ret_error)
308 {
309 	GnmPythonPluginLoader *loader_python = GNM_PYTHON_PLUGIN_LOADER (loader);
310 	gchar *func_name_file_probe, *func_name_file_open;
311 	PyObject *python_func_file_probe, *python_func_file_open;
312 
313 	g_return_if_fail (GO_IS_PLUGIN_SERVICE_FILE_OPENER (service));
314 
315 	GO_INIT_RET_ERROR_INFO (ret_error);
316 	gnm_py_interpreter_switch_to (loader_python->py_interpreter_info);
317 	func_name_file_probe = g_strconcat (
318 		go_plugin_service_get_id (service), "_file_probe", NULL);
319 	python_func_file_probe = PyDict_GetItemString (loader_python->main_module_dict,
320 	                                               func_name_file_probe);
321 	gnm_python_clear_error_if_needed (loader_python->py_object);
322 	func_name_file_open = g_strconcat (
323 		go_plugin_service_get_id (service), "_file_open", NULL);
324 	python_func_file_open = PyDict_GetItemString (loader_python->main_module_dict,
325 	                                              func_name_file_open);
326 	gnm_python_clear_error_if_needed (loader_python->py_object);
327 	if (python_func_file_open != NULL) {
328 		GOPluginServiceFileOpenerCallbacks *cbs;
329 		ServiceLoaderDataFileOpener *loader_data;
330 
331 		cbs = go_plugin_service_get_cbs (service);
332 		cbs->plugin_func_file_probe = gplp_func_file_probe;
333 		cbs->plugin_func_file_open = (gpointer) gplp_func_file_open;
334 
335 		loader_data = g_new (ServiceLoaderDataFileOpener, 1);
336 		loader_data->python_func_file_probe = python_func_file_probe;
337 		loader_data->python_func_file_open = python_func_file_open;
338 		Py_XINCREF (loader_data->python_func_file_probe);
339 		Py_INCREF (loader_data->python_func_file_open);
340 		g_object_set_data_full
341 			(G_OBJECT (service), "loader_data", loader_data,
342 			 (GDestroyNotify) gplp_loader_data_opener_free);
343 	} else {
344 		*ret_error = go_error_info_new_printf (
345 		             _("Python file \"%s\" has invalid format."),
346 		             loader_python->module_name);
347 		go_error_info_add_details (*ret_error,
348 		                        go_error_info_new_printf (
349 		                        _("File doesn't contain \"%s\" function."),
350 		                        func_name_file_open));
351 	}
352 	g_free (func_name_file_probe);
353 	g_free (func_name_file_open);
354 }
355 
356 /*
357  * Service - file_saver
358  */
359 
360 typedef struct {
361 	PyObject *python_func_file_save;
362 } ServiceLoaderDataFileSaver;
363 
364 static void
gplp_loader_data_saver_free(ServiceLoaderDataFileSaver * loader_data)365 gplp_loader_data_saver_free (ServiceLoaderDataFileSaver *loader_data)
366 {
367 	Py_DECREF (loader_data->python_func_file_save);
368 	g_free (loader_data);
369 }
370 
371 static void
gplp_func_file_save(G_GNUC_UNUSED GOFileSaver const * fs,GOPluginService * service,GOIOContext * io_context,gconstpointer wb_view,GsfOutput * output)372 gplp_func_file_save (G_GNUC_UNUSED GOFileSaver const *fs, GOPluginService *service,
373 		     GOIOContext *io_context, gconstpointer wb_view,
374 		     GsfOutput *output)
375 {
376 	ServiceLoaderDataFileSaver *saver_data;
377 	PyObject *py_workbook;
378 	PyObject *save_result = NULL;
379 	PyObject *output_wrapper;
380 
381 	g_return_if_fail (GO_IS_PLUGIN_SERVICE_FILE_SAVER (service));
382 	g_return_if_fail (output != NULL);
383 	if (_PyGObject_API == NULL) {
384 		pygobject_init (3, 0, 0);
385 		g_return_if_fail (_PyGObject_API != NULL);
386 	}
387 	g_return_if_fail (_PyGObject_API != NULL);
388 
389 	saver_data = g_object_get_data (G_OBJECT (service), "loader_data");
390 	SWITCH_TO_PLUGIN (go_plugin_service_get_plugin (service));
391 	py_workbook = pygobject_new (G_OBJECT (wb_view_get_workbook (wb_view)));
392 	output_wrapper = pygobject_new (G_OBJECT (output));
393 	if (output_wrapper != NULL) {
394 		/* wrapping adds a reference */
395 		g_object_unref (output);
396 		save_result = PyObject_CallFunction
397 			(saver_data->python_func_file_save,
398 			 "NO", py_workbook, output_wrapper);
399 		Py_DECREF (output_wrapper);
400 	}
401 	if (save_result != NULL) {
402 		Py_DECREF (save_result);
403 	} else {
404 		go_io_error_string (io_context, py_exc_to_string ());
405 		gnm_python_clear_error_if_needed (SERVICE_GET_LOADER (service)->py_object);
406 	}
407 }
408 
409 static void
gplp_load_service_file_saver(GOPluginLoader * loader,GOPluginService * service,GOErrorInfo ** ret_error)410 gplp_load_service_file_saver (GOPluginLoader *loader,
411 			      GOPluginService *service,
412 			      GOErrorInfo **ret_error)
413 {
414 	GnmPythonPluginLoader *loader_python = GNM_PYTHON_PLUGIN_LOADER (loader);
415 	gchar *func_name_file_save;
416 	PyObject *python_func_file_save;
417 
418 	g_return_if_fail (GO_IS_PLUGIN_SERVICE_FILE_SAVER (service));
419 
420 	GO_INIT_RET_ERROR_INFO (ret_error);
421 	gnm_py_interpreter_switch_to (loader_python->py_interpreter_info);
422 	func_name_file_save = g_strconcat (
423 		go_plugin_service_get_id (service), "_file_save", NULL);
424 	python_func_file_save = PyDict_GetItemString (loader_python->main_module_dict,
425 	                                              func_name_file_save);
426 	gnm_python_clear_error_if_needed (loader_python->py_object);
427 	if (python_func_file_save != NULL) {
428 		GOPluginServiceFileSaverCallbacks *cbs;
429 		ServiceLoaderDataFileSaver *saver_data;
430 
431 		cbs = go_plugin_service_get_cbs (service);
432 		cbs->plugin_func_file_save = (gpointer)gplp_func_file_save;
433 
434 		saver_data = g_new (ServiceLoaderDataFileSaver, 1);
435 		saver_data->python_func_file_save = python_func_file_save;
436 		Py_INCREF (saver_data->python_func_file_save);
437 		g_object_set_data_full
438 			(G_OBJECT (service), "loader_data", saver_data,
439 			 (GDestroyNotify) gplp_loader_data_saver_free);
440 	} else {
441 		*ret_error = go_error_info_new_printf (
442 		             _("Python file \"%s\" has invalid format."),
443 		             loader_python->module_name);
444 		if (python_func_file_save == NULL) {
445 			go_error_info_add_details (*ret_error,
446 			                        go_error_info_new_printf (
447 			                        _("File doesn't contain \"%s\" function."),
448 			                        func_name_file_save));
449 		}
450 	}
451 	g_free (func_name_file_save);
452 }
453 
454 /*
455  * Service - function_group
456  */
457 
458 typedef struct {
459 	PyObject *python_fn_info_dict;
460 } ServiceLoaderDataFunctionGroup;
461 
462 
463 static void
gplp_loader_data_fngroup_free(ServiceLoaderDataFunctionGroup * loader_data)464 gplp_loader_data_fngroup_free (ServiceLoaderDataFunctionGroup *loader_data)
465 {
466 	Py_DECREF (loader_data->python_fn_info_dict);
467 	g_free (loader_data);
468 }
469 
470 static GnmValue *
call_python_function_args(GnmFuncEvalInfo * ei,GnmValue const * const * args)471 call_python_function_args (GnmFuncEvalInfo *ei, GnmValue const * const *args)
472 {
473 	GOPluginService *service;
474 	ServiceLoaderDataFunctionGroup *loader_data;
475 	PyObject *fn_info_tuple;
476 	PyObject *python_fn;
477 	GnmFunc *fndef;
478 
479 	gint min_n_args, max_n_args, n_args;
480 
481 	g_return_val_if_fail (ei != NULL, NULL);
482 	g_return_val_if_fail (ei->func_call != NULL, NULL);
483 	g_return_val_if_fail (args != NULL, NULL);
484 
485 	fndef = ei->func_call->func;
486 	service = (GOPluginService *)g_object_get_data (G_OBJECT (fndef), SERVICE_KEY);
487 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
488 	SWITCH_TO_PLUGIN (go_plugin_service_get_plugin (service));
489 	fn_info_tuple = PyDict_GetItemString (loader_data->python_fn_info_dict,
490 	                                      gnm_func_get_name (fndef, FALSE));
491 	g_assert (fn_info_tuple != NULL && PyTuple_Check (fn_info_tuple));
492 	python_fn = PyTuple_GetItem (fn_info_tuple, 2);
493 	gnm_func_count_args (fndef, &min_n_args, &max_n_args);
494 	for (n_args = min_n_args; n_args < max_n_args && args[n_args] != NULL; n_args++) {
495 		;
496 	}
497 	return call_python_function (python_fn, ei->pos, n_args, args);
498 }
499 
500 static GnmValue *
call_python_function_nodes(GnmFuncEvalInfo * ei,int argc,GnmExprConstPtr const * argv)501 call_python_function_nodes (GnmFuncEvalInfo *ei,
502 			    int argc, GnmExprConstPtr const *argv)
503 {
504 	GOPluginService *service;
505 	ServiceLoaderDataFunctionGroup *loader_data;
506 	PyObject *python_fn;
507 	GnmFunc const * fndef;
508 	GnmValue **values;
509 	gint i;
510 	GnmValue *ret_value;
511 
512 	g_return_val_if_fail (ei != NULL, NULL);
513 	g_return_val_if_fail (ei->func_call != NULL, NULL);
514 
515 	fndef = ei->func_call->func;
516 	service = (GOPluginService *)g_object_get_data (G_OBJECT (fndef), SERVICE_KEY);
517 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
518 	SWITCH_TO_PLUGIN (go_plugin_service_get_plugin (service));
519 	python_fn = PyDict_GetItemString (loader_data->python_fn_info_dict,
520 	                                  gnm_func_get_name (fndef, FALSE));
521 
522 	values = g_new (GnmValue *, argc);
523 	for (i = 0; i < argc; i++) {
524 		values[i] = gnm_expr_eval (argv[i], ei->pos, GNM_EXPR_EVAL_PERMIT_NON_SCALAR);
525 	}
526 	ret_value = call_python_function (python_fn, ei->pos, argc,
527 					  (GnmValue const * const *)values);
528 	for (i = 0; i < argc; i++) {
529 		value_release (values[i]);
530 	}
531 	g_free (values);
532 
533 	return ret_value;
534 }
535 
FuncHelpDestructor(PyObject * object)536 static void FuncHelpDestructor (PyObject *object)
537 {
538 	g_free (PyCapsule_GetPointer (object, "FuncHelp"));
539 }
540 
541 static GnmFuncHelp const *
python_function_get_gnumeric_help(PyObject * python_fn_info_dict,PyObject * python_fn,const gchar * fn_name)542 python_function_get_gnumeric_help (PyObject *python_fn_info_dict, PyObject *python_fn,
543                                    const gchar *fn_name)
544 {
545 	gchar *help_attr_name;
546 	PyObject *cobject_help_value;
547 	PyObject *python_arg_names;
548 	PyObject *fn_info_obj;
549 	GnmFuncHelp const *res = NULL;
550 
551 	fn_info_obj = PyDict_GetItemString (python_fn_info_dict, fn_name);
552 	python_arg_names = PyTuple_Check (fn_info_obj)
553 		? PyTuple_GetItem (fn_info_obj, 1)
554 		: NULL;
555 
556 	help_attr_name = g_strdup_printf ("_CGnumericHelp_%s", fn_name);
557 	cobject_help_value = PyDict_GetItemString (python_fn_info_dict, help_attr_name);
558 	if (cobject_help_value == NULL) {
559 		PyObject *python_fn_help =
560 			PyFunction_Check (python_fn)
561 			? ((PyFunctionObject *) python_fn)->func_doc
562 			: NULL;
563 		if (python_fn_help != NULL && PyUnicode_Check (python_fn_help)) {
564 			guint n = 0;
565 			GnmFuncHelp *new_help = NULL;
566 			gboolean arg_names_written = FALSE;
567 			char const *help_text = PyUnicode_AsUTF8 (python_fn_help);
568 
569 			if (g_str_has_prefix (help_text, "@GNM_FUNC_HELP_NAME@")) {
570 				/* New-style documentation */
571 				gchar **items = g_strsplit (help_text, "\n", 0), **fitems = items;
572 				while (*items) {
573 					if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_NAME@")) {
574 						guint it = n;
575 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
576 						new_help[it].type = GNM_FUNC_HELP_NAME;
577 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_NAME@"));
578 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_ARG@")) {
579 						guint it = n;
580 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
581 						new_help[it].type = GNM_FUNC_HELP_ARG;
582 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_ARG@"));
583 						arg_names_written = TRUE;
584 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_DESCRIPTION@")) {
585 						guint it = n;
586 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
587 						new_help[it].type = GNM_FUNC_HELP_DESCRIPTION;
588 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_DESCRIPTION@"));
589 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_EXAMPLES@")) {
590 						guint it = n;
591 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
592 						new_help[it].type = GNM_FUNC_HELP_EXAMPLES;
593 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_EXAMPLES@"));
594 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_SEEALSO@")) {
595 						guint it = n;
596 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
597 						new_help[it].type = GNM_FUNC_HELP_SEEALSO;
598 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_SEEALSO@"));
599 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_EXTREF@")) {
600 						guint it = n;
601 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
602 						new_help[it].type = GNM_FUNC_HELP_EXTREF;
603 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_EXTREF@"));
604 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_NOTE@")) {
605 						guint it = n;
606 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
607 						new_help[it].type = GNM_FUNC_HELP_NOTE;
608 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_NOTE@"));
609 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_END@")) {
610 						/* ignore */
611 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_EXCEL@")) {
612 						guint it = n;
613 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
614 						new_help[it].type = GNM_FUNC_HELP_EXCEL;
615 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_EXCEL@"));
616 					} else if (g_str_has_prefix (*items, "@GNM_FUNC_HELP_ODF@")) {
617 						guint it = n;
618 						new_help = g_renew (GnmFuncHelp, new_help, ++n);
619 						new_help[it].type = GNM_FUNC_HELP_ODF;
620 						new_help[it].text = g_strdup ((*items) + strlen ("@GNM_FUNC_HELP_ODF@"));
621 					} else if (n > 0) {
622 						gchar *old_text = (gchar *) new_help[n].text;
623 						new_help[n].text = g_strconcat (old_text, "\n", *items, NULL);
624 						g_free (old_text);
625 					}
626 					items++;
627 				}
628 				g_strfreev (fitems);
629 			}
630 
631 			if (python_arg_names != NULL && !arg_names_written) {
632 				/* We only try this if we did not get argument  */
633 				/* descriptions via the new style documentation */
634 				char const *arg_names = PyUnicode_AsUTF8 (python_arg_names);
635 				if (arg_names != NULL && arg_names[0] != '\0') {
636 					gchar **args = g_strsplit (arg_names, ",", 0);
637 					guint nitems = g_strv_length (args), nstart = n, i;
638 					n += nitems;
639 					new_help = g_renew (GnmFuncHelp, new_help, n);
640 					for (i = 0; i < nitems; i++, nstart++) {
641 						char const *arg_name = args[i];
642 						while (*arg_name == ' ') arg_name++;
643 						new_help[nstart].type = GNM_FUNC_HELP_ARG;
644 						new_help[nstart].text = g_strdup_printf ("%s:", arg_name);
645 					}
646 					g_strfreev (args);
647 				}
648 			}
649 
650 			n++;
651 			new_help = g_renew (GnmFuncHelp, new_help, n);
652 			new_help[n-1].type = GNM_FUNC_HELP_END;
653 			new_help[n-1].text = NULL;
654 
655 			cobject_help_value = PyCapsule_New (new_help, "FuncHelp", FuncHelpDestructor);
656 			PyDict_SetItemString (python_fn_info_dict, help_attr_name, cobject_help_value);
657 		}
658 	}
659 	g_free (help_attr_name);
660 
661 	if (cobject_help_value) {
662 		res = (GnmFuncHelp const *) PyCapsule_GetPointer (cobject_help_value, "FuncHelp");
663 		Py_DECREF (cobject_help_value);
664 	}
665 
666 	return res;
667 }
668 
669 static void
gplp_func_load_stub(GOPluginService * service,GnmFunc * func)670 gplp_func_load_stub (GOPluginService *service,
671 		     GnmFunc *func)
672 {
673 	ServiceLoaderDataFunctionGroup *loader_data;
674 	PyObject *fn_info_obj;
675 	char const *name;
676 
677 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (service));
678 	g_return_if_fail (GNM_IS_FUNC (func));
679 
680 	name = gnm_func_get_name (func, FALSE);
681 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
682 	SWITCH_TO_PLUGIN (go_plugin_service_get_plugin (service));
683 	fn_info_obj = PyDict_GetItemString (loader_data->python_fn_info_dict,
684 					    name);
685 	if (fn_info_obj == NULL) {
686 		gnm_python_clear_error_if_needed (SERVICE_GET_LOADER (service)->py_object);
687 		return;
688 	}
689 
690 	if (PyTuple_Check (fn_info_obj)) {
691 		PyObject *python_args;
692 		PyObject *python_fn;
693 
694 		if (PyTuple_Size (fn_info_obj) == 3 &&
695 		    (python_args = PyTuple_GetItem (fn_info_obj, 0)) != NULL &&
696 			PyUnicode_Check (python_args) &&
697 		    (python_fn = PyTuple_GetItem (fn_info_obj, 2)) != NULL &&
698 		    PyCallable_Check (python_fn)) {
699 			GnmFuncHelp const *help = python_function_get_gnumeric_help
700 				(loader_data->python_fn_info_dict, python_fn, name);
701 			gnm_func_set_fixargs (func, call_python_function_args, PyUnicode_AsUTF8 (python_args));
702 			gnm_func_set_help (func, help, -1);
703 			gnm_func_set_impl_status (func, GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC);
704 			g_object_set_data (G_OBJECT (func), SERVICE_KEY, service);
705 			return;
706 		}
707 
708 		gnm_python_clear_error_if_needed (SERVICE_GET_LOADER (service)->py_object);
709 		return;
710 	}
711 
712 	if (PyCallable_Check (fn_info_obj)) {
713 		GnmFuncHelp const *help = python_function_get_gnumeric_help
714 			(loader_data->python_fn_info_dict, fn_info_obj, name);
715 		gnm_func_set_varargs (func, call_python_function_nodes, NULL);
716 		gnm_func_set_help (func, help, -1);
717 		gnm_func_set_impl_status (func, GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC);
718 		g_object_set_data (G_OBJECT (func), SERVICE_KEY, service);
719 		return;
720 	}
721 
722 	gnm_python_clear_error_if_needed (SERVICE_GET_LOADER (service)->py_object);
723 	return;
724 }
725 
726 static void
gplp_load_service_function_group(GOPluginLoader * loader,GOPluginService * service,GOErrorInfo ** ret_error)727 gplp_load_service_function_group (GOPluginLoader *loader,
728 				  GOPluginService *service,
729 				  GOErrorInfo **ret_error)
730 {
731 	GnmPythonPluginLoader *loader_python = GNM_PYTHON_PLUGIN_LOADER (loader);
732 	gchar *fn_info_dict_name;
733 	PyObject *python_fn_info_dict;
734 
735 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (service));
736 
737 	GO_INIT_RET_ERROR_INFO (ret_error);
738 	gnm_py_interpreter_switch_to (loader_python->py_interpreter_info);
739 	fn_info_dict_name = g_strconcat (
740 		go_plugin_service_get_id (service), "_functions", NULL);
741 	python_fn_info_dict = PyDict_GetItemString (loader_python->main_module_dict,
742 	                                             fn_info_dict_name);
743 	gnm_python_clear_error_if_needed (loader_python->py_object);
744 	if (python_fn_info_dict != NULL && PyDict_Check (python_fn_info_dict)) {
745 		GnmPluginServiceFunctionGroupCallbacks *cbs;
746 		ServiceLoaderDataFunctionGroup *loader_data;
747 
748 		cbs = go_plugin_service_get_cbs (service);
749 		cbs->load_stub = &gplp_func_load_stub;
750 
751 		loader_data = g_new (ServiceLoaderDataFunctionGroup, 1);
752 		loader_data->python_fn_info_dict = (PyObject *) python_fn_info_dict;
753 		Py_INCREF (loader_data->python_fn_info_dict);
754 		g_object_set_data_full
755 			(G_OBJECT (service), "loader_data", loader_data,
756 			 (GDestroyNotify) gplp_loader_data_fngroup_free);
757 	} else {
758 		*ret_error = go_error_info_new_printf (
759 		             _("Python file \"%s\" has invalid format."),
760 		             loader_python->module_name);
761 		if (python_fn_info_dict == NULL) {
762 			go_error_info_add_details (*ret_error,
763 			                        go_error_info_new_printf (
764 			                        _("File doesn't contain \"%s\" dictionary."),
765 			                        fn_info_dict_name));
766 		} else if (!PyDict_Check (python_fn_info_dict)) {
767 			go_error_info_add_details (*ret_error,
768 			                        go_error_info_new_printf (
769 			                        _("Object \"%s\" is not a dictionary."),
770 			                        fn_info_dict_name));
771 		}
772 	}
773 	g_free (fn_info_dict_name);
774 }
775 
776 static void
gplp_unload_service_function_group(GOPluginLoader * loader,GOPluginService * service,GOErrorInfo ** ret_error)777 gplp_unload_service_function_group (GOPluginLoader *loader,
778 				    GOPluginService *service,
779 				    GOErrorInfo **ret_error)
780 {
781 	ServiceLoaderDataFunctionGroup *loader_data;
782 
783 	g_return_if_fail (GNM_IS_PYTHON_PLUGIN_LOADER (loader));
784 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (service));
785 
786 	GO_INIT_RET_ERROR_INFO (ret_error);
787 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
788 	SWITCH_TO_PLUGIN (go_plugin_service_get_plugin (service));
789 	Py_CLEAR (loader_data->python_fn_info_dict);
790 }
791 
792 typedef struct {
793 	PyObject *ui_actions;
794 } ServiceLoaderDataUI;
795 
796 static void
gplp_loader_data_ui_free(ServiceLoaderDataUI * loader_data)797 gplp_loader_data_ui_free (ServiceLoaderDataUI *loader_data)
798 {
799 	Py_DECREF (loader_data->ui_actions);
800 	g_free (loader_data);
801 }
802 
803 static void
gplp_func_exec_action(GOPluginService * service,GnmAction const * action,WorkbookControl * wbc,GOErrorInfo ** ret_error)804 gplp_func_exec_action (GOPluginService *service,
805 		       GnmAction const *action,
806 		       WorkbookControl *wbc,
807 		       GOErrorInfo **ret_error)
808 {
809 	ServiceLoaderDataUI *loader_data;
810 	PyObject *fn, *ret;
811 
812 	if (_PyGObject_API == NULL) {
813 		pygobject_init (3, 0, 0);
814 		g_return_if_fail (_PyGObject_API != NULL);
815 	}
816 
817 	GO_INIT_RET_ERROR_INFO (ret_error);
818 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
819 	SWITCH_TO_PLUGIN (go_plugin_service_get_plugin (service));
820 	fn = PyDict_GetItemString (loader_data->ui_actions, action->id);
821 	if (fn == NULL) {
822 		*ret_error = go_error_info_new_printf (_("Unknown action: %s"),
823 						    action->id);
824 		return;
825 	} else if (!PyCallable_Check (fn)) {
826 		*ret_error = go_error_info_new_printf (
827 			_("Not a valid function for action: %s"), action->id);
828 		return;
829 	}
830 	ret = PyObject_CallFunction (fn, "N",
831 				     pygobject_new (G_OBJECT (WBC_GTK (wbc))));
832 	if (ret == NULL) {
833 		*ret_error = go_error_info_new_str (py_exc_to_string ());
834 		PyErr_Clear ();
835 	} else {
836 		Py_DECREF (ret);
837 	}
838 }
839 
840 static void
gplp_load_service_ui(GOPluginLoader * loader,GOPluginService * service,GOErrorInfo ** ret_error)841 gplp_load_service_ui (GOPluginLoader *loader,
842 		      GOPluginService *service,
843 		      GOErrorInfo **ret_error)
844 {
845 
846 	GnmPythonPluginLoader *loader_python = GNM_PYTHON_PLUGIN_LOADER (loader);
847 	gchar *ui_action_names;
848 	PyObject *ui_actions;
849 
850 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_UI (service));
851 
852 	GO_INIT_RET_ERROR_INFO (ret_error);
853 	gnm_py_interpreter_switch_to (loader_python->py_interpreter_info);
854 	ui_action_names = g_strconcat (go_plugin_service_get_id (service),
855 				     "_ui_actions", NULL);
856 	ui_actions = PyDict_GetItemString (loader_python->main_module_dict,
857 					   ui_action_names);
858 	gnm_python_clear_error_if_needed (loader_python->py_object);
859 	if (ui_actions != NULL && PyDict_Check (ui_actions)) {
860 		GnmPluginServiceUICallbacks *cbs;
861 		ServiceLoaderDataUI *loader_data;
862 
863 		cbs = go_plugin_service_get_cbs (service);
864 		cbs->plugin_func_exec_action = gplp_func_exec_action;
865 
866 		loader_data = g_new (ServiceLoaderDataUI, 1);
867 		loader_data->ui_actions = ui_actions;
868 		Py_INCREF (loader_data->ui_actions);
869 		g_object_set_data_full
870 			(G_OBJECT (service), "loader_data", loader_data,
871 			 (GDestroyNotify) gplp_loader_data_ui_free);
872 	} else {
873 		*ret_error = go_error_info_new_printf (
874 		             _("Python file \"%s\" has invalid format."),
875 		             loader_python->module_name);
876 		if (ui_actions == NULL) {
877 			go_error_info_add_details (*ret_error,
878 			                        go_error_info_new_printf (
879 			                        _("File doesn't contain \"%s\" dictionary."),
880 			                        ui_action_names));
881 		} else if (!PyDict_Check (ui_actions)) {
882 			go_error_info_add_details (*ret_error,
883 			                        go_error_info_new_printf (
884 			                        _("Object \"%s\" is not a dictionary."),
885 			                        ui_action_names));
886 		}
887 	}
888 	g_free (ui_action_names);
889 }
890 
891 static gboolean
gplp_service_load(GOPluginLoader * l,GOPluginService * s,GOErrorInfo ** err)892 gplp_service_load (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
893 {
894 	if (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (s))
895 		gplp_load_service_function_group (l, s, err);
896 	else if (GNM_IS_PLUGIN_SERVICE_UI (s))
897 		gplp_load_service_ui (l, s, err);
898 	else
899 		return FALSE;
900 	return TRUE;
901 }
902 
903 static gboolean
gplp_service_unload(GOPluginLoader * l,GOPluginService * s,GOErrorInfo ** err)904 gplp_service_unload (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
905 {
906 	if (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (s))
907 		gplp_unload_service_function_group (l, s, err);
908 	else if (GNM_IS_PLUGIN_SERVICE_UI (s))
909 		;
910 	else
911 		return FALSE;
912 	return TRUE;
913 }
914 
915 static void
gplp_finalize(GObject * obj)916 gplp_finalize (GObject *obj)
917 {
918 	GnmPythonPluginLoader *loader_python = GNM_PYTHON_PLUGIN_LOADER (obj);
919 
920 	g_free (loader_python->module_name);
921 	loader_python->module_name = NULL;
922 
923 	G_OBJECT_CLASS (g_type_class_peek (G_TYPE_OBJECT))->finalize (obj);
924 }
925 
926 static void
gplp_init(GnmPythonPluginLoader * loader_python)927 gplp_init (GnmPythonPluginLoader *loader_python)
928 {
929 	g_return_if_fail (GNM_IS_PYTHON_PLUGIN_LOADER (loader_python));
930 
931 	loader_python->module_name = NULL;
932 	loader_python->py_object = NULL;
933 	loader_python->py_interpreter_info = NULL;
934 }
935 
936 static void
gplp_class_init(GObjectClass * gobject_class)937 gplp_class_init (GObjectClass *gobject_class)
938 {
939 	gobject_class->finalize = gplp_finalize;
940 }
941 static void
go_plugin_loader_init(GOPluginLoaderClass * iface)942 go_plugin_loader_init (GOPluginLoaderClass *iface)
943 {
944 	iface->set_attributes		= gplp_set_attributes;
945 	iface->load_base		= gplp_load_base;
946 	iface->unload_base		= gplp_unload_base;
947 	iface->load_service_file_opener	= gplp_load_service_file_opener;
948 	iface->load_service_file_saver	= gplp_load_service_file_saver;
949 
950 	iface->service_load		= gplp_service_load;
951 	iface->service_unload		= gplp_service_unload;
952 }
953 
954 GSF_DYNAMIC_CLASS_FULL (GnmPythonPluginLoader, gnm_python_plugin_loader,
955 	NULL, NULL, gplp_class_init, NULL,
956 	gplp_init, G_TYPE_OBJECT, 0,
957 	GSF_INTERFACE_FULL (gnm_python_plugin_loader_type, go_plugin_loader_init, GO_TYPE_PLUGIN_LOADER))
958