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