1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3    anjuta-autogen.c
4     Copyright (C) 2004 Sebastien Granjoux
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19 */
20 
21 /**
22  * SECTION:anjuta-autogen
23  * @title: AnjutaAutogen
24  * @short_description: Template engine using GNU autogen program.
25  * @see_also: #AnjutaLauncher
26  * @stability: Unstable
27  * @include: libanjuta/anjuta-autogen.h
28  *
29  * GNU autogen is a program generating a text file from a template and a
30  * definition file. The template contains fixed text and variables those will
31  * be replaced under the control of the definition file.
32  *
33  * By example from the following definition file
34  * <programlisting>
35  * AutoGen Definitions .;
36  * list = { list_element = alpha;
37  *          list_info    = "some alpha stuff"; };
38  * list = { list_info    = "more beta stuff";
39  *          list_element = beta; };
40  * list = { list_element = omega;
41  *          list_info    = "final omega stuff"; }
42  * </programlisting>
43  * And the following template
44  * <programlisting>
45  * [+ AutoGen5 template +]
46  * typedef enum {[+
47  *    FOR list "," +]
48  *         IDX_[+ (string-upcase! (get "list_element")) +][+
49  *    ENDFOR list +] }  list_enum;
50  * </programlisting>
51  * Autogen generates
52  * <programlisting>
53  * typedef enum {
54  *         IDX_ALPHA,
55  *         IDX_BETA,
56  *         IDX_OMEGA }  list_enum;
57  * </programlisting>
58  *
59  * The template file can be quite complex, you can read autogen documentation
60  * <ulink url="http://www.gnu.org/software/autogen">here</ulink>.
61  *
62  * The #AnjutaAutogen object takes care of writing the definition file from
63  * a hash table and call autogen. The output can be written in a file or passed
64  * to a callback function. Autogen is executed asynchronously, so there is
65  * another callback function called when the processing is completed.
66  */
67 
68 #include <config.h>
69 
70 #include <libanjuta/anjuta-launcher.h>
71 #include <libanjuta/anjuta-autogen.h>
72 
73 #include <glib/gi18n.h>
74 #include <glib/gstdio.h>
75 #include <glib.h>
76 #include <errno.h>
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <string.h>
80 #include <glib/gstdio.h>
81 
82 /*---------------------------------------------------------------------------*/
83 
84 #define TMP_DEF_FILENAME "NPWDEFXXXXXX"
85 #define TMP_TPL_FILENAME "NPWTPLXXXXXX"
86 
87 #define FILE_BUFFER_SIZE	4096
88 
89 #define TEMPLATES_DIR PACKAGE_DATA_DIR"/templates"
90 
91 /*---------------------------------------------------------------------------*/
92 
93 struct _AnjutaAutogen
94 {
95 	GObject parent;
96 
97 	gchar* deffilename;		/* name of generated definition file */
98 	gchar* tplfilename;		/* name of template (input) file */
99 	const gchar* temptplfilename;		/* name of generated template if the
100 					 * previous file doesn't contains
101 					 * autogen marker */
102 
103 	GList *library_paths;		/* List of paths for searching include files */
104 					/* Output file name and handle used
105 					 * when autogen output is written
106 					 * in a file */
107 	gchar* outfilename;
108 	FILE* output;
109 	gboolean empty;
110 					/* Call back function and data used
111 					 * when autogen output something */
112 	AnjutaAutogenOutputFunc outfunc;
113 	gpointer outdata;
114 	GDestroyNotify destroy;
115 
116 					/* Call back function and data used
117 					 * when autogen terminate */
118 	AnjutaAutogenFunc endfunc;
119 	gpointer enddata;
120 
121 	AnjutaLauncher* launcher;
122 	gboolean busy;			/* For debugging */
123 };
124 
125 struct _AnjutaAutogenClass
126 {
127 	GObjectClass parent;
128 };
129 
130 
131 /*---------------------------------------------------------------------------*/
132 
133 /* Helper functions
134  *---------------------------------------------------------------------------*/
135 
136 /**
137  * anjuta_check_autogen:
138  *
139  * Check if autogen version 5 is installed.
140  *
141  * Return value: %TRUE if autogen is installed.
142  */
143 
144 gboolean
anjuta_check_autogen(void)145 anjuta_check_autogen (void)
146 {
147 	gchar* args[] = {"autogen", "-v", NULL};
148 	gchar* output;
149 
150 	if (g_spawn_sync (NULL, args, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
151 		NULL, NULL, &output, NULL, NULL, NULL))
152 	{
153 		GRegex *re;
154 		GMatchInfo *minfo;
155 		gint ver[3] = {0, 0, 0};
156 
157 		/* Check autogen 5 version string
158 		 * Examples:
159 		 * autogen - The Automated Program Generator - Ver. 5.5.7
160 		 * autogen (GNU AutoGen) - The Automated Program Generator - Ver. 5.11
161 		 * autogen (GNU AutoGen) 5.11.9
162 		 */
163 		re = g_regex_new ("autogen.* (\\d+)\\.(\\d+)(?:\\.(\\d+))?", 0, 0, NULL);
164 		g_regex_match (re, output, 0, &minfo);
165 		if (g_match_info_matches (minfo)) {
166 			gchar **match_strings;
167 
168 			match_strings = g_match_info_fetch_all (minfo);
169 			ver[0] = g_ascii_strtoll (match_strings[1], NULL, 10);
170 			ver[1] = g_ascii_strtoll (match_strings[2], NULL, 10);
171 			if (match_strings[3] != NULL) ver[2] = g_ascii_strtoll (match_strings[3], NULL, 10);
172 
173 			g_strfreev (match_strings);
174 		}
175 		g_match_info_free (minfo);
176 		g_regex_unref (re);
177 
178 		return ver[0] == 5;
179 	}
180 
181 	return FALSE;
182 }
183 
184 
185 
186 /* Write definitions
187  *---------------------------------------------------------------------------*/
188 
189 static void
cb_autogen_write_key(const gchar * name,const gchar * value,gpointer user_data)190 cb_autogen_write_key (const gchar* name, const gchar *value, gpointer user_data)
191 {
192 	FILE* def = (FILE *)user_data;
193 
194 	if (value != NULL)
195 	{
196 		if(*value == '{') /* Seems to be a list, so do not quote */
197 		{
198 			fprintf(def, "%s = %s;\n", name, value);
199 		}
200 		else
201 		{
202 			gchar *esc_value = g_strescape (value, NULL);
203 			fprintf (def, "%s = \"%s\";\n", name, esc_value);
204 			g_free (esc_value);
205 		}
206 	}
207 }
208 
209 /**
210  * anjuta_autogen_write_definition_file:
211  * @this: A #AnjutaAutogen object
212  * @values: (element-type utf8 utf8): A hash table containing all definitions
213  * @error: Error propagation and reporting
214  *
215  * Write the autogen definition file. The definition file defined variables
216  * those will be used, typically replaced, in the template files.
217  *
218  * The hash table keys are the names of the variables. The name can include an
219  * index in square bracket, by example "members[0]". All values are strings but
220  * but they could include children using braces, by example
221  * "{count=2; list="aa bb"}".
222  *
223  * The file is created in a temporary directory and removed when the object
224  * is destroyed.
225  *
226  * Returns: %TRUE if the file has been written without error,
227  */
228 
229 gboolean
anjuta_autogen_write_definition_file(AnjutaAutogen * this,GHashTable * values,GError ** error)230 anjuta_autogen_write_definition_file (AnjutaAutogen* this, GHashTable* values, GError **error)
231 {
232 	FILE* def;
233 
234 	/* Autogen should not be running */
235 	if (this->busy)
236 	{
237 		g_set_error_literal (error, G_FILE_ERROR,
238 		                     G_FILE_ERROR_AGAIN,
239 		                     _("Autogen is busy"));
240 
241 		return FALSE;
242 	}
243 
244 	def = fopen (this->deffilename, "wt");
245 	if (def == NULL)
246 	{
247 		g_set_error(
248 		            error,
249 		            G_FILE_ERROR,
250 		            g_file_error_from_errno(errno),
251 		            _("Could not write definition file \"%s\": %s"),
252 		            this->deffilename,
253 					g_strerror(errno)
254 		            );
255 
256 		return FALSE;
257 	}
258 
259 	/* Generate definition data for autogen */
260 	fputs ("AutoGen Definitions .;\n",def);
261 	g_hash_table_foreach (values, (GHFunc)cb_autogen_write_key, def);
262 
263 	fclose (def);
264 
265 	return TRUE;
266 }
267 
268 /* Set library path
269  *---------------------------------------------------------------------------*/
270 
271 /**
272  * anjuta_autogen_set_library_path:
273  * @this: A #AnjutaAutogen object
274  * @directory: A path containing autogen library.
275  *
276  * Add a new directory in the list of autogen libraries path.
277  *
278  * Autogen can include files. These included file will be searched by default
279  * in the same directory than the template file. This functions allows you to
280  * add other directories.
281  */
282 
283 void
anjuta_autogen_set_library_path(AnjutaAutogen * this,const gchar * directory)284 anjuta_autogen_set_library_path (AnjutaAutogen* this, const gchar *directory)
285 {
286 	g_return_if_fail (directory != NULL);
287 
288 	this->library_paths = g_list_prepend (this->library_paths, g_strdup (directory));
289 }
290 
291 /**
292  * anjuta_autogen_clear_library_path:
293  * @this: A #AnjutaAutogen object
294  *
295  * Remove all library pathes.
296  */
297 
298 void
anjuta_autogen_clear_library_path(AnjutaAutogen * this)299 anjuta_autogen_clear_library_path (AnjutaAutogen* this)
300 {
301 	g_list_foreach (this->library_paths, (GFunc)g_free, NULL);
302 	g_list_free (this->library_paths);
303 	this->library_paths = NULL;
304 }
305 
306 /**
307  * anjuta_autogen_get_library_paths:
308  * @this: A #AnjutaAutogen object
309  *
310  * Get the list of all directories searched for files included in the autogen
311  * templates.
312  *
313  * Returns: (element-type gchar*) (transfer none): A list of directories.
314  * The content and the list itself are owned by the #AnjutaAutogen object and
315  * should not be modified or freed.
316  */
317 
318 GList *
anjuta_autogen_get_library_paths(AnjutaAutogen * this)319 anjuta_autogen_get_library_paths (AnjutaAutogen* this)
320 {
321 	return this->library_paths;
322 }
323 
324 /* Set input and output
325  *---------------------------------------------------------------------------*/
326 
327 /**
328  * anjuta_autogen_set_input_file:
329  * @this: A #AnjutaAutogen object
330  * @filename: name of the input template file
331  * @start_marker: (allow-none): start marker string
332  * @end_marker: (allow-none): end marker string
333  *
334  * Read an autogen template file, optionally adding autogen markers.
335  *
336  * To be recognized as an autogen template, the first line has to contain:
337  *	- the start marker
338  *	- "autogen5 template"
339  *	- the end marker
340  *
341  * These markers are a custom sequence of up to 7 characters delimiting
342  * the start and the end of autogen variables and macros.
343  *
344  * This function can add this line using the value of @start_marker and
345  * @end_marker. If this line is already present in the file,
346  * @start_marker and @end_marker must be %NULL.
347  *
348  * Returns: %TRUE if the file has been read without error.
349  */
350 
351 gboolean
anjuta_autogen_set_input_file(AnjutaAutogen * this,const gchar * filename,const gchar * start_marker,const gchar * end_marker)352 anjuta_autogen_set_input_file (AnjutaAutogen* this, const gchar* filename, const gchar* start_marker, const gchar* end_marker)
353 {
354 	FILE* tpl;
355 	FILE* src;
356 	gboolean ok;
357 	gchar* buffer;
358 	guint len;
359 
360 	/* Autogen should not be running */
361 	g_return_val_if_fail (this->busy == FALSE, FALSE);
362 
363 	/* We need to specify start and end marker or nothing */
364 	g_return_val_if_fail ((start_marker && end_marker) || (!start_marker && !end_marker), FALSE);
365 
366 	/* Remove previous temporary file if exist */
367 	if (this->temptplfilename != NULL)
368 	{
369 		remove (this->temptplfilename);
370 		this->temptplfilename = NULL;
371 	}
372 	g_free (this->tplfilename);
373 
374 	if ((start_marker == NULL) && (end_marker == NULL))
375 	{
376 		/* input file is really an autogen file, nothig do to */
377 		this->tplfilename = g_strdup (filename);
378 
379 		return TRUE;
380 	}
381 
382 	/* Autogen definition is missing, we need to create a temporary file
383 	 * with them */
384 
385 	/* Create temporary file */
386 	this->tplfilename = g_build_filename (g_get_tmp_dir (), TMP_TPL_FILENAME, NULL);
387 	mktemp (this->tplfilename);
388 	this->temptplfilename = this->tplfilename;
389 	tpl = fopen (this->tplfilename, "wt");
390 	if (tpl == NULL) return FALSE;
391 
392 	/* Add autogen definition */
393 	fputs (start_marker, tpl);
394 	fputs (" autogen5 template ", tpl);
395 	fputs (end_marker, tpl);
396 	fputc ('\n', tpl);
397 
398 	/* Copy source file into this new file */
399 	src = fopen (filename, "rb");
400 	if (src == NULL) return FALSE;
401 
402 	buffer = g_new (gchar, FILE_BUFFER_SIZE);
403 
404 	ok = TRUE;
405 	for (;!feof (src);)
406 	{
407 		len = fread (buffer, 1, FILE_BUFFER_SIZE, src);
408 		if ((len != FILE_BUFFER_SIZE) && !feof (src))
409 		{
410 			ok = FALSE;
411 			break;
412 		}
413 
414 		if (len != fwrite (buffer, 1, len, tpl))
415 		{
416 			ok = FALSE;
417 			break;
418 		}
419 	}
420 
421 	g_free (buffer);
422 	fclose (src);
423 	fclose (tpl);
424 
425 	return ok;
426 }
427 
428 /**
429  * anjuta_autogen_set_output_file:
430  * @this: A #AnjutaAutogen object
431  * @filename: name of the generated file
432  *
433  * Define the name of the generated file.
434  *
435  * Returns: %TRUE if the file has been set without error.
436  */
437 
438 gboolean
anjuta_autogen_set_output_file(AnjutaAutogen * this,const gchar * filename)439 anjuta_autogen_set_output_file (AnjutaAutogen* this, const gchar* filename)
440 {
441 	/* Autogen should not be running */
442 	g_return_val_if_fail (this->busy == FALSE, FALSE);
443 
444 	g_free (this->outfilename);
445 	this->outfilename = g_strdup (filename);
446 	this->outfunc = NULL;
447 
448 	return TRUE;
449 }
450 
451 /**
452  * anjuta_autogen_set_output_callback:
453  * @this: A #AnjutaAutogen object
454  * @func: Function call each time we get new data from autogen
455  * @user_data: (allow-none): User data to pass to @func, or %NULL
456  * @destroy: Function call when the process is complete to free user data
457  *
458  * Define that autogen output should be send to a function as soon as it arrives.
459  *
460  * Returns: %TRUE if there is no error.
461  */
462 
463 gboolean
anjuta_autogen_set_output_callback(AnjutaAutogen * this,AnjutaAutogenOutputFunc func,gpointer user_data,GDestroyNotify destroy)464 anjuta_autogen_set_output_callback (AnjutaAutogen* this, AnjutaAutogenOutputFunc func, gpointer user_data, GDestroyNotify destroy)
465 {
466 	/* Autogen should not be running */
467 	g_return_val_if_fail (this->busy == FALSE, FALSE);
468 
469 	this->outfunc = func;
470 	this->outdata = user_data;
471 	this->destroy = destroy;
472 	this->outfilename = NULL;
473 
474 	return TRUE;
475 }
476 
477 /* Execute autogen
478  *---------------------------------------------------------------------------*/
479 
480 static void
on_autogen_output(AnjutaLauncher * launcher,AnjutaLauncherOutputType type,const gchar * output,gpointer user_data)481 on_autogen_output (AnjutaLauncher* launcher, AnjutaLauncherOutputType type, const gchar* output, gpointer user_data)
482 {
483 	AnjutaAutogen* this = (AnjutaAutogen*)user_data;
484 
485 	if (this->outfilename != NULL)
486 	{
487 		/* Write output in a file */
488 		if (this->output != NULL)
489 		{
490 			fputs (output, this->output);
491 			this->empty = FALSE;
492 		}
493 	}
494 	if (this->outfunc != NULL)
495 	{
496 		/* Call a callback function */
497 		(this->outfunc)(output, this->outdata);
498 	}
499 }
500 
501 static void
on_autogen_terminated(AnjutaLauncher * launcher,gint pid,gint status,gulong time,AnjutaAutogen * this)502 on_autogen_terminated (AnjutaLauncher* launcher, gint pid, gint status, gulong time, AnjutaAutogen* this)
503 {
504 	this->busy = FALSE;
505 	if (this->output != NULL)
506 	{
507 		fclose (this->output);
508 		this->output = NULL;
509 		/* Delete empty file */
510 		if (this->empty == TRUE)
511 		{
512 			g_remove (this->outfilename);
513 		}
514 	}
515 
516 	if (this->destroy)
517 	{
518 		(this->destroy)(this->outdata);
519 	}
520 	if (this->endfunc)
521 	{
522 		(this->endfunc)(this, this->enddata);
523 	}
524 }
525 
526 /**
527  * anjuta_autogen_execute:
528  * @this: A #AnjutaAutogen object
529  * @func: (scope async) (allow-none): A function called when autogen is terminated
530  * @data: (allow-none): User data to pass to @func, or %NULL
531  * @error: (allow-none): Error propagation and reporting
532  *
533  * Asynchronously execute autogen to generate the output, calling @func when the
534  * process is completed.
535  *
536  * Returns: %TRUE if the file has been processed without error.
537  */
538 
539 gboolean
anjuta_autogen_execute(AnjutaAutogen * this,AnjutaAutogenFunc func,gpointer data,GError ** error)540 anjuta_autogen_execute (AnjutaAutogen* this, AnjutaAutogenFunc func, gpointer data, GError** error)
541 {
542 	gchar** args;
543 	guint arg;
544 	GList *path;
545 
546 	/* Autogen should not be running */
547 	g_return_val_if_fail (this->busy == FALSE, FALSE);
548 	g_return_val_if_fail (this, FALSE);
549 	g_return_val_if_fail (this->launcher, FALSE);
550 
551 	/* Set output end callback */
552 	if (func != NULL)
553 	{
554 		this->endfunc = func;
555 		this->enddata = data;
556 	}
557 	else
558 	{
559 		this->endfunc = NULL;
560 	}
561 	args = g_new (gchar *, 5 + g_list_length (this->library_paths) * 2);
562 	args[0] = "autogen";
563 	arg = 1;
564 	for (path = g_list_first (this->library_paths); path != NULL; path = g_list_next (path))
565 	{
566 		args[arg++] = "-L";
567 		args[arg++] = (gchar *)(path->data);
568 	}
569 	args[arg++] = "-T";
570 	args[arg++] = (gchar *)this->tplfilename;
571 	args[arg++] = (gchar *)this->deffilename;
572 	args[arg] = NULL;
573 
574 	/* Check if output file can be written */
575 	if (this->outfilename != NULL)
576 	{
577 		/* Open file if it's not already done */
578 		this->output = fopen (this->outfilename, "wt");
579 		if (this->output == NULL)
580 		{
581 			g_set_error(
582 				error,
583 				G_FILE_ERROR,
584 				g_file_error_from_errno(errno),
585 				_("Could not open file \"%s\": %s"),
586 				this->outfilename,
587 				g_strerror(errno)
588 			);
589 			g_free (args);
590 
591 			return FALSE;
592 		}
593 		this->empty = TRUE;
594 	}
595 
596 	/* The template and definition file are in UTF-8 so the output too */
597 	anjuta_launcher_set_encoding (this->launcher, "UTF-8");
598 
599 	this->busy = TRUE;
600 	if (!anjuta_launcher_execute_v (this->launcher, NULL, args, NULL, on_autogen_output, this))
601 	{
602 		g_free (args);
603 
604 		return FALSE;
605 	}
606 	g_free (args);
607 
608 	return TRUE;
609 }
610 
611 /* Implement GObject
612  *---------------------------------------------------------------------------*/
613 
614 G_DEFINE_TYPE (AnjutaAutogen, anjuta_autogen, G_TYPE_OBJECT);
615 
616 static void
anjuta_autogen_init(AnjutaAutogen * this)617 anjuta_autogen_init (AnjutaAutogen *this)
618 {
619 	this->launcher = anjuta_launcher_new ();
620 	g_signal_connect (G_OBJECT (this->launcher), "child-exited", G_CALLBACK (on_autogen_terminated), this);
621 
622 	/* Create a temporary file for definitions */
623 	this->deffilename = g_build_filename (g_get_tmp_dir (), TMP_DEF_FILENAME, NULL);
624 	mktemp (this->deffilename);
625 
626 	this->library_paths = g_list_prepend (NULL, g_strdup (TEMPLATES_DIR));
627 }
628 
629 static void
anjuta_autogen_dispose(GObject * object)630 anjuta_autogen_dispose (GObject *object)
631 {
632 	AnjutaAutogen *this = ANJUTA_AUTOGEN (object);
633 
634 	if (this->output != NULL)
635 	{
636 		/* output is not used if a callback function is used */
637 		fclose (this->output);
638 		this->output = NULL;
639 	}
640 
641 	if (this->outfilename != NULL)
642 	{
643 		g_free (this->outfilename);
644 		this->outfilename = NULL;
645 	}
646 
647 	if (this->tplfilename != NULL)
648 	{
649 		g_free (this->tplfilename);
650 		this->tplfilename = NULL;
651 	}
652 
653 	if (this->temptplfilename != NULL)
654 	{
655 		/* temptplfilename is not used if input file already
656 		 * contains autogen marker */
657 		remove (this->temptplfilename);
658 		this->temptplfilename = NULL;
659 	}
660 
661 	g_list_foreach (this->library_paths, (GFunc)g_free, NULL);
662 	g_list_free (this->library_paths);
663 	this->library_paths = NULL;
664 
665 	if (this->deffilename != NULL)
666 	{
667 		remove (this->deffilename);
668 		g_free (this->deffilename);
669 		this->deffilename = NULL;
670 	}
671 
672 	if (this->launcher != NULL)
673 	{
674 		g_signal_handlers_disconnect_by_func (G_OBJECT (this->launcher), G_CALLBACK (on_autogen_terminated), this);
675 		g_object_unref (this->launcher);
676 		this->launcher = NULL;
677 	}
678 
679 	G_OBJECT_CLASS (anjuta_autogen_parent_class)->dispose (object);
680 }
681 
682 static void
anjuta_autogen_class_init(AnjutaAutogenClass * klass)683 anjuta_autogen_class_init (AnjutaAutogenClass *klass)
684 {
685 	GObjectClass* object_class = G_OBJECT_CLASS (klass);
686 
687 	object_class->dispose = anjuta_autogen_dispose;
688 }
689 
690 
691 /* Creation and Destruction
692  *---------------------------------------------------------------------------*/
693 
694 /**
695  * anjuta_autogen_new:
696  *
697  * Create a new autogen object.
698  *
699  * Returns: (transfer full): A new #AnjutaAutogen object. Free it using g_object_unref.
700  */
701 
anjuta_autogen_new(void)702 AnjutaAutogen* anjuta_autogen_new (void)
703 {
704 	return g_object_new (ANJUTA_TYPE_AUTOGEN, NULL);
705 }
706 
707 
708 
709